diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/.dockerignore b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/.dockerignore new file mode 100644 index 000000000..2d4f14ea2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/.dockerignore @@ -0,0 +1,6 @@ +.git +.dockerignore +Dockerfile +node_modules +*.swp +*.pyc diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/.gitattributes b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/.gitattributes new file mode 100644 index 000000000..147ee79bf --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/.gitattributes @@ -0,0 +1,4 @@ +*.* text eol=lf +php/test/*.php linguist-generated=true +python/ccxt/*.py linguist-generated=true +python/ccxt/__init__.py linguist-generated=false diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/CONTRIBUTING.md b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/CONTRIBUTING.md new file mode 100644 index 000000000..5ce4a0405 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/CONTRIBUTING.md @@ -0,0 +1,926 @@ +# Contributing To The CCXT Library + +- [How To Submit A Question Or Issue](#how-to-submit-an-issue) +- [How To Contribute Code](#how-to-contribute-code) + - [What You Need To Have](#what-you-need-to-have) + - [What You Need To Know](#what-you-need-to-know) + +## How To Submit An Issue + +If you want to submit an issue and you want your issue to be resolved quickly, here's a checklist for you: + +- Read the [Manual](https://github.com/ccxt/ccxt/wiki/Manual), and especially carefully read the following sections: + - [Exchange Properties](https://github.com/ccxt/ccxt/wiki/Manual#exchange-properties) + - [Rate Limit](https://github.com/ccxt/ccxt/wiki/Manual#rate-limit) + - [DDoS Protection](https://github.com/ccxt/ccxt/wiki/Manual#ddos-protection-by-cloudflare--incapsula) + - [Authentication](https://github.com/ccxt/ccxt/wiki/Manual#authentication) + - [API Keys Setup](https://github.com/ccxt/ccxt/wiki/Manual#api-keys-setup) +- Read the [Troubleshooting](https://github.com/ccxt/ccxt/wiki/Manual#troubleshooting) section and follow troubleshooting steps. +- Read the [FAQ](https://github.com/ccxt/ccxt/wiki/FAQ) for most frequently asked questions. +- Read the [API docs](https://github.com/ccxt/ccxt/wiki/Exchange-Markets) for your exchange. +- Search for similar issues first to avoid duplicates. +- If your issue is unique, along with a basic description of the failure, the following **IS REQUIRED**: + - **set `exchange.verbose = true` property on the exchange instance before calling its functions or methods** + - **DON'T POST SCREENSHOTS OF CODE OR ERRORS, POST THE OUTPUT AND CODE IN PLAIN TEXT!** + - **surround code and output with triple backticks: ```GOOD```** + - don't confuse the backtick symbol (`) with the quote symbol (\'): '''BAD''' + - don't confuse a single backtick with triple backticks: `BAD` + - paste a complete code snippet you're having difficulties with, avoid one-liners + - paste the **full verbose output** of the failing method without your keys + - the verbose output should include the request and response from the exchange (not just an error callstack) + - write your language **and version** + - write ccxt library version + - which exchange it is + - which method you're trying to call + +### Reporting Vulnerabilities And Critical Issues + +If you found a security issue or a critical vulnerability and reporting it in public would impose risk – please feel free to send us a message to info@ccxt.trade. + +## How To Contribute Code + +- **[MAKE SURE YOUR CODE IS UNIFIED](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#derived-exchange-classes)!** + + ↑ This is the most important rule of all. + +- **PLEASE, DO NOT COMMIT THE FOLLOWING FILES IN PULL REQUESTS:** + + - `/doc/*` (these files are generated from `/wiki/*`, place your edits there) + - `/build/*` (these are generated automatically) + - `/php/*` (except for base classes) + - `/python/*` (except for base classes) + - `/ccxt.js` + - `/README.md` (exchange lists are generated automatically) + - `/package.json` + - `/package.lock` + - `/wiki/*` (except for real edits, exchange lists are generated automatically) + - `/dist/ccxt.browser.js` (this is also browserified automatically) + + + These files are generated ([explained below](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#multilanguage-support)) and will be overwritten upon build. Please don't commit them to avoid bloating the repository which is already quite large. Most often, you have to commit just one single source file to submit an edit to the implementation of an exchange. + +- **PLEASE, SUBMIT ATOMIC EDITS, ONE PULL REQUEST PER ONE EXCHANGE, DO NOT MIX THEM** +- **MAKE SURE YOUR CODE PASSES ALL SYNTAX CHECKS BY RUNNING `npm run build`** + +## Pending Tasks + +Below is a list of functionality we would like to have implemented and fully **unified** in the library in the first place at this time. Most of these tasks are already in progress, implemented for some exchanges, but not all of them: + +- Margin trading +- Leverage +- Derivatives (futures, options) +- Main account / subaccounts +- Conditional orders (stop loss, take profit) +- `transfer` between subaccounts and main account +- `fetchTransfer` +- `fetchTransfers` +- `fetchLedger` +- `fetchPositions` +- `closePosition` +- `closePositions` + +If you want to contribute by submitting partial implementations be sure to look up examples of how it's done inside the library (where implemented already) and copy the adopted practices. + +If your proposal, suggestion or improvement does not relate to the above list of tasks before submitting it make sure it is: +1. really needed by the majority of ccxt users +2. designed to be a general-purpose solution, not hardcoded for your specific needs +3. done in a generalized way compatible with all exchanges (not exchange-specific) +4. portable (available in all supported languages) +5. robust +6. explicit in what it's doing +7. doesn't break anything (if you change a method, make sure that all other methods calling the edited method are not broken) + +The following is a set of rules for contributing to the ccxt library codebase. + +## What You Need To Have + +If you're not going to develop CCXT and contribute code to the CCXT library, then you don't need the Docker image nor the CCXT repository. If you just want to use CCXT inside your project simply install it as a regular package into the project folder as documented in the Manual (https://github.com/ccxt/ccxt/wiki/Install): + +- [JavaScript / Node.js / NPM](https://github.com/ccxt/ccxt/wiki/Install#javascript-npm) + + ```shell + # JavaScript / Node.js / NPM + npm install ccxt + ``` + +- [Python / PIP](https://github.com/ccxt/ccxt/wiki/Install#python) + + ```shell + # Python + pip install ccxt # or pip3 install ccxt + ``` + +- [PHP / Composer](https://github.com/ccxt/ccxt/wiki/Install#php) + + ```shell + # PHP / Composer + composer install ccxt + ``` + +### With Docker + +The easiest way is to use Docker to run an isolated build & test enviroment with all the dependencies installed: + +```shell +docker-compose run --rm ccxt +``` + +That builds a container and opens a shell, where the `npm run build` and `node run-tests` commands should simply work out of the box. + +The CCXT folder is mapped inside of the container, except the `node_modules` folder — the container would have its own ephemeral copy — so that won't mess up your locally installed modules. This means that you can edit sources on your host machine using your favorite editor and build/test them in the running container. + +This way you can keep the build tools and processes isolated, not having to work through the painful process of installing all those dependencies to your host machine manually. + +### Without Docker + +#### Dependencies + +- Git +- [Node.js](https://nodejs.org/en/download/) 8+ +- [Python](https://www.python.org/downloads/) 3.5.3+ + - requests (`pip install requests`) + - [aiohttp](https://docs.aiohttp.org/) (`pip install aiohttp`) + - [tox](https://tox.readthedocs.io) + - via pip: `pip install tox` + - MacOS with [brew](https://brew.sh): `brew install tox` + - Ubuntu Linux: `apt-get install tox` +- [PHP](https://secure.php.net/downloads.php) 5.3+ with the following extensions installed and enabled: + - cURL + - iconv + - mbstring + - PCRE + - bcmath (php<7.1) + +#### Build Steps + +```shell +git clone https://github.com/ccxt/ccxt.git +``` + +```shell +cd ccxt +``` + +```shell +npm install +``` + +```shell +npm run build +``` + +## What You Need To Know + +### Repository Structure + +The contents of the repository are structured as follows: + +```shell +/ # root directory aka npm module/package folder for Node.js +/.babelrc # babel config used for making the ES5 version of the library +/.eslintrc # linter +/.gitattributes # contains linguist settings for language detection in repo +/.gitignore # ignore it +/.npmignore # files to exclude from the NPM package +/.travis.yml # a YAML config for travis-ci (continuous integration) +/CONTRIBUTING.md # this file +/LICENSE.txt # MIT +/README.md # master markdown for GitHub, npmjs.com, npms.io, yarn and others +/build/ # build scripts +/build/export-exchanges.js # used to create tables of exchanges in the docs during the build +/build/transpile.js # the transpilation script +/build/update-badges.js # a JS script to update badges in the README and in docs +/build/vss.js # reads single-sourced version from package.json and writes it everywhere +/dist/ # a folder for the generated browser bundle of CCXT +/ccxt.js # entry point for the master JS version of the ccxt library +/ccxt.php # entry point for the PHP version of the ccxt library +/doc/ # Sphinx-generated rst-docs for http://ccxt.readthedocs.io/ +/js/ # the JS version of the library +/php/ # PHP ccxt module/package folder +/python/ # Python ccxt module/package folder for PyPI +/python/__init__.py # entry point for the Python version of the ccxt.library +/python/async_support/ # asynchronous version of the ccxt.library for Python 3.5.3+ asyncio +/python/base/ # base code for the Python version of the ccxt library +/python/MANIFEST.in # a PyPI-package file listing extra package files (license, configs, etc...) +/python/README.md # a copy of README.md for PyPI +/python/setup.cfg # wheels config file for the Python package +/python/setup.py # pip/setuptools script (build/install) for ccxt in Python +/python/tox.ini # tox config for Python +/examples/ # self-explanatory +/examples/js # ... +/examples/php # ... +/examples/py # ... +/exchanges.cfg # custom bundle config for including only the exchanges you need +/package.json # npm package file, also used in setup.py for version single-sourcing +/run-tests.js # a front-end to run individual tests of all exchanges in all languages (JS/PHP/Python) +/wiki/ # the source of all docs (edits go here) +``` + +### Multilanguage Support + +The ccxt library is available in three different languages (more to come). We encourage developers to design *portable* code, so that a single-language user could read the code in other languages and understand it easily. This helps the adoption of the library. The main goal is to provide a generalized, unified, consistent and robust interface to as many existing cryptocurrency exchanges as possible. + +At first, all language-specific versions were developed in parallel, but separately from each other. But when it became too hard to maintain and keep the code consistent among all supported languages we have decided to switch to what we call a *source/generated* process. There is now a single source version in one language, that is JavaScript. Other language-specific versions are syntactically derived (transpiled, generated) automatically from the source version. But it doesn't mean that you have to be a JS coder to contribute. The portability principle allows Python and PHP devs to effectively participate in developing the source version as well. + +The module entry points are: +- `./python/__init__.py` for the Python pip package +- `./python/async/__init__.py` for the Python 3.5.3+ ccxt.async_support subpackage +- `./ccxt.js` for the Node.js npm package +- `./dist/ccxt.browser.js` for the browser bundle +- `./ccxt.php` for PHP + +Generated versions and docs are transpiled from the source `ccxt.js` file and files in `./js/` by the `npm run build` command. + +### Transpiled (generated) files + +- All derived exchange classes are transpiled automatically from source JS files. The source files are language-agnostic, easily mapped line-to-line to any other language and written in a cross-language-compatible way. Any coder can read it (by design). +- All base classes are **not** transpiled, those are language-specific. + +#### JavaScript + +The `ccxt.browser.js` is generated with Babel from source. + +#### Python + +These files containing derived exchange classes are transpiled from JS into Python: + +- `js/[_a-z].js` → `python/ccxt/async/[_a-z].py` +- `python/ccxt/async[_a-z].py` → `python/ccxt/[_a-z].py` (Python 3 asyncio → Python 2 sync transpilation stage) +- `python/test/test_async.py` → `python/test/test.py` (the sync test is generated from the async test) + +These Python base classes and files are not transpiled: + +- `python/ccxt/base/*` +- `python/ccxt/async/base/*` + +#### PHP + +These files containing derived exchange classes are transpiled from JS into PHP: + +- `js/[_a-z].js` → `php/[_a-z].php` + +These PHP base classes and files are not transpiled: + +- `php/base/*` + +#### Typescript + +- `js/[_a-z].js` → `ccxt.d.ts` + +### Base Class + +```UNDER CONSTRUCTION``` + +### Derived Exchange Classes + +Transpiler is regex-based and heavily relies on specific formatting rules. If you break them then the transpiler will either +fail to generate Python/PHP classes at all or will generate malformed Python/PHP syntax. + +Below are key notes on how to keep the JS code transpileable. + +Use the linter `npm run lint js/your-exchange-implementation.js` before you build. It will cover many (but not all) the issues, +so manual checking will still be required if transpilation fails. + +If you see a `[TypeError] Cannot read property '1' of null` exception or any other transpilation error when you `npm run build`, check if your code satisfies the following rules: + +- don't put empty lines inside your methods +- always use Python-style indentation, it is preserved as is for all languages +- indent with 4 spaces **exactly**, avoid tabs +- put an empty line between each of your methods +- avoid mixed comment styles, use double-slash `//` in JS for line comments +- avoid multi-line comments + +If the transpiling process finishes successfully, but generates incorrect Python/PHP syntax, check for the following: + +- every opening bracket like `(` or `{` should have a space before it! +- do not use language-specific code syntax sugar, even if you really want to +- unfold all maps and comprehensions to basic for-loops +- don't change the arguments of overridden inherited methods, keep them uniform across all exchanges +- do everything with base class methods only (for example, use `this.json ()` for converting objects to json). +- always put a semicolon `;` at the end of each statement, as in PHP/C-style +- all associative keys must be single-quoted strings everywhere, `array['good'], array.bad` +- variables should be declared with `const` or `let` keywords semantically (no `var`!), prefer `const` everywhere + +And structurally: + +- if you need another base method you will have to implement it in all three languages +- do not issue more than one HTTP request from a unified method +- try to reduce syntax to basic one-liner expressions +- multiple lines are ok, but you should avoid deep nesting with lots of brackets +- avoid changing the contents of the arguments and params passed by reference into function calls +- do not use conditional statements that are too complex (heavy if-bracketing) +- do not use heavy ternary conditionals +- avoid operators clutter (**don't do this**: `a && b || c ? d + 80 : e ** f`) +- do not use `.includes()`, use `.indexOf()` instead! +- never use `.toString()` on floats: `Number (0.00000001).toString () === '1e-8'` +- do not use closures, `a.map` or `a.filter (x => (x === 'foobar'))` is not acceptable in derived classes +- do not use the `in` operator to check if a value is in a non-associative array (list) +- don't add custom currency or symbol/pair conversions and formatting, copy from existing code instead +- **don't access non-existent keys, `array['key'] || {}` won't work in other languages!** +- keep it simple, don't do more than one statement in one line + +#### Sending Market Ids + +Most of exchanges' API endpoints will require an exchange-specific market symbol or trading pair or instrument to be specified in the request. + +**We don't send unified symbols to exchanges directly!** They are not interchangeable! There is a significant difference between *exchange-specific market-ids* and *unified symbols*! This is explained in the Manual, here: + +- https://github.com/ccxt/ccxt/wiki/Manual#markets +- https://github.com/ccxt/ccxt/wiki/Manual#symbols-and-market-ids + +**NEVER DO THIS:** + +```JavaScript +async fetchTicker (symbol, params = {}) { + const request = { + 'pair': symbol, // very bad, sending unified symbols to the exchange directly + }; + const response = await this.publicGetEndpoint (request); + // parse in a unified way... +} +``` + +**DO NOT DO THIS EITHER:** + +```JavaScript +async fetchTicker (symbol, params = {}) { + const request = { + 'symbol': symbol, // very bad, sending unified symbols to the exchange directly + }; + const response = await this.publicGetEndpoint (request); + // parse in a unified way... +} +``` + +Instead of sending a unified CCXT symbol to the exchange, we **always** take the exchange-specific market-`id` that corresponds to that symbol. If it so happens that an exchange specific market-id is exactly the same as the CCXT unified symbol – that's a happy coincidence, but we never rely on that in the unified CCXT API. + +To get the exchange-specific market-id by a unified CCXT symbol, use the following methods: + +- `this.market (symbol)` – returns the entire unified market structure, containing the `id`, `baseId`, `quoteId`, and many other interesting things +- `this.marketId (symbol)` – returns just the exchange-specific `id` of a market by a unified symbol (if you don't need anything else) + +**GOOD EXAMPLES:** + +```JavaScript +async fetchTicker (symbol, params = {}) { + const market = this.market (symbol); // the entire market structure + const request = { + 'pair': market['id'], // good, they may me equal, but often differ, it's ok + }; + const response = await this.publicGetEndpoint (this.extend (request, params)); + // parse in a unified way... +} +``` + +```JavaScript +async fetchTicker (symbol, params = {}) { + const marketId = this.marketId (symbol); // just the id + const request = { + 'symbol': marketId, // good, they may me equal, but often differ, it's ok + }; + const response = await this.publicGetEndpoint (this.extend (request, params)); + // parse in a unified way... +} +``` + +#### Parsing Symbols + +When sending requests to the exchange unified symbols have to be _"converted"_ to exchange-specific market-`id`s like shown above. The same is true on the other side – when receiving an exchange response it has an exchange-specific market-`id` inside it that has to be _"converted back"_ to a unified CCXT symbol. + +**We don't put exchange-specific market-`id`s in unified structures directly!** We can't freely interchange symbols with ids! There is a significant difference between an *exchange-specific market-ids* and *unified symbols*! This is explained in the Manual, here: + +- https://github.com/ccxt/ccxt/wiki/Manual#markets +- https://github.com/ccxt/ccxt/wiki/Manual#symbols-and-market-ids + +**NEVER DO THIS:**: + +```JavaScript +parseTrade (trade, market = undefined) { + // parsing code... + return { + 'info': trade, + 'symbol': trade['pair'], // very bad, returning exchange-specific market-ids in a unified structure! + // other fields... + }; +} +``` + +**DO NOT DO THIS EITHER** + +```JavaScript +parseTrade (trade, market = undefined) { + // parsing code... + return { + 'info': trade, + 'symbol': trade['symbol'], // very bad, returning exchange-specific market-ids in a unified structure! + // other fields... + }; +} +``` + +In order to handle the market-`id` properly it has to be looked-up in the info cached on this exchange with `loadMarkets()`: + +**GOOD EXAMPLE:** + +```JavaScript +parseTrade (trade, market = undefined) { + const marketId = this.safeString (trade, 'pair'); + // safeSymbol is used to parse the market id to a unified symbol + const symbol = this.safeSymbol (marketId, market); + return { + 'info': trade, + 'symbol': symbol, // very good, a unified symbol here now + // other fields... + }; +} +``` + +#### Accessing Dictionary Keys + +In JavaScript, dictionary keys can be accessed in two notations: + +- `object['key']` (single-quoted string key notation) +- `object.key` (property notation) + +Both work almost identically, and one is implicitly converted to another upon executing the JavaScript code. + +While the above does work in JavaScript, it will not work in Python or PHP. In most languages, associative dictionary keys are not treated in the same way as properties. Therefore, in Python `object.key` is not the same as `object['key']`. In PHP `$object->key` is not the same as `$object['key']` as well. Languages that differentiate between associative keys and properties use different notations for the two. + +To keep the code transpileable, please, remember this simple rule: *always use the single-quoted string key notation `object['key']` for accessing all associative dictionary keys in all languages everywhere throughout this library!* + +#### Sanitizing Input With `safe`-Methods + +JavaScript is less restrictive than other languages. It will tolerate an attempt to dereference a non-existent key where other languages will throw an Exception: + +```JavaScript +// JavaScript + +const someObject = {} + +if (someObject['nonExistentKey']) { + // the body of this conditional will not execute in JavaScript + // because someObject['nonExistentKey'] === undefined === false + // but JavaScript will not throw an exception on the if-clause +} +``` + +However, the above logic with *"an undefined value by default"* will not work in Python or PHP. + +```Python +# Python +some_dictionary = {} + +# breaks +if some_dictionary['nonExistentKey']: + # in Python the attempt to dereference the nonExistentKey value + # will throw a standard built-in KeyError exception + +# works +if 'nonExistentKey' in some_dictionary and some_dictionary['nonExistentKey']: + # this is a way to check if the key exists before accessing the value + +# also works +if some_dictionary.get('nonExistentKey'): + # another a way to check if the key exists before accessing the value... +``` + +Most languages will not tolerate an attempt to access a non-existent key in an object. + +For the above reasons, please, **never do this** in the transpiled JS files: + +```JavaScript +// JavaScript +const value = object['key'] || other_value; // will not work in Python or PHP! +if (object['key'] || other_value) { /* will not work in Python or PHP! */ } +``` + +Therefore we have a family of `safe*` functions: + +- `safeInteger (object, key)`, `safeInteger2 (object, key1, key2)` +- `safeFloat (object, key)`, `safeFloat2 (object, key1, key2)` +- `safeString (object, key)`, `safeString2 (object, key1, key2)` +- `safeValue (object, key)`, `safeValue2 (object, key1, key2)` + +The `safeValue` function is used for objects inside objects, arrays inside objects and boolean `true/false` values. + +The above safe-functions will check for the existence of the key in the object and will properly return `undefined/None/null` values for JS/Python/PHP. Each function also accepts the default value to be returned instead of `undefined/None/null` in the last argument. + +Alternatively, you could check for the key existence first... + +So, you have to change this: + +```JavaScript +if (params['foo'] !== undefined) { +} +``` + +↓ + +```JavaScript +const foo = this.safeValue (params, 'foo'); +if (foo !== undefined) { +} +``` + +Or: + +```JavaScript +if ('foo' in params) { +} +``` + +#### Using Base Class Cryptography Methods For Authentication + +Do not reinvent the wheel. Always use base-class methods for cryptography. + +The CCXT library supports the following authentication algorithms and cryptography algorithms: + +- HMAC +- JWT (JSON Web Token) +- RSA +- ECDSA Elliptic Curve Cryptography + - NIST P256 + - secp256k1 +- OTP 2FA (one-time password 2-factor authentication) + +The base `Exchange` class offers several methods that are key to practically all cryptography in this lib. Derived exchange implementations must not use external dependencies for cryptography, everything should be done with base methods only. + +- `hash (message, hash = 'md5', digest = 'hex')` +- `hmac (message, secret, hash = 'sha256', digest = 'hex')` +- `jwt (message, secret, hash = 'HS256')` +- `rsa (message, secret, alg = 'RS256')` +- `ecdsa (request, secret, algorithm = 'p256', hash = undefined)` +- `totp (secret)` +- `stringToBase64()`, `base64ToBinary()`, `binaryToBase64()`... + +The `hash()` method supports the following `hash` algorithms: + +- `'md5'` +- `'sha1'` +- `'sha3'` +- `'sha256'` +- `'sha384'` +- `'sha512'` +- `'keccak'` + +The `digest` encoding argument accepts the following values: + +- `'hex'` +- `'binary'` + +The `hmac()` method also supports `'base64'` for the `digest` argument. This is for `hmac()` only, other implementations should use `'binary'` with `binaryToBase64()`. + +#### Timestamps + +**All timestamps throughout all unified structures within this library are integer timestamp _in milliseconds_!** + +In order to convert to milliseconds timestamps, CCXT implements the following methods: + +```JavaScript +const data = { + 'unixTimestampInSeconds': 1565242530, + 'unixTimestampInMilliseconds': 1565242530165, + 'stringInSeconds': '1565242530', +}; + +// convert to integer if the underlying value is already in milliseconds +const timestamp = this.safeInteger (data, 'unixTimestampInMilliseconds'); // === 1565242530165 + +// convert to integer and multiply by a thousand if the value is a UNIX timestamp in seconds +const timestamp = this.safeTimestamp (data, 'unixTimestampInSeconds'); // === 1565242530000 + +// convert to integer and multiply by a thousand if the value is in seconds +const timestamp = this.safeTimestamp (data, 'stringInSeconds'); // === 1565242530000 +``` + +#### Working With Array Lengths + +In JavaScript the common syntax to get a length of a string or an array is to reference the `.length` property like shown here: + +```JavaScript +someArray.length + +// or + +someString.length +``` + +And it works for both strings and arrays. In Python this is done in a similar way: + +```Python +len(some_array) + +# or + +len(some_string) +``` + +So the length is accessible in the same way for both strings and arrays and both work fine. + +However, with PHP this is different, so the syntax for string lengths and array lengths is different: + +```PHP +count(some_array); + +// or + +strlen(some_string); // or mb_strlen +``` + +Because the transpiler works line-by-line and does no code introspection, it cannot tell arrays from strings and cannot properly transpile `.length` to PHP without additional hinting. It will always transpile JS `.length` to PHP `strlen` and will prefer string lengths over array lengths. In order to indicate an array length properly we have to do the following: + +```JavaScript +const arrayLength = someArray.length; +// the above line ends with .length; +// that ending is a hint for the transpiler that will recognize someArray +// as an array variable in this place, rather than a string type variable +// now we can use arrayLength for the arithmetic +``` + +That `.length;` line ending does the trick. The only case when the array `.length` is preferred over the string `.length` is the `for` loop. In the header of the `for` loop, the `.length` always refers to array length (not string length). + +#### Adding Numbers And Concatenating Strings + +In JS the arithmetic addition `+` operator handles both strings and numbers. So, it can concatenate strings with `+` and can sum up numbers with `+` as well. The same is true with Python. With PHP this is different, so it has different operators for string concatenation (the "dot" operator `.`) and for arithmetic addition (the "plus" operator `+`). Once again, because the transpiler does no code introspection it cannot tell if you're adding up numbers or strings in JS. This works fine until you want to transpile this to other languages, be it PHP or whatever other language it is. In order to help the transpiler we have to use `this.sum` for arithmetic additions. + +The rule of thumb is: **`+` is for string concatenation only (!)** and **`this.sum (a, b, c, ...)` is for arithmetic additions**. The same logic applies to operator `+=` vs operator `.=` – `this.sum()` has to be used in those cases as well. + +#### Formatting Decimal Numbers To Precision + +The `.toFixed ()` method has [known rounding bugs](https://www.google.com/search?q=toFixed+bug) in JavaScript, and so do the other rounding methods in the other languages as well. In order to work with number formatting consistently, we need to use the [`decimalToPrecision` method as described in the Manual](https://github.com/ccxt/ccxt/wiki/Manual#methods-for-formatting-decimals). + +#### Escaped Control Characters + +When using strings containing control characters like `"\n"`, `"\t"`, always enclose them in double quotes (`"`), not single quotes (`'`)! Single-quoted strings are not parsed for control characters and are treated as is in many languages apart from JavaScript. Therefore for tabs and newlines to work in PHP, we need to surround them with double quotes (especially in the `sign()` implementation). + +Bad: + +```JavaScript +const a = 'GET' + method.toLowerCase () + '\n' + path; +const b = 'api\nfoobar.com\n'; +``` + +Good: + +```JavaScript +const a = 'GET' + method.toLowerCase () + "\n" + path; // eslint-disable-line quotes +// eslint-disable-next-line quotes +const b = "api\nfoobar.com\n"; +``` + +**↑ The `eslint-*` comments are mandatory!** + +#### Using Ternary Conditionals + +Do not use heavy ternary (`?:`) conditionals, **always use brackets in ternary operators!** + +Despite that there is operator precedence in the programming languages themselves, the transpiler is regex-based and it does no code introspection, therefore it treats everything as plaintext. + +The brackets are needed to hint the transpiler which part of the conditional is which. In the absence of brackets it's hard to understand the line and the intent of the developer. + +Here are some examples of a badly-designed code that will break the transpiler: + +```JavaScript +// this is an example of bad codestyle that will likely break the transpiler +const foo = { + 'bar': 'a' + qux === 'baz' ? this.a () : this.b () + 'b', +}; +``` + +```JavaScript +// this confuses the transpiler and a human developer as well +const foo = 'bar' + baz + qux ? 'a' : '' + this.c (); +``` + +Adding surrounding brackets to corresponding parts would be a more or less correct way to resolve it. + +```JavaScript +const foo = { + 'bar': (qux === 'baz') ? this.a () : this.b (), // much better now +}; +``` + +The universally-working way to solve it is to just break the complex line into a few simpler lines, even at a cost of adding extra lines and conditionals: + +```JavaScript +// before: +// const foo = { +// 'bar': 'a' + qux === 'baz' ? this.a () : this.b () + 'b', +// }; +// ---------------------------------------------------------------------------- +// after: +const bar = (qux === 'baz') ? this.a () : this.b (); +const foo = { + 'bar': 'a' + bar + 'b', +}; +``` + +Or even: + +```JavaScript +// before: +// const foo = 'bar' + baz + qux ? 'a' : '' + this.c (); +// ---------------------------------------------------------------------------- +// after: +let foo = 'bar' + baz; +if (qux) { + foo += 'a'; +}; +foo += this.c (); +``` + +--- + +### New Exchange Integrations + +**REMEMBER:** The key reason why this library is used at all is **Unification**. When developing a new exchange file the goal is not to implement it somehow, but to implement it in a very pedantic, precise and exact way, just as the other exchanges are implemented. For that we have to copy bits of logic from other exchanges and make sure that the new exchange conforms to the Manual in the following aspects: + +- market ids, trading pair symbols, currency ids, token codes, symbolic unification and `commonCurrencies` must be standardized in all parsing methods (`fetchMarkets`, `fetchCurrencies`, `parseTrade`, `parseOrder`, ...) +- all unified API method names and arguments are standard – can't add or change them freely +- all parser input must be `safe`-sanitized as [described above](#sanitizing-input-with-safe-methods) +- for bulk operations the base methods should be used (`parseTrades`, `parseOrders`, note the `s` plural ending) +- use as much of base functionality as you can, do not reinvent the wheel, nor the bicycle, nor the bicycle wheel +- respect default argument values in `fetch`-methods, check if `since` and `limit` are `undefined` and do not send them to the exchange, we intentionally use the exchanges' defaults in such cases +- when implementing a unified method that has some arguments – we can't ignore or miss any of those arguments +- all structures returned from the unified methods must conform to their specifications from the Manual +- all API endpoints have to be listed out with proper support for params substituted in the URLs + +Please, see the following document for new integrations: https://github.com/ccxt/ccxt/wiki/Requirements + +A quick merge of a Pull Request for a new exchange integration depends on consistency and compliance with the above unified rules and standards. Breaking one of those is the key reason for not merging a Pull Request. + +**If you want to add (support for) another exchange, or implement a new method for a particular exchange, then the best way to make it a consistent improvement is to learn from example. Take a look at how same things are implemented in other exchanges (we recommend certified exchanges) and try to copy the code flow and style.** + +The basic JSON-skeleton for a new exchange integration is as follows: + +``` +{ + 'id': 'example', + 'name': 'Example Exchange', + 'country': [ 'US', 'EU', 'CN', 'RU' ], + 'rateLimit': 1000, + 'version': '1', + 'comment': 'This comment is optional', + 'urls': { + 'logo': 'https://example.com/image.jpg', + 'api': 'https://api.example.com/api', + 'www': 'https://www.example.com', + 'doc': [ + 'https://www.example.com/docs/api', + 'https://www.example.com/docs/howto', + 'https://github.com/example/docs', + ], + }, + 'api': { + 'public': { + 'get': [ + 'endpoint/example', + 'orderbook/{pair}/full', + '{pair}/ticker', + ], + }, + 'private': { + 'post': [ + 'balance', + ], + }, + }, +} +``` + +### Implicit API Methods + +In the code for each exchange, you'll notice that the functions that make API requests aren't explicitly defined. This is because the `api` definition in the exchange description JSON is used to create *magic functions* (aka *partial functions* or *closures*) inside the exchange subclass. That implicit injection is done by the `defineRestApi/define_rest_api` base exchange method. + +Each partial function takes a dictionary of `params` and returns the API response. In the example JSON above, the `'endpoint/example'` results in the injection of a `this.publicGetEndpointExample` function. Similarly, the `'orderbook/{pair}/full'` results in a `this.publicGetOrderbookPairFull` function, that takes a ``pair`` parameter (again, passed in the `params` argument). + +Upon instantiation the base exchange class takes each URL from its list of endpoints, splits it into words, and then makes up a callable function name from those words by using a partial construct. That process is the same in JS, Python and PHP as well. It is also described here: + +- https://github.com/ccxt/ccxt/wiki/Manual#api-methods--endpoints +- https://github.com/ccxt/ccxt/wiki/Manual#implicit-api-methods +- https://github.com/ccxt-dev/ccxt/wiki/Manual#api-method-naming-conventions + +```UNDER CONSTRUCTION``` + +### Continuous Integration + +Builds are automated with [Travis CI](https://travis-ci.org/ccxt/ccxt). The build steps for Travis CI are described in the [`.travis.yml`](https://github.com/ccxt/ccxt/blob/master/.travis.yml) file. + +Windows builds are automated with [Appveyor](https://ci.appveyor.com/project/ccxt/ccxt). The build steps for Appveyor are in the [`appveyor.yml`](https://github.com/ccxt/ccxt/blob/master/appveyor.yml) file. + +Incoming pull requests are automatically validated by the CI service. You can watch the build process online here: [travis-ci.org/ccxt/ccxt/builds](https://travis-ci.org/ccxt/ccxt/builds). + +### How To Build & Run Tests On Your Local Machine + +Before building for the first time, install Node dependencies (skip this step if you're running our Docker image): + +``` +npm install +``` + +The command below will build everything and generate PHP/Python versions from source JS files: + +``` +npm run build +``` + +The following command will test the built generated files (for all exchanges, symbols and languages): + +``` +node run-tests +``` + +You can restrict tests to a specific language, a particular exchange or symbol: + +``` +node run-tests [--php] [--js] [--python] [--python3] [exchange] [symbol] +``` + +For example, the first of the following lines will only test the source JS version of the library (`ccxt.js`). It does not require an `npm run build` before running it (can be useful if you need to verify quickly whether your changes break the code or not): + +```shell + +node run-tests --js # test master ccxt.js, all exchanges + +# other examples require the 'npm run build' to run + +node run-tests --python # test Python 2 version, all exchanges +node run-tests --php bitfinex # test Bitfinex with PHP +node run-tests --python3 kraken # test Kraken with Python 3, requires 'npm run build' +``` + +## Committing Changes To The Repository + +The build process generates many changes in the transpiled exchange files, e.g. for Python and PHP. **You should NOT commit them to GitHub, commit only the base (JS) file changes please**. + +You can hide the changes in the generated files by running this command (after that, the generated files are no longer marked as changed): + +``` +npm run git-ignore-generated-files +``` + +Previously we had that command implemented as a final build step, but it caused problems with subsequent `git pull` and also branch selection commands (when a conflict occurred in those files that have been marked as ignored). So if you experience an issue with that, you can un-ignore those files by executing: + +``` +npm run git-unignore-generated-files +``` + +## Financial Contributions + +We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/ccxt). +Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed. + +``` +ETH 0x26a3CB49578F07000575405a57888681249c35Fd (ETH only!) +BTC 33RmVRfhK2WZVQR1R83h2e9yXoqRNDvJva +BCH 1GN9p233TvNcNQFthCgfiHUnj5JRKEc2Ze +LTC LbT8mkAqQBphc4yxLXEDgYDfEax74et3bP +``` + +## Credits + +### Contributors + +Thank you to all the people who have already contributed to ccxt! + + + +### Backers + +Thank you to all our backers! [[Become a backer](https://opencollective.com/ccxt#backer)] + + + +### Supporters + +Support this project by becoming a supporter. Your avatar will show up here with a link to your website. + +[[Become a supporter](https://opencollective.com/ccxt#supporter)] + + + + + + + + + + + + +### Sponsors + +Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/ccxt#sponsor)) + +[[Become a sponsor](https://opencollective.com/ccxt#sponsor)] + + + + + + + + + + + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/LICENSE.txt b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/LICENSE.txt new file mode 100644 index 000000000..34bd60267 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2017 Igor Kroitor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/README.md b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/README.md new file mode 100644 index 000000000..157fbb463 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/README.md @@ -0,0 +1,551 @@ +# CCXT – CryptoCurrency eXchange Trading Library + +[![Build Status](https://travis-ci.com/ccxt/ccxt.svg?branch=master)](https://travis-ci.com/ccxt/ccxt) [![npm](https://img.shields.io/npm/v/ccxt.svg)](https://npmjs.com/package/ccxt) [![PyPI](https://img.shields.io/pypi/v/ccxt.svg)](https://pypi.python.org/pypi/ccxt) [![NPM Downloads](https://img.shields.io/npm/dm/ccxt.svg)](https://www.npmjs.com/package/ccxt) [![Discord](https://img.shields.io/discord/690203284119617602?logo=discord&logoColor=white)](https://discord.gg/dhzSKYU) [![Supported Exchanges](https://img.shields.io/badge/exchanges-123-blue.svg)](https://github.com/ccxt/ccxt/wiki/Exchange-Markets) [![Open Collective](https://opencollective.com/ccxt/backers/badge.svg)](https://opencollective.com/ccxt) +[![Twitter Follow](https://img.shields.io/twitter/follow/ccxt_official.svg?style=social&label=CCXT)](https://twitter.com/ccxt_official) + +A JavaScript / Python / PHP library for cryptocurrency trading and e-commerce with support for many bitcoin/ether/altcoin exchange markets and merchant APIs. + +### [Install](#install) · [Usage](#usage) · [Manual](https://github.com/ccxt/ccxt/wiki) · [FAQ](https://github.com/ccxt/ccxt/wiki/FAQ) · [Examples](https://github.com/ccxt/ccxt/tree/master/examples) · [Contributing](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md) · [Social](#social) + +The **CCXT** library is used to connect and trade with cryptocurrency exchanges and payment processing services worldwide. It provides quick access to market data for storage, analysis, visualization, indicator development, algorithmic trading, strategy backtesting, bot programming, and related software engineering. + +It is intended to be used by **coders, developers, technically-skilled traders, data-scientists and financial analysts** for building trading algorithms. + +Current feature list: + +- support for many cryptocurrency exchanges — more coming soon +- fully implemented public and private APIs +- optional normalized data for cross-exchange analytics and arbitrage +- an out of the box unified API that is extremely easy to integrate +- works in Node 7.6+, Python 3, PHP 5.4+, and web browsers + +## Sponsored Promotion + +[![CCXT Pro – A JavaScript / Python / PHP cryptocurrency exchange trading WebSocket API for professionals](https://user-images.githubusercontent.com/1294454/93346896-22dfb080-f83d-11ea-957f-6b8ae008f62a.png)](https://ccxt.pro) **[A JavaScript / Python / PHP cryptocurrency exchange trading WebSocket API for professionals](https://ccxt.pro)** + +## See Also + +- [![Quadency](https://user-images.githubusercontent.com/1294454/69334382-a0618100-0c6b-11ea-9ba9-40aa97440cfa.png)](https://quadency.com?utm_source=ccxt)  **[Quadency](https://quadency.com?utm_source=ccxt)** — trading automation terminal for leading exchanges with streaming APIs + Python IDE. +- [![TabTrader](https://user-images.githubusercontent.com/1294454/66755907-9c3e8880-eea1-11e9-846e-0bff349ceb87.png)](https://tab-trader.com/?utm_source=ccxt)  **[TabTrader](https://tab-trader.com/?utm_source=ccxt)** — trading on all exchanges in one app. Avaliable on [Android](https://play.google.com/store/apps/details?id=com.tabtrader.android&referrer=utm_source%3Dccxt) and [iOS](https://itunes.apple.com/app/apple-store/id1095716562?mt=8). + + +## Certified Cryptocurrency Exchanges + + +| logo | id | name | ver | doc | certified | pro | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------|:---:|:----------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| +| [![binance](https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg)](https://www.binance.com/?ref=10205187) | binance | [Binance](https://www.binance.com/?ref=10205187) | * | [API](https://binance-docs.github.io/apidocs/spot/en) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitfinex](https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg)](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 1 | [API](https://docs.bitfinex.com/v1/docs) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bittrex](https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg)](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | bittrex | [Bittrex](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | 3 | [API](https://bittrex.github.io/api/v3) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitvavo](https://user-images.githubusercontent.com/1294454/83165440-2f1cf200-a116-11ea-9046-a255d09fb2ed.jpg)](https://bitvavo.com/?a=24F34952F7) | bitvavo | [Bitvavo](https://bitvavo.com/?a=24F34952F7) | 2 | [API](https://docs.bitvavo.com/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bytetrade](https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg)](https://www.byte-trade.com) | bytetrade | [ByteTrade](https://www.byte-trade.com) | * | [API](https://docs.byte-trade.com/#description) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![currencycom](https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg)](https://currency.com/trading/signup?c=362jaimv&pid=referral) | currencycom | [Currency.com](https://currency.com/trading/signup?c=362jaimv&pid=referral) | 1 | [API](https://currency.com/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![eterbase](https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg)](https://eterbase.exchange/invite/1wjjh4Pe) | eterbase | [Eterbase](https://eterbase.exchange/invite/1wjjh4Pe) | 1 | [API](https://developers.eterbase.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![ftx](https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg)](https://ftx.com/#a=1623029) | ftx | [FTX](https://ftx.com/#a=1623029) | * | [API](https://github.com/ftexchange/ftx) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![idex](https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg)](https://idex.io) | idex | [IDEX](https://idex.io) | 2 | [API](https://docs.idex.io/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![kraken](https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg)](https://www.kraken.com) | kraken | [Kraken](https://www.kraken.com) | 0 | [API](https://www.kraken.com/features/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![upbit](https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg)](https://upbit.com) | upbit | [Upbit](https://upbit.com) | 1 | [API](https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![wavesexchange](https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg)](https://waves.exchange) | wavesexchange | [Waves.Exchange](https://waves.exchange) | * | [API](https://docs.waves.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![xena](https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg)](https://xena.exchange) | xena | [Xena Exchange](https://xena.exchange) | * | [API](https://support.xena.exchange/support/solutions/44000808700) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | + +## Supported Cryptocurrency Exchange Markets + +The CCXT library currently supports the following 123 cryptocurrency exchange markets and trading APIs: + +| logo | id | name | ver | doc | certified | pro | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|-----------------------------------------------------------------------------------------|:---:|:-------------------------------------------------------------------------------------------:|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------| +| [![acx](https://user-images.githubusercontent.com/1294454/30247614-1fe61c74-9621-11e7-9e8c-f1a627afa279.jpg)](https://acx.io) | acx | [ACX](https://acx.io) | 2 | [API](https://acx.io/documents/api_v2) | | | +| [![aofex](https://user-images.githubusercontent.com/51840849/77670271-056d1080-6f97-11ea-9ac2-4268e9ed0c1f.jpg)](https://aofex.com/#/register?key=9763840) | aofex | [AOFEX](https://aofex.com/#/register?key=9763840) | * | [API](https://aofex.zendesk.com/hc/en-us/sections/360005576574-API) | | | +| [![bcex](https://user-images.githubusercontent.com/51840849/77231516-851c6900-6bac-11ea-8fd6-ee5c23eddbd4.jpg)](https://www.bcex.top/register?invite_code=758978&lang=en) | bcex | [BCEX](https://www.bcex.top/register?invite_code=758978&lang=en) | 1 | [API](https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Interface) | | | +| [![bequant](https://user-images.githubusercontent.com/1294454/55248342-a75dfe00-525a-11e9-8aa2-05e9dca943c6.jpg)](https://bequant.io) | bequant | [Bequant](https://bequant.io) | 2 | [API](https://api.bequant.io/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bibox](https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg)](https://w2.bibox365.com/login/register?invite_code=05Kj3I) | bibox | [Bibox](https://w2.bibox365.com/login/register?invite_code=05Kj3I) | 1 | [API](https://biboxcom.github.io/en/) | | | +| [![bigone](https://user-images.githubusercontent.com/1294454/69354403-1d532180-0c91-11ea-88ed-44c06cefdf87.jpg)](https://b1.run/users/new?code=D3LLBVFT) | bigone | [BigONE](https://b1.run/users/new?code=D3LLBVFT) | 3 | [API](https://open.big.one/docs/api.html) | | | +| [![binance](https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg)](https://www.binance.com/?ref=10205187) | binance | [Binance](https://www.binance.com/?ref=10205187) | * | [API](https://binance-docs.github.io/apidocs/spot/en) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![binanceus](https://user-images.githubusercontent.com/1294454/65177307-217b7c80-da5f-11e9-876e-0b748ba0a358.jpg)](https://www.binance.us/?ref=35005074) | binanceus | [Binance US](https://www.binance.us/?ref=35005074) | * | [API](https://github.com/binance-us/binance-official-api-docs) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bit2c](https://user-images.githubusercontent.com/1294454/27766119-3593220e-5ece-11e7-8b3a-5a041f6bcc3f.jpg)](https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf) | bit2c | [Bit2C](https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf) | * | [API](https://www.bit2c.co.il/home/api) | | | +| [![bitbank](https://user-images.githubusercontent.com/1294454/37808081-b87f2d9c-2e59-11e8-894d-c1900b7584fe.jpg)](https://bitbank.cc/) | bitbank | [bitbank](https://bitbank.cc/) | 1 | [API](https://docs.bitbank.cc/) | | | +| [![bitbay](https://user-images.githubusercontent.com/1294454/27766132-978a7bd8-5ece-11e7-9540-bc96d1e9bbb8.jpg)](https://auth.bitbay.net/ref/jHlbB4mIkdS1) | bitbay | [BitBay](https://auth.bitbay.net/ref/jHlbB4mIkdS1) | * | [API](https://bitbay.net/public-api) | | | +| [![bitcoincom](https://user-images.githubusercontent.com/1294454/97296144-514fa300-1861-11eb-952b-3d55d492200b.jpg)](https://exchange.bitcoin.com/referral/da948b21d6c92d69) | bitcoincom | [bitcoin.com](https://exchange.bitcoin.com/referral/da948b21d6c92d69) | 2 | [API](https://api.exchange.bitcoin.com/api/2/explore) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitfinex](https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg)](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 1 | [API](https://docs.bitfinex.com/v1/docs) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitfinex2](https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg)](https://www.bitfinex.com/?refcode=P61eYxFL) | bitfinex2 | [Bitfinex](https://www.bitfinex.com/?refcode=P61eYxFL) | 2 | [API](https://docs.bitfinex.com/v2/docs/) | | | +| [![bitflyer](https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg)](https://bitflyer.com) | bitflyer | [bitFlyer](https://bitflyer.com) | 1 | [API](https://lightning.bitflyer.com/docs?lang=en) | | | +| [![bitforex](https://user-images.githubusercontent.com/51840849/87295553-1160ec00-c50e-11ea-8ea0-df79276a9646.jpg)](https://www.bitforex.com/en/invitationRegister?inviterId=1867438) | bitforex | [Bitforex](https://www.bitforex.com/en/invitationRegister?inviterId=1867438) | 1 | [API](https://github.com/githubdev2020/API_Doc_en/wiki) | | | +| [![bitget](https://user-images.githubusercontent.com/51840849/88317935-a8a21c80-cd22-11ea-8e2b-4b9fac5975eb.jpg)](https://www.bitget.com/expressly?languageType=0&channelCode=ccxt&vipCode=tg9j) | bitget | [Bitget](https://www.bitget.com/expressly?languageType=0&channelCode=ccxt&vipCode=tg9j) | 3 | [API](https://bitgetlimited.github.io/apidoc/en/swap) | | | +| [![bithumb](https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg)](https://www.bithumb.com) | bithumb | [Bithumb](https://www.bithumb.com) | * | [API](https://apidocs.bithumb.com) | | | +| [![bitkk](https://user-images.githubusercontent.com/1294454/32859187-cd5214f0-ca5e-11e7-967d-96568e2e2bd1.jpg)](https://www.bitkk.com) | bitkk | [bitkk](https://www.bitkk.com) | 1 | [API](https://www.bitkk.com/i/developer) | | | +| [![bitmart](https://user-images.githubusercontent.com/1294454/61835713-a2662f80-ae85-11e9-9d00-6442919701fd.jpg)](http://www.bitmart.com/?r=rQCFLh) | bitmart | [BitMart](http://www.bitmart.com/?r=rQCFLh) | 1 | [API](https://github.com/bitmartexchange/bitmart-official-api-docs) | | | +| [![bitmax](https://user-images.githubusercontent.com/1294454/66820319-19710880-ef49-11e9-8fbe-16be62a11992.jpg)](https://bitmax.io/#/register?inviteCode=EL6BXBQM) | bitmax | [BitMax](https://bitmax.io/#/register?inviteCode=EL6BXBQM) | 1 | [API](https://bitmax-exchange.github.io/bitmax-pro-api/#bitmax-pro-api-documentation) | | | +| [![bitmex](https://user-images.githubusercontent.com/1294454/27766319-f653c6e6-5ed4-11e7-933d-f0bc3699ae8f.jpg)](https://www.bitmex.com/register/upZpOX) | bitmex | [BitMEX](https://www.bitmex.com/register/upZpOX) | 1 | [API](https://www.bitmex.com/app/apiOverview) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitpanda](https://user-images.githubusercontent.com/51840849/87591171-9a377d80-c6f0-11ea-94ac-97a126eac3bc.jpg)](https://www.bitpanda.com/en/pro) | bitpanda | [Bitpanda Pro](https://www.bitpanda.com/en/pro) | 1 | [API](https://developers.bitpanda.com/exchange/) | | | +| [![bitso](https://user-images.githubusercontent.com/51840849/87295554-11f98280-c50e-11ea-80d6-15b3bafa8cbf.jpg)](https://bitso.com/?ref=itej) | bitso | [Bitso](https://bitso.com/?ref=itej) | 3 | [API](https://bitso.com/api_info) | | | +| [![bitstamp](https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg)](https://www.bitstamp.net) | bitstamp | [Bitstamp](https://www.bitstamp.net) | 2 | [API](https://www.bitstamp.net/api) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitstamp1](https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg)](https://www.bitstamp.net) | bitstamp1 | [Bitstamp](https://www.bitstamp.net) | 1 | [API](https://www.bitstamp.net/api) | | | +| [![bittrex](https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg)](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | bittrex | [Bittrex](https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B) | 3 | [API](https://bittrex.github.io/api/v3) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitvavo](https://user-images.githubusercontent.com/1294454/83165440-2f1cf200-a116-11ea-9046-a255d09fb2ed.jpg)](https://bitvavo.com/?a=24F34952F7) | bitvavo | [Bitvavo](https://bitvavo.com/?a=24F34952F7) | 2 | [API](https://docs.bitvavo.com/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![bitz](https://user-images.githubusercontent.com/51840849/87443304-fec5e000-c5fd-11ea-98f8-ba8e67f7eaff.jpg)](https://u.bitz.com/register?invite_code=1429193) | bitz | [Bit-Z](https://u.bitz.com/register?invite_code=1429193) | 2 | [API](https://apidoc.bitz.com/en/) | | | +| [![bl3p](https://user-images.githubusercontent.com/1294454/28501752-60c21b82-6feb-11e7-818b-055ee6d0e754.jpg)](https://bl3p.eu) | bl3p | [BL3P](https://bl3p.eu) | 1 | [API](https://github.com/BitonicNL/bl3p-api/tree/master/docs) | | | +| [![bleutrade](https://user-images.githubusercontent.com/1294454/30303000-b602dbe6-976d-11e7-956d-36c5049c01e7.jpg)](https://bleutrade.com) | bleutrade | [Bleutrade](https://bleutrade.com) | * | [API](https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0) | | | +| [![braziliex](https://user-images.githubusercontent.com/1294454/34703593-c4498674-f504-11e7-8d14-ff8e44fb78c1.jpg)](https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465) | braziliex | [Braziliex](https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465) | * | [API](https://braziliex.com/exchange/api.php) | | | +| [![btcalpha](https://user-images.githubusercontent.com/1294454/42625213-dabaa5da-85cf-11e8-8f99-aa8f8f7699f0.jpg)](https://btc-alpha.com/?r=123788) | btcalpha | [BTC-Alpha](https://btc-alpha.com/?r=123788) | 1 | [API](https://btc-alpha.github.io/api-docs) | | | +| [![btcbox](https://user-images.githubusercontent.com/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg)](https://www.btcbox.co.jp/) | btcbox | [BtcBox](https://www.btcbox.co.jp/) | 1 | [API](https://blog.btcbox.jp/en/archives/8762) | | | +| [![btcmarkets](https://user-images.githubusercontent.com/51840849/89731817-b3fb8480-da52-11ea-817f-783b08aaf32b.jpg)](https://btcmarkets.net) | btcmarkets | [BTC Markets](https://btcmarkets.net) | 3 | [API](https://api.btcmarkets.net/doc/v3) | | | +| [![btctradeua](https://user-images.githubusercontent.com/1294454/27941483-79fc7350-62d9-11e7-9f61-ac47f28fcd96.jpg)](https://btc-trade.com.ua/registration/22689) | btctradeua | [BTC Trade UA](https://btc-trade.com.ua/registration/22689) | * | [API](https://docs.google.com/document/d/1ocYA0yMy_RXd561sfG3qEPZ80kyll36HUxvCRe5GbhE/edit) | | | +| [![btcturk](https://user-images.githubusercontent.com/51840849/87153926-efbef500-c2c0-11ea-9842-05b63612c4b9.jpg)](https://www.btcturk.com) | btcturk | [BTCTurk](https://www.btcturk.com) | * | [API](https://github.com/BTCTrader/broker-api-docs) | | | +| [![buda](https://user-images.githubusercontent.com/1294454/47380619-8a029200-d706-11e8-91e0-8a391fe48de3.jpg)](https://www.buda.com) | buda | [Buda](https://www.buda.com) | 2 | [API](https://api.buda.com) | | | +| [![bw](https://user-images.githubusercontent.com/1294454/69436317-31128c80-0d52-11ea-91d1-eb7bb5818812.jpg)](https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0) | bw | [BW](https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0) | 1 | [API](https://github.com/bw-exchange/api_docs_en/wiki) | | | +| [![bybit](https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg)](https://www.bybit.com/app/register?ref=X7Prm) | bybit | [Bybit](https://www.bybit.com/app/register?ref=X7Prm) | 2 | [API](https://bybit-exchange.github.io/docs/inverse/) | | | +| [![bytetrade](https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg)](https://www.byte-trade.com) | bytetrade | [ByteTrade](https://www.byte-trade.com) | * | [API](https://docs.byte-trade.com/#description) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![cex](https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg)](https://cex.io/r/0/up105393824/0/) | cex | [CEX.IO](https://cex.io/r/0/up105393824/0/) | * | [API](https://cex.io/cex-api) | | | +| [![chilebit](https://user-images.githubusercontent.com/1294454/27991414-1298f0d8-647f-11e7-9c40-d56409266336.jpg)](https://chilebit.net) | chilebit | [ChileBit](https://chilebit.net) | 1 | [API](https://blinktrade.com/docs) | | | +| [![coinbase](https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg)](https://www.coinbase.com/join/58cbe25a355148797479dbd2) | coinbase | [Coinbase](https://www.coinbase.com/join/58cbe25a355148797479dbd2) | 2 | [API](https://developers.coinbase.com/api/v2) | | | +| [![coinbaseprime](https://user-images.githubusercontent.com/1294454/44539184-29f26e00-a70c-11e8-868f-e907fc236a7c.jpg)](https://prime.coinbase.com) | coinbaseprime | [Coinbase Prime](https://prime.coinbase.com) | * | [API](https://docs.prime.coinbase.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![coinbasepro](https://user-images.githubusercontent.com/1294454/41764625-63b7ffde-760a-11e8-996d-a6328fa9347a.jpg)](https://pro.coinbase.com/) | coinbasepro | [Coinbase Pro](https://pro.coinbase.com/) | * | [API](https://docs.pro.coinbase.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![coincheck](https://user-images.githubusercontent.com/51840849/87182088-1d6d6380-c2ec-11ea-9c64-8ab9f9b289f5.jpg)](https://coincheck.com) | coincheck | [coincheck](https://coincheck.com) | * | [API](https://coincheck.com/documents/exchange/api) | | | +| [![coinegg](https://user-images.githubusercontent.com/1294454/36770310-adfa764e-1c5a-11e8-8e09-449daac3d2fb.jpg)](https://www.coinegg.com/user/register?invite=523218) | coinegg | [CoinEgg](https://www.coinegg.com/user/register?invite=523218) | * | [API](https://www.coinegg.com/explain.api.html) | | | +| [![coinex](https://user-images.githubusercontent.com/51840849/87182089-1e05fa00-c2ec-11ea-8da9-cc73b45abbbc.jpg)](https://www.coinex.com/register?refer_code=yw5fz) | coinex | [CoinEx](https://www.coinex.com/register?refer_code=yw5fz) | 1 | [API](https://github.com/coinexcom/coinex_exchange_api/wiki) | | | +| [![coinfalcon](https://user-images.githubusercontent.com/1294454/41822275-ed982188-77f5-11e8-92bb-496bcd14ca52.jpg)](https://coinfalcon.com/?ref=CFJSVGTUPASB) | coinfalcon | [CoinFalcon](https://coinfalcon.com/?ref=CFJSVGTUPASB) | 1 | [API](https://docs.coinfalcon.com) | | | +| [![coinfloor](https://user-images.githubusercontent.com/51840849/87153925-ef265e80-c2c0-11ea-91b5-020c804b90e0.jpg)](https://www.coinfloor.co.uk) | coinfloor | [coinfloor](https://www.coinfloor.co.uk) | * | [API](https://github.com/coinfloor/api) | | | +| [![coingi](https://user-images.githubusercontent.com/1294454/28619707-5c9232a8-7212-11e7-86d6-98fe5d15cc6e.jpg)](https://www.coingi.com/?r=XTPPMC) | coingi | [Coingi](https://www.coingi.com/?r=XTPPMC) | * | [API](https://coingi.docs.apiary.io) | | | +| [![coinmarketcap](https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg)](https://coinmarketcap.com) | coinmarketcap | [CoinMarketCap](https://coinmarketcap.com) | 1 | [API](https://coinmarketcap.com/api) | | | +| [![coinmate](https://user-images.githubusercontent.com/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg)](https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0) | coinmate | [CoinMate](https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0) | * | [API](https://coinmate.docs.apiary.io) | | | +| [![coinone](https://user-images.githubusercontent.com/1294454/38003300-adc12fba-323f-11e8-8525-725f53c4a659.jpg)](https://coinone.co.kr) | coinone | [CoinOne](https://coinone.co.kr) | 2 | [API](https://doc.coinone.co.kr) | | | +| [![coinspot](https://user-images.githubusercontent.com/1294454/28208429-3cacdf9a-6896-11e7-854e-4c79a772a30f.jpg)](https://www.coinspot.com.au/register?code=PJURCU) | coinspot | [CoinSpot](https://www.coinspot.com.au/register?code=PJURCU) | * | [API](https://www.coinspot.com.au/api) | | | +| [![crex24](https://user-images.githubusercontent.com/1294454/47813922-6f12cc00-dd5d-11e8-97c6-70f957712d47.jpg)](https://crex24.com/?refid=slxsjsjtil8xexl9hksr) | crex24 | [CREX24](https://crex24.com/?refid=slxsjsjtil8xexl9hksr) | 2 | [API](https://docs.crex24.com/trade-api/v2) | | | +| [![currencycom](https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg)](https://currency.com/trading/signup?c=362jaimv&pid=referral) | currencycom | [Currency.com](https://currency.com/trading/signup?c=362jaimv&pid=referral) | 1 | [API](https://currency.com/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![deribit](https://user-images.githubusercontent.com/1294454/41933112-9e2dd65a-798b-11e8-8440-5bab2959fcb8.jpg)](https://www.deribit.com/reg-1189.4038) | deribit | [Deribit](https://www.deribit.com/reg-1189.4038) | 2 | [API](https://docs.deribit.com/v2) | | | +| [![digifinex](https://user-images.githubusercontent.com/51840849/87443315-01283a00-c5fe-11ea-8628-c2a0feaf07ac.jpg)](https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp) | digifinex | [DigiFinex](https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp) | 3 | [API](https://docs.digifinex.com) | | | +| [![dsx](https://user-images.githubusercontent.com/51840849/76909626-cb2bb100-68bc-11ea-99e0-28ba54f04792.jpg)](https://dsxglobal.com) | dsx | [DSX](https://dsxglobal.com) | 3 | [API](https://dsxglobal.com/developers/publicApi) | | | +| [![eterbase](https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg)](https://eterbase.exchange/invite/1wjjh4Pe) | eterbase | [Eterbase](https://eterbase.exchange/invite/1wjjh4Pe) | 1 | [API](https://developers.eterbase.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![exmo](https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg)](https://exmo.me/?ref=131685) | exmo | [EXMO](https://exmo.me/?ref=131685) | 1.1 | [API](https://exmo.me/en/api_doc?ref=131685) | | | +| [![exx](https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg)](https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1) | exx | [EXX](https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1) | * | [API](https://www.exx.com/help/restApi) | | | +| [![fcoin](https://user-images.githubusercontent.com/1294454/42244210-c8c42e1e-7f1c-11e8-8710-a5fb63b165c4.jpg)](https://www.fcoin.com/i/Z5P7V) | fcoin | [FCoin](https://www.fcoin.com/i/Z5P7V) | 2 | [API](https://developer.fcoin.com) | | | +| [![fcoinjp](https://user-images.githubusercontent.com/1294454/54219174-08b66b00-4500-11e9-862d-f522d0fe08c6.jpg)](https://www.fcoinjp.com) | fcoinjp | [FCoinJP](https://www.fcoinjp.com) | 2 | [API](https://developer.fcoin.com) | | | +| [![flowbtc](https://user-images.githubusercontent.com/51840849/87443317-01c0d080-c5fe-11ea-95c2-9ebe1a8fafd9.jpg)](https://www.flowbtc.com.br) | flowbtc | [flowBTC](https://www.flowbtc.com.br) | 1 | [API](https://www.flowbtc.com.br/api.html) | | | +| [![foxbit](https://user-images.githubusercontent.com/51840849/87443320-01c0d080-c5fe-11ea-92e2-4ef56d32b026.jpg)](https://foxbit.com.br/exchange) | foxbit | [FoxBit](https://foxbit.com.br/exchange) | 1 | [API](https://foxbit.com.br/api/) | | | +| [![ftx](https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg)](https://ftx.com/#a=1623029) | ftx | [FTX](https://ftx.com/#a=1623029) | * | [API](https://github.com/ftexchange/ftx) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![gateio](https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg)](https://www.gate.io/signup/2436035) | gateio | [Gate.io](https://www.gate.io/signup/2436035) | 2 | [API](https://gate.io/api2) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![gemini](https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg)](https://gemini.com/) | gemini | [Gemini](https://gemini.com/) | 1 | [API](https://docs.gemini.com/rest-api) | | | +| [![hbtc](https://user-images.githubusercontent.com/51840849/80134449-70663300-85a7-11ea-8942-e204cdeaab5d.jpg)](https://www.hbtc.com/register/O2S8NS) | hbtc | [HBTC](https://www.hbtc.com/register/O2S8NS) | 1 | [API](https://github.com/bhexopen/BHEX-OpenApi/tree/master/doc) | | | +| [![hitbtc](https://user-images.githubusercontent.com/1294454/27766555-8eaec20e-5edc-11e7-9c5b-6dc69fc42f5e.jpg)](https://hitbtc.com/?ref_id=5a5d39a65d466) | hitbtc | [HitBTC](https://hitbtc.com/?ref_id=5a5d39a65d466) | 2 | [API](https://api.hitbtc.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![hollaex](https://user-images.githubusercontent.com/1294454/75841031-ca375180-5ddd-11ea-8417-b975674c23cb.jpg)](https://pro.hollaex.com/signup?affiliation_code=QSWA6G) | hollaex | [HollaEx](https://pro.hollaex.com/signup?affiliation_code=QSWA6G) | 1 | [API](https://apidocs.hollaex.com) | | | +| [![huobijp](https://user-images.githubusercontent.com/1294454/85734211-85755480-b705-11ea-8b35-0b7f1db33a2f.jpg)](https://www.huobi.co.jp/register/?invite_code=znnq3) | huobijp | [Huobi Japan](https://www.huobi.co.jp/register/?invite_code=znnq3) | 1 | [API](https://api-doc.huobi.co.jp) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![huobipro](https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg)](https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3) | huobipro | [Huobi Pro](https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3) | 1 | [API](https://huobiapi.github.io/docs/spot/v1/cn/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![huobiru](https://user-images.githubusercontent.com/1294454/52978816-e8552e00-33e3-11e9-98ed-845acfece834.jpg)](https://www.huobi.com.ru/invite?invite_code=esc74) | huobiru | [Huobi Russia](https://www.huobi.com.ru/invite?invite_code=esc74) | 1 | [API](https://github.com/cloudapidoc/API_Docs_en) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![ice3x](https://user-images.githubusercontent.com/51840849/87460809-1dd06c00-c616-11ea-98ad-7d5e1cb7fcdd.jpg)](https://ice3x.com?ref=14341802) | ice3x | [ICE3X](https://ice3x.com?ref=14341802) | 1 | [API](https://ice3x.co.za/ice-cubed-bitcoin-exchange-api-documentation-1-june-2017) | | | +| [![idex](https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg)](https://idex.io) | idex | [IDEX](https://idex.io) | 2 | [API](https://docs.idex.io/) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![independentreserve](https://user-images.githubusercontent.com/51840849/87182090-1e9e9080-c2ec-11ea-8e49-563db9a38f37.jpg)](https://www.independentreserve.com) | independentreserve | [Independent Reserve](https://www.independentreserve.com) | * | [API](https://www.independentreserve.com/API) | | | +| [![indodax](https://user-images.githubusercontent.com/51840849/87070508-9358c880-c221-11ea-8dc5-5391afbbb422.jpg)](https://indodax.com/ref/testbitcoincoid/1) | indodax | [INDODAX](https://indodax.com/ref/testbitcoincoid/1) | 2.0 | [API](https://github.com/btcid/indodax-official-api-docs) | | | +| [![itbit](https://user-images.githubusercontent.com/1294454/27822159-66153620-60ad-11e7-89e7-005f6d7f3de0.jpg)](https://www.itbit.com) | itbit | [itBit](https://www.itbit.com) | 1 | [API](https://api.itbit.com/docs) | | | +| [![kraken](https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg)](https://www.kraken.com) | kraken | [Kraken](https://www.kraken.com) | 0 | [API](https://www.kraken.com/features/api) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![kucoin](https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg)](https://www.kucoin.com/?rcode=E5wkqe) | kucoin | [KuCoin](https://www.kucoin.com/?rcode=E5wkqe) | 2 | [API](https://docs.kucoin.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![kuna](https://user-images.githubusercontent.com/51840849/87153927-f0578b80-c2c0-11ea-84b6-74612568e9e1.jpg)](https://kuna.io?r=kunaid-gvfihe8az7o4) | kuna | [Kuna](https://kuna.io?r=kunaid-gvfihe8az7o4) | 2 | [API](https://kuna.io/documents/api) | | | +| [![lakebtc](https://user-images.githubusercontent.com/1294454/28074120-72b7c38a-6660-11e7-92d9-d9027502281d.jpg)](https://www.lakebtc.com) | lakebtc | [LakeBTC](https://www.lakebtc.com) | 2 | [API](https://www.lakebtc.com/s/api_v2) | | | +| [![latoken](https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg)](https://latoken.com) | latoken | [Latoken](https://latoken.com) | 1 | [API](https://api.latoken.com) | | | +| [![lbank](https://user-images.githubusercontent.com/1294454/38063602-9605e28a-3302-11e8-81be-64b1e53c4cfb.jpg)](https://www.lbex.io/invite?icode=7QCY) | lbank | [LBank](https://www.lbex.io/invite?icode=7QCY) | 1 | [API](https://github.com/LBank-exchange/lbank-official-api-docs) | | | +| [![liquid](https://user-images.githubusercontent.com/1294454/45798859-1a872600-bcb4-11e8-8746-69291ce87b04.jpg)](https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976) | liquid | [Liquid](https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976) | 2 | [API](https://developers.liquid.com) | | | +| [![livecoin](https://user-images.githubusercontent.com/1294454/27980768-f22fc424-638a-11e7-89c9-6010a54ff9be.jpg)](https://livecoin.net/?from=Livecoin-CQ1hfx44) | livecoin | [LiveCoin](https://livecoin.net/?from=Livecoin-CQ1hfx44) | * | [API](https://www.livecoin.net/api?lang=en) | | | +| [![luno](https://user-images.githubusercontent.com/1294454/27766607-8c1a69d8-5ede-11e7-930c-540b5eb9be24.jpg)](https://www.luno.com/invite/44893A) | luno | [luno](https://www.luno.com/invite/44893A) | 1 | [API](https://www.luno.com/en/api) | | | +| [![lykke](https://user-images.githubusercontent.com/1294454/34487620-3139a7b0-efe6-11e7-90f5-e520cef74451.jpg)](https://www.lykke.com) | lykke | [Lykke](https://www.lykke.com) | 1 | [API](https://hft-api.lykke.com/swagger/ui/) | | | +| [![mercado](https://user-images.githubusercontent.com/1294454/27837060-e7c58714-60ea-11e7-9192-f05e86adb83f.jpg)](https://www.mercadobitcoin.com.br) | mercado | [Mercado Bitcoin](https://www.mercadobitcoin.com.br) | 3 | [API](https://www.mercadobitcoin.com.br/api-doc) | | | +| [![mixcoins](https://user-images.githubusercontent.com/51840849/87460810-1dd06c00-c616-11ea-9276-956f400d6ffa.jpg)](https://mixcoins.com) | mixcoins | [MixCoins](https://mixcoins.com) | 1 | [API](https://mixcoins.com/help/api/) | | | +| [![novadax](https://user-images.githubusercontent.com/1294454/92337550-2b085500-f0b3-11ea-98e7-5794fb07dd3b.jpg)](https://www.novadax.com.br/?s=ccxt) | novadax | [NovaDAX](https://www.novadax.com.br/?s=ccxt) | 1 | [API](https://doc.novadax.com/pt-BR/) | | | +| [![oceanex](https://user-images.githubusercontent.com/1294454/58385970-794e2d80-8001-11e9-889c-0567cd79b78e.jpg)](https://oceanex.pro/signup?referral=VE24QX) | oceanex | [OceanEx](https://oceanex.pro/signup?referral=VE24QX) | 1 | [API](https://api.oceanex.pro/doc/v1) | | | +| [![okcoin](https://user-images.githubusercontent.com/51840849/87295551-102fbf00-c50e-11ea-90a9-462eebba5829.jpg)](https://www.okcoin.com/account/register?flag=activity&channelId=600001513) | okcoin | [OKCoin](https://www.okcoin.com/account/register?flag=activity&channelId=600001513) | 3 | [API](https://www.okcoin.com/docs/en/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![okex](https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg)](https://www.okex.com/join/1888677) | okex | [OKEX](https://www.okex.com/join/1888677) | 3 | [API](https://www.okex.com/docs/en/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![paymium](https://user-images.githubusercontent.com/51840849/87153930-f0f02200-c2c0-11ea-9c0a-40337375ae89.jpg)](https://www.paymium.com/page/sign-up?referral=eDAzPoRQFMvaAB8sf-qj) | paymium | [Paymium](https://www.paymium.com/page/sign-up?referral=eDAzPoRQFMvaAB8sf-qj) | 1 | [API](https://github.com/Paymium/api-documentation) | | | +| [![phemex](https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg)](https://phemex.com/register?referralCode=EDNVJ) | phemex | [Phemex](https://phemex.com/register?referralCode=EDNVJ) | 1 | [API](https://github.com/phemex/phemex-api-docs) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![poloniex](https://user-images.githubusercontent.com/1294454/27766817-e9456312-5ee6-11e7-9b3c-b628ca5626a5.jpg)](https://poloniex.com/signup?c=UBFZJRPJ) | poloniex | [Poloniex](https://poloniex.com/signup?c=UBFZJRPJ) | * | [API](https://docs.poloniex.com) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![probit](https://user-images.githubusercontent.com/51840849/79268032-c4379480-7ea2-11ea-80b3-dd96bb29fd0d.jpg)](https://www.probit.com/r/34608773) | probit | [ProBit](https://www.probit.com/r/34608773) | 1 | [API](https://docs-en.probit.com) | | | +| [![qtrade](https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg)](https://qtrade.io/?ref=BKOQWVFGRH2C) | qtrade | [qTrade](https://qtrade.io/?ref=BKOQWVFGRH2C) | 1 | [API](https://qtrade-exchange.github.io/qtrade-docs) | | | +| [![rightbtc](https://user-images.githubusercontent.com/51840849/87182092-1f372700-c2ec-11ea-8f9e-01b4d3ff8941.jpg)](https://www.rightbtc.com) | rightbtc | [RightBTC](https://www.rightbtc.com) | * | [API](https://docs.rightbtc.com/api/) | | | +| [![ripio](https://user-images.githubusercontent.com/1294454/94507548-a83d6a80-0218-11eb-9998-28b9cec54165.jpg)](https://exchange.ripio.com) | ripio | [Ripio](https://exchange.ripio.com) | 1 | [API](https://exchange.ripio.com/en/api/) | | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![southxchange](https://user-images.githubusercontent.com/1294454/27838912-4f94ec8a-60f6-11e7-9e5d-bbf9bd50a559.jpg)](https://www.southxchange.com) | southxchange | [SouthXchange](https://www.southxchange.com) | * | [API](https://www.southxchange.com/Home/Api) | | | +| [![stex](https://user-images.githubusercontent.com/1294454/69680782-03fd0b80-10bd-11ea-909e-7f603500e9cc.jpg)](https://app.stex.com?ref=36416021) | stex | [STEX](https://app.stex.com?ref=36416021) | 3 | [API](https://help.stex.com/en/collections/1593608-api-v3-documentation) | | | +| [![surbitcoin](https://user-images.githubusercontent.com/1294454/27991511-f0a50194-6481-11e7-99b5-8f02932424cc.jpg)](https://surbitcoin.com) | surbitcoin | [SurBitcoin](https://surbitcoin.com) | 1 | [API](https://blinktrade.com/docs) | | | +| [![therock](https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg)](https://therocktrading.com) | therock | [TheRockTrading](https://therocktrading.com) | 1 | [API](https://api.therocktrading.com/doc/v1/index.html) | | | +| [![tidebit](https://user-images.githubusercontent.com/51840849/87460811-1e690280-c616-11ea-8652-69f187305add.jpg)](http://bit.ly/2IX0LrM) | tidebit | [TideBit](http://bit.ly/2IX0LrM) | 2 | [API](https://www.tidebit.com/documents/api/guide) | | | +| [![tidex](https://user-images.githubusercontent.com/1294454/30781780-03149dc4-a12e-11e7-82bb-313b269d24d4.jpg)](https://tidex.com/exchange/?ref=57f5638d9cd7) | tidex | [Tidex](https://tidex.com/exchange/?ref=57f5638d9cd7) | 3 | [API](https://tidex.com/exchange/public-api) | | | +| [![timex](https://user-images.githubusercontent.com/1294454/70423869-6839ab00-1a7f-11ea-8f94-13ae72c31115.jpg)](https://timex.io/?refcode=1x27vNkTbP1uwkCck) | timex | [TimeX](https://timex.io/?refcode=1x27vNkTbP1uwkCck) | 1 | [API](https://docs.timex.io) | | | +| [![upbit](https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg)](https://upbit.com) | upbit | [Upbit](https://upbit.com) | 1 | [API](https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | [![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro) | +| [![vaultoro](https://user-images.githubusercontent.com/1294454/27766880-f205e870-5ee9-11e7-8fe2-0d5b15880752.jpg)](https://www.vaultoro.com) | vaultoro | [Vaultoro](https://www.vaultoro.com) | 1 | [API](https://api.vaultoro.com) | | | +| [![vbtc](https://user-images.githubusercontent.com/1294454/27991481-1f53d1d8-6481-11e7-884e-21d17e7939db.jpg)](https://vbtc.exchange) | vbtc | [VBTC](https://vbtc.exchange) | 1 | [API](https://blinktrade.com/docs) | | | +| [![vcc](https://user-images.githubusercontent.com/1294454/100545356-8427f500-326c-11eb-9539-7d338242d61b.jpg)](https://vcc.exchange?ref=l4xhrH) | vcc | [VCC Exchange](https://vcc.exchange?ref=l4xhrH) | 3 | [API](https://vcc.exchange/api) | | | +| [![wavesexchange](https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg)](https://waves.exchange) | wavesexchange | [Waves.Exchange](https://waves.exchange) | * | [API](https://docs.waves.exchange) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![whitebit](https://user-images.githubusercontent.com/1294454/66732963-8eb7dd00-ee66-11e9-849b-10d9282bb9e0.jpg)](https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963) | whitebit | [WhiteBit](https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963) | 2 | [API](https://documenter.getpostman.com/view/7473075/Szzj8dgv?version=latest) | | | +| [![xbtce](https://user-images.githubusercontent.com/1294454/28059414-e235970c-662c-11e7-8c3a-08e31f78684b.jpg)](https://xbtce.com/?agent=XX97BTCXXXG687021000B) | xbtce | [xBTCe](https://xbtce.com/?agent=XX97BTCXXXG687021000B) | 1 | [API](https://www.xbtce.com/tradeapi) | | | +| [![xena](https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg)](https://xena.exchange) | xena | [Xena Exchange](https://xena.exchange) | * | [API](https://support.xena.exchange/support/solutions/44000808700) | [![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification) | | +| [![yobit](https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg)](https://www.yobit.net) | yobit | [YoBit](https://www.yobit.net) | 3 | [API](https://www.yobit.net/en/api/) | | | +| [![zaif](https://user-images.githubusercontent.com/1294454/27766927-39ca2ada-5eeb-11e7-972f-1b4199518ca6.jpg)](https://zaif.jp) | zaif | [Zaif](https://zaif.jp) | 1 | [API](https://techbureau-api-document.readthedocs.io/ja/latest/index.html) | | | +| [![zb](https://user-images.githubusercontent.com/1294454/32859187-cd5214f0-ca5e-11e7-967d-96568e2e2bd1.jpg)](https://www.zb.com) | zb | [ZB](https://www.zb.com) | 1 | [API](https://www.zb.com/i/developer) | | | + +The list above is updated frequently, new crypto markets, exchanges, bug fixes, and API endpoints are introduced on a regular basis. See the [Manual](https://github.com/ccxt/ccxt/wiki) for more details. If you can't find a cryptocurrency exchange in the list above and want it to be added, post a link to it by opening an issue here on GitHub or send us an email. + +The library is under [MIT license](https://github.com/ccxt/ccxt/blob/master/LICENSE.txt), that means it's absolutely free for any developer to build commercial and opensource software on top of it, but use it at your own risk with no warranties, as is. + +--- + +## Install + +The easiest way to install the CCXT library is to use a package manager: + +- [ccxt in **NPM**](https://www.npmjs.com/package/ccxt) (JavaScript / Node v7.6+) +- [ccxt in **PyPI**](https://pypi.python.org/pypi/ccxt) (Python 3.5.3+) +- [ccxt in **Packagist/Composer**](https://packagist.org/packages/ccxt/ccxt) (PHP 5.4+) + +This library is shipped as an all-in-one module implementation with minimalistic dependencies and requirements: + +- [`js/`](https://github.com/ccxt/ccxt/blob/master/js/) in JavaScript +- [`python/`](https://github.com/ccxt/ccxt/blob/master/python/) in Python (generated from JS) +- [`php/`](https://github.com/ccxt/ccxt/blob/master/php/) in PHP (generated from JS) + +You can also clone it into your project directory from [ccxt GitHub repository](https://github.com/ccxt/ccxt): + +```shell +git clone https://github.com/ccxt/ccxt.git +``` + +### JavaScript (NPM) + +JavaScript version of CCXT works in both Node and web browsers. Requires ES6 and `async/await` syntax support (Node 7.6.0+). When compiling with Webpack and Babel, make sure it is [not excluded](https://github.com/ccxt/ccxt/issues/225#issuecomment-331905178) in your `babel-loader` config. + +[ccxt in **NPM**](https://www.npmjs.com/package/ccxt) + +```shell +npm install ccxt +``` + +```JavaScript +var ccxt = require ('ccxt') + +console.log (ccxt.exchanges) // print all available exchanges +``` + +### JavaScript (for use with the ` +``` + +Creates a global `ccxt` object: + +```JavaScript +console.log (ccxt.exchanges) // print all available exchanges +``` + +### Python + +[ccxt in **PyPI**](https://pypi.python.org/pypi/ccxt) + +```shell +pip install ccxt +``` + +```Python +import ccxt +print(ccxt.exchanges) # print a list of all available exchange classes +``` + +The library supports concurrent asynchronous mode with asyncio and async/await in Python 3.5.3+ + +```Python +import ccxt.async_support as ccxt # link against the asynchronous version of ccxt +``` + +### PHP + +[ccxt in PHP with **Packagist/Composer**](https://packagist.org/packages/ccxt/ccxt) (PHP 5.4+) + +It requires common PHP modules: + +- cURL +- mbstring (using UTF-8 is highly recommended) +- PCRE +- iconv +- gmp (this is a built-in extension as of PHP 7.2+) + +```PHP +include "ccxt.php"; +var_dump (\ccxt\Exchange::$exchanges); // print a list of all available exchange classes +``` + +### Docker + +You can get CCXT installed in a container along with all the supported languages and dependencies. This may be useful if you want to contribute to CCXT (e.g. run the build scripts and tests — please see the [Contributing](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md) document for the details on that). + +Using `docker-compose` (in the cloned CCXT repository): + +```shell +docker-compose run --rm ccxt +``` + +You don't need the Docker image if you're not going to develop CCXT. If you just want to use CCXT – just install it as a regular package into your project. + +--- + +## Documentation + +Read the [Manual](https://github.com/ccxt/ccxt/wiki) for more details. + +## Usage + +### Intro + +The CCXT library consists of a public part and a private part. Anyone can use the public part immediately after installation. Public APIs provide unrestricted access to public information for all exchange markets without the need to register a user account or have an API key. + +Public APIs include the following: + +- market data +- instruments/trading pairs +- price feeds (exchange rates) +- order books +- trade history +- tickers +- OHLC(V) for charting +- other public endpoints + +In order to trade with private APIs you need to obtain API keys from an exchange's website. It usually means signing up to the exchange and creating API keys for your account. Some exchanges require personal info or identification. Sometimes verification may be necessary as well. In this case you will need to register yourself, this library will not create accounts or API keys for you. Some exchanges expose API endpoints for registering an account, but most exchanges don't. You will have to sign up and create API keys on their websites. + +Private APIs allow the following: + +- manage personal account info +- query account balances +- trade by making market and limit orders +- deposit and withdraw fiat and crypto funds +- query personal orders +- get ledger history +- transfer funds between accounts +- use merchant services + +This library implements full public and private REST APIs for all exchanges. WebSocket and FIX implementations in JavaScript, PHP, Python are available in [CCXT Pro](https://ccxt.pro), which is a professional addon to CCXT with support for WebSocket streams. + +The CCXT library supports both camelcase notation (preferred in JavaScript) and underscore notation (preferred in Python and PHP), therefore all methods can be called in either notation or coding style in any language. + +```JavaScript +// both of these notations work in JavaScript/Python/PHP +exchange.methodName () // camelcase pseudocode +exchange.method_name () // underscore pseudocode +``` + +Read the [Manual](https://github.com/ccxt/ccxt/wiki) for more details. + +### JavaScript + +```JavaScript +'use strict'; +const ccxt = require ('ccxt'); + +(async function () { + let kraken = new ccxt.kraken () + let bitfinex = new ccxt.bitfinex ({ verbose: true }) + let huobipro = new ccxt.huobipro () + let okcoinusd = new ccxt.okcoinusd ({ + apiKey: 'YOUR_PUBLIC_API_KEY', + secret: 'YOUR_SECRET_PRIVATE_KEY', + }) + + const exchangeId = 'binance' + , exchangeClass = ccxt[exchangeId] + , exchange = new exchangeClass ({ + 'apiKey': 'YOUR_API_KEY', + 'secret': 'YOUR_SECRET', + 'timeout': 30000, + 'enableRateLimit': true, + }) + + console.log (kraken.id, await kraken.loadMarkets ()) + console.log (bitfinex.id, await bitfinex.loadMarkets ()) + console.log (huobipro.id, await huobipro.loadMarkets ()) + + console.log (kraken.id, await kraken.fetchOrderBook (kraken.symbols[0])) + console.log (bitfinex.id, await bitfinex.fetchTicker ('BTC/USD')) + console.log (huobipro.id, await huobipro.fetchTrades ('ETH/USDT')) + + console.log (okcoinusd.id, await okcoinusd.fetchBalance ()) + + // sell 1 BTC/USD for market price, sell a bitcoin for dollars immediately + console.log (okcoinusd.id, await okcoinusd.createMarketSellOrder ('BTC/USD', 1)) + + // buy 1 BTC/USD for $2500, you pay $2500 and receive ฿1 when the order is closed + console.log (okcoinusd.id, await okcoinusd.createLimitBuyOrder ('BTC/USD', 1, 2500.00)) + + // pass/redefine custom exchange-specific order params: type, amount, price or whatever + // use a custom order type + bitfinex.createLimitSellOrder ('BTC/USD', 1, 10, { 'type': 'trailing-stop' }) + +}) (); +``` + +### Python + +```Python +# coding=utf-8 + +import ccxt + +hitbtc = ccxt.hitbtc({'verbose': True}) +bitmex = ccxt.bitmex() +huobipro = ccxt.huobipro() +exmo = ccxt.exmo({ + 'apiKey': 'YOUR_PUBLIC_API_KEY', + 'secret': 'YOUR_SECRET_PRIVATE_KEY', +}) +kraken = ccxt.kraken({ + 'apiKey': 'YOUR_PUBLIC_API_KEY', + 'secret': 'YOUR_SECRET_PRIVATE_KEY', +}) + +exchange_id = 'binance' +exchange_class = getattr(ccxt, exchange_id) +exchange = exchange_class({ + 'apiKey': 'YOUR_API_KEY', + 'secret': 'YOUR_SECRET', + 'timeout': 30000, + 'enableRateLimit': True, +}) + +hitbtc_markets = hitbtc.load_markets() + +print(hitbtc.id, hitbtc_markets) +print(bitmex.id, bitmex.load_markets()) +print(huobipro.id, huobipro.load_markets()) + +print(hitbtc.fetch_order_book(hitbtc.symbols[0])) +print(bitmex.fetch_ticker('BTC/USD')) +print(huobipro.fetch_trades('LTC/USDT')) + +print(exmo.fetch_balance()) + +# sell one ฿ for market price and receive $ right now +print(exmo.id, exmo.create_market_sell_order('BTC/USD', 1)) + +# limit buy BTC/EUR, you pay €2500 and receive ฿1 when the order is closed +print(exmo.id, exmo.create_limit_buy_order('BTC/EUR', 1, 2500.00)) + +# pass/redefine custom exchange-specific order params: type, amount, price, flags, etc... +kraken.create_market_buy_order('BTC/USD', 1, {'trading_agreement': 'agree'}) +``` + +### PHP + +```PHP +include 'ccxt.php'; + +$poloniex = new \ccxt\poloniex (); +$bittrex = new \ccxt\bittrex (array ('verbose' => true)); +$quoinex = new \ccxt\quoinex (); +$zaif = new \ccxt\zaif (array ( + 'apiKey' => 'YOUR_PUBLIC_API_KEY', + 'secret' => 'YOUR_SECRET_PRIVATE_KEY', +)); +$hitbtc = new \ccxt\hitbtc (array ( + 'apiKey' => 'YOUR_PUBLIC_API_KEY', + 'secret' => 'YOUR_SECRET_PRIVATE_KEY', +)); + +$exchange_id = 'binance'; +$exchange_class = "\\ccxt\\$exchange_id"; +$exchange = new $exchange_class (array ( + 'apiKey' => 'YOUR_API_KEY', + 'secret' => 'YOUR_SECRET', + 'timeout' => 30000, + 'enableRateLimit' => true, +)); + +$poloniex_markets = $poloniex->load_markets (); + +var_dump ($poloniex_markets); +var_dump ($bittrex->load_markets ()); +var_dump ($quoinex->load_markets ()); + +var_dump ($poloniex->fetch_order_book ($poloniex->symbols[0])); +var_dump ($bittrex->fetch_trades ('BTC/USD')); +var_dump ($quoinex->fetch_ticker ('ETH/EUR')); +var_dump ($zaif->fetch_ticker ('BTC/JPY')); + +var_dump ($zaif->fetch_balance ()); + +// sell 1 BTC/JPY for market price, you pay ¥ and receive ฿ immediately +var_dump ($zaif->id, $zaif->create_market_sell_order ('BTC/JPY', 1)); + +// buy BTC/JPY, you receive ฿1 for ¥285000 when the order closes +var_dump ($zaif->id, $zaif->create_limit_buy_order ('BTC/JPY', 1, 285000)); + +// set a custom user-defined id to your order +$hitbtc->create_order ('BTC/USD', 'limit', 'buy', 1, 3000, array ('clientOrderId' => '123')); +``` + +## Contributing + +Please read the [CONTRIBUTING](https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md) document before making changes that you would like adopted in the code. Also, read the [Manual](https://github.com/ccxt/ccxt/wiki) for more details. + +## Support Developer Team + +We are investing a significant amount of time into the development of this library. If CCXT made your life easier and you want to help us improve it further, or if you want to speed up development of new features and exchanges, please support us with a tip. We appreciate all contributions! + +### Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. + +[[Become a sponsor](https://opencollective.com/ccxt#sponsor)] + + + + + + + + + + + + +### Supporters + +Support this project by becoming a supporter. Your avatar will show up here with a link to your website. + +[[Become a supporter](https://opencollective.com/ccxt#supporter)] + + + + + + + + + + + + +### Backers + +Thank you to all our backers! [[Become a backer](https://opencollective.com/ccxt#backer)] + + + +### Crypto + +``` +ETH 0x26a3CB49578F07000575405a57888681249c35Fd (ETH only) +BTC 33RmVRfhK2WZVQR1R83h2e9yXoqRNDvJva +BCH 1GN9p233TvNcNQFthCgfiHUnj5JRKEc2Ze +LTC LgM5hkvPzytEFfQGGDFGKYkE743jwUA7qv +``` + +Thank you! + +## Social + +- [Follow us on Twitter](https://twitter.com/ccxt_official) +- [Read our blog on Medium](https://medium.com/@ccxt) +- [![Discord](https://img.shields.io/discord/690203284119617602?logo=discord&logoColor=white)](https://discord.gg/dhzSKYU) + +## Contact Us + +For business inquiries: info@ccxt.trade diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/cleanup-old-tags.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/cleanup-old-tags.js new file mode 100644 index 000000000..d304afd1d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/cleanup-old-tags.js @@ -0,0 +1,94 @@ +"use strict" + +const { execSync } = require ('child_process') +const log = require ('ololog').noLocate +const { groupBy } = require ('../ccxt.js') +const { values } = Object +const assert = require ('assert') + +function cleanupOldTags () { + + const tags = execSync ('git tag').toString ().split ('\n').filter (s => s).map (t => { + + const [major, minor, patch] = t.replace ('v', '').split ('.').map (Number) + + assert (major < 100) + assert (minor < 100) + + return { + key: (major * 100) + minor, + tag: t, + major, + minor, + patch, + } + }) + + const tagsByMajorMinor = values (groupBy (tags, 'key')).sort ((a, b) => a[0].key - b[0].key) + + // Preserve all tags for first 5 minor versions + + for (let i = 0; i < 5; i++) { + + const tags = tagsByMajorMinor.pop () + + if (tags) { + log.green.bright ('Preserving', tags[0].tag, '...', tags[tags.length - 1].tag) + } + } + + // For older versions, leave only "round" numbered versions (1/10th) + + let tagsToDelete = [] + + for (const tags of tagsByMajorMinor) { + + for (const { tag, patch } of tags) { + + if (patch === 1) { + log.green ('Preserving', tag) + + } else { + tagsToDelete.push (tag) + } + } + } + + log.bright.red ('Deleting', tagsToDelete.length, 'tags...') + + if (!process.argv.includes ('--paper')) { + + /* If it happens on a CI server, we don't want it to fail the build because of a super + long execution time (one tag deletion takes ~5 sec...), hence that limit here */ + + if (process.argv.includes ('--limit')) { + tagsToDelete = tagsToDelete.slice (-500) + } + + for (const tag of tagsToDelete) { + + log.dim ('Deleting', tag) + execSync (`git tag -d ${tag} && git push origin :refs/tags/${tag}`) + } + } +} + +// ============================================================================ +// main entry point + +if (require.main === module) { + + // if called directly like `node module` + + cleanupOldTags () + +} else { + + // do nothing if required as a module +} + +// ============================================================================ + +module.exports = { + cleanupOldTags, +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/countries.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/countries.js new file mode 100644 index 000000000..e28cdd635 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/countries.js @@ -0,0 +1,256 @@ +"use strict"; + +module.exports = { + + AF: 'Afghanistan', + AX: 'Åland Islands', + AL: 'Albania', + DZ: 'Algeria', + AS: 'American Samoa', + AD: 'Andorra', + AO: 'Angola', + AI: 'Anguilla', + AQ: 'Antarctica', + AG: 'Antigua and Barbuda', + AR: 'Argentina', + AM: 'Armenia', + AW: 'Aruba', + AU: 'Australia', + AT: 'Austria', + AZ: 'Azerbaijan', + BS: 'Bahamas', + BH: 'Bahrain', + BD: 'Bangladesh', + BB: 'Barbados', + BY: 'Belarus', + BE: 'Belgium', + BZ: 'Belize', + BJ: 'Benin', + BM: 'Bermuda', + BT: 'Bhutan', + BO: 'Bolivia', + BQ: 'Bonaire, Sint Eustatius and Saba', + BA: 'Bosnia and Herzegovina', + BW: 'Botswana', + BV: 'Bouvet Island', + BR: 'Brazil', + IO: 'British Indian Ocean Territory', + BN: 'Brunei Darussalam', + BG: 'Bulgaria', + BF: 'Burkina Faso', + BI: 'Burundi', + KH: 'Cambodia', + CM: 'Cameroon', + CA: 'Canada', + CV: 'Cabo Verde', + KY: 'Cayman Islands', + CF: 'Central African Republic', + TD: 'Chad', + CL: 'Chile', + CN: 'China', + CX: 'Christmas Island', + CC: 'Cocos (Keeling) Islands', + CO: 'Colombia', + KM: 'Comoros', + CG: 'Congo', + CD: 'Congo', + CK: 'Cook Islands', + CR: 'Costa Rica', + CI: 'Côte d\'Ivoire', + HR: 'Croatia', + CU: 'Cuba', + CW: 'Curaçao', + CY: 'Cyprus', + CZ: 'Czech Republic', + DK: 'Denmark', + DJ: 'Djibouti', + DM: 'Dominica', + DO: 'Dominican Republic', + EC: 'Ecuador', + EG: 'Egypt', + SV: 'El Salvador', + GQ: 'Equatorial Guinea', + ER: 'Eritrea', + EE: 'Estonia', + ET: 'Ethiopia', + EU: 'EU', + FK: 'Falkland Islands', + FO: 'Faroe Islands', + FJ: 'Fiji', + FI: 'Finland', + FR: 'France', + GF: 'French Guiana', + PF: 'French Polynesia', + TF: 'French Southern Territories', + GA: 'Gabon', + GM: 'Gambia', + GE: 'Georgia', + DE: 'Germany', + GH: 'Ghana', + GI: 'Gibraltar', + GR: 'Greece', + GL: 'Greenland', + GD: 'Grenada', + GP: 'Guadeloupe', + GU: 'Guam', + GT: 'Guatemala', + GG: 'Guernsey', + GN: 'Guinea', + GW: 'Guinea-Bissau', + GY: 'Guyana', + HT: 'Haiti', + HM: 'Heard Island and McDonald Islands', + VA: 'Holy See', + HN: 'Honduras', + HK: 'Hong Kong', + HU: 'Hungary', + IS: 'Iceland', + IN: 'India', + ID: 'Indonesia', + IR: 'Iran', + IQ: 'Iraq', + IE: 'Ireland', + IM: 'Isle of Man', + IL: 'Israel', + IT: 'Italy', + JM: 'Jamaica', + JP: 'Japan', + JE: 'Jersey', + JO: 'Jordan', + KZ: 'Kazakhstan', + KE: 'Kenya', + KI: 'Kiribati', + KP: 'North Korea (DPRK)', + KR: 'South Korea', + KW: 'Kuwait', + KG: 'Kyrgyzstan', + LA: 'Lao People\'s Democratic Republic', + LV: 'Latvia', + LB: 'Lebanon', + LS: 'Lesotho', + LR: 'Liberia', + LY: 'Libya', + LI: 'Liechtenstein', + LT: 'Lithuania', + LU: 'Luxembourg', + MO: 'Macao', + MK: 'Macedonia', + MG: 'Madagascar', + MW: 'Malawi', + MY: 'Malaysia', + MV: 'Maldives', + ML: 'Mali', + MT: 'Malta', + MH: 'Marshall Islands', + MQ: 'Martinique', + MR: 'Mauritania', + MU: 'Mauritius', + YT: 'Mayotte', + MX: 'Mexico', + FM: 'Micronesia (Federated States of)', + MD: 'Moldova', + MC: 'Monaco', + MN: 'Mongolia', + ME: 'Montenegro', + MS: 'Montserrat', + MA: 'Morocco', + MZ: 'Mozambique', + MM: 'Myanmar', + NA: 'Namibia', + NR: 'Nauru', + NP: 'Nepal', + NL: 'Netherlands', + NC: 'New Caledonia', + NZ: 'New Zealand', + NI: 'Nicaragua', + NE: 'Niger', + NG: 'Nigeria', + NU: 'Niue', + NF: 'Norfolk Island', + MP: 'Northern Mariana Islands', + NO: 'Norway', + OM: 'Oman', + PK: 'Pakistan', + PW: 'Palau', + PS: 'Palestine, State of', + PA: 'Panama', + PG: 'Papua New Guinea', + PY: 'Paraguay', + PE: 'Peru', + PH: 'Philippines', + PN: 'Pitcairn', + PL: 'Poland', + PT: 'Portugal', + PR: 'Puerto Rico', + QA: 'Qatar', + RE: 'Réunion', + RO: 'Romania', + RU: 'Russia', + RW: 'Rwanda', + BL: 'Saint Barthélemy', + SH: 'Saint Helena, Ascension and Tristan da Cunha', + KN: 'Saint Kitts and Nevis', + LC: 'Saint Lucia', + MF: 'Saint Martin (French)', + PM: 'Saint Pierre and Miquelon', + VC: 'St. Vincent & Grenadines', + WS: 'Samoa', + SM: 'San Marino', + ST: 'Sao Tome and Principe', + SA: 'Saudi Arabia', + SN: 'Senegal', + RS: 'Serbia', + SC: 'Seychelles', + SL: 'Sierra Leone', + SG: 'Singapore', + SX: 'Sint Maarten (Dutch)', + SK: 'Slovakia', + SI: 'Slovenia', + SB: 'Solomon Islands', + SO: 'Somalia', + ZA: 'South Africa', + GS: 'South Georgia and the South Sandwich Islands', + SS: 'South Sudan', + ES: 'Spain', + LK: 'Sri Lanka', + SD: 'Sudan', + SR: 'Suriname', + SJ: 'Svalbard and Jan Mayen', + SZ: 'Swaziland', + SE: 'Sweden', + CH: 'Switzerland', + SY: 'Syrian Arab Republic', + TW: 'Taiwan', + TJ: 'Tajikistan', + TZ: 'Tanzania', + TH: 'Thailand', + TL: 'Timor-Leste', + TG: 'Togo', + TK: 'Tokelau', + TO: 'Tonga', + TT: 'Trinidad and Tobago', + TN: 'Tunisia', + TR: 'Turkey', + TM: 'Turkmenistan', + TC: 'Turks and Caicos Islands', + TV: 'Tuvalu', + UG: 'Uganda', + UA: 'Ukraine', + AE: 'United Arab Emirates', + GB: 'UK', + UK: 'UK', + US: 'US', + UM: 'US Minor Islands', + UY: 'Uruguay', + UZ: 'Uzbekistan', + VU: 'Vanuatu', + VE: 'Venezuela', + VN: 'Vietnam', + VG: 'British Virgin Islands', + VI: 'US Virgin Islands', + WF: 'Wallis and Futuna', + EH: 'Western Sahara', + YE: 'Yemen', + ZM: 'Zambia', + ZW: 'Zimbabwe', +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/export-exchanges.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/export-exchanges.js new file mode 100644 index 000000000..580d2cb2c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/export-exchanges.js @@ -0,0 +1,454 @@ +// ---------------------------------------------------------------------------- +// Usage: +// +// npm run export-exchanges +// ---------------------------------------------------------------------------- + +"use strict"; + +const fs = require ('fs') + , countries = require ('./countries') + , asTable = require ('as-table').configure ({ + delimiter: '|', + print: (x) => ' ' + x + ' ' + }) + , execSync = require ('child_process').execSync + , log = require ('ololog').unlimited + , ansi = require ('ansicolor').nice + , { keys, values, entries } = Object + , { replaceInFile } = require ('./fs.js') + +// ---------------------------------------------------------------------------- + +function cloneGitHubWiki (gitWikiPath) { + + if (!fs.existsSync (gitWikiPath)) { + log.bright.cyan ('Cloning ccxt.wiki...') + execSync ('git clone https://github.com/ccxt/ccxt.wiki.git ' + gitWikiPath) + } +} + +// ---------------------------------------------------------------------------- + +function logExportExchanges (filename, regex, replacement) { + log.bright.cyan ('Exporting exchanges →', filename.yellow) + replaceInFile (filename, regex, replacement) +} + +// ---------------------------------------------------------------------------- + +function getIncludedExchangeIds () { + + const includedIds = fs.readFileSync ('exchanges.cfg') + .toString () // Buffer → String + .split ('\n') // String → Array + .map (line => line.split ('#')[0].trim ()) // trim comments + .filter (exchange => exchange); // filter empty lines + + const isIncluded = (id) => ((includedIds.length === 0) || includedIds.includes (id)) + + const ids = fs.readdirSync ('./js/') + .filter (file => file.match (/[a-zA-Z0-9_-]+.js$/)) + .map (file => file.slice (0, -3)) + .filter (isIncluded); + + return ids +} + +// ---------------------------------------------------------------------------- + +function exportExchanges (replacements) { + + log.bright.yellow ('Exporting exchanges...') + + replacements.forEach (({ file, regex, replacement }) => { + logExportExchanges (file, regex, replacement) + }) + + log.bright.green ('Base sources updated successfully.') +} + +// ---------------------------------------------------------------------------- + +function createExchanges (ids) { + + const ccxt = require ('../ccxt.js') + + const createExchange = (id) => { + ccxt[id].prototype.checkRequiredDependencies = () => {} // suppress it + return new (ccxt)[id] () + } + + return ccxt.indexBy (ids.map (createExchange), 'id') +} + +// ---------------------------------------------------------------------------- + +const ccxtCertifiedBadge = '[![CCXT Certified](https://img.shields.io/badge/CCXT-Certified-green.svg)](https://github.com/ccxt/ccxt/wiki/Certification)' + , ccxtProBadge = '[![CCXT Pro](https://img.shields.io/badge/CCXT-Pro-black)](https://ccxt.pro)' + +// ---------------------------------------------------------------------------- + +function createMarkdownListOfExchanges (exchanges) { + + return exchanges.map ((exchange) => { + + const www = exchange.urls.www + , url = exchange.urls.referral || www + , doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc + , version = exchange.version ? exchange.version.replace (/[^0-9\.]+/, '') : '\*' + + return { + 'logo': '[![' + exchange.id + '](' + exchange.urls.logo + ')](' + url + ')', + 'id': exchange.id, + 'name': '[' + exchange.name + '](' + url + ')', + 'ver': version, + 'doc': '[API](' + doc + ')', + 'certified': exchange.certified ? ccxtCertifiedBadge : '', + 'pro': exchange.pro ? ccxtProBadge : '', + } + }) +} + +// ---------------------------------------------------------------------------- + +const sortByCountry = (a, b) => { + if (a['country / region'] > b['country / region']) { + return 1 + } else if (a['country / region'] < b['country / region']) { + return -1; + } else { + if (a['id'] > b['id']) { + return 1; + } else if (a['id'] < b['id']) { + return -1; + } else { + return 0; + } + } +} + +// ---------------------------------------------------------------------------- + +function createMarkdownListOfExchangesByCountries (exchanges) { + + const exchangesByCountries = [] + + keys (countries).forEach (code => { + + exchanges.forEach (exchange => { + + const website = Array.isArray (exchange.urls.www) ? exchange.urls.www[0] : exchange.urls.www + , url = exchange.urls.referral || website + , doc = Array.isArray (exchange.urls.doc) ? exchange.urls.doc[0] : exchange.urls.doc + , version = exchange.version ? exchange.version.replace (/[^0-9\.]+/, '') : '\*' + + const exchangeInCountry = + (Array.isArray (exchange.countries) && exchange.countries.includes (code)) || + (code === exchange.countries) + + if (exchangeInCountry) { + + exchangesByCountries.push ({ + 'country / region': countries[code], + 'logo': '[![' + exchange.id + '](' + exchange.urls.logo + ')](' + url + ')', + 'id': exchange.id, + 'name': '[' + exchange.name + '](' + url + ')', + 'ver': version, + 'doc': '[API](' + doc + ')', + }) + } + }) + }); + + return exchangesByCountries.sort (sortByCountry) +} + +// ---------------------------------------------------------------------------- + +function createMarkdownTable (array, markdownMethod, centeredColumns) { + + array = markdownMethod (array) + + const table = asTable (array) + const lines = table.split ("\n") + + // + // asTable creates a header underline like + // + // logo | id | name | ver | doc | certified | pro + // ------------------------------------------------ + // + // we fix it to match markdown underline like + // + // logo | id | name | ver | doc | certified | pro + // ------|----|------|-----|-----|-----------|----- + // + + const underline = lines[0].replace (/[^\|]/g, '-') + + // + // ver and doc columns should be centered so we convert it to + // + // logo | id | name | ver | doc | certified | pro + // ------|----|------|:---:|:---:|-----------|----- + // + + const columns = underline.split ('|') + + for (const i of centeredColumns) { + columns[i] = ':' + columns[i].slice (1, columns[i].length - 1) + ':' + } + + lines.splice (1, 1, columns.join ('|')) + + // + // prepend and append a vertical bar to each line + // + // | logo | id | name | ver | doc | certified | pro | + // |------|----|------|:---:|:---:|-----------|-----| + // + + return lines.map (line => '|' + line + '|').join ("\n") +} + +// ---------------------------------------------------------------------------- + +function exportSupportedAndCertifiedExchanges (exchanges, { allExchangesPaths, certifiedExchangesPaths, exchangesByCountriesPaths, proExchangesPaths }) { + + const exchangesNotListedInDocs = [ 'hitbtc2' ] + + const arrayOfExchanges = values (exchanges) + + if (allExchangesPaths) { + + const supportedExchangesMarkdownTable = createMarkdownTable (arrayOfExchanges, createMarkdownListOfExchanges, [ 3, 4 ]) + , numExchanges = arrayOfExchanges.length + , beginning = "The CCXT library currently supports the following " + , ending = " cryptocurrency exchange markets and trading APIs:\n\n" + , totalString = beginning + numExchanges + ending + , allExchangesReplacement = totalString + supportedExchangesMarkdownTable + "$1" + , allExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\| logo[^`]+\\|([\n][\n]|[\n]$|$)", 'm') + + for (const path of allExchangesPaths) { + logExportExchanges (path, allExchangesRegex, allExchangesReplacement) + } + } + + if (proExchangesPaths) { + + const proExchanges = arrayOfExchanges.filter (exchange => exchange.pro) + , proExchangesMarkdownTable = createMarkdownTable (proExchanges, createMarkdownListOfExchanges, [ 3, 4 ]) + , numExchanges = proExchanges.length + , beginning = "The CCXT Pro library currently supports the following " + , ending = " cryptocurrency exchange markets and WebSocket trading APIs:\n\n" + , totalString = beginning + numExchanges + ending + , proExchangesReplacement = totalString + proExchangesMarkdownTable + "$1" + , proExchangesRegex = new RegExp ("[^\n]+[\n]{2}\\|[^`]+\\|([\n][\n]|[\n]$|$)", 'm') + + for (const path of proExchangesPaths) { + logExportExchanges (path, proExchangesRegex, proExchangesReplacement) + } + } + + if (certifiedExchangesPaths) { + const certifiedExchanges = arrayOfExchanges.filter (exchange => exchange.certified) + , certifiedExchangesMarkdownTable = createMarkdownTable (certifiedExchanges, createMarkdownListOfExchanges, [ 3, 4 ]) + , certifiedExchangesReplacement = '$1' + certifiedExchangesMarkdownTable + "\n" + , certifiedExchangesRegex = new RegExp ("^(## Certified Cryptocurrency Exchanges\n{3})(?:\\|.+\\|$\n)+", 'm') + + for (const path of certifiedExchangesPaths) { + logExportExchanges (path, certifiedExchangesRegex, certifiedExchangesReplacement) + } + } + + if (exchangesByCountriesPaths) { + + const exchangesByCountriesMarkdownTable = createMarkdownTable (arrayOfExchanges, createMarkdownListOfExchangesByCountries, [ 4, 5 ]) + const result = "# Exchanges By Country\n\nThe ccxt library currently supports the following cryptocurrency exchange markets and trading APIs:\n\n" + exchangesByCountriesMarkdownTable + "\n\n" + for (const path of exchangesByCountriesPaths) { + fs.truncateSync (path) + fs.writeFileSync (path, result) + } + } +} + +// ---------------------------------------------------------------------------- + +function exportExchangeIdsToExchangesJson (exchanges) { + log.bright ('Exporting exchange ids to'.cyan, 'exchanges.json'.yellow) + const ids = keys (exchanges) + console.log (ids) + fs.writeFileSync ('exchanges.json', JSON.stringify ({ ids }, null, 4)) +} + +// ---------------------------------------------------------------------------- + +function exportWikiToGitHub (wikiPath, gitWikiPath) { + + log.bright.cyan ('Exporting wiki to GitHub') + + const ccxtWikiFiles = { + 'README.md': 'Home.md', + 'Install.md': 'Install.md', + 'Manual.md': 'Manual.md', + 'Exchange-Markets.md': 'Exchange-Markets.md', + 'Exchange-Markets-By-Country.md': 'Exchange-Markets-By-Country.md', + 'ccxt.pro.md': 'ccxt.pro.md', + 'ccxt.pro.install.md': 'ccxt.pro.install.md', + 'ccxt.pro.manual.md': 'ccxt.pro.manual.md', + } + + for (const [ sourceFile, destinationFile ] of entries (ccxtWikiFiles)) { + + const sourcePath = wikiPath + '/' + sourceFile + const destinationPath = gitWikiPath + '/' + destinationFile + log.bright.cyan ('Exporting', sourcePath.yellow, '→', destinationPath.yellow) + fs.writeFileSync (destinationPath, fs.readFileSync (sourcePath)) + } +} + +// ---------------------------------------------------------------------------- + +function exportKeywordsToPackageJson (exchanges) { + + log.bright ('Exporting exchange keywords to'.cyan, 'package.json'.yellow) + + // const packageJSON = require ('../package.json') + const packageJSON = JSON.parse (fs.readFileSync ('./package.json')) + const keywords = new Set (packageJSON.keywords) + + for (const ex of values (exchanges)) { + for (const url of Array.isArray (ex.urls.www) ? ex.urls.www : [ex.urls.www]) { + keywords.add (url.replace (/(http|https):\/\/(www\.)?/, '').replace (/\/.*/, '')) + } + keywords.add (ex.name) + } + + packageJSON.keywords = [...keywords] + fs.writeFileSync ('./package.json', JSON.stringify (packageJSON, null, 2) + "\n") +} + +// ---------------------------------------------------------------------------- + +function flatten (nested, result = []) { + for (const key in nested) { + result.push (key) + if (Object.keys (nested[key]).length) + flatten (nested[key], result) + } + return result +} + +// ---------------------------------------------------------------------------- + +function exportEverything () { + const ids = getIncludedExchangeIds () + const errorHierarchy = require ('../js/base/errorHierarchy.js') + const flat = flatten (errorHierarchy) + flat.push ('error_hierarchy') + + const replacements = [ + { + file: './ccxt.js', + regex: /(?:const|var)\s+exchanges\s+\=\s+\{[^\}]+\}/, + replacement: "const exchanges = {\n" + ids.map (id => (" '" + id + "':").padEnd (30) + " require ('./js/" + id + ".js'),").join ("\n") + " \n}", + }, + { + file: './python/ccxt/__init__.py', + regex: /exchanges \= \[[^\]]+\]/, + replacement: "exchanges = [\n" + " '" + ids.join ("',\n '") + "'," + "\n]", + }, + { + file: './python/ccxt/__init__.py', + regex: /(?:from ccxt\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/, + replacement: ids.map (id => ('from ccxt.' + id + ' import ' + id).padEnd (60) + '# noqa: F401').join ("\n") + "\n\nexchanges", + }, + { + file: './python/ccxt/__init__.py', + regex: /(?:from ccxt\.base\.errors import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]/, + replacement: flat.map (error => ('from ccxt.base.errors' + ' import ' + error).padEnd (60) + '# noqa: F401').join ("\n") + "\n\n", + }, + { + file: './python/ccxt/async_support/__init__.py', + regex: /(?:from ccxt\.base\.errors import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]/, + replacement: flat.map (error => ('from ccxt.base.errors' + ' import ' + error).padEnd (60) + '# noqa: F401').join ("\n") + "\n\n", + }, + { + file: './python/ccxt/async_support/__init__.py', + regex: /(?:from ccxt\.async_support\.[^\.]+ import [^\s]+\s+\# noqa\: F401[\r]?[\n])+[\r]?[\n]exchanges/, + replacement: ids.map (id => ('from ccxt.async_support.' + id + ' import ' + id).padEnd (74) + '# noqa: F401').join ("\n") + "\n\nexchanges", + }, + { + file: './python/ccxt/async_support/__init__.py', + regex: /exchanges \= \[[^\]]+\]/, + replacement: "exchanges = [\n" + " '" + ids.join ("',\n '") + "'," + "\n]", + }, + { + file: './php/base/Exchange.php', + regex: /public static \$exchanges \= array\s*\([^\)]+\)/, + replacement: "public static $exchanges = array(\n '" + ids.join ("',\n '") + "',\n )", + }, + ] + + exportExchanges (replacements) + + // strategically placed exactly here (we can require it AFTER the export) + const exchanges = createExchanges (ids) + + const wikiPath = 'wiki' + , gitWikiPath = 'build/ccxt.wiki' + + cloneGitHubWiki (gitWikiPath) + + exportSupportedAndCertifiedExchanges (exchanges, { + allExchangesPaths: [ + 'README.md', + wikiPath + '/Manual.md', + wikiPath + '/Exchange-Markets.md' + ], + certifiedExchangesPaths: [ + 'README.md', + ], + exchangesByCountriesPaths: [ + wikiPath + '/Exchange-Markets-By-Country.md' + ], + proExchangesPaths: [ + wikiPath + '/ccxt.pro.manual.md', + ], + }) + + exportExchangeIdsToExchangesJson (exchanges) + exportWikiToGitHub (wikiPath, gitWikiPath) + exportKeywordsToPackageJson (exchanges) + + log.bright.green ('Exported successfully.') +} + +// ============================================================================ +// main entry point + +if (require.main === module) { + + // if called directly like `node module` + + exportEverything () + +} else { + + // do nothing if required as a module +} + +// ============================================================================ + +module.exports = { + cloneGitHubWiki, + getIncludedExchangeIds, + exportExchanges, + createExchanges, + exportSupportedAndCertifiedExchanges, + exportExchangeIdsToExchangesJson, + exportWikiToGitHub, + exportKeywordsToPackageJson, + exportEverything, +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/fs.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/fs.js new file mode 100644 index 000000000..bd3fe9381 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/fs.js @@ -0,0 +1,51 @@ +"use strict"; + +const fs = require ('fs') + , path = require ('path') + +function replaceInFile (filename, regex, replacement) { + const contents = fs.readFileSync (filename, 'utf8') + const newContents = contents.replace (regex, replacement) + fs.truncateSync (filename) + fs.writeFileSync (filename, newContents) +} + +function copyFile (oldName, newName) { + const contents = fs.readFileSync (oldName, 'utf8') + if (fs.existsSync (newName)) { + fs.truncateSync (newName) + } + fs.writeFileSync (newName, contents) +} + +function overwriteFile (filename, contents) { + // log.cyan ('Overwriting → ' + filename.yellow) + fs.closeSync (fs.openSync (filename, 'a')); + fs.truncateSync (filename) + fs.writeFileSync (filename, contents) +} + +function createFolder (folder) { + try { + fs.mkdirSync (folder) + } catch (err) { + if (err.code !== 'EEXIST') { + throw err + } + } +} + +function createFolderRecursively (folder) { + const parts = folder.split (path.sep) + for (let i = 1; i <= parts.length; i++) { + createFolder (path.join.apply (null, parts.slice (0, i))) + } +} + +module.exports = { + replaceInFile, + copyFile, + overwriteFile, + createFolder, + createFolderRecursively, +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/transpile.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/transpile.js new file mode 100644 index 000000000..0f4d1dcc6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/transpile.js @@ -0,0 +1,1487 @@ +// --------------------------------------------------------------------------- +// Usage: npm run transpile +// --------------------------------------------------------------------------- + +"use strict"; + +const fs = require ('fs') + , log = require ('ololog').unlimited + , _ = require ('ansicolor').nice + , errors = require ('../js/base/errors.js') + , functions = require ('../js/base/functions.js') + , { + unCamelCase, + precisionConstants, + safeString, + unique, + } = functions + , { basename } = require ('path') + , { + createFolderRecursively, + replaceInFile, + overwriteFile, + } = require ('./fs.js') + , Exchange = require ('../js/base/Exchange.js') + +class Transpiler { + + getCommonRegexes () { + return [ + + [ /\.deepExtend\s/g, '.deep_extend'], + [ /\.safeFloat2\s/g, '.safe_float_2'], + [ /\.safeInteger2\s/g, '.safe_integer_2'], + [ /\.safeIntegerProduct2\s/g, '.safe_integer_product_2'], + [ /\.safeTimestamp2\s/g, '.safe_timestamp_2'], + [ /\.safeString2\s/g, '.safe_string_2'], + [ /\.safeStringLower2\s/g, '.safe_string_lower_2'], + [ /\.safeStringUpper2\s/g, '.safe_string_upper_2'], + [ /\.safeValue2\s/g, '.safe_value_2'], + [ /\.safeFloat\s/g, '.safe_float'], + [ /\.safeInteger\s/g, '.safe_integer'], + [ /\.safeIntegerProduct\s/g, '.safe_integer_product'], + [ /\.safeTimestamp\s/g, '.safe_timestamp'], + [ /\.safeString\s/g, '.safe_string'], + [ /\.safeStringLower\s/g, '.safe_string_lower'], + [ /\.safeStringUpper\s/g, '.safe_string_upper'], + [ /\.safeValue\s/g, '.safe_value'], + [ /\.inArray\s/g, '.in_array'], + [ /\.toArray\s/g, '.to_array'], + [ /\.isEmpty\s/g, '.is_empty'], + [ /\.arrayConcat\s/g, '.array_concat'], + [ /\.binaryConcat\s/g, '.binary_concat'], + [ /\.binaryConcatArray\s/g, '.binary_concat_array'], + [ /\.binaryToString\s/g, '.binary_to_string' ], + [ /\.precisionFromString\s/g, '.precision_from_string'], + [ /\.implodeParams\s/g, '.implode_params'], + [ /\.extractParams\s/g, '.extract_params'], + [ /\.parseBalance\s/g, '.parse_balance'], + [ /\.parseBalanceResponse\s/g, '.parse_balance_response'], + [ /\.parseOHLCVs\s/g, '.parse_ohlcvs'], + [ /\.parseOHLCV\s/g, '.parse_ohlcv'], + [ /\.parseDate\s/g, '.parse_date'], + [ /\.parseLedgerEntry\s/g, '.parse_ledger_entry'], + [ /\.parseLedger\s/g, '.parse_ledger'], + [ /\.parseTicker\s/g, '.parse_ticker'], + [ /\.parseTimeframe\s/g, '.parse_timeframe'], + [ /\.parseTradesData\s/g, '.parse_trades_data'], + [ /\.parseTrades\s/g, '.parse_trades'], + [ /\.parseTrade\s/g, '.parse_trade'], + [ /\.parseTradingViewOHLCV\s/g, '.parse_trading_view_ohlcv'], + [ /\.convertTradingViewToOHLCV\s/g, '.convert_trading_view_to_ohlcv'], + [ /\.parseTransaction\s/g, '.parse_transaction'], + [ /\.parseTransactions\s/g, '.parse_transactions'], + [ /\.parseOrderBook\s/g, '.parse_order_book'], + [ /\.parseBidsAsks\s/g, '.parse_bids_asks'], + [ /\.parseBidAsk\s/g, '.parse_bid_ask'], + [ /\.parseOrders\s/g, '.parse_orders'], + [ /\.parseOrderStatus\s/g, '.parse_order_status'], + [ /\.parseOrder\s/g, '.parse_order'], + [ /\.parseJson\s/g, '.parse_json'], + [ /\.filterByArray\s/g, '.filter_by_array'], + [ /\.filterBySymbolSinceLimit\s/g, '.filter_by_symbol_since_limit'], + [ /\.filterBySinceLimit\s/g, '.filter_by_since_limit'], + [ /\.filterBySymbol\s/g, '.filter_by_symbol'], + [ /\.getVersionString\s/g, '.get_version_string'], + [ /\.indexBy\s/g, '.index_by'], + [ /\.sortBy\s/g, '.sort_by'], + [ /\.filterBy\s/g, '.filter_by'], + [ /\.groupBy\s/g, '.group_by'], + [ /\.marketIds\s/g, '.market_ids'], + [ /\.marketId\s/g, '.market_id'], + [ /\.fetchFundingFees\s/g, '.fetch_funding_fees'], + [ /\.fetchTradingFees\s/g, '.fetch_trading_fees'], + [ /\.fetchTradingFee\s/g, '.fetch_trading_fee'], + [ /\.fetchFees\s/g, '.fetch_fees'], + [ /\.fetchL2OrderBook\s/g, '.fetch_l2_order_book'], + [ /\.fetchOrderBook\s/g, '.fetch_order_book'], + [ /\.fetchMyTrades\s/g, '.fetch_my_trades'], + [ /\.fetchOrderStatus\s/g, '.fetch_order_status'], + [ /\.fetchOpenOrders\s/g, '.fetch_open_orders'], + [ /\.fetchOpenOrder\s/g, '.fetch_open_order'], + [ /\.fetchOrders\s/g, '.fetch_orders'], + [ /\.fetchOrderTrades\s/g, '.fetch_order_trades'], + [ /\.fetchOrder\s/g, '.fetch_order'], + [ /\.fetchBidsAsks\s/g, '.fetch_bids_asks'], + [ /\.fetchTickers\s/g, '.fetch_tickers'], + [ /\.fetchTicker\s/g, '.fetch_ticker'], + [ /\.fetchCurrencies\s/g, '.fetch_currencies'], + [ /\.numberToString\s/g, '.number_to_string' ], + [ /\.decimalToPrecision\s/g, '.decimal_to_precision'], + [ /\.priceToPrecision\s/g, '.price_to_precision'], + [ /\.amountToPrecision\s/g, '.amount_to_precision'], + [ /\.amountToLots\s/g, '.amount_to_lots'], + [ /\.feeToPrecision\s/g, '.fee_to_precision'], + [ /\.currencyToPrecision\s/g, '.currency_to_precision'], + [ /\.costToPrecision\s/g, '.cost_to_precision'], + [ /\.commonCurrencyCode\s/g, '.common_currency_code'], + [ /\.loadAccounts\s/g, '.load_accounts'], + [ /\.loadFees\s/g, '.load_fees'], + [ /\.loadMarkets\s/g, '.load_markets'], + [ /\.fetchMarkets\s/g, '.fetch_markets'], + [ /\.appendInactiveMarkets\s/g, '.append_inactive_markets'], + [ /\.fetchCategories\s/g, '.fetch_categories'], + [ /\.calculateFee\s/g, '.calculate_fee'], + [ /\.editLimitBuyOrder\s/g, '.edit_limit_buy_order'], + [ /\.editLimitSellOrder\s/g, '.edit_limit_sell_order'], + [ /\.editLimitOrder\s/g, '.edit_limit_order'], + [ /\.editOrder\s/g, '.edit_order'], + [ /\.encodeURIComponent\s/g, '.encode_uri_component'], + [ /\.throwExceptionOnError\s/g, '.throw_exception_on_error'], + [ /\.handleErrors\s/g, '.handle_errors'], + [ /\.checkRequiredCredentials\s/g, '.check_required_credentials'], + [ /\.checkRequiredDependencies\s/g, '.check_required_dependencies'], + [ /\.checkAddress\s/g, '.check_address'], + [ /\.convertTradingViewToOHLCV\s/g, '.convert_trading_view_to_ohlcv'], + [ /\.convertOHLCVToTradingView\s/g, '.convert_ohlcv_to_trading_view'], + [ /\.signBodyWithSecret\s/g, '.sign_body_with_secret'], + [ /\.isJsonEncodedObject\s/g, '.is_json_encoded_object'], + [ /\.setSandboxMode\s/g, '.set_sandbox_mode'], + [ /\.safeCurrencyCode\s/g, '.safe_currency_code'], + [ /\.safeCurrency\s/g, '.safe_currency'], + [ /\.safeSymbol\s/g, '.safe_symbol'], + [ /\.safeMarket\s/g, '.safe_market'], + [ /\.roundTimeframe/g, '.round_timeframe'], + [ /\.integerDivide/g, '.integer_divide'], + [ /\.integerModulo/g, '.integer_modulo'], + [ /\.integerPow/g, '.integer_pow'], + [ /\.findBroadlyMatchedKey\s/g, '.find_broadly_matched_key'], + [ /\.throwBroadlyMatchedException\s/g, '.throw_broadly_matched_exception'], + [ /\.throwExactlyMatchedException\s/g, '.throw_exactly_matched_exception'], + [ /\.findTimeframe\s/g, '.find_timeframe'], + [ /errorHierarchy/g, 'error_hierarchy'], + [ /\.base16ToBinary/g, '.base16_to_binary'], + [ /\'use strict\';?\s+/g, '' ], + [ /\.urlencodeWithArrayRepeat\s/g, '.urlencode_with_array_repeat' ], + [ /\.call\s*\(this, /g, '(' ] + ] + } + + getPythonRegexes () { + + return [ + + [ /Array\.isArray\s*\(([^\)]+)\)/g, 'isinstance($1, list)' ], + [ /([^\(\s]+)\s+instanceof\s+([^\)\s]+)/g, 'isinstance($1, $2)' ], + + [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'undefined\'/g, '$1[$2] is None' ], + [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'undefined\'/g, '$1[$2] is not None' ], + [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'undefined\'/g, '$1 is None' ], + [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'undefined\'/g, '$1 is not None' ], + [ /typeof\s+(.+?)\s+\=\=\=?\s+\'undefined\'/g, '$1 is None' ], + [ /typeof\s+(.+?)\s+\!\=\=?\s+\'undefined\'/g, '$1 is not None' ], + + [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'number\'/g, "isinstance($1[$2], numbers.Real)" ], + [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'number\'/g, "isinstance($1[$2], numbers.Real)'" ], + [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'number\'/g, "isinstance($1, numbers.Real)" ], + [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'number\'/g, "isinstance($1, numbers.Real)" ], + + [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+undefined/g, '$1[$2] is None' ], + [ /([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+undefined/g, '$1[$2] is not None' ], + [ /([^\s]+)\s+\=\=\=?\s+undefined/g, '$1 is None' ], + [ /([^\s]+)\s+\!\=\=?\s+undefined/g, '$1 is not None' ], + [ /(.+?)\s+\=\=\=?\s+undefined/g, '$1 is None' ], + [ /(.+?)\s+\!\=\=?\s+undefined/g, '$1 is not None' ], + + [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\=\=\=?\s+\'string\'/g, 'isinstance($1[$2], basestring)' ], + [ /typeof\s+([^\s\[]+)(?:\s|\[(.+?)\])\s+\!\=\=?\s+\'string\'/g, 'not isinstance($1[$2], basestring)' ], + [ /typeof\s+([^\s]+)\s+\=\=\=?\s+\'string\'/g, 'isinstance($1, basestring)' ], + [ /typeof\s+([^\s]+)\s+\!\=\=?\s+\'string\'/g, 'not isinstance($1, basestring)' ], + [ /undefined/g, 'None' ], + [ /\=\=\=?/g, '==' ], + [ /\!\=\=?/g, '!=' ], + [ /this\.stringToBinary\s*\((.*)\)/g, '$1' ], + [ /\.shift\s*\(\)/g, '.pop(0)' ], + [ /Number\.MAX_SAFE_INTEGER/g, 'float(\'inf\')'], + [ /function\s*(\w+\s*\([^)]+\))\s*{/g, 'def $1:'], + [ /assert\s*\((.+)\);/g, 'assert $1'], + + // insert common regexes in the middle (critical) + ].concat (this.getCommonRegexes ()).concat ([ + + // [ /this\.urlencode\s/g, '_urlencode.urlencode ' ], // use self.urlencode instead + [ /this\./g, 'self.' ], + [ /([^a-zA-Z\'])this([^a-zA-Z])/g, '$1self$2' ], + [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1$2' ], + [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}\s\=\s([^\;]+)/g, '$1$2 = (lambda $2: ($2))(**$3)' ], + [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ], + [ /Object\.keys\s*\((.*)\)\.length/g, '$1' ], + [ /Object\.keys\s*\((.*)\)/g, 'list($1.keys())' ], + [ /Object\.values\s*\((.*)\)/g, 'list($1.values())' ], + [ /\[([^\]]+)\]\.join\s*\(([^\)]+)\)/g, "$2.join([$1])" ], + [ /hash \(([^,]+)\, \'(sha[0-9])\'/g, "hash($1, '$2'" ], + [ /hmac \(([^,]+)\, ([^,]+)\, \'(md5)\'/g, 'hmac($1, $2, hashlib.$3' ], + [ /hmac \(([^,]+)\, ([^,]+)\, \'(sha[0-9]+)\'/g, 'hmac($1, $2, hashlib.$3' ], + [ /throw new ([\S]+) \((.*)\)/g, 'raise $1($2)'], + [ /throw ([\S]+)/g, 'raise $1'], + [ /try {/g, 'try:'], + [ /\}\s+catch \(([\S]+)\) {/g, 'except Exception as $1:'], + [ /([\s\(])extend(\s)/g, '$1self.extend$2' ], + [ /\} else if/g, 'elif' ], + [ /else if/g, 'elif' ], + [ /if\s+\((.*)\)\s+\{/g, 'if $1:' ], + [ /if\s+\((.*)\)\s*[\n]/g, "if $1:\n" ], + [ /\}\s*else\s*\{/g, 'else:' ], + [ /else\s*[\n]/g, "else:\n" ], + [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(?:\<=|\>=|<|>)\s*(.*)\.length\s*\;[^\)]+\)\s*{/g, 'for $1 in range($2, len($3)):'], + [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(?:\<=|\>=|<|>)\s*(.*)\s*\;[^\)]+\)\s*{/g, 'for $1 in range($2, $3):'], + [ /\s\|\|\s/g, ' or ' ], + [ /\s\&\&\s/g, ' and ' ], + [ /\!([^\='"])/g, 'not $1'], + [ /\.push\s*\(([\s\S]+?)\);/g, '.append($1);' ], + [ /^(\s*}\s*$)+/gm, '' ], + [ /\;(\s+?\/\/.+?)/g, '$1' ], + [ /\;$/gm, '' ], + [ /\.toUpperCase\s*/g, '.upper' ], + [ /\.toLowerCase\s*/g, '.lower' ], + [ /JSON\.stringify\s*/g, 'json.dumps' ], + [ /JSON\.parse\s*/g, "json.loads" ], + // [ /([^\(\s]+)\.includes\s+\(([^\)]+)\)/g, '$2 in $1' ], + // [ /\'%([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "'{:$1}'.format($2)" ], + [ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "'{:.$2f}'.format($1)" ], + [ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "('{:.' + str($2) + 'f}').format($1)" ], + [ /parseFloat\s*/g, 'float'], + [ /parseInt\s*/g, 'int'], + [ /self\[([^\]+]+)\]/g, 'getattr(self, $1)' ], + [ /([^\s]+)\.slice \(([^\,\)]+)\,\s?([^\)]+)\)/g, '$1[$2:$3]' ], + [ /([^\s]+)\.slice \(([^\)\:]+)\)/g, '$1[$2:]' ], + [ /([^\s(:]+)\.length/g, 'len($1)' ], + [ /Math\.floor\s*\(([^\)]+)\)/g, 'int(math.floor($1))' ], + [ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ], + [ /Math\.pow\s*\(([^\)]+)\)/g, 'math.pow($1)' ], + [ /Math\.round\s*\(([^\)]+)\)/g, 'int(round($1))' ], + [ /Math\.ceil\s*\(([^\)]+)\)/g, 'int(math.ceil($1))' ], + [ /Math\.log/g, 'math.log' ], + [ /([a-zA-Z0-9_\.]*\([^\)]+\)|[^\s]+)\s+\?\s*([^\:]+)\s+\:\s*([^\n]+)/g, '$2 if $1 else $3'], + [ /(^|\s)\/\//g, '$1#' ], + [ /([^\n\s]) #/g, '$1 #' ], // PEP8 E261 + [ /\.indexOf/g, '.find'], + [ /(\s|\()true/g, '$1True'], + [ /(\s|\()false/g, '$1False'], + [ /([^\s]+\s*\(\))\.toString\s+\(\)/g, 'str($1)' ], + [ /([^\s]+)\.toString \(\)/g, 'str($1)' ], + [ /([^\s]+)\.join\s*\(\s*([^\)\[\]]+?)\s*\)/g, '$2.join($1)' ], + [ /Math\.(max|min)\s/g, '$1' ], + [ / = new /g, ' = ' ], // python does not have a 'new' keyword + [ /console\.log\s/g, 'print' ], + [ /process\.exit\s+/g, 'sys.exit' ], + [ /([^:+=\/\*\s-]+) \(/g, '$1(' ], // PEP8 E225 remove whitespaces before left ( round bracket + [ /\sand\(/g, ' and (' ], + [ /\sor\(/g, ' or (' ], + [ /\snot\(/g, ' not (' ], + [ /\[ /g, '[' ], // PEP8 E201 remove whitespaces after left [ square bracket + [ /\{ /g, '{' ], // PEP8 E201 remove whitespaces after left { bracket + [ /(?<=[^\s#]) \]/g, ']' ], // PEP8 E202 remove whitespaces before right ] square bracket + [ /(?<=[^\s#]) \}/g, '}' ], // PEP8 E202 remove whitespaces before right } bracket + [ /([^a-z])(elif|if|or|else)\(/g, '$1$2 \(' ], // a correction for PEP8 E225 side-effect for compound and ternary conditionals + [ /\=\=\sTrue/g, 'is True' ], // a correction for PEP8 E712, it likes "is True", not "== True" + [ /\sdelete\s/g, ' del ' ], + [ /(?' ], + [ / this;/g, ' $this;' ], + [ /([^'])this_\./g, '$1$this_->' ], + [ /\{\}/g, 'array()' ], + [ /\[\]/g, 'array()' ], + + // add {}-array syntax conversions up to 20 levels deep in the same line + ]).concat ([ ... Array (20) ].map (x => [ /\{([^\n\}]+)\}/g, 'array($1)' ] )).concat ([ + + [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\[\s*([^\]]+)\s\]/g, '$1list($2)' ], + [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s\{\s*([^\}]+)\s\}/g, '$1array_values(list($2))' ], + [ /(^|[^a-zA-Z0-9_])(?:let|const|var)\s/g, '$1' ], + [ /Object\.keys\s*\((.*)\)\.length/g, '$1' ], + [ /Object\.keys\s*\((.*)\)/g, 'is_array($1) ? array_keys($1) : array()' ], + [ /Object\.values\s*\((.*)\)/g, 'is_array($1) ? array_values($1) : array()' ], + [ /([^\s]+\s*\(\))\.toString \(\)/g, '(string) $1' ], + [ /([^\s]+)\.toString \(\)/g, '(string) $1' ], + [ /throw new Error \((.*)\)/g, 'throw new \\Exception($1)' ], + [ /throw new ([\S]+) \((.*)\)/g, 'throw new $1($2)' ], + [ /throw ([\S]+)\;/g, 'throw $$$1;' ], + [ '([^a-z]+) (' + Object.keys (errors).join ('|') + ')([^\\s])', "$1 '\\\\ccxt\\\\$2'$3" ], + [ /\}\s+catch \(([\S]+)\) {/g, '} catch (Exception $$$1) {' ], + [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\.length\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 count($4);$5) {' ], + [ /for\s+\(([a-zA-Z0-9_]+)\s*=\s*([^\;\s]+\s*)\;[^\<\>\=]+(\<=|\>=|<|>)\s*(.*)\s*\;([^\)]+)\)\s*{/g, 'for ($1 = $2; $1 $3 $4;$5) {' ], + [ /([^\s]+)\.length\;/g, 'is_array($1) ? count($1) : 0;' ], + [ /\.push\s*\(([\s\S]+?)\)\;/g, '[] = $1;' ], + [ /(\s)await(\s)/g, '$1' ], + [ /([\S])\: /g, '$1 => ' ], + + // add {}-array syntax conversions up to 20 levels deep + ]).concat ([ ... Array (20) ].map (x => [ /\{([^\{]+?)\}([^\s])/g, 'array($1)$2' ])).concat ([ + + [ /\[\s*([^\]]+?)\s*\]\.join\s*\(\s*([^\)]+?)\s*\)/g, "implode($2, array($1))" ], + + // add []-array syntax conversions up to 20 levels deep + ]).concat ([ ... Array (20) ].map (x => [ /\[(\s[^\]]+?\s)\]/g, 'array($1)' ])).concat ([ + + [ /JSON\.stringify/g, 'json_encode' ], + [ /JSON\.parse\s+\(([^\)]+)\)/g, 'json_decode($1, $$as_associative_array = true)' ], + [ /([^\(\s]+)\.includes\s+\(([^\)]+)\)/g, 'mb_strpos($1, $2)' ], + // [ /\'([^\']+)\'\.sprintf\s*\(([^\)]+)\)/g, "sprintf ('$1', $2)" ], + [ /([^\s]+)\.toFixed\s*\(([0-9]+)\)/g, "sprintf('%.$2f', $1)" ], + [ /([^\s]+)\.toFixed\s*\(([^\)]+)\)/g, "sprintf('%.' . $2 . 'f', $1)" ], + [ /parseFloat\s/g, 'floatval'], + [ /parseInt\s/g, 'intval'], + [ / \+ (?!\d)/g, ' . ' ], + [ / \+\= (?!\d)/g, ' .= ' ], + [ /([^\s\(]+(?:\s*\(.+\))?)\.toUpperCase\s*\(\)/g, 'strtoupper($1)' ], + [ /([^\s\(]+(?:\s*\(.+\))?)\.toLowerCase\s*\(\)/g, 'strtolower($1)' ], + [ /([^\s\(]+(?:\s*\(.+\))?)\.replace\s*\(([^)]+)\)/g, 'str_replace($2, $1)' ], + [ /this\[([^\]+]+)\]/g, '$$this->$$$1' ], + [ /([^\s\(]+).slice \(([^\)\:,]+)\)/g, 'mb_substr($1, $2)' ], + [ /([^\s\(]+).slice \(([^\,\)]+)\,\s*([^\)]+)\)/g, 'mb_substr($1, $2, $3 - $2)' ], + [ /([^\s\(]+).split \(('[^']*'|[^\,]+?)\)/g, 'explode($2, $1)' ], + [ /([^\s\(]+)\.length/g, 'strlen($1)' ], + [ /Math\.floor\s*\(([^\)]+)\)/g, '(int) floor($1)' ], + [ /Math\.abs\s*\(([^\)]+)\)/g, 'abs($1)' ], + [ /Math\.round\s*\(([^\)]+)\)/g, '(int) round($1)' ], + [ /Math\.ceil\s*\(([^\)]+)\)/g, '(int) ceil($1)' ], + [ /Math\.pow\s*\(([^\)]+)\)/g, 'pow($1)' ], + [ /Math\.log/g, 'log' ], + [ /([^\(\s]+)\s+%\s+([^\s\,\;\)]+)/g, 'fmod($1, $2)' ], + [ /\(([^\s\(]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0\)/g, '(mb_strpos($1, $2) !== false)' ], + [ /([^\s\(]+)\.indexOf\s*\(([^\)]+)\)\s*\>\=\s*0/g, 'mb_strpos($1, $2) !== false' ], + [ /([^\s\(]+)\.indexOf\s*\(([^\)]+)\)/g, 'mb_strpos($1, $2)' ], + [ /\(([^\s\(]+)\sin\s([^\)]+)\)/g, '(is_array($2) && array_key_exists($1, $2))' ], + [ /([^\s]+)\.join\s*\(\s*([^\)]+?)\s*\)/g, 'implode($2, $1)' ], + [ 'new ccxt\\.', 'new \\ccxt\\' ], // a special case for test_exchange_datetime_functions.php (and for other files, maybe) + [ /Math\.(max|min)/g, '$1' ], + [ /console\.log/g, 'var_dump'], + [ /process\.exit/g, 'exit'], + [ /super\./g, 'parent::'], + [ /\sdelete\s([^\n]+)\;/g, ' unset($1);' ], + [ /\~([a-zA-Z0-9_-]+?)\~/g, '{$1}' ], // resolve the "arrays vs url params" conflict (both are in {}-brackets) + ]) + } + + getBaseClass () { + return new Exchange () + } + + getBaseMethods () { + const baseExchange = this.getBaseClass () + let object = baseExchange + let properties = [] + while (object !== Object.prototype) { + properties = properties.concat (Object.getOwnPropertyNames (object)) + object = Object.getPrototypeOf (object) + } + return properties.filter (x => typeof baseExchange[x] === 'function') + } + + getPythonBaseMethods () { + return this.getBaseMethods () + } + + getPHPBaseMethods () { + return this.getBaseMethods () + } + + //------------------------------------------------------------------------- + // the following common headers are used for transpiled tests + + getPythonPreamble () { + return [ + "import os", + "import sys", + "", + "root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))", + "sys.path.append(root)", + "", + "# ----------------------------------------------------------------------------", + "", + "# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:", + "# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code", + "", + "# ----------------------------------------------------------------------------", + "", + ].join ("\n") + } + + getPHPPreamble (include = true) { + return [ + "= 0) { + precisionImports.push ('from ccxt.base.decimal_to_precision import ' + constant) + } + } + + header = header.concat (libraries, errorImports, precisionImports) + + methods = methods.concat (this.getPythonBaseMethods ()) + + for (let method of methods) { + const regex = new RegExp ('self\\.(' + method + ')([^a-zA-Z0-9_])', 'g') + bodyAsString = bodyAsString.replace (regex, + (match, p1, p2) => ('self.' + unCamelCase (p1) + p2)) + } + + header.push ("\n\n" + this.createPythonClassDeclaration (className, baseClass)) + + const footer = [ + '', // footer (last empty line) + ] + + const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n') + return result + } + + // ------------------------------------------------------------------------ + + createPHPClassDeclaration (className, baseClass) { + return 'class ' + className + ' extends ' + baseClass + ' {' + } + + createPHPClassHeader (className, baseClass, bodyAsString) { + return [ + "(' + method + ')\\s?(\\(|[^a-zA-Z0-9_])', 'g') + bodyAsString = bodyAsString.replace (regex, + (match, p1, p2) => { + return ((p2 === '(') ? + ('$this->' + unCamelCase (p1) + p2) : // support direct php calls + ("array($this, '" + unCamelCase (p1) + "')" + p2)) // as well as passing instance methods as callables + }) + } + + header.push ("\n" + this.createPHPClassDeclaration (className, baseClass)) + + const footer =[ + "}\n", + ] + + const result = header.join ("\n") + "\n" + bodyAsString + "\n" + footer.join ('\n') + return result + } + + // ======================================================================== + + transpileJavaScriptToPython3 ({ js, className, removeEmptyLines }) { + + // transpile JS → Python 3 + let python3Body = this.regexAll (js, this.getPythonRegexes ()) + + if (removeEmptyLines) + python3Body = python3Body.replace (/$\s*$/gm, '') + + python3Body = python3Body.replace (/\'([абвгдеёжзийклмнопрстуфхцчшщъыьэюя服务端忙碌]+)\'/gm, "u'$1'") + + // special case for Python OrderedDicts + let orderedDictRegex = /\.ordered\s+\(\{([^\}]+)\}\)/g + let orderedDictMatches = undefined + while (orderedDictMatches = orderedDictRegex.exec (python3Body)) { + let replaced = orderedDictMatches[1].replace (/^(\s+)([^\:]+)\:\s*([^\,]+)\,$/gm, '$1($2, $3),') + python3Body = python3Body.replace (orderedDictRegex, '\.ordered([' + replaced + '])') + } + + // snake case function names + python3Body = python3Body.replace (/def (\w+)/g, (match, group1) => 'def ' + unCamelCase (group1)) + + // special case for Python super + if (className) { + python3Body = python3Body.replace (/super\./g, 'super(' + className + ', self).') + } + + return python3Body + } + + // ------------------------------------------------------------------------ + + transpilePython3ToPython2 (py) { + + // remove await from Python 2 body (transpile Python 3 → Python 2) + let python2Body = this.regexAll (py, this.getPython2Regexes ()) + + return python2Body + } + + // ------------------------------------------------------------------------ + + transpileJavaScriptToPHP ({ js, variables }) { + + // match all local variables (let, const or var) + let localVariablesRegex = /(?:^|[^a-zA-Z0-9_])(?:let|const|var)\s+(?:\[([^\]]+)\]|([a-zA-Z0-9_]+))/g // local variables + + let allVariables = (variables || []).map (x => x); // clone the array + // process the variables created in destructuring assignments as well + let localVariablesMatches + while (localVariablesMatches = localVariablesRegex.exec (js)) { + if (localVariablesMatches[1]) { + // this is a destructuring assignment like + // let [ a, b, c ] = 'a-b-c'.split ('-') + let matches = localVariablesMatches[1].trim ().split (', ') // split the destructuring assignment by comma + matches.forEach (x => allVariables.push (x.trim ())) // trim each variable name + } else { + // this is a single variable assignment + allVariables.push (localVariablesMatches[2].trim ()) // add it to the list of local variables + } + } + + // match all variables instantiated in the catch()-block of a try-catch clause + let catchClauseRegex = /catch \(([^)]+)\)/g + let catchClauseMatches + while (catchClauseMatches = catchClauseRegex.exec (js)) { + allVariables.push (catchClauseMatches[1]) + } + + // match all variables instantiated as function parameters + let functionParamRegex = /function\s*(\w+)\s*\(([^)]+)\)/g + js = js.replace (functionParamRegex, (match, group1, group2) => 'function ' + unCamelCase (group1) + '(' + group2 + ')') + let functionParamVariables + while (functionParamVariables = functionParamRegex.exec (js)) { + const match = functionParamVariables[2] + const tokens = match.split (', ') + allVariables = allVariables.concat (tokens) + } + + allVariables = allVariables.map (error => this.regexAll (error, this.getCommonRegexes ())) + + // append $ to all variables in the method (PHP syntax demands $ at the beginning of a variable name) + let phpVariablesRegexes = allVariables.map (x => [ "(^|[^$$a-zA-Z0-9\\.\\>'_/])" + x + "([^a-zA-Z0-9'_/])", '$1$$' + x + '$2' ]) + + // support for php syntax for object-pointer dereference + // convert all $variable.property to $variable->property + let variablePropertiesRegexes = allVariables.map (x => [ "(^|[^a-zA-Z0-9\\.\\>'_])" + x + '\\.', '$1' + x + '->' ]) + + // transpile JS → PHP + const phpRegexes = this.getPHPRegexes () + let phpBody = this.regexAll (js, phpRegexes.concat (phpVariablesRegexes).concat (variablePropertiesRegexes)) + + return phpBody + } + + // ------------------------------------------------------------------------ + + transpileJavaScriptToPythonAndPHP (args) { + + // transpile JS → Python 3 + let python3Body = this.transpileJavaScriptToPython3 (args) + + // remove await from Python 2 body (transpile Python 3 → Python 2) + let python2Body = this.transpilePython3ToPython2 (python3Body) + + // transpile JS → PHP + let phpBody = this.transpileJavaScriptToPHP (args) + + return { python3Body, python2Body, phpBody } + } + + //----------------------------------------------------------------------------- + + transpilePythonAsyncToSync () { + + const async = './python/test/test_async.py' + const sync = './python/test/test.py' + log.magenta ('Transpiling ' + async .yellow + ' → ' + sync.yellow) + const fileContents = fs.readFileSync (async, 'utf8') + let lines = fileContents.split ("\n") + + lines = lines.filter (line => ![ 'import asyncio' ].includes (line)) + .map (line => { + return ( + line.replace ('asyncio.get_event_loop().run_until_complete(main())', 'main()') + .replace ('import ccxt.async_support as ccxt', 'import ccxt') + .replace (/.*token\_bucket.*/g, '') + .replace ('await asyncio.sleep', 'time.sleep') + .replace ('async ', '') + .replace ('await ', '')) + }) + + // lines.forEach (line => log (line)) + + function deleteFunction (f, from) { + // the following regexes make a technical error + // since it won't cut away a single function + // it will delete everything up to the beginning of the next comment + const re1 = new RegExp ('def ' + f + '[^\#]+', 'g') + const re2 = new RegExp ('[\\s]+' + f + '\\(exchange\\)', 'g') + return from.replace (re1, '').replace (re2, '') + } + + let newContents = lines.join ('\n') + + newContents = deleteFunction ('test_tickers_async', newContents) + newContents = deleteFunction ('test_l2_order_books_async', newContents) + + fs.truncateSync (sync) + fs.writeFileSync (sync, newContents) + } + + // ------------------------------------------------------------------------ + + transpileDerivedExchangeClass (contents, methodNames = undefined) { + + let exchangeClassDeclarationMatches = contents.match (/^module\.exports\s*=\s*class\s+([\S]+)\s+extends\s+([\S]+)\s+{([\s\S]+?)^};*/m) + + // log.green (file, exchangeClassDeclarationMatches[3]) + + let className = exchangeClassDeclarationMatches[1] + let baseClass = exchangeClassDeclarationMatches[2] + + let methods = exchangeClassDeclarationMatches[3].trim ().split (/\n\s*\n/) + + let python2 = [] + let python3 = [] + let php = [] + + methodNames = [] // methodNames || [] + + // run through all methods + for (let i = 0; i < methods.length; i++) { + // parse the method signature + let part = methods[i].trim () + let lines = part.split ("\n") + let signature = lines[0].trim () + let methodSignatureRegex = /(async |)([\S]+)\s\(([^)]*)\)\s*{/ // signature line + let matches = methodSignatureRegex.exec (signature) + + if (!matches) { + log.red (methods[i]) + log.yellow.bright ("\nMake sure your methods don't have empty lines!\n") + } + + // async or not + let keyword = matches[1] + // try { + // keyword = matches[1] + // } catch (e) { + // log.red (e) + // log.green (methods[i]) + // log.yellow (exchangeClassDeclarationMatches[3].trim ().split (/\n\s*\n/)) + // process.exit () + // } + + // method name + let method = matches[2] + + methodNames.push (method) + + method = unCamelCase (method) + + // method arguments + let args = matches[3].trim () + + // extract argument names and local variables + args = args.length ? args.split (',').map (x => x.trim ()) : [] + + // get names of all method arguments for later substitutions + let variables = args.map (arg => arg.split ('=').map (x => x.trim ()) [0]) + + // add $ to each argument name in PHP method signature + let phpArgs = args.join (', $').trim ().replace (/undefined/g, 'null').replace ('{}', 'array ()') + phpArgs = phpArgs.length ? ('$' + phpArgs) : '' + + // remove excessive spacing from argument defaults in Python method signature + let pythonArgs = args.map (x => x.replace (' = ', '=')) + .join (', ') + .replace (/undefined/g, 'None') + .replace (/false/g, 'False') + .replace (/true/g, 'True') + + // method body without the signature (first line) + // and without the closing bracket (last line) + let js = lines.slice (1, -1).join ("\n") + + // transpile everything + let { python3Body, python2Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js, className, variables, removeEmptyLines: true }) + + // compile the final Python code for the method signature + let pythonString = 'def ' + method + '(self' + (pythonArgs.length ? ', ' + pythonArgs : '') + '):' + + // compile signature + body for Python 2 + python2.push (''); + python2.push (' ' + pythonString); + python2.push (python2Body); + + // compile signature + body for Python 3 + python3.push (''); + python3.push (' ' + keyword + pythonString); + python3.push (python3Body); + + // compile signature + body for PHP + php.push (''); + php.push (' public function ' + method + '(' + phpArgs + ') {'); + php.push (phpBody); + php.push (' }') + + } + + return { + + // altogether in PHP, Python 2 and 3 (async) + python2: this.createPythonClass (className, baseClass, python2, methodNames), + python3: this.createPythonClass (className, baseClass, python3, methodNames, true), + php: this.createPHPClass (className, baseClass, php, methodNames), + + className, + baseClass, + } + } + + // ======================================================================== + + transpileDerivedExchangeFile (jsFolder, filename, options) { + + // todo normalize jsFolder and other arguments + + try { + + const { python2Folder, python3Folder, phpFolder } = options + const path = jsFolder + filename + const contents = fs.readFileSync (path, 'utf8') + + // function getMethodNames (object) { + // let functions = [] + // let o = object + // do { + // functions = functions.concat (Object.getOwnPropertyNames (o)) + // } while (o = Object.getPrototypeOf (o)) + // return unique (functions.filter (f => (typeof object[f] === 'function'))) + // } + // const exchangeClass = require ('.' + path) + // const exchange = new exchangeClass () + // const methodNames = getMethodNames (exchange) + // const { python2, python3, php, className, baseClass } = + // this.transpileDerivedExchangeClass (contents, methodNames) + + const { python2, python3, php, className, baseClass } = + this.transpileDerivedExchangeClass (contents) + + log.cyan ('Transpiling from', filename.yellow) + + ;[ + [ python2Folder, filename.replace ('.js', '.py'), python2 ], + [ python3Folder, filename.replace ('.js', '.py'), python3 ], + [ phpFolder, filename.replace ('.js', '.php'), php ], + ].forEach (([ folder, filename, code ]) => { + if (folder) { + overwriteFile (folder + filename, code) + } + }) + + return { className, baseClass } + + } catch (e) { + + log.red ('\nFailed to transpile source code from', filename.yellow) + log.red ('See https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md on how to build this library properly\n') + throw e // rethrow it + } + } + + //------------------------------------------------------------------------- + + transpileDerivedExchangeFiles (jsFolder, options, pattern = '.js') { + + // todo normalize jsFolder and other arguments + + const { python2Folder, python3Folder, phpFolder } = options + + // exchanges.json accounts for ids included in exchanges.cfg + let ids = undefined + try { + ids = require ('../exchanges.json').ids; + } catch (e) { + } + + const regex = new RegExp (pattern.replace (/[.*+?^${}()|[\]\\]/g, '\\$&')) + + const classNames = fs.readdirSync (jsFolder) + .filter (file => file.match (regex) && (!ids || ids.includes (basename (file, '.js')))) + .map (file => this.transpileDerivedExchangeFile (jsFolder, file, options)) + + const classes = {} + + if (classNames.length === 0) { + return null + } + + classNames.forEach (({ className, baseClass }) => { + classes[className] = baseClass + }) + + if (classNames.length > 1) { + + function deleteOldTranspiledFiles (folder, pattern) { + fs.readdirSync (folder) + .filter (file => + !fs.lstatSync (folder + file).isDirectory () && + file.match (regex) && + !(file.replace (/\.[a-z]+$/, '') in classes) && + !file.match (/^Exchange|errors|__init__|\\./)) + .map (file => folder + file) + .forEach (file => log.red ('Deleting ' + file.yellow) && fs.unlinkSync (file)) + } + + [ + [ python2Folder, /\.pyc?$/ ], + [ python3Folder, /\.pyc?$/ ], + [ phpFolder, /\.php$/ ], + ].forEach (([ folder, pattern ]) => { + if (folder) { + deleteOldTranspiledFiles (folder, pattern) + } + }) + + } + + return classes + } + + // ======================================================================== + + exportTypeScriptDeclarations (file, classes) { + + log.bright.cyan ('Exporting TypeScript declarations →', file.yellow) + + const regex = /\/[\n]{2}(?: export class [^\s]+ extends [^\s]+ \{\}[\r]?[\n])+/ + const replacement = "/\n\n" + Object.keys (classes).map (className => { + const baseClass = classes[className] + return ' export class ' + className + ' extends ' + baseClass + " {}" + }).join ("\n") + "\n" + + replaceInFile (file, regex, replacement) + } + + // ======================================================================== + + transpileErrorHierarchy () { + + const errorHierarchyFilename = './js/base/errorHierarchy.js' + const errorHierarchy = require ('.' + errorHierarchyFilename) + + let js = fs.readFileSync (errorHierarchyFilename, 'utf8') + + js = this.regexAll (js, [ + [ /module\.exports = [^\;]+\;\n/s, '' ], + ]).trim () + + const message = 'Transpiling error hierachy →' + const root = errorHierarchy['BaseError'] + + const { python3Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js }) + + // a helper to generate a list of exception class declarations + // properly derived from corresponding parent classes according + // to the error hierarchy + + function intellisense (map, parent, generate, classes) { + function* generator(map, parent, generate, classes) { + for (const key in map) { + yield generate (key, parent, classes) + yield* generator (map[key], key, generate, classes) + } + } + return Array.from (generator (map, parent, generate, classes)) + } + + // Python ------------------------------------------------------------- + + function pythonDeclareErrorClass (name, parent, classes) { + classes.push (name) + return [ + 'class ' + name + '(' + parent + '):', + ' pass', + '', + '', + ].join ('\n'); + } + + const pythonBaseError = [ + 'class BaseError(Exception):', + ' pass', + '', + '', + ].join ('\n'); + + const quote = (s) => "'" + s + "'" // helper to add quotes around class names + + const pythonExports = [ 'error_hierarchy' ] + const pythonErrors = intellisense (root, 'BaseError', pythonDeclareErrorClass, pythonExports) + const pythonAll = '__all__ = [\n ' + pythonExports.map (quote).join (',\n ') + '\n]' + + const python3BodyIntellisense = python3Body + '\n\n\n' + pythonBaseError + '\n' + pythonErrors.join ('\n') + '\n' + pythonAll + '\n' + + const pythonFilename = './python/ccxt/base/errors.py' + log.bright.cyan (message, pythonFilename.yellow) + fs.writeFileSync (pythonFilename, python3BodyIntellisense) + + // PHP ---------------------------------------------------------------- + + function phpMakeErrorClassFile (name, parent) { + + const useClause = "\nuse " + parent + ";\n" + const requireClause = "\nrequire_once PATH_TO_CCXT_BASE . '" + parent + ".php';\n" + + const phpBody = [ + 'expectException ('ccxt\\\\BaseError');", + "// $this->expectExceptionMessageRegExp ('/Negative precision is not yet supported/');", + "// Exchange::decimalToPrecision ('123456.789', TRUNCATE, -2, DECIMAL_PLACES);", + "//", + "// $this->expectException ('ccxt\\\\BaseError');", + "// $this->expectExceptionMessageRegExp ('/Invalid number/');", + "// Exchange::decimalToPrecision ('foo');", + "", + "// ----------------------------------------------------------------------------", + "", + "function decimal_to_precision ($x, $roundingMode = ROUND, $numPrecisionDigits = null, $countingMode = DECIMAL_PLACES, $paddingMode = NO_PADDING) {", + " return Exchange::decimal_to_precision ($x, $roundingMode, $numPrecisionDigits, $countingMode, $paddingMode);", + "}", + "function number_to_string ($x) {", + " return Exchange::number_to_string ($x);", + "}", + "function toWei ($amount, $decimals) {", + " return Exchange::to_wei ($amount, $decimals);", + "}", + "function fromWei ($amount, $decimals) {", + " return Exchange::from_wei ($amount, $decimals);", + "}", + "", + ].join ("\n") + + const python = this.getPythonPreamble () + pythonHeader + python2Body + const php = this.getPHPPreamble () + phpHeader + phpBody + + log.magenta ('→', pyFile.yellow) + log.magenta ('→', phpFile.yellow) + + overwriteFile (pyFile, python) + overwriteFile (phpFile, php) + } + + //------------------------------------------------------------------------- + + transpileCryptoTests () { + const jsFile = './js/test/base/functions/test.crypto.js' + const pyFile = './python/test/test_crypto.py' + const phpFile = './php/test/test_crypto.php' + + log.magenta ('Transpiling from', jsFile.yellow) + let js = fs.readFileSync (jsFile).toString () + + js = this.regexAll (js, [ + [ /\'use strict\';?\s+/g, '' ], + [ /[^\n]+require[^\n]+\n/g, '' ], + [ /function equals \([\S\s]+?return true\n}\n/g, '' ], + ]) + + let { python3Body, python2Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false }) + + const pythonHeader = [ + "", + "import ccxt # noqa: F402", + "", + "Exchange = ccxt.Exchange", + "hash = Exchange.hash", + "ecdsa = Exchange.ecdsa", + "jwt = Exchange.jwt", + "encode = Exchange.encode", + "", + "", + "def equals(a, b):", + " return a == b", + "", + ].join ("\n") + + const phpHeader = [ + "", + "function hash(...$args) {", + " return Exchange::hash(...$args);", + "}", + "", + "function encode(...$args) {", + " return Exchange::encode(...$args);", + "}", + "", + "function ecdsa(...$args) {", + " return Exchange::ecdsa(...$args);", + "}", + "", + "function jwt(...$args) {", + " return Exchange::jwt(...$args);", + "}", + "", + "function equals($a, $b) {", + " return $a === $b;", + "}", + ].join ("\n") + + const python = this.getPythonPreamble () + pythonHeader + python2Body + const php = this.getPHPPreamble () + phpHeader + phpBody + + log.magenta ('→', pyFile.yellow) + log.magenta ('→', phpFile.yellow) + + overwriteFile (pyFile, python) + overwriteFile (phpFile, php) + } + + // ============================================================================ + + transpileExchangeTests () { + const tests = [ + { + 'jsFile': './js/test/Exchange/test.trade.js', + 'pyFile': './python/test/test_trade.py', + 'phpFile': './php/test/test_trade.php', + }, + { + 'jsFile': './js/test/Exchange/test.order.js', + 'pyFile': './python/test/test_order.py', + 'phpFile': './php/test/test_order.php', + }, + { + 'jsFile': './js/test/Exchange/test.transaction.js', + 'pyFile': './python/test/test_transaction.py', + 'phpFile': './php/test/test_transaction.php', + }, + { + 'jsFile': './js/test/Exchange/test.ohlcv.js', + 'pyFile': './python/test/test_ohlcv.py', + 'phpFile': './php/test/test_ohlcv.php', + }, + ] + for (const test of tests) { + this.transpileTest (test) + } + } + + // ============================================================================ + + transpileTest (test) { + log.magenta ('Transpiling from', test.jsFile.yellow) + let js = fs.readFileSync (test.jsFile).toString () + + js = this.regexAll (js, [ + [ /\'use strict\';?\s+/g, '' ], + [ /[^\n]+require[^\n]+\n/g, '' ], + [ /module.exports\s+=\s+[^;]+;/g, '' ], + ]) + + const pythonHeader = [ + '', + 'import numbers # noqa: E402', + 'try:', + ' basestring # basestring was removed in Python 3', + 'except NameError:', + ' basestring = str', + '', + '', + ].join('\n') + + let { python3Body, python2Body, phpBody } = this.transpileJavaScriptToPythonAndPHP ({ js, removeEmptyLines: false }) + const python = this.getPythonPreamble () + pythonHeader + python3Body; + const php = this.getPHPPreamble (false) + phpBody; + + log.magenta ('→', test.pyFile.yellow) + log.magenta ('→', test.phpFile.yellow) + + overwriteFile (test.pyFile, python) + overwriteFile (test.phpFile, php) + } + + // ============================================================================ + + transpileTests () { + + this.transpilePrecisionTests () + this.transpileDateTimeTests () + this.transpileCryptoTests () + + this.transpileExchangeTests () + } + + // ============================================================================ + + transpileEverything () { + + // default pattern is '.js' + const [ /* node */, /* script */, pattern ] = process.argv + , python2Folder = './python/ccxt/' + , python3Folder = './python/ccxt/async_support/' + , phpFolder = './php/' + , options = { python2Folder, python3Folder, phpFolder } + + createFolderRecursively (python2Folder) + createFolderRecursively (python3Folder) + createFolderRecursively (phpFolder) + + //* + + const classes = this.transpileDerivedExchangeFiles ('./js/', options, pattern) + + if (classes === null) { + log.bright.yellow ('0 files transpiled.') + return; + } + + // HINT: if we're going to support specific class definitions + // this process won't work anymore as it will override the definitions + this.exportTypeScriptDeclarations ('./ccxt.d.ts', classes) + + //*/ + + this.transpileErrorHierarchy () + + this.transpileTests () + + this.transpilePythonAsyncToSync () + + log.bright.green ('Transpiled successfully.') + } +} + +// ============================================================================ +// main entry point + +if (require.main === module) { // called directly like `node module` + + const transpiler = new Transpiler () + const test = process.argv.includes ('--test') || process.argv.includes ('--tests') + const errors = process.argv.includes ('--error') || process.argv.includes ('--errors') + if (test) { + transpiler.transpileTests () + } else if (errors) { + transpiler.transpileErrorHierarchy () + } else { + transpiler.transpileEverything () + } + +} else { // if required as a module + + // do nothing +} + +// ============================================================================ + +module.exports = Transpiler diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/vss.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/vss.js new file mode 100644 index 000000000..24db17ce1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/build/vss.js @@ -0,0 +1,96 @@ +// --------------------------------------------------------------------------- +// Usage: +// +// npm run vss +// --------------------------------------------------------------------------- + +"use strict"; + +const fs = require ('fs') + , log = require ('ololog') + , ansi = require ('ansicolor').nice + , { execSync } = require ('child_process') + , { copyFile } = require ('./fs.js') + + +//----------------------------------------------------------------------------- + +function incrementVersionPatchNumber (version) { + + let [ major, minor, patch ] = version.split ('.') + + // we don't increment it here anymore, because + // npm version patch will be explicitly called before + + // patch = (parseInt (patch) + 1).toString () + + version = [ major, minor, patch ].join ('.') + + return version +} + +//----------------------------------------------------------------------------- + +function vss (filename, template, version) { + log.bright.cyan ('Single-sourcing version', version, './package.json → ' + filename.yellow) + const content = fs.readFileSync (filename, 'utf8') + const regexp = new RegExp (template.replace (/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') // escape string for use in regexp + .replace ('\\{version\\}', '\\d+\\.\\d+\\.\\d+'), 'g') + fs.truncateSync (filename) + fs.writeFileSync (filename, content.replace (regexp, template.replace ('{version}', version))) +} + +// ---------------------------------------------------------------------------- + +function vssEverything () { + + let { version } = require ('../package.json') + + log.bright ('Old version: '.dim, version) + version = incrementVersionPatchNumber (version) + log.bright ('New version: '.cyan, version) + + vss ('./ccxt.js', "const version = '{version}'", version) + vss ('./php/base/Exchange.php', "$version = '{version}'", version) + vss ('./php/base/Exchange.php', "VERSION = '{version}'", version) + vss ('./python/ccxt/__init__.py', "__version__ = '{version}'", version) + vss ('./python/ccxt/base/exchange.py', "__version__ = '{version}'", version) + vss ('./python/ccxt/async_support/__init__.py', "__version__ = '{version}'", version) + vss ('./python/ccxt/async_support/base/exchange.py', "__version__ = '{version}'", version) + + vss ('./README.md', "ccxt@{version}", version) + vss ('./wiki/Install.md', "ccxt@{version}", version) + + const pythonFiles = [ + 'package.json', + 'LICENSE.txt', + 'keys.json', + 'README.md', + ] + + pythonFiles.forEach ((fileName) => copyFile ('./' + fileName, './python/' + fileName)) + + log.bright.green ('Version single-sourced successfully.') +} + +// ============================================================================ +// main entry point + +if (require.main === module) { + + // if called directly like `node module` + + vssEverything () + +} else { + + // do nothing if required as a module +} + +// ============================================================================ + +module.exports = { + incrementVersionPatchNumber, + vss, + vssEverything, +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.browser.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.browser.js new file mode 100644 index 000000000..ab6625575 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.browser.js @@ -0,0 +1,6 @@ +/* A entry point for the browser bundle version. This gets compiled by: + + browserify --debug ./ccxt.browser.js > ./dist/ccxt.browser.js + */ + +window.ccxt = require ('./ccxt') \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.d.ts b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.d.ts new file mode 100644 index 000000000..eae728f4c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.d.ts @@ -0,0 +1,546 @@ +declare module 'ccxt' { + + /** + * Represents an associative array of a same type. + */ + interface Dictionary { + [key: string]: T; + } + + // errors.js ----------------------------------------- + + export class BaseError extends Error { + constructor(message: string); + } + + export class ExchangeError extends BaseError {} + export class AuthenticationError extends ExchangeError {} + export class PermissionDenied extends AuthenticationError {} + export class AccountSuspended extends AuthenticationError {} + export class ArgumentsRequired extends ExchangeError {} + export class BadRequest extends ExchangeError {} + export class BadSymbol extends BadRequest {} + export class BadResponse extends ExchangeError {} + export class NullResponse extends BadResponse {} + export class InsufficientFunds extends ExchangeError {} + export class InvalidAddress extends ExchangeError {} + export class AddressPending extends InvalidAddress {} + export class InvalidOrder extends ExchangeError {} + export class OrderNotFound extends InvalidOrder {} + export class OrderNotCached extends InvalidOrder {} + export class CancelPending extends InvalidOrder {} + export class OrderImmediatelyFillable extends InvalidOrder {} + export class OrderNotFillable extends InvalidOrder {} + export class DuplicateOrderId extends InvalidOrder {} + export class NotSupported extends ExchangeError {} + export class NetworkError extends BaseError {} + export class DDoSProtection extends NetworkError {} + export class RateLimitExceeded extends DDoSProtection {} + export class ExchangeNotAvailable extends NetworkError {} + export class OnMaintenance extends ExchangeNotAvailable {} + export class InvalidNonce extends NetworkError {} + export class RequestTimeout extends NetworkError {} + + // ----------------------------------------------- + + export const version: string; + export const exchanges: string[]; + + export interface MinMax { + min: number; + max: number | undefined; + } + + export interface Market { + id: string; + symbol: string; + base: string; + quote: string; + baseId: string, + quoteId: string, + active: boolean; + precision: { base: number, quote: number, amount: number, price: number }; + limits: { amount: MinMax, price: MinMax, cost?: MinMax }; + tierBased: boolean, + percentage: boolean, + taker: number, + maker: number, + info: any, + } + + export interface Order { + id: string; + clientOrderId: string; + datetime: string; + timestamp: number; + lastTradeTimestamp: number; + status: 'open' | 'closed' | 'canceled'; + symbol: string; + type: string; + timeInForce?: string; + side: 'buy' | 'sell'; + price: number; + average?: number; + amount: number; + filled: number; + remaining: number; + cost: number; + trades: Trade[]; + fee: Fee; + info: any; + } + + export interface OrderBook { + asks: [number, number][]; + bids: [number, number][]; + datetime: string; + timestamp: number; + nonce: number; + } + + export interface Trade { + amount: number; // amount of base currency + datetime: string; // ISO8601 datetime with milliseconds; + id: string; // string trade id + info: any; // the original decoded JSON as is + order?: string; // string order id or undefined/None/null + price: number; // float price in quote currency + timestamp: number; // Unix timestamp in milliseconds + type?: string; // order type, 'market', 'limit', ... or undefined/None/null + side: 'buy' | 'sell'; // direction of the trade, 'buy' or 'sell' + symbol: string; // symbol in CCXT format + takerOrMaker: 'taker' | 'maker'; // string, 'taker' or 'maker' + cost: number; // total cost (including fees), `price * amount` + fee: Fee; + } + + export interface Ticker { + symbol: string; + info: any; + timestamp: number; + datetime: string; + high: number; + low: number; + bid: number; + bidVolume?: number; + ask: number; + askVolume?: number; + vwap?: number; + open?: number; + close?: number; + last?: number; + previousClose?: number; + change?: number; + percentage?: number; + average?: number; + quoteVolume?: number; + baseVolume?: number; + } + + export interface Transaction { + info: any; + id: string; + txid?: string; + timestamp: number; + datetime: string; + address: string; + type: "deposit" | "withdrawal"; + amount: number; + currency: string; + status: "pending" | "ok"; + updated: number; + fee: Fee; + } + + export interface Tickers extends Dictionary { + info: any; + } + + export interface Currency { + id: string; + code: string; + numericId?: number; + precision: number; + } + + export interface Balance { + free: number; + used: number; + total: number; + } + + export interface PartialBalances extends Dictionary { + } + + export interface Balances extends Dictionary { + info: any; + } + + export interface DepositAddress { + currency: string; + address: string; + status: string; + info: any; + } + + export interface Fee { + type: 'taker' | 'maker'; + currency: string; + rate: number; + cost: number; + } + + export interface WithdrawalResponse { + info: any; + id: string; + } + + export interface DepositAddressResponse { + currency: string; + address: string; + info: any; + tag?: string; + } + + /** [ timestamp, open, high, low, close, volume ] */ + export type OHLCV = [number, number, number, number, number, number]; + + /** Request parameters */ + type Params = Dictionary; + + export class Exchange { + constructor(config?: {[key in keyof Exchange]?: Exchange[key]}); + // allow dynamic keys + [key: string]: any; + // properties + version: string; + apiKey: string; + secret: string; + password: string; + uid: string; + requiredCredentials: { + apiKey: boolean; + secret: boolean; + uid: boolean; + login: boolean; + password: boolean; + twofa: boolean; + privateKey: boolean; + walletAddress: boolean; + token: boolean; + }; + options: { + [key: string]: any; + }; + urls: { + logo: string; + api: string | Dictionary; + test: string | Dictionary; + www: string; + doc: string[]; + api_management?: string; + fees: string; + referral: string; + }; + precisionMode: number; + hash: any; + hmac: any; + jwt: any; + binaryConcat: any; + stringToBinary: any; + stringToBase64: any; + base64ToBinary: any; + base64ToString: any; + binaryToString: any; + utf16ToBase64: any; + urlencode: any; + pluck: any; + unique: any; + extend: any; + deepExtend: any; + flatten: any; + groupBy: any; + indexBy: any; + sortBy: any; + keysort: any; + decimal: any; + safeFloat: any; + safeString: any; + safeInteger: any; + safeValue: any; + capitalize: any; + json: JSON["stringify"] + sum: any; + ordered: any; + aggregate: any; + truncate: any; + name: string; + // nodeVersion: string; + fees: object; + enableRateLimit: boolean; + countries: string[]; + // set by loadMarkets + markets: Dictionary; + marketsById: Dictionary; + currencies: Dictionary; + ids: string[]; + symbols: string[]; + id: string; + proxy: string; + parse8601: typeof Date.parse + milliseconds: typeof Date.now; + rateLimit: number; // milliseconds = seconds * 1000 + timeout: number; // milliseconds + verbose: boolean; + twofa: boolean;// two-factor authentication + substituteCommonCurrencyCodes: boolean; + timeframes: Dictionary; + has: Dictionary; // https://github.com/ccxt/ccxt/pull/1984 + balance: object; + orderbooks: object; + orders: object; + trades: object; + userAgent: { 'User-Agent': string } | false; + limits: { amount: MinMax, price: MinMax, cost: MinMax }; + hasCancelAllOrders: boolean; + hasCancelOrder: boolean; + hasCancelOrders: boolean; + hasCORS: boolean; + hasCreateDepositAddress: boolean; + hasCreateLimitOrder: boolean; + hasCreateMarketOrder: boolean; + hasCreateOrder: boolean; + hasDeposit: boolean; + hasEditOrder: boolean; + hasFetchBalance: boolean; + hasFetchBidsAsks: boolean; + hasFetchClosedOrders: boolean; + hasFetchCurrencies: boolean; + hasFetchDepositAddress: boolean; + hasFetchDeposits: boolean; + hasFetchFundingFees: boolean; + hasFetchL2OrderBook: boolean; + hasFetchLedger: boolean; + hasFetchMarkets: boolean; + hasFetchMyTrades: boolean; + hasFetchOHLCV: boolean; + hasFetchOpenOrders: boolean; + hasFetchOrder: boolean; + hasFetchOrderBook: boolean; + hasFetchOrderBooks: boolean; + hasFetchOrders: boolean; + hasFetchStatus: boolean; + hasFetchTicker: boolean; + hasFetchTickers: boolean; + hasFetchTime: boolean; + hasFetchTrades: boolean; + hasFetchTradingFee: boolean; + hasFetchTradingFees: boolean; + hasFetchTradingLimits: boolean; + hasFetchTransactions: boolean; + hasFetchWithdrawals: boolean; + hasPrivateAPI: boolean; + hasPublicAPI: boolean; + hasWithdraw: boolean; + + // methods + account (): Balance; + cancelAllOrders (...args: any): Promise; // TODO: add function signatures + cancelOrder (id: string, symbol?: string, params?: Params): Promise; + cancelOrders (...args: any): Promise; // TODO: add function signatures + checkRequiredCredentials (): void; + commonCurrencyCode (currency: string): string; + createDepositAddress (currency: string, params?: Params): Promise; + createLimitOrder (symbol: string, side: Order['side'], amount: number, price?: number, params?: Params): Promise; + createMarketOrder (symbol: string, side: Order['side'], amount: number, price?: number, params?: Params): Promise; + createOrder (symbol: string, type: Order['type'], side: Order['side'], amount: number, price?: number, params?: Params): Promise; + decode (str: string): string; + defaults (): any; + defineRestApi (api: any, methodName: any, options?: Dictionary): void; + deposit (...args: any): Promise; // TODO: add function signatures + describe (): any; + editOrder (id: string, symbol: string, type: Order['type'], side: Order['side'], amount: number, price?: number, params?: Params): Promise; + encode (str: string): string; + encodeURIComponent (...args: any[]): string; + extractParams (str: string): string[]; + fetch (url: string, method?: string, headers?: any, body?: any): Promise; + fetch2 (path: any, api?: string, method?: string, params?: Params, headers?: any, body?: any): Promise; + fetchBalance (params?: Params): Promise; + fetchBidsAsks (symbols?: string[], params?: Params): Promise; + fetchClosedOrders (symbol?: string, since?: number, limit?: number, params?: Params): Promise; + fetchCurrencies (params?: Params): Promise>; + fetchDepositAddress (currency: string, params?: Params): Promise; + fetchDeposits (currency?: string, since?: number, limit?: number, params?: Params): Promise; + fetchFreeBalance (params?: Params): Promise; + fetchFundingFees (...args: any): Promise; // TODO: add function signatures + fetchL2OrderBook (...args: any): Promise; // TODO: add function signatures + fetchLedger (...args: any): Promise; // TODO: add function signatures + fetchMarkets (): Promise; + fetchMyTrades (symbol?: string, since?: any, limit?: any, params?: Params): Promise; + fetchOHLCV (symbol: string, timeframe?: string, since?: number, limit?: number, params?: Params): Promise; + fetchOpenOrders (symbol?: string, since?: number, limit?: number, params?: Params): Promise; + fetchOrder (id: string, symbol: string, params?: Params): Promise; + fetchOrderBook (symbol: string, limit?: number, params?: Params): Promise; + fetchOrderBooks (...args: any): Promise; // TODO: add function signatures + fetchOrders (symbol?: string, since?: number, limit?: number, params?: Params): Promise; + fetchOrderStatus (id: string, market: string): Promise; + fetchStatus (...args: any): Promise; // TODO: add function signatures + fetchTicker (symbol: string, params?: Params): Promise; + fetchTickers (symbols?: string[], params?: Params): Promise>; + fetchTime (params?: Params): Promise; + fetchTotalBalance (params?: Params): Promise; + fetchTrades (symbol: string, since?: number, limit?: number, params?: Params): Promise; + fetchTradingFee (...args: any): Promise; // TODO: add function signatures + fetchTradingFees (...args: any): Promise; // TODO: add function signatures + fetchTradingLimits (...args: any): Promise; // TODO: add function signatures + fetchTransactions (currency?: string, since?: number, limit?: number, params?: Params): Promise; + fetchUsedBalance (params?: Params): Promise; + fetchWithdrawals (currency?: string, since?: number, limit?: number, params?: Params): Promise; + getMarket (symbol: string): Market; + initRestRateLimiter (): void; + iso8601 (timestamp: number | string): string; + loadMarkets (reload?: boolean): Promise>; + market (symbol: string): Market; + marketId (symbol: string): string; + marketIds (symbols: string[]): string[]; + microseconds (): number; + nonce (): number; + parseTimeframe (timeframe: string): number; + purgeCachedOrders (timestamp: number): void; + request (path: string, api?: string, method?: string, params?: Params, headers?: any, body?: any): Promise; + seconds (): number; + setMarkets (markets: Market[], currencies?: Currency[]): Dictionary; + symbol (symbol: string): string; + withdraw (currency: string, amount: number, address: string, tag?: string, params?: Params): Promise; + YmdHMS (timestamp: string, infix: string) : string; + } + + /* tslint:disable */ + + export class acx extends Exchange {} + export class aofex extends Exchange {} + export class bcex extends Exchange {} + export class bequant extends hitbtc {} + export class bibox extends Exchange {} + export class bigone extends Exchange {} + export class binance extends Exchange {} + export class binanceus extends binance {} + export class bit2c extends Exchange {} + export class bitbank extends Exchange {} + export class bitbay extends Exchange {} + export class bitcoincom extends hitbtc {} + export class bitfinex extends Exchange {} + export class bitfinex2 extends bitfinex {} + export class bitflyer extends Exchange {} + export class bitforex extends Exchange {} + export class bitget extends Exchange {} + export class bithumb extends Exchange {} + export class bitkk extends zb {} + export class bitmart extends Exchange {} + export class bitmax extends Exchange {} + export class bitmex extends Exchange {} + export class bitpanda extends Exchange {} + export class bitso extends Exchange {} + export class bitstamp extends Exchange {} + export class bitstamp1 extends Exchange {} + export class bittrex extends Exchange {} + export class bitvavo extends Exchange {} + export class bitz extends Exchange {} + export class bl3p extends Exchange {} + export class bleutrade extends Exchange {} + export class braziliex extends Exchange {} + export class btcalpha extends Exchange {} + export class btcbox extends Exchange {} + export class btcmarkets extends Exchange {} + export class btctradeua extends Exchange {} + export class btcturk extends Exchange {} + export class buda extends Exchange {} + export class bw extends Exchange {} + export class bybit extends Exchange {} + export class bytetrade extends Exchange {} + export class cex extends Exchange {} + export class chilebit extends foxbit {} + export class coinbase extends Exchange {} + export class coinbaseprime extends coinbasepro {} + export class coinbasepro extends Exchange {} + export class coincheck extends Exchange {} + export class coinegg extends Exchange {} + export class coinex extends Exchange {} + export class coinfalcon extends Exchange {} + export class coinfloor extends Exchange {} + export class coingi extends Exchange {} + export class coinmarketcap extends Exchange {} + export class coinmate extends Exchange {} + export class coinone extends Exchange {} + export class coinspot extends Exchange {} + export class crex24 extends Exchange {} + export class currencycom extends Exchange {} + export class deribit extends Exchange {} + export class digifinex extends Exchange {} + export class dsx extends Exchange {} + export class eterbase extends Exchange {} + export class exmo extends Exchange {} + export class exx extends Exchange {} + export class fcoin extends Exchange {} + export class fcoinjp extends fcoin {} + export class flowbtc extends Exchange {} + export class foxbit extends Exchange {} + export class ftx extends Exchange {} + export class gateio extends Exchange {} + export class gemini extends Exchange {} + export class hbtc extends Exchange {} + export class hitbtc extends Exchange {} + export class hollaex extends Exchange {} + export class huobijp extends huobipro {} + export class huobipro extends Exchange {} + export class huobiru extends huobipro {} + export class ice3x extends Exchange {} + export class idex extends Exchange {} + export class independentreserve extends Exchange {} + export class indodax extends Exchange {} + export class itbit extends Exchange {} + export class kraken extends Exchange {} + export class kucoin extends Exchange {} + export class kuna extends acx {} + export class lakebtc extends Exchange {} + export class latoken extends Exchange {} + export class lbank extends Exchange {} + export class liquid extends Exchange {} + export class livecoin extends Exchange {} + export class luno extends Exchange {} + export class lykke extends Exchange {} + export class mercado extends Exchange {} + export class mixcoins extends Exchange {} + export class novadax extends Exchange {} + export class oceanex extends Exchange {} + export class okcoin extends okex {} + export class okex extends Exchange {} + export class paymium extends Exchange {} + export class phemex extends Exchange {} + export class poloniex extends Exchange {} + export class probit extends Exchange {} + export class qtrade extends Exchange {} + export class rightbtc extends Exchange {} + export class ripio extends Exchange {} + export class southxchange extends Exchange {} + export class stex extends Exchange {} + export class surbitcoin extends foxbit {} + export class therock extends Exchange {} + export class tidebit extends Exchange {} + export class tidex extends Exchange {} + export class timex extends Exchange {} + export class upbit extends Exchange {} + export class vaultoro extends Exchange {} + export class vbtc extends foxbit {} + export class vcc extends Exchange {} + export class wavesexchange extends Exchange {} + export class whitebit extends Exchange {} + export class xbtce extends Exchange {} + export class xena extends Exchange {} + export class yobit extends Exchange {} + export class zaif extends Exchange {} + export class zb extends Exchange {} + + /* tslint:enable */ + +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.js new file mode 100644 index 000000000..df3e4ae38 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/ccxt.js @@ -0,0 +1,174 @@ +"use strict"; + +/* + +MIT License + +Copyright (c) 2017 Igor Kroitor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +//----------------------------------------------------------------------------- + +const Exchange = require ('./js/base/Exchange') + , functions = require ('./js/base/functions') + , errors = require ('./js/base/errors') + +//----------------------------------------------------------------------------- +// this is updated by vss.js when building + +const version = '1.38.72' + +Exchange.ccxtVersion = version + +//----------------------------------------------------------------------------- + +const exchanges = { + 'acx': require ('./js/acx.js'), + 'aofex': require ('./js/aofex.js'), + 'bcex': require ('./js/bcex.js'), + 'bequant': require ('./js/bequant.js'), + 'bibox': require ('./js/bibox.js'), + 'bigone': require ('./js/bigone.js'), + 'binance': require ('./js/binance.js'), + 'binanceus': require ('./js/binanceus.js'), + 'bit2c': require ('./js/bit2c.js'), + 'bitbank': require ('./js/bitbank.js'), + 'bitbay': require ('./js/bitbay.js'), + 'bitcoincom': require ('./js/bitcoincom.js'), + 'bitfinex': require ('./js/bitfinex.js'), + 'bitfinex2': require ('./js/bitfinex2.js'), + 'bitflyer': require ('./js/bitflyer.js'), + 'bitforex': require ('./js/bitforex.js'), + 'bitget': require ('./js/bitget.js'), + 'bithumb': require ('./js/bithumb.js'), + 'bitkk': require ('./js/bitkk.js'), + 'bitmart': require ('./js/bitmart.js'), + 'bitmax': require ('./js/bitmax.js'), + 'bitmex': require ('./js/bitmex.js'), + 'bitpanda': require ('./js/bitpanda.js'), + 'bitso': require ('./js/bitso.js'), + 'bitstamp': require ('./js/bitstamp.js'), + 'bitstamp1': require ('./js/bitstamp1.js'), + 'bittrex': require ('./js/bittrex.js'), + 'bitvavo': require ('./js/bitvavo.js'), + 'bitz': require ('./js/bitz.js'), + 'bl3p': require ('./js/bl3p.js'), + 'bleutrade': require ('./js/bleutrade.js'), + 'braziliex': require ('./js/braziliex.js'), + 'btcalpha': require ('./js/btcalpha.js'), + 'btcbox': require ('./js/btcbox.js'), + 'btcmarkets': require ('./js/btcmarkets.js'), + 'btctradeua': require ('./js/btctradeua.js'), + 'btcturk': require ('./js/btcturk.js'), + 'buda': require ('./js/buda.js'), + 'bw': require ('./js/bw.js'), + 'bybit': require ('./js/bybit.js'), + 'bytetrade': require ('./js/bytetrade.js'), + 'cex': require ('./js/cex.js'), + 'chilebit': require ('./js/chilebit.js'), + 'coinbase': require ('./js/coinbase.js'), + 'coinbaseprime': require ('./js/coinbaseprime.js'), + 'coinbasepro': require ('./js/coinbasepro.js'), + 'coincheck': require ('./js/coincheck.js'), + 'coinegg': require ('./js/coinegg.js'), + 'coinex': require ('./js/coinex.js'), + 'coinfalcon': require ('./js/coinfalcon.js'), + 'coinfloor': require ('./js/coinfloor.js'), + 'coingi': require ('./js/coingi.js'), + 'coinmarketcap': require ('./js/coinmarketcap.js'), + 'coinmate': require ('./js/coinmate.js'), + 'coinone': require ('./js/coinone.js'), + 'coinspot': require ('./js/coinspot.js'), + 'crex24': require ('./js/crex24.js'), + 'currencycom': require ('./js/currencycom.js'), + 'deribit': require ('./js/deribit.js'), + 'digifinex': require ('./js/digifinex.js'), + 'dsx': require ('./js/dsx.js'), + 'eterbase': require ('./js/eterbase.js'), + 'exmo': require ('./js/exmo.js'), + 'exx': require ('./js/exx.js'), + 'fcoin': require ('./js/fcoin.js'), + 'fcoinjp': require ('./js/fcoinjp.js'), + 'flowbtc': require ('./js/flowbtc.js'), + 'foxbit': require ('./js/foxbit.js'), + 'ftx': require ('./js/ftx.js'), + 'gateio': require ('./js/gateio.js'), + 'gemini': require ('./js/gemini.js'), + 'hbtc': require ('./js/hbtc.js'), + 'hitbtc': require ('./js/hitbtc.js'), + 'hollaex': require ('./js/hollaex.js'), + 'huobijp': require ('./js/huobijp.js'), + 'huobipro': require ('./js/huobipro.js'), + 'huobiru': require ('./js/huobiru.js'), + 'ice3x': require ('./js/ice3x.js'), + 'idex': require ('./js/idex.js'), + 'independentreserve': require ('./js/independentreserve.js'), + 'indodax': require ('./js/indodax.js'), + 'itbit': require ('./js/itbit.js'), + 'kraken': require ('./js/kraken.js'), + 'kucoin': require ('./js/kucoin.js'), + 'kuna': require ('./js/kuna.js'), + 'lakebtc': require ('./js/lakebtc.js'), + 'latoken': require ('./js/latoken.js'), + 'lbank': require ('./js/lbank.js'), + 'liquid': require ('./js/liquid.js'), + 'livecoin': require ('./js/livecoin.js'), + 'luno': require ('./js/luno.js'), + 'lykke': require ('./js/lykke.js'), + 'mercado': require ('./js/mercado.js'), + 'mixcoins': require ('./js/mixcoins.js'), + 'novadax': require ('./js/novadax.js'), + 'oceanex': require ('./js/oceanex.js'), + 'okcoin': require ('./js/okcoin.js'), + 'okex': require ('./js/okex.js'), + 'paymium': require ('./js/paymium.js'), + 'phemex': require ('./js/phemex.js'), + 'poloniex': require ('./js/poloniex.js'), + 'probit': require ('./js/probit.js'), + 'qtrade': require ('./js/qtrade.js'), + 'rightbtc': require ('./js/rightbtc.js'), + 'ripio': require ('./js/ripio.js'), + 'southxchange': require ('./js/southxchange.js'), + 'stex': require ('./js/stex.js'), + 'surbitcoin': require ('./js/surbitcoin.js'), + 'therock': require ('./js/therock.js'), + 'tidebit': require ('./js/tidebit.js'), + 'tidex': require ('./js/tidex.js'), + 'timex': require ('./js/timex.js'), + 'upbit': require ('./js/upbit.js'), + 'vaultoro': require ('./js/vaultoro.js'), + 'vbtc': require ('./js/vbtc.js'), + 'vcc': require ('./js/vcc.js'), + 'wavesexchange': require ('./js/wavesexchange.js'), + 'whitebit': require ('./js/whitebit.js'), + 'xbtce': require ('./js/xbtce.js'), + 'xena': require ('./js/xena.js'), + 'yobit': require ('./js/yobit.js'), + 'zaif': require ('./js/zaif.js'), + 'zb': require ('./js/zb.js'), +} + +//----------------------------------------------------------------------------- + +module.exports = Object.assign ({ version, Exchange, 'exchanges': Object.keys (exchanges) }, exchanges, functions, errors) + +//----------------------------------------------------------------------------- diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/dist/ccxt.browser.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/dist/ccxt.browser.js new file mode 100644 index 000000000..91c54aa60 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/dist/ccxt.browser.js @@ -0,0 +1,152824 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o ./dist/ccxt.browser.js + */ + +window.ccxt = require ('./ccxt') +},{"./ccxt":2}],2:[function(require,module,exports){ +"use strict"; + +/* + +MIT License + +Copyright (c) 2017 Igor Kroitor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +//----------------------------------------------------------------------------- + +const Exchange = require ('./js/base/Exchange') + , functions = require ('./js/base/functions') + , errors = require ('./js/base/errors') + +//----------------------------------------------------------------------------- +// this is updated by vss.js when building + +const version = '1.38.72' + +Exchange.ccxtVersion = version + +//----------------------------------------------------------------------------- + +const exchanges = { + 'acx': require ('./js/acx.js'), + 'aofex': require ('./js/aofex.js'), + 'bcex': require ('./js/bcex.js'), + 'bequant': require ('./js/bequant.js'), + 'bibox': require ('./js/bibox.js'), + 'bigone': require ('./js/bigone.js'), + 'binance': require ('./js/binance.js'), + 'binanceus': require ('./js/binanceus.js'), + 'bit2c': require ('./js/bit2c.js'), + 'bitbank': require ('./js/bitbank.js'), + 'bitbay': require ('./js/bitbay.js'), + 'bitcoincom': require ('./js/bitcoincom.js'), + 'bitfinex': require ('./js/bitfinex.js'), + 'bitfinex2': require ('./js/bitfinex2.js'), + 'bitflyer': require ('./js/bitflyer.js'), + 'bitforex': require ('./js/bitforex.js'), + 'bitget': require ('./js/bitget.js'), + 'bithumb': require ('./js/bithumb.js'), + 'bitkk': require ('./js/bitkk.js'), + 'bitmart': require ('./js/bitmart.js'), + 'bitmax': require ('./js/bitmax.js'), + 'bitmex': require ('./js/bitmex.js'), + 'bitpanda': require ('./js/bitpanda.js'), + 'bitso': require ('./js/bitso.js'), + 'bitstamp': require ('./js/bitstamp.js'), + 'bitstamp1': require ('./js/bitstamp1.js'), + 'bittrex': require ('./js/bittrex.js'), + 'bitvavo': require ('./js/bitvavo.js'), + 'bitz': require ('./js/bitz.js'), + 'bl3p': require ('./js/bl3p.js'), + 'bleutrade': require ('./js/bleutrade.js'), + 'braziliex': require ('./js/braziliex.js'), + 'btcalpha': require ('./js/btcalpha.js'), + 'btcbox': require ('./js/btcbox.js'), + 'btcmarkets': require ('./js/btcmarkets.js'), + 'btctradeua': require ('./js/btctradeua.js'), + 'btcturk': require ('./js/btcturk.js'), + 'buda': require ('./js/buda.js'), + 'bw': require ('./js/bw.js'), + 'bybit': require ('./js/bybit.js'), + 'bytetrade': require ('./js/bytetrade.js'), + 'cex': require ('./js/cex.js'), + 'chilebit': require ('./js/chilebit.js'), + 'coinbase': require ('./js/coinbase.js'), + 'coinbaseprime': require ('./js/coinbaseprime.js'), + 'coinbasepro': require ('./js/coinbasepro.js'), + 'coincheck': require ('./js/coincheck.js'), + 'coinegg': require ('./js/coinegg.js'), + 'coinex': require ('./js/coinex.js'), + 'coinfalcon': require ('./js/coinfalcon.js'), + 'coinfloor': require ('./js/coinfloor.js'), + 'coingi': require ('./js/coingi.js'), + 'coinmarketcap': require ('./js/coinmarketcap.js'), + 'coinmate': require ('./js/coinmate.js'), + 'coinone': require ('./js/coinone.js'), + 'coinspot': require ('./js/coinspot.js'), + 'crex24': require ('./js/crex24.js'), + 'currencycom': require ('./js/currencycom.js'), + 'deribit': require ('./js/deribit.js'), + 'digifinex': require ('./js/digifinex.js'), + 'dsx': require ('./js/dsx.js'), + 'eterbase': require ('./js/eterbase.js'), + 'exmo': require ('./js/exmo.js'), + 'exx': require ('./js/exx.js'), + 'fcoin': require ('./js/fcoin.js'), + 'fcoinjp': require ('./js/fcoinjp.js'), + 'flowbtc': require ('./js/flowbtc.js'), + 'foxbit': require ('./js/foxbit.js'), + 'ftx': require ('./js/ftx.js'), + 'gateio': require ('./js/gateio.js'), + 'gemini': require ('./js/gemini.js'), + 'hbtc': require ('./js/hbtc.js'), + 'hitbtc': require ('./js/hitbtc.js'), + 'hollaex': require ('./js/hollaex.js'), + 'huobijp': require ('./js/huobijp.js'), + 'huobipro': require ('./js/huobipro.js'), + 'huobiru': require ('./js/huobiru.js'), + 'ice3x': require ('./js/ice3x.js'), + 'idex': require ('./js/idex.js'), + 'independentreserve': require ('./js/independentreserve.js'), + 'indodax': require ('./js/indodax.js'), + 'itbit': require ('./js/itbit.js'), + 'kraken': require ('./js/kraken.js'), + 'kucoin': require ('./js/kucoin.js'), + 'kuna': require ('./js/kuna.js'), + 'lakebtc': require ('./js/lakebtc.js'), + 'latoken': require ('./js/latoken.js'), + 'lbank': require ('./js/lbank.js'), + 'liquid': require ('./js/liquid.js'), + 'livecoin': require ('./js/livecoin.js'), + 'luno': require ('./js/luno.js'), + 'lykke': require ('./js/lykke.js'), + 'mercado': require ('./js/mercado.js'), + 'mixcoins': require ('./js/mixcoins.js'), + 'novadax': require ('./js/novadax.js'), + 'oceanex': require ('./js/oceanex.js'), + 'okcoin': require ('./js/okcoin.js'), + 'okex': require ('./js/okex.js'), + 'paymium': require ('./js/paymium.js'), + 'phemex': require ('./js/phemex.js'), + 'poloniex': require ('./js/poloniex.js'), + 'probit': require ('./js/probit.js'), + 'qtrade': require ('./js/qtrade.js'), + 'rightbtc': require ('./js/rightbtc.js'), + 'ripio': require ('./js/ripio.js'), + 'southxchange': require ('./js/southxchange.js'), + 'stex': require ('./js/stex.js'), + 'surbitcoin': require ('./js/surbitcoin.js'), + 'therock': require ('./js/therock.js'), + 'tidebit': require ('./js/tidebit.js'), + 'tidex': require ('./js/tidex.js'), + 'timex': require ('./js/timex.js'), + 'upbit': require ('./js/upbit.js'), + 'vaultoro': require ('./js/vaultoro.js'), + 'vbtc': require ('./js/vbtc.js'), + 'vcc': require ('./js/vcc.js'), + 'wavesexchange': require ('./js/wavesexchange.js'), + 'whitebit': require ('./js/whitebit.js'), + 'xbtce': require ('./js/xbtce.js'), + 'xena': require ('./js/xena.js'), + 'yobit': require ('./js/yobit.js'), + 'zaif': require ('./js/zaif.js'), + 'zb': require ('./js/zb.js'), +} + +//----------------------------------------------------------------------------- + +module.exports = Object.assign ({ version, Exchange, 'exchanges': Object.keys (exchanges) }, exchanges, functions, errors) + +//----------------------------------------------------------------------------- + +},{"./js/acx.js":3,"./js/aofex.js":4,"./js/base/Exchange":5,"./js/base/errors":7,"./js/base/functions":8,"./js/bcex.js":19,"./js/bequant.js":20,"./js/bibox.js":21,"./js/bigone.js":22,"./js/binance.js":23,"./js/binanceus.js":24,"./js/bit2c.js":25,"./js/bitbank.js":26,"./js/bitbay.js":27,"./js/bitcoincom.js":28,"./js/bitfinex.js":29,"./js/bitfinex2.js":30,"./js/bitflyer.js":31,"./js/bitforex.js":32,"./js/bitget.js":33,"./js/bithumb.js":34,"./js/bitkk.js":35,"./js/bitmart.js":36,"./js/bitmax.js":37,"./js/bitmex.js":38,"./js/bitpanda.js":39,"./js/bitso.js":40,"./js/bitstamp.js":41,"./js/bitstamp1.js":42,"./js/bittrex.js":43,"./js/bitvavo.js":44,"./js/bitz.js":45,"./js/bl3p.js":46,"./js/bleutrade.js":47,"./js/braziliex.js":48,"./js/btcalpha.js":49,"./js/btcbox.js":50,"./js/btcmarkets.js":51,"./js/btctradeua.js":52,"./js/btcturk.js":53,"./js/buda.js":54,"./js/bw.js":55,"./js/bybit.js":56,"./js/bytetrade.js":57,"./js/cex.js":58,"./js/chilebit.js":59,"./js/coinbase.js":60,"./js/coinbaseprime.js":61,"./js/coinbasepro.js":62,"./js/coincheck.js":63,"./js/coinegg.js":64,"./js/coinex.js":65,"./js/coinfalcon.js":66,"./js/coinfloor.js":67,"./js/coingi.js":68,"./js/coinmarketcap.js":69,"./js/coinmate.js":70,"./js/coinone.js":71,"./js/coinspot.js":72,"./js/crex24.js":73,"./js/currencycom.js":74,"./js/deribit.js":75,"./js/digifinex.js":76,"./js/dsx.js":77,"./js/eterbase.js":78,"./js/exmo.js":79,"./js/exx.js":80,"./js/fcoin.js":81,"./js/fcoinjp.js":82,"./js/flowbtc.js":83,"./js/foxbit.js":84,"./js/ftx.js":85,"./js/gateio.js":86,"./js/gemini.js":87,"./js/hbtc.js":88,"./js/hitbtc.js":89,"./js/hollaex.js":90,"./js/huobijp.js":91,"./js/huobipro.js":92,"./js/huobiru.js":93,"./js/ice3x.js":94,"./js/idex.js":95,"./js/independentreserve.js":96,"./js/indodax.js":97,"./js/itbit.js":98,"./js/kraken.js":99,"./js/kucoin.js":100,"./js/kuna.js":101,"./js/lakebtc.js":102,"./js/latoken.js":103,"./js/lbank.js":104,"./js/liquid.js":105,"./js/livecoin.js":106,"./js/luno.js":107,"./js/lykke.js":108,"./js/mercado.js":109,"./js/mixcoins.js":110,"./js/novadax.js":111,"./js/oceanex.js":112,"./js/okcoin.js":113,"./js/okex.js":114,"./js/paymium.js":115,"./js/phemex.js":116,"./js/poloniex.js":117,"./js/probit.js":118,"./js/qtrade.js":119,"./js/rightbtc.js":120,"./js/ripio.js":121,"./js/southxchange.js":122,"./js/stex.js":163,"./js/surbitcoin.js":164,"./js/therock.js":165,"./js/tidebit.js":166,"./js/tidex.js":167,"./js/timex.js":168,"./js/upbit.js":169,"./js/vaultoro.js":170,"./js/vbtc.js":171,"./js/vcc.js":172,"./js/wavesexchange.js":173,"./js/whitebit.js":174,"./js/xbtce.js":175,"./js/xena.js":176,"./js/yobit.js":177,"./js/zaif.js":178,"./js/zb.js":179}],3:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InsufficientFunds, OrderNotFound } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class acx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'acx', + 'name': 'ACX', + 'countries': [ 'AU' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '12h': '720', + '1d': '1440', + '3d': '4320', + '1w': '10080', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30247614-1fe61c74-9621-11e7-9e8c-f1a627afa279.jpg', + 'extension': '.json', + 'api': 'https://acx.io/api', + 'www': 'https://acx.io', + 'doc': 'https://acx.io/documents/api_v2', + }, + 'api': { + 'public': { + 'get': [ + 'depth', // Get depth or specified market Both asks and bids are sorted from highest price to lowest. + 'k_with_pending_trades', // Get K data with pending trades, which are the trades not included in K data yet, because there's delay between trade generated and processed by K data generator + 'k', // Get OHLC(k line) of specific market + 'markets', // Get all available markets + 'order_book', // Get the order book of specified market + 'order_book/{market}', + 'tickers', // Get ticker of all markets + 'tickers/{market}', // Get ticker of specific market + 'timestamp', // Get server current time, in seconds since Unix epoch + 'trades', // Get recent trades on market, each trade is included only once Trades are sorted in reverse creation order. + 'trades/{market}', + ], + }, + 'private': { + 'get': [ + 'members/me', // Get your profile and accounts info + 'deposits', // Get your deposits history + 'deposit', // Get details of specific deposit + 'deposit_address', // Where to deposit The address field could be empty when a new address is generating (e.g. for bitcoin), you should try again later in that case. + 'orders', // Get your orders, results is paginated + 'order', // Get information of specified order + 'trades/my', // Get your executed trades Trades are sorted in reverse creation order. + 'withdraws', // Get your cryptocurrency withdraws + 'withdraw', // Get your cryptocurrency withdraw + ], + 'post': [ + 'orders', // Create a Sell/Buy order + 'orders/multi', // Create multiple sell/buy orders + 'orders/clear', // Cancel all my orders + 'order/delete', // Cancel an order + 'withdraw', // Create a withdraw + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': true, + 'withdraw': {}, // There is only 1% fee on withdrawals to your bank account. + }, + }, + 'commonCurrencies': { + 'PLA': 'Plair', + }, + 'exceptions': { + '2002': InsufficientFunds, + '2003': OrderNotFound, + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = market['id']; + const symbol = market['name']; + let baseId = this.safeString (market, 'base_unit'); + let quoteId = this.safeString (market, 'quote_unit'); + if ((baseId === undefined) || (quoteId === undefined)) { + const ids = symbol.split ('/'); + baseId = ids[0].toLowerCase (); + quoteId = ids[1].toLowerCase (); + } + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + // todo: find out their undocumented precision and limits + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'info': market, + 'active': undefined, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetMembersMe (params); + const balances = this.safeValue (response, 'accounts'); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 300 + } + const orderbook = await this.publicGetDepth (this.extend (request, params)); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'at'); + ticker = ticker['ticker']; + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let market = undefined; + let symbol = id; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } else { + let base = id.slice (0, 3); + let quote = id.slice (3, 6); + base = base.toUpperCase (); + quote = quote.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + symbol = base + '/' + quote; + } + result[symbol] = this.parseTicker (response[id], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTickersMarket (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const id = this.safeString (trade, 'tid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': this.safeFloat (trade, 'price'), + 'amount': this.safeFloat (trade, 'volume'), + 'cost': this.safeFloat (trade, 'funds'), + 'fee': undefined, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTimestamp (params); + // + // 1594911427 + // + return response * 1000; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 500; // default is 30 + } + const request = { + 'market': market['id'], + 'period': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.publicGetK (this.extend (request, params)); + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'done': 'closed', + 'wait': 'open', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'amount': this.safeFloat (order, 'volume'), + 'filled': this.safeFloat (order, 'executed_volume'), + 'remaining': this.safeFloat (order, 'remaining_volume'), + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'cost': undefined, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': parseInt (id), + }; + const response = await this.privateGetOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'side': side, + 'volume': amount.toString (), + 'ord_type': type, + }; + if (type === 'limit') { + request['price'] = price.toString (); + } + const response = await this.privatePostOrders (this.extend (request, params)); + const marketId = this.safeValue (response, 'market'); + const market = this.safeValue (this.markets_by_id, marketId); + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrderDelete (this.extend (request, params)); + const order = this.parseOrder (response); + const status = order['status']; + if (status === 'closed' || status === 'canceled') { + throw new OrderNotFound (this.id + ' ' + this.json (order)); + } + return order; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // they have XRP but no docs on memo/tag + const request = { + 'currency': currency['id'], + 'sum': amount, + 'address': address, + }; + const response = await this.privatePostWithdraw (this.extend (request, params)); + // withdrawal response is undocumented + return { + 'info': response, + 'id': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + encodeParams (params) { + if ('orders' in params) { + const orders = params['orders']; + let query = this.urlencode (this.keysort (this.omit (params, 'orders'))); + for (let i = 0; i < orders.length; i++) { + const order = orders[i]; + const keys = Object.keys (order); + for (let k = 0; k < keys.length; k++) { + const key = keys[k]; + const value = order[key]; + query += '&orders%5B%5D%5B' + key + '%5D=' + value.toString (); + } + } + return query; + } + return this.urlencode (this.keysort (params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/api/' + this.version + '/' + this.implodeParams (path, params); + if ('extension' in this.urls) { + request += this.urls['extension']; + } + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + request; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const query = this.encodeParams (this.extend ({ + 'access_key': this.apiKey, + 'tonce': nonce, + }, params)); + const auth = method + '|' + request + '|' + query; + const signed = this.hmac (this.encode (auth), this.encode (this.secret)); + const suffix = query + '&signature=' + signed; + if (method === 'GET') { + url += '?' + suffix; + } else { + body = suffix; + headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code === 400) { + const error = this.safeValue (response, 'error'); + const errorCode = this.safeString (error, 'code'); + const feedback = this.id + ' ' + this.json (response); + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + // fallback to default error handler + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],4:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, BadSymbol, InvalidOrder, PermissionDenied, InvalidAddress, AuthenticationError, InvalidNonce, BadRequest, InsufficientFunds, OrderNotFound } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class aofex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'aofex', + 'name': 'AOFEX', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'has': { + 'fetchMarkets': true, + 'fetchCurrencies': false, + 'fetchOrderBook': true, + 'fetchTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOHLCV': true, + 'fetchBalance': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchClosedOrder': true, + 'fetchOrderTrades': true, + 'fetchTradingFee': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '6h': '6hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/77670271-056d1080-6f97-11ea-9ac2-4268e9ed0c1f.jpg', + 'api': { + 'public': 'https://openapi.aofex.com/openApi', + 'private': 'https://openapi.aofex.com/openApi', + }, + 'www': 'https://aofex.com', + 'doc': 'https://aofex.zendesk.com/hc/en-us/sections/360005576574-API', + 'fees': 'https://aofex.zendesk.com/hc/en-us/articles/360025814934-Fees-on-AOFEX', + 'referral': 'https://aofex.com/#/register?key=9763840', + }, + 'api': { + 'public': { + 'get': [ + 'market/symbols', + 'market/trade', + 'market/depth', + 'market/kline', + 'market/precision', + 'market/24kline', + 'market/gears_depth', + 'market/detail', + ], + }, + 'private': { + 'get': [ + 'entrust/currentList', + 'entrust/historyList', + 'entrust/rate', + 'wallet/list', + 'entrust/detail', + ], + 'post': [ + 'entrust/add', + 'entrust/cancel', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.0019, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + '20001': ExchangeError, // request failure + '20401': PermissionDenied, // no permission + '20500': ExchangeError, // system error + '20501': BadSymbol, // base symbol error + '20502': ExchangeError, // base currency error + '20503': ExchangeError, // base date error + '20504': InsufficientFunds, // account frozen balance insufficient error + '20505': BadRequest, // bad argument + '20506': AuthenticationError, // api signature not valid + '20507': ExchangeError, // gateway internal error + '20508': InvalidAddress, // ad ethereum addresss + '20509': InsufficientFunds, // order accountbalance error + '20510': InvalidOrder, // order limitorder price error + '20511': InvalidOrder, // order limitorder amount error + '20512': InvalidOrder, // order orderprice precision error + '20513': InvalidOrder, // order orderamount precision error + '20514': InvalidOrder, // order marketorder amount error + '20515': InvalidOrder, // order queryorder invalid + '20516': InvalidOrder, // order orderstate error + '20517': InvalidOrder, // order datelimit error + '50518': InvalidOrder, // order update error + '20519': InvalidNonce, // the nonce has been used + '20520': InvalidNonce, // nonce expires, please verify server time + '20521': BadRequest, // incomplete header parameters + '20522': ExchangeError, // not getting the current user + '20523': AuthenticationError, // please authenticate + '20524': PermissionDenied, // btc account lockout + '20525': AuthenticationError, // get API Key error + '20526': PermissionDenied, // no query permission + '20527': PermissionDenied, // no deal permission + '20528': PermissionDenied, // no withdrawal permission + '20529': AuthenticationError, // API Key expired + '20530': PermissionDenied, // no permission + }, + 'broad': { + }, + }, + 'options': { + 'fetchBalance': { + 'show_all': '0', // '1' to show zero balances + }, + }, + }); + } + + async fetchMarkets (params = {}) { + let markets = await this.publicGetMarketSymbols (params); + // + // { + // errno: 0, + // errmsg: 'success', + // result: [ + // { + // id: 2, + // symbol: 'BTC-USDT', + // base_currency: 'BTC', + // quote_currency: 'USDT', + // min_size: 0.00008, + // max_size: 1300, + // min_price: 1000, + // max_price: 110000, + // maker_fee: 1, + // taker_fee: 1, + // isHot: null, + // isNew: null, + // crown: null + // }, + // ] + // } + // + let precisions = await this.publicGetMarketPrecision (); + // + // { + // errno: 0, + // errmsg: 'success', + // result: { + // 'MANA-USDT': { + // amount: '2', + // minQuantity: '32', + // maxQuantity: '46000000', + // price: '4', + // minPrice: '0.003', + // maxPrice: '0.35' + // }, + // } + // } + // + precisions = this.safeValue (precisions, 'result', {}); + markets = this.safeValue (markets, 'result', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const numericId = this.safeInteger (market, 'id'); + const precision = this.safeValue (precisions, id, {}); + const makerFee = this.safeFloat (market, 'maker_fee'); + const takerFee = this.safeFloat (market, 'taker_fee'); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': undefined, + 'maker': makerFee / 1000, + 'taker': takerFee / 1000, + 'precision': { + 'amount': this.safeInteger (precision, 'amount'), + 'price': this.safeInteger (precision, 'price'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_size'), + 'max': this.safeFloat (market, 'max_size'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // id: 1584950100, + // amount: "329.196", + // count: 81, + // open: "0.021155", + // close: "0.021158", + // low: "0.021144", + // high: "0.021161", + // vol: "6.963557767" + // } + // + return [ + this.safeTimestamp (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'amount'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 150; // default 150, max 2000 + } + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + 'size': limit, // default 150, max 2000 + }; + const response = await this.publicGetMarketKline (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: { + // ts: 1584950139003, + // symbol: "ETH-BTC", + // period: "1min", + // data: [ + // { + // id: 1584950100, + // amount: "329.196", + // count: 81, + // open: "0.021155", + // close: "0.021158", + // low: "0.021144", + // high: "0.021161", + // vol: "6.963557767" + // }, + // { + // id: 1584950040, + // amount: "513.265", + // count: 151, + // open: "0.021165", + // close: "0.021155", + // low: "0.021151", + // high: "0.02118", + // vol: "10.862806573" + // }, + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseOHLCVs (data, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchBalance', {}); + const showAll = this.safeValue (options, 'show_all', '0'); + const request = { + // 'currency': 'BTC', + 'show_all': showAll, // required to show zero balances + }; + const response = await this.privateGetWalletList (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": [ + // { "available": "0", "frozen": "0", "currency": "BTC" } + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'result', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'frozen'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privateGetEntrustRate (this.extend (request, params)); + // + // { + // "errno":0, + // "errmsg":"success", + // "result": { + // "toFee":"0.002","fromFee":"0.002" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return { + 'info': response, + 'symbol': symbol, + 'maker': this.safeFloat (result, 'fromFee'), + 'taker': this.safeFloat (result, 'toFee'), + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetMarketDepth (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: { + // buyType: 1, + // sellType: 1, + // ts: 1584950701050, + // symbol: "ETH-BTC", + // asks: [ + // ["0.021227", "0.182"], + // ["0.021249", "0.035"], + // ["0.021253", "0.058"], + // ], + // bids: [ + // ["0.021207", "0.039"], + // ["0.021203", "0.051"], + // ["0.02117", "2.326"], + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const timestamp = this.safeInteger (result, 'ts'); + return this.parseOrderBook (result, timestamp); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // id: 1584890087, + // amount: '150032.919', + // count: 134538, + // open: '0.021394', + // close: '0.021177', + // low: '0.021053', + // high: '0.021595', + // vol: '3201.72451442' + // } + // + const timestamp = this.safeTimestamp (ticker, 'id'); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const open = this.safeFloat (ticker, 'open'); + const last = this.safeFloat (ticker, 'close'); + let change = undefined; + if (symbol !== undefined) { + change = parseFloat (this.priceToPrecision (symbol, last - open)); + } else { + change = last - open; + } + const average = this.sum (last, open) / 2; + const percentage = change / open * 100; + const baseVolume = this.safeFloat (ticker, 'amount'); + const quoteVolume = this.safeFloat (ticker, 'vol'); + let vwap = this.vwap (baseVolume, quoteVolume); + if (vwap !== undefined) { + vwap = parseFloat (this.priceToPrecision (symbol, vwap)); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['symbol'] = ids.join (','); + } + const response = await this.publicGetMarket24kline (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: [ + // { + // symbol: "HB-AQ", + // data: { + // id: 1584893403, + // amount: "4753751.243400354852648809", + // count: 4724, + // open: "6.3497", + // close: "6.3318", + // low: "6.011", + // high: "6.5", + // vol: "29538384.7873528796542891343493" + // } + // }, + // ] + // } + // + const tickers = this.safeValue (response, 'result', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const marketId = this.safeString (tickers[i], 'symbol'); + const market = this.safeMarket (marketId, undefined, '-'); + const symbol = market['symbol']; + const data = this.safeValue (tickers[i], 'data', {}); + result[symbol] = this.parseTicker (data, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketDetail (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: 'success', + // result: { + // id: 1584890087, + // amount: '150032.919', + // count: 134538, + // open: '0.021394', + // close: '0.021177', + // low: '0.021053', + // high: '0.021595', + // vol: '3201.72451442' + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTicker (result, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // id: 1584948803298490, + // amount: "2.737", + // price: "0.021209", + // direction: "sell", + // ts: 1584948803 + // } + // + // fetchOrder trades + // + // { + // "id":null, + // "ctime":"2020-03-23 20:07:17", + // "price":"123.9", + // "number":"0.010688626311541565", + // "total_price":"1.324320799999999903", + // "fee":"0.000021377252623083" + // } + // + const id = this.safeString (trade, 'id'); + const ctime = this.parse8601 (this.safeString (trade, 'ctime')); + const timestamp = this.safeTimestamp (trade, 'ts', ctime) - 28800000; // 8 hours, adjust to UTC; + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'direction'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'amount', 'number'); + let cost = this.safeFloat (trade, 'total_price'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrencyCode = undefined; + if (market !== undefined) { + if (side === 'buy') { + feeCurrencyCode = market['base']; + } else if (side === 'sell') { + feeCurrencyCode = market['quote']; + } + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketTrade (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: { + // symbol: "ETH-BTC", + // ts: 1584948805439, + // data: [ + // { + // id: 1584948803300883, + // amount: "0.583", + // price: "0.021209", + // direction: "buy", + // ts: 1584948803 + // }, + // { + // id: 1584948803298490, + // amount: "2.737", + // price: "0.021209", + // direction: "sell", + // ts: 1584948803 + // }, + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + '1': 'open', + '2': 'open', // partially filled + '3': 'closed', + '4': 'canceled', // canceling + '5': 'canceled', // partially canceled + '6': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { order_sn: 'BM7442641584965237751ZMAKJ5' } + // + // fetchOpenOrders, fetchClosedOrders + // + // { + // "order_sn": "BL74426415849672087836G48N1", + // "symbol": "ETH-USDT", + // "ctime": "2020-03-23 20:40:08", + // "type": 2, + // "side": "buy", + // "price": "90", // undefined for market orders + // "number": "0.1", + // "total_price": "9.0", // 0 for market orders + // "deal_number": null, + // "deal_price": null, + // "status": 1, + // } + // + // fetchOrder + // + // { + // order_sn: 'BM7442641584965237751ZMAKJ5', + // symbol: 'ETH-USDT', + // ctime: '2020-03-23 20:07:17', + // type: 1, + // side: 'buy', + // price: '0', + // number: '10', + // total_price: '0', + // deal_number: '0.080718626311541565', + // deal_price: '123.890000000000000000', + // status: 3, + // // the trades field is injected by fetchOrder + // trades: [ + // { + // id: null, + // ctime: '2020-03-23 20:07:17', + // price: '123.9', + // number: '0.010688626311541565', + // total_price: '1.324320799999999903', + // fee: '0.000021377252623083' + // } + // ] + // } + // + const id = this.safeString (order, 'order_sn'); + const orderStatus = this.safeString (order, 'status'); + const status = this.parseOrderStatus (orderStatus); + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market, '-'); + let timestamp = this.parse8601 (this.safeString (order, 'ctime')); + if (timestamp !== undefined) { + timestamp -= 28800000; // 8 hours, adjust to UTC + } + const orderType = this.safeString (order, 'type'); + const type = (orderType === '2') ? 'limit' : 'market'; + const side = this.safeString (order, 'side'); + // const amount = this.safeFloat (order, 'number'); + // const price = this.safeFloat (order, 'price'); + let cost = undefined; + let price = undefined; + let amount = undefined; + let average = undefined; + const number = this.safeFloat (order, 'number'); + const totalPrice = this.safeFloat (order, 'total_price'); + if (type === 'limit') { + amount = number; + price = this.safeFloat (order, 'price'); + } else { + average = this.safeFloat (order, 'deal_price'); + if (side === 'buy') { + amount = this.safeFloat (order, 'deal_number'); + } else { + amount = number; + } + } + let fee = undefined; + let trades = undefined; + let filled = undefined; + let feeCost = undefined; + let remaining = undefined; + let lastTradeTimestamp = undefined; + // all orders except new orders and canceled orders + if ((orderStatus !== '1') && (orderStatus !== '6')) { + const rawTrades = this.safeValue (order, 'trades'); + if (rawTrades !== undefined) { + for (let i = 0; i < rawTrades.length; i++) { + rawTrades[i]['direction'] = side; + } + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'symbol': market['symbol'], + 'order': id, + 'side': side, + 'type': type, + }); + const tradesLength = trades.length; + if (tradesLength > 0) { + const firstTrade = trades[0]; + feeCost = firstTrade['fee']['cost']; + lastTradeTimestamp = firstTrade['timestamp']; + filled = firstTrade['amount']; + cost = firstTrade['cost']; + for (let i = 1; i < trades.length; i++) { + const trade = trades[i]; + feeCost = this.sum (feeCost, trade['fee']['cost']); + filled = this.sum (filled, trade['amount']); + cost = this.sum (cost, trade['cost']); + lastTradeTimestamp = Math.max (lastTradeTimestamp, trade['timestamp']); + } + if (amount !== undefined) { + filled = Math.min (amount, filled); + } + if (filled > 0) { + average = cost / filled; + } + } + if (feeCost !== undefined) { + const feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + } + } else { + filled = 0; + cost = 0; + } + if (cost === undefined) { + if (type === 'limit') { + cost = totalPrice; + } else if (side === 'buy') { + cost = number; + } + } + if (filled === undefined) { + if ((type === 'limit') && (orderStatus === '3')) { + filled = amount; + } + } + if (filled !== undefined) { + if (amount !== undefined) { + remaining = Math.max (amount - filled, 0); + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + }; + } + + async fetchClosedOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_sn': id, + }; + const response = await this.privateGetEntrustDetail (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": { + // "trades": [ + // { + // "id":null, + // "ctime":"2020-03-23 20:07:17", + // "price":"123.9", + // "number":"0.010688626311541565", + // "total_price":"1.324320799999999903", + // "fee":"0.000021377252623083" + // }, + // ], + // "entrust":{ + // "order_sn":"BM7442641584965237751ZMAKJ5", + // "symbol":"ETH-USDT", + // "ctime":"2020-03-23 20:07:17", + // "type":1, + // "side":"buy", + // "price":"0", + // "number":"10", + // "total_price":"0", + // "deal_number":"0.080718626311541565", + // "deal_price":"123.890000000000000000", + // "status":3 + // } + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + const order = this.safeValue (result, 'entrust', {}); + order['trades'] = trades; + return this.parseOrder (order); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.fetchClosedOrder (id, symbol, params); + return this.safeValue (response, 'trades', []); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'from': 'BM7442641584965237751ZMAKJ5', // query start order_sn + 'direct': 'prev', // next + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 100 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": [ + // { + // "order_sn": "BL74426415849672087836G48N1", + // "symbol": "ETH-USDT", + // "ctime": "2020-03-23 20:40:08", + // "type": 2, + // "side": "buy", + // "price": "90", + // "number": "0.1", + // "total_price": "9.0", + // "deal_number": null, + // "deal_price": null, + // "status": 1, + // } + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetEntrustCurrentList', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetEntrustHistoryList', symbol, since, limit, params); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderType = side + '-' + type; + const request = { + 'symbol': market['id'], + 'type': orderType, + }; + if (type === 'limit') { + request['amount'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + let cost = amount; + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + cost = amount * price; + } else { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument"); + } + } + const precision = market['precision']['price']; + request['amount'] = this.decimalToPrecision (cost, TRUNCATE, precision, this.precisionMode); + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this.privatePostEntrustAdd (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: 'success', + // result: { order_sn: 'BM7442641584965237751ZMAKJ5' } + // } + // + const result = this.safeValue (response, 'result', {}); + const order = this.parseOrder (result, market); + const timestamp = this.milliseconds (); + return this.extend (order, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'amount': amount, + 'price': price, + 'type': type, + 'side': side, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_ids': id, + }; + const response = await this.privatePostEntrustCancel (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": { + // "success": [ "avl12121", "bl3123123" ], + // "failed": [ "sd24564", "sdf6564564" ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const success = this.safeValue (result, 'success', []); + if (!this.inArray (id, success)) { + throw new OrderNotFound (this.id + ' order id ' + id + ' not found in successfully canceled orders: ' + this.json (response)); + } + const timestamp = undefined; + return { + 'info': response, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': 'canceled', + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'cost': undefined, + 'average': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + }; + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privatePostEntrustCancel (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": { + // "success": [ "avl12121", "bl3123123" ], + // "failed": [ "sd24564", "sdf6564564" ] + // } + // } + // + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + path; + let keys = Object.keys (params); + const keysLength = keys.length; + if (api === 'public') { + if (keysLength > 0) { + url += '?' + this.urlencode (params); + } + } else { + const nonce = this.nonce ().toString (); + const uuid = this.uuid (); + const randomString = uuid.slice (0, 5); + const nonceString = nonce + '_' + randomString; + const auth = {}; + auth[this.apiKey] = this.apiKey; + auth[this.secret] = this.secret; + auth[nonceString] = nonceString; + for (let i = 0; i < keysLength; i++) { + const key = keys[i]; + auth[key] = key + '=' + params[key]; + } + const keysorted = this.keysort (auth); + let stringToSign = ''; + keys = Object.keys (keysorted); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + stringToSign += keysorted[key]; + } + const signature = this.hash (this.encode (stringToSign), 'sha1'); + headers = { + 'Nonce': nonceString, + 'Token': this.apiKey, + 'Signature': signature, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + if (keysLength > 0) { + body = this.urlencode (params); + } + } else { + if (keysLength > 0) { + url += '?' + this.urlencode (params); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"errno":20501,"errmsg":"base symbol error"} + // + const error = this.safeString (response, 'errno'); + if ((error !== undefined) && (error !== '0')) { + const message = this.safeString (response, 'errmsg'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],5:[function(require,module,exports){ +"use strict"; + +// ---------------------------------------------------------------------------- + +const functions = require ('./functions') + +const { + isNode + , keys + , values + , deepExtend + , extend + , clone + , flatten + , unique + , indexBy + , sortBy + , groupBy + , aggregate + , uuid + , unCamelCase + , precisionFromString + , throttle + , capitalize + , now + , timeout + , TimedOut + , buildOHLCVC + , decimalToPrecision + , defaultFetch +} = functions + +const { // eslint-disable-line object-curly-newline + ExchangeError + , BadSymbol + , InvalidAddress + , NotSupported + , AuthenticationError + , DDoSProtection + , RequestTimeout + , ExchangeNotAvailable + , RateLimitExceeded } = require ('./errors') + +const { TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING } = functions.precisionConstants + +const BN = require ('../static_dependencies/BN/bn') + +// ---------------------------------------------------------------------------- + +module.exports = class Exchange { + + describe () { + return { + 'id': undefined, + 'name': undefined, + 'countries': undefined, + 'enableRateLimit': false, + 'rateLimit': 2000, // milliseconds = seconds * 1000 + 'certified': false, + 'pro': false, + 'has': { + 'loadMarkets': true, + 'cancelAllOrders': false, + 'cancelOrder': true, + 'cancelOrders': false, + 'CORS': false, + 'createDepositAddress': false, + 'createLimitOrder': true, + 'createMarketOrder': true, + 'createOrder': true, + 'deposit': false, + 'editOrder': 'emulated', + 'fetchBalance': true, + 'fetchBidsAsks': false, + 'fetchClosedOrders': false, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchDeposits': false, + 'fetchFundingFees': false, + 'fetchL2OrderBook': true, + 'fetchLedger': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': 'emulated', + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': true, + 'fetchOrderBooks': false, + 'fetchOrders': false, + 'fetchOrderTrades': false, + 'fetchStatus': 'emulated', + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTime': false, + 'fetchTrades': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTradingLimits': false, + 'fetchTransactions': false, + 'fetchWithdrawals': false, + 'privateAPI': true, + 'publicAPI': true, + 'signIn': false, + 'withdraw': false, + }, + 'urls': { + 'logo': undefined, + 'api': undefined, + 'www': undefined, + 'doc': undefined, + 'fees': undefined, + }, + 'api': undefined, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': false, + 'login': false, + 'password': false, + 'twofa': false, // 2-factor authentication (one-time password key) + 'privateKey': false, // a "0x"-prefixed hexstring private key for a wallet + 'walletAddress': false, // the wallet address "0x"-prefixed hexstring + 'token': false, // reserved for HTTP auth in some cases + }, + 'markets': undefined, // to be filled manually or by fetchMarkets + 'currencies': {}, // to be filled manually or by fetchMarkets + 'timeframes': undefined, // redefine if the exchange has.fetchOHLCV + 'fees': { + 'trading': { + 'tierBased': undefined, + 'percentage': undefined, + 'taker': undefined, + 'maker': undefined, + }, + 'funding': { + 'tierBased': undefined, + 'percentage': undefined, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'status': { + 'status': 'ok', + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + }, + 'exceptions': undefined, + 'httpExceptions': { + '422': ExchangeError, + '418': DDoSProtection, + '429': RateLimitExceeded, + '404': ExchangeNotAvailable, + '409': ExchangeNotAvailable, + '410': ExchangeNotAvailable, + '500': ExchangeNotAvailable, + '501': ExchangeNotAvailable, + '502': ExchangeNotAvailable, + '520': ExchangeNotAvailable, + '521': ExchangeNotAvailable, + '522': ExchangeNotAvailable, + '525': ExchangeNotAvailable, + '526': ExchangeNotAvailable, + '400': ExchangeNotAvailable, + '403': ExchangeNotAvailable, + '405': ExchangeNotAvailable, + '503': ExchangeNotAvailable, + '530': ExchangeNotAvailable, + '408': RequestTimeout, + '504': RequestTimeout, + '401': AuthenticationError, + '511': AuthenticationError, + }, + // some exchanges report only 'free' on `fetchBlance` call (i.e. report no 'used' funds) + // in this case ccxt will try to infer 'used' funds from open order cache, which might be stale + // still, some exchanges report number of open orders together with balance + // if you set the following flag to 'true' ccxt will leave 'used' funds undefined in case of discrepancy + 'dontGetUsedBalanceFromStaleCache': false, + 'commonCurrencies': { // gets extended/overwritten in subclasses + 'XBT': 'BTC', + 'BCC': 'BCH', + 'DRK': 'DASH', + 'BCHABC': 'BCH', + 'BCHSV': 'BSV', + }, + 'precisionMode': DECIMAL_PLACES, + 'paddingMode': NO_PADDING, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + } // return + } // describe () + + constructor (userConfig = {}) { + Object.assign (this, functions) + // if (isNode) { + // this.nodeVersion = process.version.match (/\d+\.\d+\.\d+/)[0] + // this.userAgent = { + // 'User-Agent': 'ccxt/' + Exchange.ccxtVersion + + // ' (+https://github.com/ccxt/ccxt)' + + // ' Node.js/' + this.nodeVersion + ' (JavaScript)' + // } + // } + + this.options = {} // exchange-specific options, if any + + // fetch implementation options (JS only) + this.fetchOptions = { + // keepalive: true, // does not work in Chrome, https://github.com/ccxt/ccxt/issues/6368 + } + + this.userAgents = { + 'chrome': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', + 'chrome39': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', + } + + this.headers = {} + + // prepended to URL, like https://proxy.com/https://exchange.com/api... + this.proxy = '' + this.origin = '*' // CORS origin + + this.minFundingAddressLength = 1 // used in checkAddress + this.substituteCommonCurrencyCodes = true // reserved + + // do not delete this line, it is needed for users to be able to define their own fetchImplementation + this.fetchImplementation = defaultFetch + + this.timeout = 10000 // milliseconds + this.verbose = false + this.debug = false + this.userAgent = undefined + this.twofa = undefined // two-factor authentication (2FA) + + this.apiKey = undefined + this.secret = undefined + this.uid = undefined + this.login = undefined + this.password = undefined + this.privateKey = undefined // a "0x"-prefixed hexstring private key for a wallet + this.walletAddress = undefined // a wallet address "0x"-prefixed hexstring + this.token = undefined // reserved for HTTP auth in some cases + + this.balance = {} + this.orderbooks = {} + this.tickers = {} + this.orders = undefined + this.trades = {} + this.transactions = {} + this.ohlcvs = {} + this.myTrades = undefined + + this.requiresWeb3 = false + this.requiresEddsa = false + this.precision = {} + + this.enableLastJsonResponse = true + this.enableLastHttpResponse = true + this.enableLastResponseHeaders = true + this.last_http_response = undefined + this.last_json_response = undefined + this.last_response_headers = undefined + + const unCamelCaseProperties = (obj = this) => { + if (obj !== null) { + const ownPropertyNames = Object.getOwnPropertyNames (obj) + for (let i = 0; i < ownPropertyNames.length; i++) { + const k = ownPropertyNames[i] + this[unCamelCase (k)] = this[k] + } + unCamelCaseProperties (Object.getPrototypeOf (obj)) + } + } + unCamelCaseProperties () + + // merge to this + const configEntries = Object.entries (this.describe ()).concat (Object.entries (userConfig)) + for (let i = 0; i < configEntries.length; i++) { + const [property, value] = configEntries[i] + if (value && Object.getPrototypeOf (value) === Object.prototype) { + this[property] = deepExtend (this[property], value) + } else { + this[property] = value + } + } + + const agentOptions = { + 'keepAlive': true, + } + + if (!this.httpAgent && defaultFetch.http && isNode) { + this.httpAgent = new defaultFetch.http.Agent (agentOptions) + } + + if (!this.httpsAgent && defaultFetch.https && isNode) { + this.httpsAgent = new defaultFetch.https.Agent (agentOptions) + } + + // generate old metainfo interface + const hasKeys = Object.keys (this.has) + for (let i = 0; i < hasKeys.length; i++) { + const k = hasKeys[i] + this['has' + capitalize (k)] = !!this.has[k] // converts 'emulated' to true + } + + if (this.api) { + this.defineRestApi (this.api, 'request') + } + + this.initRestRateLimiter () + + if (this.markets) { + this.setMarkets (this.markets) + } + } + + defaults () { + return { /* override me */ } + } + + nonce () { + return this.seconds () + } + + encodeURIComponent (...args) { + return encodeURIComponent (...args) + } + + checkRequiredCredentials (error = true) { + const keys = Object.keys (this.requiredCredentials) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (this.requiredCredentials[key] && !this[key]) { + if (error) { + throw new AuthenticationError (this.id + ' requires `' + key + '` credential') + } else { + return error + } + } + } + return true + } + + checkAddress (address) { + + if (address === undefined) { + throw new InvalidAddress (this.id + ' address is undefined') + } + + // check the address is not the same letter like 'aaaaa' nor too short nor has a space + if ((unique (address).length === 1) || address.length < this.minFundingAddressLength || address.includes (' ')) { + throw new InvalidAddress (this.id + ' address is invalid or has less than ' + this.minFundingAddressLength.toString () + ' characters: "' + this.json (address) + '"') + } + + return address + } + + initRestRateLimiter () { + + if (this.rateLimit === undefined) { + throw new Error (this.id + '.rateLimit property is not configured') + } + + this.tokenBucket = this.extend ({ + delay: 1, + capacity: 1, + defaultCost: 1, + maxCapacity: 1000, + }, this.tokenBucket) + + this.throttle = throttle (this.tokenBucket) + + this.executeRestRequest = (url, method = 'GET', headers = undefined, body = undefined) => { + + // fetchImplementation cannot be called on this. in browsers: + // TypeError Failed to execute 'fetch' on 'Window': Illegal invocation + const fetchImplementation = this.fetchImplementation + + const params = { method, headers, body, timeout: this.timeout } + + if (this.agent) { + params['agent'] = this.agent + } else if (this.httpAgent && url.indexOf ('http://') === 0) { + params['agent'] = this.httpAgent + } else if (this.httpsAgent && url.indexOf ('https://') === 0) { + params['agent'] = this.httpsAgent + } + + const promise = + fetchImplementation (url, this.extend (params, this.fetchOptions)) + .catch ((e) => { + if (isNode) { + throw new ExchangeNotAvailable ([ this.id, method, url, e.type, e.message ].join (' ')) + } + throw e // rethrow all unknown errors + }) + .then ((response) => this.handleRestResponse (response, url, method, headers, body)) + + return timeout (this.timeout, promise).catch ((e) => { + if (e instanceof TimedOut) { + throw new RequestTimeout (this.id + ' ' + method + ' ' + url + ' request timed out (' + this.timeout + ' ms)') + } + throw e + }) + } + } + + setSandboxMode (enabled) { + if (!!enabled) { // eslint-disable-line no-extra-boolean-cast + if ('test' in this.urls) { + if (typeof this.urls['api'] === 'string') { + this.urls['api_backup'] = this.urls['api'] + this.urls['api'] = this.urls['test'] + } else { + this.urls['api_backup'] = clone (this.urls['api']) + this.urls['api'] = clone (this.urls['test']) + } + } else { + throw new NotSupported (this.id + ' does not have a sandbox URL') + } + } else if ('api_backup' in this.urls) { + if (typeof this.urls['api'] === 'string') { + this.urls['api'] = this.urls['api_backup'] + } else { + this.urls['api'] = clone (this.urls['api_backup']) + } + } + } + + defineRestApi (api, methodName, paths = []) { + const keys = Object.keys (api) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const value = api[key] + if (Array.isArray (value)) { + const uppercaseMethod = key.toUpperCase () + const lowercaseMethod = key.toLowerCase () + const camelcaseMethod = this.capitalize (lowercaseMethod) + for (let k = 0; k < value.length; k++) { + const path = value[k].trim () + const splitPath = path.split (/[^a-zA-Z0-9]/) + const camelcaseSuffix = splitPath.map (this.capitalize).join ('') + const underscoreSuffix = splitPath.map ((x) => x.trim ().toLowerCase ()).filter ((x) => x.length > 0).join ('_') + const camelcasePrefix = [ paths[0] ].concat (paths.slice (1).map (this.capitalize)).join ('') + const underscorePrefix = [ paths[0] ].concat (paths.slice (1).map ((x) => x.trim ()).filter ((x) => x.length > 0)).join ('_') + const camelcase = camelcasePrefix + camelcaseMethod + this.capitalize (camelcaseSuffix) + const underscore = underscorePrefix + '_' + lowercaseMethod + '_' + underscoreSuffix + const typeArgument = (paths.length > 1) ? paths : paths[0] + const partial = async (params) => this[methodName] (path, typeArgument, uppercaseMethod, params || {}) + this[camelcase] = partial + this[underscore] = partial + } + } else { + this.defineRestApi (value, methodName, paths.concat ([ key ])) + } + } + } + + print (... args) { + console.log (... args) + } + + setHeaders (headers) { + return headers; + } + + fetch (url, method = 'GET', headers = undefined, body = undefined) { + + if (isNode && this.userAgent) { + if (typeof this.userAgent === 'string') { + headers = extend ({ 'User-Agent': this.userAgent }, headers) + } else if ((typeof this.userAgent === 'object') && ('User-Agent' in this.userAgent)) { + headers = extend (this.userAgent, headers) + } + } + + if (typeof this.proxy === 'function') { + + url = this.proxy (url) + if (isNode) { + headers = extend ({ 'Origin': this.origin }, headers) + } + + } else if (typeof this.proxy === 'string') { + + if (this.proxy.length && isNode) { + headers = extend ({ 'Origin': this.origin }, headers) + } + + url = this.proxy + url + } + + headers = extend (this.headers, headers) + headers = this.setHeaders (headers) + + if (this.verbose) { + this.print ("fetch:\n", this.id, method, url, "\nRequest:\n", headers, "\n", body, "\n") + } + + return this.executeRestRequest (url, method, headers, body) + } + + async fetch2 (path, type = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + + if (this.enableRateLimit) { + await this.throttle (this.rateLimit) + } + + const request = this.sign (path, type, method, params, headers, body) + return this.fetch (request.url, request.method, request.headers, request.body) + } + + request (path, type = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + return this.fetch2 (path, type, method, params, headers, body) + } + + parseJson (jsonString) { + try { + if (this.isJsonEncodedObject (jsonString)) { + return JSON.parse (jsonString) + } + } catch (e) { + // SyntaxError + return undefined + } + } + + throwExactlyMatchedException (exact, string, message) { + if (string in exact) { + throw new exact[string] (message) + } + } + + throwBroadlyMatchedException (broad, string, message) { + const broadKey = this.findBroadlyMatchedKey (broad, string) + if (broadKey !== undefined) { + throw new broad[broadKey] (message) + } + } + + // a helper for matching error strings exactly vs broadly + findBroadlyMatchedKey (broad, string) { + const keys = Object.keys (broad) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (string.indexOf (key) >= 0) { + return key + } + } + return undefined + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + // override me + } + + handleHttpStatusCode (code, reason, url, method, body) { + const codeAsString = code.toString () + if (codeAsString in this.httpExceptions) { + const ErrorClass = this.httpExceptions[codeAsString] + throw new ErrorClass ([ this.id, method, url, code, reason, body ].join (' ')) + } + } + + getResponseHeaders (response) { + const result = {} + response.headers.forEach ((value, key) => { + key = key.split ('-').map ((word) => capitalize (word)).join ('-') + result[key] = value + }) + return result + } + + handleRestResponse (response, url, method = 'GET', requestHeaders = undefined, requestBody = undefined) { + + return response.text ().then ((responseBody) => { + + const json = this.parseJson (responseBody) + + const responseHeaders = this.getResponseHeaders (response) + + if (this.enableLastResponseHeaders) { + this.last_response_headers = responseHeaders + } + + if (this.enableLastHttpResponse) { + this.last_http_response = responseBody + } + + if (this.enableLastJsonResponse) { + this.last_json_response = json + } + + if (this.verbose) { + this.print ("handleRestResponse:\n", this.id, method, url, response.status, response.statusText, "\nResponse:\n", responseHeaders, "\n", responseBody, "\n") + } + + this.handleErrors (response.status, response.statusText, url, method, responseHeaders, responseBody, json, requestHeaders, requestBody) + this.handleHttpStatusCode (response.status, response.statusText, url, method, responseBody) + + return json || responseBody + }) + } + + setMarkets (markets, currencies = undefined) { + const values = Object.values (markets).map ((market) => deepExtend ({ + 'limits': this.limits, + 'precision': this.precision, + }, this.fees['trading'], market)) + this.markets = indexBy (values, 'symbol') + this.marketsById = indexBy (markets, 'id') + this.markets_by_id = this.marketsById + this.symbols = Object.keys (this.markets).sort () + this.ids = Object.keys (this.markets_by_id).sort () + if (currencies) { + this.currencies = deepExtend (currencies, this.currencies) + } else { + let baseCurrencies = + values.filter ((market) => 'base' in market) + .map ((market) => ({ + id: market.baseId || market.base, + numericId: (market.baseNumericId !== undefined) ? market.baseNumericId : undefined, + code: market.base, + precision: market.precision ? (market.precision.base || market.precision.amount) : 8, + })) + let quoteCurrencies = + values.filter ((market) => 'quote' in market) + .map ((market) => ({ + id: market.quoteId || market.quote, + numericId: (market.quoteNumericId !== undefined) ? market.quoteNumericId : undefined, + code: market.quote, + precision: market.precision ? (market.precision.quote || market.precision.price) : 8, + })) + baseCurrencies = sortBy (baseCurrencies, 'code') + quoteCurrencies = sortBy (quoteCurrencies, 'code') + this.baseCurrencies = indexBy (baseCurrencies, 'code') + this.quoteCurrencies = indexBy (quoteCurrencies, 'code') + const allCurrencies = baseCurrencies.concat (quoteCurrencies) + const groupedCurrencies = groupBy (allCurrencies, 'code') + const currencies = Object.keys (groupedCurrencies).map ((code) => + groupedCurrencies[code].reduce ((previous, current) => // eslint-disable-line implicit-arrow-linebreak + ((previous.precision > current.precision) ? previous : current), groupedCurrencies[code][0])) // eslint-disable-line implicit-arrow-linebreak + const sortedCurrencies = sortBy (flatten (currencies), 'code') + this.currencies = deepExtend (indexBy (sortedCurrencies, 'code'), this.currencies) + } + this.currencies_by_id = indexBy (this.currencies, 'id') + return this.markets + } + + async loadMarketsHelper (reload = false, params = {}) { + if (!reload && this.markets) { + if (!this.markets_by_id) { + return this.setMarkets (this.markets) + } + return this.markets + } + let currencies = undefined + if (this.has.fetchCurrencies) { + currencies = await this.fetchCurrencies () + } + const markets = await this.fetchMarkets (params) + return this.setMarkets (markets, currencies) + } + + // is async (returns a promise) + loadMarkets (reload = false, params = {}) { + if ((reload && !this.reloadingMarkets) || !this.marketsLoading) { + this.reloadingMarkets = true + this.marketsLoading = this.loadMarketsHelper (reload, params).then ((resolved) => { + this.reloadingMarkets = false + return resolved + }, (error) => { + this.reloadingMarkets = false + throw error + }) + } + return this.marketsLoading + } + + async loadAccounts (reload = false, params = {}) { + if (reload) { + this.accounts = await this.fetchAccounts (params) + } else { + if (this.accounts) { + return this.accounts + } else { + this.accounts = await this.fetchAccounts (params) + } + } + this.accountsById = this.indexBy (this.accounts, 'id') + return this.accounts + } + + fetchBidsAsks (symbols = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchBidsAsks not supported yet') + } + + async fetchOHLCVC (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + if (!this.has['fetchTrades']) { + throw new NotSupported (this.id + ' fetchOHLCV() not supported yet') + } + await this.loadMarkets () + const trades = await this.fetchTrades (symbol, since, limit, params) + const ohlcvc = buildOHLCVC (trades, timeframe, since, limit) + return ohlcvc + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + if (!this.has['fetchTrades']) { + throw new NotSupported (this.id + ' fetchOHLCV() not supported yet') + } + await this.loadMarkets () + const trades = await this.fetchTrades (symbol, since, limit, params) + const ohlcvc = buildOHLCVC (trades, timeframe, since, limit) + return ohlcvc.map ((c) => c.slice (0, -1)) + } + + parseTradingViewOHLCV (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { + const result = this.convertTradingViewToOHLCV (ohlcvs) + return this.parseOHLCVs (result, market, timeframe, since, limit) + } + + convertTradingViewToOHLCV (ohlcvs, t = 't', o = 'o', h = 'h', l = 'l', c = 'c', v = 'v', ms = false) { + const result = []; + for (let i = 0; i < ohlcvs[t].length; i++) { + result.push ([ + ms ? ohlcvs[t][i] : (ohlcvs[t][i] * 1000), + ohlcvs[o][i], + ohlcvs[h][i], + ohlcvs[l][i], + ohlcvs[c][i], + ohlcvs[v][i], + ]) + } + return result + } + + convertOHLCVToTradingView (ohlcvs, t = 't', o = 'o', h = 'h', l = 'l', c = 'c', v = 'v', ms = false) { + const result = {} + result[t] = [] + result[o] = [] + result[h] = [] + result[l] = [] + result[c] = [] + result[v] = [] + for (let i = 0; i < ohlcvs.length; i++) { + result[t].push (ms ? ohlcvs[i][0] : parseInt (ohlcvs[i][0] / 1000)) + result[o].push (ohlcvs[i][1]) + result[h].push (ohlcvs[i][2]) + result[l].push (ohlcvs[i][3]) + result[c].push (ohlcvs[i][4]) + result[v].push (ohlcvs[i][5]) + } + return result + } + + fetchTicker (symbol, params = {}) { + throw new NotSupported (this.id + ' fetchTicker not supported yet') + } + + fetchTickers (symbols = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchTickers not supported yet') + } + + fetchOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOrder not supported yet'); + } + + fetchUnifiedOrder (order, params = {}) { + return this.fetchOrder (this.safeValue (order, 'id'), this.safeValue (order, 'symbol'), params); + } + + createOrder (symbol, type, side, amount, price = undefined, params = {}) { + throw new NotSupported (this.id + ' createOrder not supported yet'); + } + + cancelOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' cancelOrder not supported yet'); + } + + cancelUnifiedOrder (order, params = {}) { + return this.cancelOrder (this.safeValue (order, 'id'), this.safeValue (order, 'symbol'), params); + } + + fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOrders not supported yet'); + } + + fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOpenOrders not supported yet'); + } + + fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchClosedOrders not supported yet'); + } + + fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchMyTrades not supported yet'); + } + + fetchTransactions (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchTransactions not supported yet'); + } + + fetchDeposits (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchDeposits not supported yet'); + } + + fetchWithdrawals (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchWithdrawals not supported yet'); + } + + fetchCurrencies (params = {}) { + // markets are returned as a list + // currencies are returned as a dict + // this is for historical reasons + // and may be changed for consistency later + return new Promise ((resolve, reject) => resolve (this.currencies)); + } + + fetchMarkets (params = {}) { + // markets are returned as a list + // currencies are returned as a dict + // this is for historical reasons + // and may be changed for consistency later + return new Promise ((resolve, reject) => resolve (Object.values (this.markets))) + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + const order = await this.fetchOrder (id, symbol, params); + return order['status']; + } + + account () { + return { + 'free': undefined, + 'used': undefined, + 'total': undefined, + } + } + + commonCurrencyCode (currency) { + if (!this.substituteCommonCurrencyCodes) { + return currency + } + return this.safeString (this.commonCurrencies, currency, currency) + } + + currencyId (commonCode) { + + if (this.currencies === undefined) { + throw new ExchangeError (this.id + ' currencies not loaded') + } + + if (commonCode in this.currencies) { + return this.currencies[commonCode]['id']; + } + + const currencyIds = {} + const distinct = Object.keys (this.commonCurrencies) + for (let i = 0; i < distinct.length; i++) { + const k = distinct[i] + currencyIds[this.commonCurrencies[k]] = k + } + + return this.safeString (currencyIds, commonCode, commonCode) + } + + currency (code) { + + if (this.currencies === undefined) { + throw new ExchangeError (this.id + ' currencies not loaded') + } + + if ((typeof code === 'string') && (code in this.currencies)) { + return this.currencies[code] + } + + throw new ExchangeError (this.id + ' does not have currency code ' + code) + } + + market (symbol) { + + if (this.markets === undefined) { + throw new ExchangeError (this.id + ' markets not loaded') + } + + if ((typeof symbol === 'string') && (symbol in this.markets)) { + return this.markets[symbol] + } + + throw new BadSymbol (this.id + ' does not have market symbol ' + symbol) + } + + marketId (symbol) { + const market = this.market (symbol) + return (market !== undefined ? market['id'] : symbol) + } + + marketIds (symbols) { + return symbols.map ((symbol) => this.marketId (symbol)) + } + + symbol (symbol) { + return this.market (symbol).symbol || symbol + } + + url (path, params = {}) { + let result = this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)) + if (Object.keys (query).length) { + result += '?' + this.urlencode (query) + } + return result + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + const price = parseFloat (bidask[priceKey]) + const amount = parseFloat (bidask[amountKey]) + return [ price, amount ] + } + + parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) { + return Object.values (bidasks || []).map ((bidask) => this.parseBidAsk (bidask, priceKey, amountKey)) + } + + async fetchL2OrderBook (symbol, limit = undefined, params = {}) { + const orderbook = await this.fetchOrderBook (symbol, limit, params) + return extend (orderbook, { + 'bids': sortBy (aggregate (orderbook.bids), 0, true), + 'asks': sortBy (aggregate (orderbook.asks), 0), + }) + } + + parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1) { + return { + 'bids': sortBy ((bidsKey in orderbook) ? this.parseBidsAsks (orderbook[bidsKey], priceKey, amountKey) : [], 0, true), + 'asks': sortBy ((asksKey in orderbook) ? this.parseBidsAsks (orderbook[asksKey], priceKey, amountKey) : [], 0), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + } + } + + parseBalance (balance) { + + const codes = Object.keys (this.omit (balance, [ 'info', 'free', 'used', 'total' ])); + + balance['free'] = {} + balance['used'] = {} + balance['total'] = {} + + for (let i = 0; i < codes.length; i++) { + const code = codes[i] + if (balance[code].total === undefined) { + if (balance[code].free !== undefined && balance[code].used !== undefined) { + balance[code].total = this.sum (balance[code].free, balance[code].used) + } + } + if (balance[code].free === undefined) { + if (balance[code].total !== undefined && balance[code].used !== undefined) { + balance[code].free = this.sum (balance[code].total, -balance[code].used) + } + } + if (balance[code].used === undefined) { + if (balance[code].total !== undefined && balance[code].free !== undefined) { + balance[code].used = this.sum (balance[code].total, -balance[code].free) + } + } + + balance.free[code] = balance[code].free + balance.used[code] = balance[code].used + balance.total[code] = balance[code].total + } + + return balance + } + + async fetchPartialBalance (part, params = {}) { + const balance = await this.fetchBalance (params) + return balance[part] + } + + fetchFreeBalance (params = {}) { + return this.fetchPartialBalance ('free', params) + } + + fetchUsedBalance (params = {}) { + return this.fetchPartialBalance ('used', params) + } + + fetchTotalBalance (params = {}) { + return this.fetchPartialBalance ('total', params) + } + + async fetchStatus (params = {}) { + if (this.has['fetchTime']) { + const time = await this.fetchTime (params) + this.status = this.extend (this.status, { + 'updated': time, + }) + } + return this.status + } + + async fetchTradingFees (params = {}) { + throw new NotSupported (this.id + ' fetchTradingFees not supported yet') + } + + async fetchTradingFee (symbol, params = {}) { + if (!this.has['fetchTradingFees']) { + throw new NotSupported (this.id + ' fetchTradingFee not supported yet') + } + return await this.fetchTradingFees (params) + } + + async loadTradingLimits (symbols = undefined, reload = false, params = {}) { + if (this.has['fetchTradingLimits']) { + if (reload || !('limitsLoaded' in this.options)) { + const response = await this.fetchTradingLimits (symbols); + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + this.markets[symbol] = this.deepExtend (this.markets[symbol], response[symbol]); + } + this.options['limitsLoaded'] = this.milliseconds (); + } + } + return this.markets; + } + + filterBySinceLimit (array, since = undefined, limit = undefined, key = 'timestamp', tail = false) { + const sinceIsDefined = (since !== undefined && since !== null) + if (sinceIsDefined) { + array = array.filter ((entry) => entry[key] >= since) + } + if (limit !== undefined && limit !== null) { + array = (tail && !sinceIsDefined) ? array.slice (-limit) : array.slice (0, limit) + } + return array + } + + filterByValueSinceLimit (array, field, value = undefined, since = undefined, limit = undefined, key = 'timestamp', tail = false) { + + const valueIsDefined = value !== undefined && value !== null + const sinceIsDefined = since !== undefined && since !== null + + // single-pass filter for both symbol and since + if (valueIsDefined || sinceIsDefined) { + array = array.filter ((entry) => + ((valueIsDefined ? (entry[field] === value) : true) && + (sinceIsDefined ? (entry[key] >= since) : true))) + } + + if (limit !== undefined && limit !== null) { + array = (tail && !sinceIsDefined) ? array.slice (-limit) : array.slice (0, limit) + } + + return array + } + + filterBySymbolSinceLimit (array, symbol = undefined, since = undefined, limit = undefined) { + return this.filterByValueSinceLimit (array, 'symbol', symbol, since, limit) + } + + filterByCurrencySinceLimit (array, code = undefined, since = undefined, limit = undefined) { + return this.filterByValueSinceLimit (array, 'currency', code, since, limit) + } + + filterByArray (objects, key, values = undefined, indexed = true) { + + objects = Object.values (objects) + + // return all of them if no values were passed + if (values === undefined || values === null) { + return indexed ? indexBy (objects, key) : objects + } + + const result = [] + for (let i = 0; i < objects.length; i++) { + if (values.includes (objects[i][key])) { + result.push (objects[i]) + } + } + + return indexed ? indexBy (result, key) : result + } + + parseTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { + // this code is commented out temporarily to catch for exchange-specific errors + // if (!this.isArray (trades)) { + // throw new ExchangeError (this.id + ' parseTrades expected an array in the trades argument, but got ' + typeof trades); + // } + let result = Object.values (trades || []).map ((trade) => this.extend (this.parseTrade (trade, market), params)) + result = sortBy (result, 'timestamp') + const symbol = (market !== undefined) ? market['symbol'] : undefined + return this.filterBySymbolSinceLimit (result, symbol, since, limit) + } + + parseTransactions (transactions, currency = undefined, since = undefined, limit = undefined, params = {}) { + // this code is commented out temporarily to catch for exchange-specific errors + // if (!this.isArray (transactions)) { + // throw new ExchangeError (this.id + ' parseTransactions expected an array in the transactions argument, but got ' + typeof transactions); + // } + let result = Object.values (transactions || []).map ((transaction) => this.extend (this.parseTransaction (transaction, currency), params)) + result = this.sortBy (result, 'timestamp'); + const code = (currency !== undefined) ? currency['code'] : undefined; + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + parseLedger (data, currency = undefined, since = undefined, limit = undefined, params = {}) { + let result = []; + const array = Object.values (data || []); + for (let i = 0; i < array.length; i++) { + const itemOrItems = this.parseLedgerEntry (array[i], currency); + if (Array.isArray (itemOrItems)) { + for (let j = 0; j < itemOrItems.length; j++) { + result.push (this.extend (itemOrItems[j], params)); + } + } else { + result.push (this.extend (itemOrItems, params)); + } + } + result = this.sortBy (result, 'timestamp'); + const code = (currency !== undefined) ? currency['code'] : undefined; + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { + // + // the value of orders is either a dict or a list + // + // dict + // + // { + // 'id1': { ... }, + // 'id2': { ... }, + // 'id3': { ... }, + // ... + // } + // + // list + // + // [ + // { 'id': 'id1', ... }, + // { 'id': 'id2', ... }, + // { 'id': 'id3', ... }, + // ... + // ] + // + let result = Array.isArray (orders) ? + Object.values (orders).map ((order) => this.extend (this.parseOrder (order, market), params)) : + Object.entries (orders).map (([ id, order ]) => this.extend (this.parseOrder (this.extend ({ 'id': id }, order), market), params)) + result = sortBy (result, 'timestamp') + const symbol = (market !== undefined) ? market['symbol'] : undefined + return this.filterBySymbolSinceLimit (result, symbol, since, limit) + } + + safeCurrency (currencyId, currency = undefined) { + if ((currencyId === undefined) && (currency !== undefined)) { + return currency + } + if ((this.currencies_by_id !== undefined) && (currencyId in this.currencies_by_id)) { + return this.currencies_by_id[currencyId] + } + return { + 'id': currencyId, + 'code': (currencyId === undefined) ? currencyId : this.commonCurrencyCode (currencyId.toUpperCase ()), + } + } + + safeCurrencyCode (currencyId, currency = undefined) { + currency = this.safeCurrency (currencyId, currency) + return currency['code'] + } + + safeMarket (marketId, market = undefined, delimiter = undefined) { + if (marketId !== undefined) { + if (this.markets_by_id !== undefined && marketId in this.markets_by_id) { + market = this.markets_by_id[marketId] + } else if (delimiter !== undefined) { + const [ baseId, quoteId ] = marketId.split (delimiter) + const base = this.safeCurrencyCode (baseId) + const quote = this.safeCurrencyCode (quoteId) + const symbol = base + '/' + quote + return { + 'id': marketId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + } + } + } + if (market !== undefined) { + return market + } + return { + 'id': marketId, + 'symbol': marketId, + 'base': undefined, + 'quote': undefined, + 'baseId': undefined, + 'quoteId': undefined, + } + } + + safeSymbol (marketId, market = undefined, delimiter = undefined) { + market = this.safeMarket (marketId, market, delimiter) + return market['symbol']; + } + + filterBySymbol (array, symbol = undefined) { + return ((symbol !== undefined) ? array.filter ((entry) => entry.symbol === symbol) : array) + } + + parseOHLCV (ohlcv, market = undefined) { + return Array.isArray (ohlcv) ? ohlcv.slice (0, 6) : ohlcv + } + + parseOHLCVs (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { + // this code is commented out temporarily to catch for exchange-specific errors + // if (!this.isArray (ohlcvs)) { + // throw new ExchangeError (this.id + ' parseOHLCVs expected an array in the ohlcvs argument, but got ' + typeof ohlcvs); + // } + ohlcvs = Object.values (ohlcvs || []) + const result = [] + for (let i = 0; i < ohlcvs.length; i++) { + if (limit && (result.length >= limit)) { + break; + } + const ohlcv = this.parseOHLCV (ohlcvs[i], market) + if (since && (ohlcv[0] < since)) { + continue + } + result.push (ohlcv) + } + return this.sortBy (result, 0) + } + + editLimitBuyOrder (id, symbol, ...args) { + return this.editLimitOrder (id, symbol, 'buy', ...args) + } + + editLimitSellOrder (id, symbol, ...args) { + return this.editLimitOrder (id, symbol, 'sell', ...args) + } + + editLimitOrder (id, symbol, ...args) { + return this.editOrder (id, symbol, 'limit', ...args) + } + + async editOrder (id, symbol, ...args) { + if (!this.enableRateLimit) { + throw new ExchangeError (this.id + ' editOrder() requires enableRateLimit = true') + } + await this.cancelOrder (id, symbol); + return this.createOrder (symbol, ...args) + } + + createLimitOrder (symbol, ...args) { + return this.createOrder (symbol, 'limit', ...args) + } + + createMarketOrder (symbol, ...args) { + return this.createOrder (symbol, 'market', ...args) + } + + createLimitBuyOrder (symbol, ...args) { + return this.createOrder (symbol, 'limit', 'buy', ...args) + } + + createLimitSellOrder (symbol, ...args) { + return this.createOrder (symbol, 'limit', 'sell', ...args) + } + + createMarketBuyOrder (symbol, amount, params = {}) { + return this.createOrder (symbol, 'market', 'buy', amount, undefined, params) + } + + createMarketSellOrder (symbol, amount, params = {}) { + return this.createOrder (symbol, 'market', 'sell', amount, undefined, params) + } + + costToPrecision (symbol, cost) { + return decimalToPrecision (cost, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode) + } + + priceToPrecision (symbol, price) { + return decimalToPrecision (price, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode) + } + + amountToPrecision (symbol, amount) { + return decimalToPrecision (amount, TRUNCATE, this.markets[symbol].precision.amount, this.precisionMode, this.paddingMode) + } + + feeToPrecision (symbol, fee) { + return decimalToPrecision (fee, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode) + } + + currencyToPrecision (currency, fee) { + return decimalToPrecision (fee, ROUND, this.currencies[currency]['precision'], this.precisionMode, this.paddingMode); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol] + const rate = market[takerOrMaker] + const cost = parseFloat (this.costToPrecision (symbol, amount * price)) + return { + 'type': takerOrMaker, + 'currency': market['quote'], + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, rate * cost)), + } + } + + checkRequiredDependencies () { + return + } + + soliditySha3 (array) { + // we only support address, uint256, and string solidity types + const encoded = [] + for (let i = 0; i < array.length; i++) { + const value = array[i] + if (Number.isInteger (value) || value.match (/^[0-9]+$/)) { + encoded.push (this.numberToBE (this.numberToString (value), 32)) + } else { + const noPrefix = this.remove0xPrefix (value) + if (noPrefix.length === 40 && noPrefix.toLowerCase ().match (/^[0-9a-f]+$/)) { // check if it is an address + encoded.push (this.base16ToBinary (noPrefix)) + } else { + encoded.push (this.stringToBinary (noPrefix)) + } + } + } + const concated = this.binaryConcatArray (encoded) + return '0x' + this.hash (concated, 'keccak', 'hex') + } + + remove0xPrefix (hexData) { + if (hexData.slice (0, 2) === '0x') { + return hexData.slice (2) + } else { + return hexData + } + } + + hashMessage (message) { + // takes a hex encoded message + const binaryMessage = this.base16ToBinary (this.remove0xPrefix (message)) + const prefix = this.stringToBinary ('\x19Ethereum Signed Message:\n' + binaryMessage.sigBytes) + return '0x' + this.hash (this.binaryConcat (prefix, binaryMessage), 'keccak', 'hex') + } + + signHash (hash, privateKey) { + const signature = this.ecdsa (hash.slice (-64), privateKey.slice (-64), 'secp256k1', undefined) + return { + 'r': '0x' + signature['r'], + 's': '0x' + signature['s'], + 'v': 27 + signature['v'], + } + } + + signMessage (message, privateKey) { + return this.signHash (this.hashMessage (message), privateKey.slice (-64)) + } + + signMessageString (message, privateKey) { + // still takes the input as a hex string + // same as above but returns a string instead of an object + const signature = this.signMessage (message, privateKey) + return signature['r'] + this.remove0xPrefix (signature['s']) + this.binaryToBase16 (this.numberToBE (signature['v'])); + } + + oath () { + if (typeof this.twofa !== 'undefined') { + return this.totp (this.twofa) + } else { + throw new ExchangeError (this.id + ' this.twofa has not been set') + } + } + + // the following functions take and return numbers represented as strings + // this is useful for arbitrary precision maths that floats lack + integerDivide (a, b) { + return new BN (a).div (new BN (b)) + } + + integerModulo (a, b) { + return new BN (a).mod (new BN (b)) + } + + integerPow (a, b) { + return new BN (a).pow (new BN (b)) + } +} + + +},{"../static_dependencies/BN/bn":123,"./errors":7,"./functions":8}],6:[function(require,module,exports){ +'use strict'; + +const errorHierarchy = { + 'BaseError': { + 'ExchangeError': { + 'AuthenticationError': { + 'PermissionDenied': {}, + 'AccountSuspended': {}, + }, + 'ArgumentsRequired': {}, + 'BadRequest': { + 'BadSymbol': {}, + }, + 'BadResponse': { + 'NullResponse': {}, + }, + 'InsufficientFunds': {}, + 'InvalidAddress': { + 'AddressPending': {}, + }, + 'InvalidOrder': { + 'OrderNotFound': {}, + 'OrderNotCached': {}, + 'CancelPending': {}, + 'OrderImmediatelyFillable': {}, + 'OrderNotFillable': {}, + 'DuplicateOrderId': {}, + }, + 'NotSupported': {}, + }, + 'NetworkError': { + 'DDoSProtection': { + 'RateLimitExceeded': {}, + }, + 'ExchangeNotAvailable': { + 'OnMaintenance': {}, + }, + 'InvalidNonce': {}, + 'RequestTimeout': {}, + }, + }, +}; + +module.exports = errorHierarchy; + +},{}],7:[function(require,module,exports){ +'use strict'; + +const errorHierarchy = require ('./errorHierarchy.js') + +/* ------------------------------------------------------------------------ */ + +function subclass (BaseClass, classes, namespace = {}) { + + for (const [className, subclasses] of Object.entries (classes)) { + + const Class = Object.assign (namespace, { + + /* By creating a named property, we trick compiler to assign our class constructor function a name. + Otherwise, all our error constructors would be shown as [Function: Error] in the debugger! And + the super-useful `e.constructor.name` magic wouldn't work — we then would have no chance to + obtain a error type string from an error instance programmatically! */ + + [className]: class extends BaseClass { + + constructor (message) { + + super (message) + + /* A workaround to make `instanceof` work on custom Error classes in transpiled ES5. + See my blog post for the explanation of this hack: + + https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801 */ + + this.constructor = Class + this.__proto__ = Class.prototype + this.name = className + this.message = message + } + } + + })[className] + + subclass (Class, subclasses, namespace) + } + + return namespace +} + +/* ------------------------------------------------------------------------ */ + +module.exports = subclass ( + // Root class + Error, + // Derived class hierarchy + errorHierarchy +) + +},{"./errorHierarchy.js":6}],8:[function(require,module,exports){ +'use strict'; + +/* ------------------------------------------------------------------------ */ + +const { unCamelCase } = require ('./functions/string') + +const unCamelCasePropertyNames = x => { + for (const k in x) x[unCamelCase (k)] = x[k] // camel_case_method = camelCaseMethod + return x +} + +/* ------------------------------------------------------------------------ */ + +module.exports = unCamelCasePropertyNames (Object.assign ({} + + , require ('./functions/platform') + , require ('./functions/generic') + , require ('./functions/string') + , require ('./functions/type') + , require ('./functions/number') + , require ('./functions/encode') + , require ('./functions/crypto') + , require ('./functions/time') + , require ('./functions/throttle') + , require ('./functions/misc') +)) + +/* ------------------------------------------------------------------------ */ + +},{"./functions/crypto":9,"./functions/encode":10,"./functions/generic":11,"./functions/misc":12,"./functions/number":13,"./functions/platform":14,"./functions/string":15,"./functions/throttle":16,"./functions/time":17,"./functions/type":18}],9:[function(require,module,exports){ +'use strict' + +/* ------------------------------------------------------------------------ */ + +const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js') +const { capitalize } = require ('./string') +const { stringToBase64, urlencodeBase64 } = require ('./encode') +const NodeRSA = require ('./../../static_dependencies/node-rsa/NodeRSA') +const { binaryToBase58, byteArrayToWordArray } = require ('./encode') +const elliptic = require ('./../../static_dependencies/elliptic/lib/elliptic') +const EC = elliptic.ec +const EDDSA = elliptic.eddsa +const { ArgumentsRequired } = require ('./../errors') +const BN = require ('../../static_dependencies/BN/bn.js') + +/* ------------------------------------------------------------------------ */ + +const hash = (request, hash = 'md5', digest = 'hex') => { + const options = {} + if (hash === 'keccak') { + hash = 'SHA3' + options['outputLength'] = 256 + } + const result = CryptoJS[hash.toUpperCase ()] (request, options) + return (digest === 'binary') ? result : result.toString (CryptoJS.enc[capitalize (digest)]) +} + +/* ............................................. */ + +const hmac = (request, secret, hash = 'sha256', digest = 'hex') => { + const result = CryptoJS['Hmac' + hash.toUpperCase ()] (request, secret) + if (digest) { + const encoding = (digest === 'binary') ? 'Latin1' : capitalize (digest) + return result.toString (CryptoJS.enc[capitalize (encoding)]) + } + return result +} + +/* ............................................. */ + +function rsa (request, secret, alg = 'RS256') { + const algos = { + 'RS256': 'pkcs1-sha256', + 'RS512': 'pkcs1-sha512', + } + if (!(alg in algos)) { + throw new ExchangeError (alg + ' is not a supported rsa signing algorithm.') + } + const algorithm = algos[alg] + let key = new NodeRSA (secret, { + 'environment': 'browser', + 'signingScheme': algorithm, + }) + return key.sign (request, 'base64', 'binary') +} + + +/** + * @return {string} + */ +function jwt (request, secret, alg = 'HS256') { + const algos = { + 'HS256': 'sha256', + 'HS384': 'sha384', + 'HS512': 'sha512', + }; + const encodedHeader = urlencodeBase64 (stringToBase64 (JSON.stringify ({ 'alg': alg, 'typ': 'JWT' }))) + const encodedData = urlencodeBase64 (stringToBase64 (JSON.stringify (request))) + const token = [ encodedHeader, encodedData ].join ('.') + const algoType = alg.slice (0, 2); + const algorithm = algos[alg] + let signature = undefined + if (algoType === 'HS') { + signature = urlencodeBase64 (hmac (token, secret, algorithm, 'base64')) + } else if (algoType === 'RS') { + signature = urlencodeBase64 (rsa (token, secret, alg)) + } + return [ token, signature ].join ('.') +} + +function ecdsa (request, secret, algorithm = 'p256', hashFunction = undefined, fixedLength = false) { + let digest = request + if (hashFunction !== undefined) { + digest = hash (request, hashFunction, 'hex') + } + const curve = new EC (algorithm) + let signature = curve.sign (digest, secret, 'hex', { 'canonical': true }) + let counter = new BN ('0') + const minimum_size = new BN ('1').shln (8 * 31).sub (new BN ('1')) + while (fixedLength && (signature.r.gt (curve.nh) || signature.r.lte (minimum_size) || signature.s.lte (minimum_size))) { + signature = curve.sign (digest, secret, 'hex', { 'canonical': true, 'extraEntropy': counter.toArray ('le', 32)}) + counter = counter.add (new BN ('1')) + } + return { + 'r': signature.r.toString (16).padStart (64, '0'), + 's': signature.s.toString (16).padStart (64, '0'), + 'v': signature.recoveryParam, + } +} + + +function eddsa (request, secret, algorithm = 'ed25519') { + // used for waves.exchange (that's why the output is base58) + const curve = new EDDSA (algorithm) + const signature = curve.signModified (request, secret) + return binaryToBase58 (byteArrayToWordArray (signature.toBytes ())) +} + +/* ------------------------------------------------------------------------ */ + +const totp = (secret) => { + + const dec2hex = s => ((s < 15.5 ? '0' : '') + Math.round (s).toString (16)) + , hex2dec = s => parseInt (s, 16) + , leftpad = (s, p) => (p + s).slice (-p.length) // both s and p are short strings + + const base32tohex = (base32) => { + let base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' + let bits = '' + let hex = '' + for (let i = 0; i < base32.length; i++) { + let val = base32chars.indexOf (base32.charAt (i).toUpperCase ()) + bits += leftpad (val.toString (2), '00000') + } + for (let i = 0; i + 4 <= bits.length; i += 4) { + let chunk = bits.substr (i, 4) + hex = hex + parseInt (chunk, 2).toString (16) + } + return hex + } + + const getOTP = (secret) => { + secret = secret.replace (' ', '') // support 2fa-secrets with spaces like "4TDV WOGO" → "4TDVWOGO" + let epoch = Math.round (new Date ().getTime () / 1000.0) + let time = leftpad (dec2hex (Math.floor (epoch / 30)), '0000000000000000') + let hmacRes = hmac (CryptoJS.enc.Hex.parse (time), CryptoJS.enc.Hex.parse (base32tohex (secret)), 'sha1', 'hex') + let offset = hex2dec (hmacRes.substring (hmacRes.length - 1)) + let otp = (hex2dec (hmacRes.substr (offset * 2, 8)) & hex2dec ('7fffffff')) + '' + otp = (otp).substr (otp.length - 6, 6) + return otp + } + + return getOTP (secret) +} + +/* ------------------------------------------------------------------------ */ + +module.exports = { + hash, + hmac, + jwt, + totp, + rsa, + ecdsa, + eddsa, +} + +/* ------------------------------------------------------------------------ */ + +},{"../../static_dependencies/BN/bn.js":123,"../../static_dependencies/crypto-js/crypto-js":124,"./../../static_dependencies/elliptic/lib/elliptic":125,"./../../static_dependencies/node-rsa/NodeRSA":143,"./../errors":7,"./encode":10,"./string":15}],10:[function(require,module,exports){ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js') +const qs = require ('../../static_dependencies/qs/index') +const BN = require ('../../static_dependencies/BN/bn') + + +/* ------------------------------------------------------------------------ */ + +// global vars for base58 encoding +const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +let base58Decoder = null +let base58Encoder = null + +/* ------------------------------------------------------------------------ */ + +module.exports = + + { json: (data, params = undefined) => JSON.stringify (data) + , unjson: JSON.parse + + , isJsonEncodedObject: object => ( + (typeof object === 'string') && + (object.length >= 2) && + ((object[0] === '{') || (object[0] === '[')) + ) + + , stringToBinary: string => CryptoJS.enc.Latin1.parse (string) + , stringToBase64: string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64) + , base64ToBinary: string => CryptoJS.enc.Base64.parse (string) + , base64ToString: string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8) + , binaryToBase64: binary => binary.toString (CryptoJS.enc.Base64) + , base16ToBinary: string => CryptoJS.enc.Hex.parse (string) + , binaryToBase16: binary => binary.toString (CryptoJS.enc.Hex) + , binaryConcat: (...args) => args.reduce ((a, b) => a.concat (b)) + , binaryConcatArray: (arr) => arr.reduce ((a, b) => a.concat (b)) + + , urlencode: object => qs.stringify (object) + , urlencodeWithArrayRepeat: object => qs.stringify (object, { arrayFormat: 'repeat' }) + , rawencode: object => qs.stringify (object, { encode: false }) + , encode: x => x + , decode: x => x + + // Url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores + + , urlencodeBase64: base64string => base64string.replace (/[=]+$/, '') + .replace (/\+/g, '-') + .replace (/\//g, '_') + + , numberToLE: (n, padding) => { + const hexArray = new BN (n).toArray ('le', padding) + return byteArrayToWordArray (hexArray) + } + + , numberToBE: (n, padding) => { + const hexArray = new BN (n).toArray ('be', padding) + return byteArrayToWordArray (hexArray) + } + + , base58ToBinary: (string) => { + if (!base58Decoder) { + base58Decoder = {} + base58Encoder = {} + for (let i = 0; i < 58; i++) { + const c = base58Alphabet[i] + const bigNum = new BN (i) + base58Decoder[c] = bigNum + base58Encoder[bigNum] = c + } + } + let result = new BN (0) + const base = new BN (58) + for (let i = 0; i < string.length; i++) { + const character = string[i] + result.imul (base) + result.iadd (base58Decoder[character]) + } + return byteArrayToWordArray (result.toArray ('be')) + } + + , binaryToBase58: (wordArray) => { + if (!base58Encoder) { + base58Decoder = {} + base58Encoder = {} + for (let i = 0; i < 58; i++) { + const c = base58Alphabet[i] + const bigNum = new BN (i) + base58Decoder[c] = bigNum + base58Encoder[bigNum] = c + } + } + const base = new BN (58) + // hex is only compatible encoding between cryptojs and BN + const hexString = wordArray.toString (CryptoJS.enc.Hex) + let result = new BN (hexString, 16) + let string = [] + while (!result.isZero ()) { + const { div, mod } = result.divmod (base) + result = div + string.push (base58Encoder[mod]) + } + return string.reverse ().join ('') + } +} + +function byteArrayToWordArray (ba) { + const wa = [] + for (let i = 0; i < ba.length; i++) { + wa[(i / 4) | 0] |= ba[i] << (24 - 8 * i) + } + return CryptoJS.lib.WordArray.create (wa, ba.length) +} + +module.exports['byteArrayToWordArray'] = byteArrayToWordArray + +/* ------------------------------------------------------------------------ */ + +},{"../../static_dependencies/BN/bn":123,"../../static_dependencies/crypto-js/crypto-js":124,"../../static_dependencies/qs/index":159}],11:[function(require,module,exports){ +"use strict"; + +// ---------------------------------------------------------------------------- + +const { isObject, isNumber, isDictionary, isArray } = require ('./type') + +// ---------------------------------------------------------------------------- + +const keys = Object.keys + , values = (x) => ((!isArray (x)) ? Object.values (x) : x) // don't copy arrays if they're already arrays + , index = (x) => new Set (values (x)) + , extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free + , clone = (x) => (isArray (x) ? Array.from (x) : extend (x)) // clone arrays or objects + +// ---------------------------------------------------------------------------- + +module.exports = { + keys + , values + , extend + , clone + , index + , ordered: (x) => x // a stub to keep assoc keys in order (in JS it does nothing, it's mostly for Python) + , unique: (x) => Array.from (index (x)) + , arrayConcat: (a, b) => a.concat (b) + + // ------------------------------------------------------------------------ + + , inArray (needle, haystack) { + + return haystack.includes (needle) + } + + , toArray (object) { + + return Object.values (object) + } + + , isEmpty (object) { + if (!object) { + return true + } + return (Array.isArray (object) ? object : Object.keys (object)).length < 1; + } + + // ------------------------------------------------------------------------ + + , keysort (x, out = {}) { + + for (const k of keys (x).sort ()) + out[k] = x[k] + + return out + } + + // ------------------------------------------------------------------------ + + /* + Accepts a map/array of objects and a key name to be used as an index: + array = [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value2', anotherKey: 'anotherValue2' }, + { someKey: 'value3', anotherKey: 'anotherValue3' }, + ] + key = 'someKey' + + Returns a map: + { + value1: { someKey: 'value1', anotherKey: 'anotherValue1' }, + value2: { someKey: 'value2', anotherKey: 'anotherValue2' }, + value3: { someKey: 'value3', anotherKey: 'anotherValue3' }, + } + */ + + , indexBy (x, k, out = {}) { + + for (const v of values (x)) { + if (k in v) { + out[v[k]] = v + } + } + + return out + } + + // ------------------------------------------------------------------------ + + /* + Accepts a map/array of objects and a key name to be used as a grouping parameter: + array = [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value1', anotherKey: 'anotherValue2' }, + { someKey: 'value3', anotherKey: 'anotherValue3' }, + ] + key = 'someKey' + + Returns a map: + { + value1: [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value1', anotherKey: 'anotherValue2' }, + ] + value3: [ + { someKey: 'value3', anotherKey: 'anotherValue3' } + ], + } + */ + + , groupBy (x, k, out = {}) { + + for (const v of values (x)) { + if (k in v) { + const p = v[k] + out[p] = out[p] || [] + out[p].push (v) + } + } + return out + } + + // ------------------------------------------------------------------------ + + /* + Accepts a map/array of objects, a key name and a key value to be used as a filter: + array = [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value2', anotherKey: 'anotherValue2' }, + { someKey: 'value3', anotherKey: 'anotherValue3' }, + ] + key = 'someKey' + value = 'value1' + + Returns an array: + [ + value1: { someKey: 'value1', anotherKey: 'anotherValue1' }, + ] + */ + + , filterBy (x, k, value = undefined, out = []) { + + for (const v of values (x)) { + if (v[k] === value) { + out.push (v) + } + } + + return out + } + + // ------------------------------------------------------------------------ + // NB: MUTATES ARRAY! + + , sortBy: (array, key, descending = false, direction = descending ? -1 : 1) => array.sort ((a, b) => { + if (a[key] < b[key]) { + return -direction + } else if (a[key] > b[key]) { + return direction + } else { + return 0 + } + }) + + // ------------------------------------------------------------------------ + + , flatten: function flatten (x, out = []) { + + for (const v of x) { + if (isArray (v)) { + flatten (v, out) + } else { + out.push (v) + } + } + + return out + } + + // ------------------------------------------------------------------------ + + , pluck: (x, k) => values (x).filter ((v) => k in v).map ((v) => v[k]) + + // ------------------------------------------------------------------------ + + , omit (x, ...args) { + + if (!Array.isArray (x)) { + + const out = clone (x) + + for (const k of args) { + if (isArray (k)) { // omit (x, ['a', 'b']) + for (const kk of k) { + delete out[kk] + } + } else { + delete out[k] // omit (x, 'a', 'b') + } + } + + return out + } + + return x + } + + // ------------------------------------------------------------------------ + + , sum (...xs) { + + const ns = xs.filter (isNumber) // leave only numbers + + return (ns.length > 0) ? ns.reduce ((a, b) => a + b, 0) : undefined + } + + // ------------------------------------------------------------------------ + + , deepExtend: function deepExtend (...xs) { + let out = undefined + for (const x of xs) { + if (isDictionary (x)) { + if (!isDictionary (out)) { + out = {} + } + for (const k in x) { + out[k] = deepExtend (out[k], x[k]) + } + } else { + out = x + } + } + return out + } + +// ---------------------------------------------------------------------------- + +} + +},{"./type":18}],12:[function(require,module,exports){ +'use strict'; + +const { ROUND_UP, ROUND_DOWN } = require ('./number') +const { NotSupported } = require ('../errors') + +//------------------------------------------------------------------------- +// converts timeframe to seconds +const parseTimeframe = (timeframe) => { + + const amount = timeframe.slice (0, -1) + const unit = timeframe.slice (-1) + let scale = undefined; + + if (unit === 'y') { + scale = 60 * 60 * 24 * 365 + } else if (unit === 'M') { + scale = 60 * 60 * 24 * 30 + } else if (unit === 'w') { + scale = 60 * 60 * 24 * 7 + } else if (unit === 'd') { + scale = 60 * 60 * 24 + } else if (unit === 'h') { + scale = 60 * 60 + } else if (unit === 'm') { + scale = 60 + } else if (unit === 's') { + scale = 1 + } else { + throw new NotSupported ('timeframe unit ' + unit + ' is not supported') + } + + return amount * scale +} + +const roundTimeframe = (timeframe, timestamp, direction = ROUND_DOWN) => { + const ms = parseTimeframe (timeframe) * 1000 + // Get offset based on timeframe in milliseconds + const offset = timestamp % ms + return timestamp - offset + ((direction === ROUND_UP) ? ms : 0); +} + +// given a sorted arrays of trades (recent last) and a timeframe builds an array of OHLCV candles +const buildOHLCVC = (trades, timeframe = '1m', since = -Infinity, limit = Infinity) => { + const ms = parseTimeframe (timeframe) * 1000; + const ohlcvs = []; + const [ timestamp, /* open */, high, low, close, volume, count ] = [ 0, 1, 2, 3, 4, 5, 6 ]; + const oldest = Math.min (trades.length - 1, limit); + + for (let i = 0; i <= oldest; i++) { + const trade = trades[i]; + if (trade.timestamp < since) { + continue; + } + const openingTime = Math.floor (trade.timestamp / ms) * ms; // shift to the edge of m/h/d (but not M) + const candle = ohlcvs.length - 1; + + if (candle === -1 || openingTime >= ohlcvs[candle][timestamp] + ms) { + // moved to a new timeframe -> create a new candle from opening trade + ohlcvs.push ([ + openingTime, // timestamp + trade.price, // O + trade.price, // H + trade.price, // L + trade.price, // C + trade.amount, // V + 1, // count + ]); + } else { + // still processing the same timeframe -> update opening trade + ohlcvs[candle][high] = Math.max (ohlcvs[candle][high], trade.price); + ohlcvs[candle][low] = Math.min (ohlcvs[candle][low], trade.price); + ohlcvs[candle][close] = trade.price; + ohlcvs[candle][volume] += trade.amount; + ohlcvs[candle][count]++; + } // if + } // for + return ohlcvs; +} + +const extractParams = (string) => { + const re = /{([\w-]+)}/g + const matches = [] + let match = re.exec (string) + while (match) { + matches.push (match[1]) + match = re.exec (string) + } + return matches +} + +const implodeParams = (string, params) => { + if (!Array.isArray (params)) { + const keys = Object.keys (params) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (!Array.isArray (params[key])) { + string = string.replace ('{' + key + '}', params[key]) + } + } + } + return string +} + +function vwap (baseVolume, quoteVolume) { + return ((baseVolume !== undefined) && (quoteVolume !== undefined) && (baseVolume > 0)) ? (quoteVolume / baseVolume) : undefined +} + +/* ------------------------------------------------------------------------ */ + +module.exports = { + + aggregate (bidasks) { + + const result = {} + + for (let i = 0; i < bidasks.length; i++) { + const [ price, volume ] = bidasks[i]; + if (volume > 0) { + result[price] = (result[price] || 0) + volume + } + } + + return Object.keys (result).map ((price) => [parseFloat (price), parseFloat (result[price])]) + }, + + parseTimeframe, + roundTimeframe, + buildOHLCVC, + ROUND_UP, + ROUND_DOWN, + + implodeParams, + extractParams, + + vwap, +} + +/* ------------------------------------------------------------------------ */ + +},{"../errors":7,"./number":13}],13:[function(require,module,exports){ +'use strict' + +/* ------------------------------------------------------------------------ + + NB: initially, I used objects for options passing: + + decimalToPrecision ('123.456', { digits: 2, round: true, afterPoint: true }) + + ...but it turns out it's hard to port that across different languages and it is also + probably has a performance penalty -- while it's a performance critical code! So + I switched to using named constants instead, as it is actually more readable and + succinct, and surely doesn't come with any inherent performance downside: + + decimalToPrecision ('123.456', ROUND, 2, DECIMAL_PLACES) */ + +const ROUND = 0 // rounding mode + , TRUNCATE = 1 + , ROUND_UP = 2 + , ROUND_DOWN = 3 + +const DECIMAL_PLACES = 0 // digits counting mode + , SIGNIFICANT_DIGITS = 1 + , TICK_SIZE = 2 + +const NO_PADDING = 0 // zero-padding mode + , PAD_WITH_ZERO = 1 + +const precisionConstants = { + ROUND, + TRUNCATE, + ROUND_UP, + ROUND_DOWN, + DECIMAL_PLACES, + SIGNIFICANT_DIGITS, + TICK_SIZE, + NO_PADDING, + PAD_WITH_ZERO, +} + +/* ------------------------------------------------------------------------ */ + +// See https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript for discussion + +function numberToString (x) { // avoids scientific notation for too large and too small numbers + + if (typeof x === 'string') return x + + const s = x.toString () + if (Math.abs (x) < 1.0) { + const n_e = s.split ('e-') + const n = n_e[0].replace ('.', '') + const e = parseInt (n_e[1]) + const neg = (s[0] === '-') + if (e) { + x = (neg ? '-' : '') + '0.' + (new Array (e)).join ('0') + n.substring (neg) + return x + } + } else { + const parts = s.split ('e') + if (parts[1]) { + let e = parseInt (parts[1]) + const m = parts[0].split ('.') + if (m[1]) { + e -= m[1].length + } + return m[0] + m[1] + (new Array (e + 1)).join ('0') + } + } + return s +} + +//----------------------------------------------------------------------------- +// expects non-scientific notation + +const truncate_regExpCache = [] + , truncate_to_string = (num, precision = 0) => { + num = numberToString (num) + if (precision > 0) { + const re = truncate_regExpCache[precision] || (truncate_regExpCache[precision] = new RegExp ("([-]*\\d+\\.\\d{" + precision + "})(\\d)")) + const [ , result] = num.toString ().match (re) || [null, num] + return result.toString () + } + return parseInt (num).toString () + } + , truncate = (num, precision = 0) => parseFloat (truncate_to_string (num, precision)) + +function precisionFromString (string) { + const split = string.replace (/0+$/g, '').split ('.') + return (split.length > 1) ? (split[1].length) : 0 +} + +/* ------------------------------------------------------------------------ */ + +const decimalToPrecision = (x, roundingMode + , numPrecisionDigits + , countingMode = DECIMAL_PLACES + , paddingMode = NO_PADDING) => { + + if (countingMode === TICK_SIZE) { + if (numPrecisionDigits <= 0) { + throw new Error ('TICK_SIZE cant be used with negative or zero numPrecisionDigits') + } + } + + if (numPrecisionDigits < 0) { + const toNearest = Math.pow (10, -numPrecisionDigits) + if (roundingMode === ROUND) { + return (toNearest * decimalToPrecision (x / toNearest, roundingMode, 0, countingMode, paddingMode)).toString () + } + if (roundingMode === TRUNCATE) { + return (x - (x % toNearest)).toString () + } + } + +/* handle tick size */ + if (countingMode === TICK_SIZE) { + const precisionDigitsString = decimalToPrecision (numPrecisionDigits, ROUND, 22, DECIMAL_PLACES, NO_PADDING) + const newNumPrecisionDigits = precisionFromString (precisionDigitsString) + let missing = x % numPrecisionDigits + // See: https://github.com/ccxt/ccxt/pull/6486 + missing = Number (decimalToPrecision (missing, ROUND, 8, DECIMAL_PLACES, NO_PADDING)); + const fpError = decimalToPrecision (missing / numPrecisionDigits, ROUND, Math.max (newNumPrecisionDigits, 8), DECIMAL_PLACES, NO_PADDING) + if (precisionFromString (fpError) !== 0) { + if (roundingMode === ROUND) { + if (x > 0) { + if (missing >= numPrecisionDigits / 2) { + x = x - missing + numPrecisionDigits + } else { + x = x - missing + } + } else { + if (missing >= numPrecisionDigits / 2) { + x = Number (x) - missing + } else { + x = Number (x) - missing - numPrecisionDigits + } + } + } else if (roundingMode === TRUNCATE) { + x = x - missing + } + } + return decimalToPrecision (x, ROUND, newNumPrecisionDigits, DECIMAL_PLACES, paddingMode); + } + +/* Convert to a string (if needed), skip leading minus sign (if any) */ + + const str = numberToString (x) + , isNegative = str[0] === '-' + , strStart = isNegative ? 1 : 0 + , strEnd = str.length + +/* Find the dot position in the source buffer */ + + for (var strDot = 0; strDot < strEnd; strDot++) + if (str[strDot] === '.') + break + + const hasDot = strDot < str.length + +/* Char code constants */ + + const MINUS = 45 + , DOT = 46 + , ZERO = 48 + , ONE = (ZERO + 1) + , FIVE = (ZERO + 5) + , NINE = (ZERO + 9) + +/* For -123.4567 the `chars` array will hold 01234567 (leading zero is reserved for rounding cases when 099 → 100) */ + + const chars = new Uint8Array ((strEnd - strStart) + (hasDot ? 0 : 1)) + chars[0] = ZERO + +/* Validate & copy digits, determine certain locations in the resulting buffer */ + + let afterDot = chars.length + , digitsStart = -1 // significant digits + , digitsEnd = -1 + + for (var i = 1, j = strStart; j < strEnd; j++, i++) { + + const c = str.charCodeAt (j) + + if (c === DOT) { + afterDot = i-- + + } else if ((c < ZERO) || (c > NINE)) { + throw new Error (`${str}: invalid number (contains an illegal character '${str[i - 1]}')`) + + } else { + chars[i] = c + if ((c !== ZERO) && (digitsStart < 0)) digitsStart = i + } + } + + if (digitsStart < 0) digitsStart = 1 + +/* Determine the range to cut */ + + let precisionStart = (countingMode === DECIMAL_PLACES) ? afterDot // 0.(0)001234567 + : digitsStart // 0.00(1)234567 + , precisionEnd = precisionStart + + numPrecisionDigits + +/* Reset the last significant digit index, as it will change during the rounding/truncation. */ + + digitsEnd = -1 + +/* Perform rounding/truncation per digit, from digitsEnd to digitsStart, by using the following + algorithm (rounding 999 → 1000, as an example): + + step = i=3 i=2 i=1 i=0 + + chars = 0999 0999 0900 1000 + memo = ---0 --1- -1-- 0--- */ + + let allZeros = true; + let signNeeded = isNegative; + + for (let i = chars.length - 1, memo = 0; i >= 0; i--) { + + let c = chars[i] + + if (i !== 0) { + c += memo + + if (i >= (precisionStart + numPrecisionDigits)) { + + const ceil = (roundingMode === ROUND) && + (c >= FIVE) && + !((c === FIVE) && memo) // prevents rounding of 1.45 to 2 + + c = ceil ? (NINE + 1) : ZERO + } + if (c > NINE) { c = ZERO; memo = 1; } + else memo = 0 + + } else if (memo) c = ONE // leading extra digit (0900 → 1000) + + chars[i] = c + + if (c !== ZERO) { + allZeros = false + digitsStart = i + digitsEnd = (digitsEnd < 0) ? (i + 1) : digitsEnd + } + } + +/* Update the precision range, as `digitsStart` may have changed... & the need for a negative sign if it is only 0 */ + + if (countingMode === SIGNIFICANT_DIGITS) { + precisionStart = digitsStart + precisionEnd = precisionStart + numPrecisionDigits + } + + if (allZeros) { + signNeeded = false + } + +/* Determine the input character range */ + + const readStart = ((digitsStart >= afterDot) || allZeros) ? (afterDot - 1) : digitsStart // 0.000(1)234 ----> (0).0001234 + , readEnd = (digitsEnd < afterDot) ? (afterDot ) : digitsEnd // 12(3)000 ----> 123000( ) + +/* Compute various sub-ranges */ + + const nSign = (signNeeded ? 1 : 0) // (-)123.456 + , nBeforeDot = (nSign + (afterDot - readStart)) // (-123).456 + , nAfterDot = Math.max (readEnd - afterDot, 0) // -123.(456) + , actualLength = (readEnd - readStart) // -(123.456) + , desiredLength = (paddingMode === NO_PADDING) + ? (actualLength) // -(123.456) + : (precisionEnd - readStart) // -(123.456 ) + + , pad = Math.max (desiredLength - actualLength, 0) // -123.456( ) + , padStart = (nBeforeDot + 1 + nAfterDot) // -123.456( ) + , padEnd = (padStart + pad) // -123.456 ( ) + , isInteger = (nAfterDot + pad) === 0 // -123 + +/* Fill the output buffer with characters */ + + const out = new Uint8Array (nBeforeDot + (isInteger ? 0 : 1) + nAfterDot + pad) + // ------------------------------------------------------------------------------------------ // --------------------- + if (signNeeded) out[0] = MINUS // - minus sign + for (i = nSign, j = readStart; i < nBeforeDot; i++, j++) out[i] = chars[j] // 123 before dot + if (!isInteger) out[nBeforeDot] = DOT // . dot + for (i = nBeforeDot + 1, j = afterDot; i < padStart; i++, j++) out[i] = chars[j] // 456 after dot + for (i = padStart; i < padEnd; i++) out[i] = ZERO // 000 padding + +/* Build a string from the output buffer */ + + return String.fromCharCode (...out) +} + +// toWei / fromWei + +function fromWei (amount, decimals = 18) { + if (amount === undefined) { + return amount + } + const exponential = Math.floor (amount).toExponential () // wei must be whole numbers + const [ n, exponent ] = exponential.split ('e') + const newExponent = parseInt (exponent) - decimals + return parseFloat (n + 'e' + newExponent) +} + +function toWei (amount, decimals = 18) { + if (amount === undefined) { + return amount + } + const exponential = parseFloat (amount).toExponential () + const [ n, exponent ] = exponential.split ('e') + const newExponent = parseInt (exponent) + decimals + return numberToString (Math.floor (parseFloat (n + 'e' + newExponent))) // wei must be whole numbers +} + +/* ------------------------------------------------------------------------ */ + +module.exports = { + toWei, + fromWei, + numberToString, + precisionFromString, + decimalToPrecision, + truncate_to_string, + truncate, + precisionConstants, + ROUND, + TRUNCATE, + ROUND_UP, + ROUND_DOWN, + DECIMAL_PLACES, + SIGNIFICANT_DIGITS, + TICK_SIZE, + NO_PADDING, + PAD_WITH_ZERO, +} + +/* ------------------------------------------------------------------------ */ + +},{}],14:[function(require,module,exports){ +(function (process){ +"use strict"; + +// ---------------------------------------------------------------------------- +// There's been a lot of messing with this code... +// The problem is to satisfy the following requirements: +// - properly detect isNode == true on server side and isNode == false in the browser (on client side) +// - make sure create-react-app, react-starter-kit and other react frameworks work +// - make sure it does not break the browserified version (when linked into a html from a cdn) +// - make sure it does not break the webpacking and babel-transpiled scripts +// - make sure it works in Electron +// - make sure it works with Angular.js +// - make sure it does not break other possible usage scenarios + +const isBrowser = typeof window !== 'undefined' + +const isElectron = typeof process !== 'undefined' && + typeof process.versions !== 'undefined' && + typeof process.versions.electron !== 'undefined' + +const isWebWorker = typeof WorkerGlobalScope !== 'undefined' && (self instanceof WorkerGlobalScope) + +const isWindows = typeof process !== 'undefined' && process.platform === "win32" + +const isNode = !(isBrowser || isWebWorker) + +const defaultFetch = typeof (fetch) === "undefined" ? require ('../../static_dependencies/fetch-ponyfill/fetch-node') ().fetch : fetch + +// ---------------------------------------------------------------------------- + +module.exports = { + + isBrowser, + isElectron, + isWebWorker, + isNode, + isWindows, + defaultFetch, +} +}).call(this,require('_process')) +},{"../../static_dependencies/fetch-ponyfill/fetch-node":141,"_process":207}],15:[function(require,module,exports){ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const uuid = a => a ? (a ^ Math.random () * 16 >> a / 4).toString (16) + : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace (/[018]/g, uuid) + +module.exports = + + { uuid + + // hasFetchOHLCV → has_fetch_ohlcv; parseHTTPResponse → parse_http_response + , unCamelCase: s => s.match (/^[A-Z0-9_]+$/) ? s : (s.replace (/[a-z0-9][A-Z]/g, x => x[0] + '_' + x[1]).replace(/[A-Z0-9][A-Z0-9][a-z]/g, x => x[0] + '_' + x[1] + x[2]).toLowerCase ()) + + , capitalize: s => s.length + ? (s.charAt (0).toUpperCase () + s.slice (1)) + : s + , strip: s => s.replace(/^\s+|\s+$/g, '') + } + +/* ------------------------------------------------------------------------ */ + +},{}],16:[function(require,module,exports){ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const { sleep + , now } = require ('./time') + +/* ------------------------------------------------------------------------ */ + +module.exports = { + + throttle: function throttle (cfg) { + + let lastTimestamp = now () + , numTokens = (cfg.numTokens !== undefined) ? cfg.numTokens : cfg.capacity + , running = false + , counter = 0 + + const queue = [] + + return Object.assign ((rateLimit, cost) => { + + if (queue.length > cfg.maxCapacity) + throw new Error ('Backlog is over max capacity of ' + cfg.maxCapacity) + + return new Promise (async (resolve, reject) => { + + try { + queue.push ({ cost, resolve, reject }) + + if (!running) { + running = true + while (queue.length > 0) { + const hasEnoughTokens = cfg.capacity ? (numTokens > 0) : (numTokens >= 0) + if (hasEnoughTokens) { + if (queue.length > 0) { + let { cost, resolve, reject } = queue[0] + cost = (cost || cfg.defaultCost) + if (numTokens >= Math.min (cost, cfg.capacity)) { + numTokens -= cost + queue.shift () + resolve () + } + } + } + const t = now () + , elapsed = t - lastTimestamp + lastTimestamp = t + numTokens = Math.min (cfg.capacity, numTokens + elapsed * (1 / rateLimit)) + await sleep (cfg.delay) + } + running = false + } + + } catch (e) { + reject (e) + } + }) + + }, cfg, { configure: newCfg => throttle (Object.assign ({}, cfg, newCfg)) }) + } +} + +/* ------------------------------------------------------------------------ */ + +},{"./time":17}],17:[function(require,module,exports){ +'use strict'; + +/* ------------------------------------------------------------------------ */ + +const now = Date.now // TODO: figure out how to utilize performance.now () properly – it's not as easy as it does not return a unix timestamp... +const microseconds = () => now () * 1000 // TODO: utilize performance.now for that purpose +const milliseconds = now +const seconds = () => Math.floor (now () / 1000) +const uuidv1 = () => { + const biasSeconds = 12219292800 // seconds from 15th Oct 1572 to Jan 1st 1970 + const bias = biasSeconds * 10000000 // in hundreds of nanoseconds + const time = microseconds () * 10 + bias + const timeHex = time.toString (16) + const arranged = timeHex.slice (7, 15) + timeHex.slice (3, 7) + '1' + timeHex.slice (0, 3) + // these should be random, but we're not making more than 10 requests per microsecond so who cares + const clockId = '9696' // a 14 bit number + const macAddress = 'ff'.repeat (6) + return arranged + clockId + macAddress +} + +/* ------------------------------------------------------------------------ */ + +const setTimeout_original = setTimeout +const setTimeout_safe = (done, ms, setTimeout = setTimeout_original /* overrideable for mocking purposes */, targetTime = now () + ms) => { + +/* The built-in setTimeout function can fire its callback earlier than specified, so we + need to ensure that it does not happen: sleep recursively until `targetTime` is reached... */ + + let clearInnerTimeout = () => {} + let active = true + + const id = setTimeout (() => { + active = true + const rest = targetTime - now () + if (rest > 0) { + clearInnerTimeout = setTimeout_safe (done, rest, setTimeout, targetTime) // try sleep more + } else { + done () + } + }, ms) + + return function clear () { + if (active) { + active = false // dunno if IDs are unique on various platforms, so it's better to rely on this flag to exclude the possible cancellation of the wrong timer (if called after completion) + clearTimeout (id) + } + clearInnerTimeout () + } +} + +/* ------------------------------------------------------------------------ */ + +class TimedOut extends Error { + + constructor () { + const message = 'timed out' + super (message) + this.constructor = TimedOut + this.__proto__ = TimedOut.prototype + this.message = message + } +} + +/* ------------------------------------------------------------------------ */ + +const iso8601 = (timestamp) => { + let _timestampNumber = undefined; + if (typeof timestamp === 'number') { + _timestampNumber = Math.floor (timestamp); + } else { + _timestampNumber = parseInt (timestamp, 10); + } + + // undefined, null and lots of nasty non-numeric values yield NaN + if (Number.isNaN (_timestampNumber) || _timestampNumber < 0) { + return undefined; + } + + // last line of defence + try { + return new Date (_timestampNumber).toISOString (); + } catch (e) { + return undefined; + } +} + +const parse8601 = (x) => { + if (typeof x !== 'string' || !x) { + return undefined; + } + + if (x.match (/^[0-9]+$/)) { + // a valid number in a string, not a date. + return undefined; + } + + if (x.indexOf ('-') < 0 || x.indexOf (':') < 0) { // no date can be without a dash and a colon + return undefined; + } + + // last line of defence + try { + const candidate = Date.parse (((x.indexOf ('+') >= 0) || (x.slice (-1) === 'Z')) ? x : (x + 'Z').replace (/\s(\d\d):/, 'T$1:')); + if (Number.isNaN (candidate)) { + return undefined; + } + return candidate; + } catch (e) { + return undefined; + } +} + +const parseDate = (x) => { + if (typeof x !== 'string' || !x) { + return undefined; + } + + if (x.indexOf ('GMT') >= 0) { + try { + return Date.parse (x); + } catch (e) { + return undefined; + } + } + + return parse8601 (x); +} + +const rfc2616 = (timestamp = undefined) => { + return new Date (timestamp).toUTCString (); +} + +const mdy = (timestamp, infix = '-') => { + infix = infix || '' + const date = new Date (timestamp) + const Y = date.getUTCFullYear ().toString () + let m = date.getUTCMonth () + 1 + let d = date.getUTCDate () + m = m < 10 ? ('0' + m) : m.toString () + d = d < 10 ? ('0' + d) : d.toString () + return m + infix + d + infix + Y +} + +const ymd = (timestamp, infix = '-') => { + infix = infix || '' + const date = new Date (timestamp) + const Y = date.getUTCFullYear ().toString () + let m = date.getUTCMonth () + 1 + let d = date.getUTCDate () + m = m < 10 ? ('0' + m) : m.toString () + d = d < 10 ? ('0' + d) : d.toString () + return Y + infix + m + infix + d +} + +const ymdhms = (timestamp, infix = ' ') => { + const date = new Date (timestamp) + const Y = date.getUTCFullYear () + let m = date.getUTCMonth () + 1 + let d = date.getUTCDate () + let H = date.getUTCHours () + let M = date.getUTCMinutes () + let S = date.getUTCSeconds () + m = m < 10 ? ('0' + m) : m + d = d < 10 ? ('0' + d) : d + H = H < 10 ? ('0' + H) : H + M = M < 10 ? ('0' + M) : M + S = S < 10 ? ('0' + S) : S + return Y + '-' + m + '-' + d + infix + H + ':' + M + ':' + S +} + +module.exports = + + { + now + , microseconds + , milliseconds + , seconds + , iso8601 + , parse8601 + , rfc2616 + , uuidv1 + , parseDate + , mdy + , ymd + , ymdhms + , setTimeout_safe + , sleep: ms => new Promise (resolve => setTimeout_safe (resolve, ms)) + , TimedOut + , timeout: async (ms, promise) => { + + let clear = () => {} + const expires = new Promise (resolve => (clear = setTimeout_safe (resolve, ms))) + + try { + return await Promise.race ([promise, expires.then (() => { throw new TimedOut () })]) + } finally { + clear () // fixes https://github.com/ccxt/ccxt/issues/749 + } + } +} + +/* ------------------------------------------------------------------------ */ + +},{}],18:[function(require,module,exports){ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const isNumber = Number.isFinite + , isInteger = Number.isInteger + , isArray = Array.isArray + , hasProps = o => ((o !== undefined) && (o !== null)) + , isString = s => (typeof s === 'string') + , isObject = o => ((o !== null) && (typeof o === 'object')) + , isRegExp = o => (o instanceof RegExp) + , isDictionary = o => (isObject (o) && (Object.getPrototypeOf (o) === Object.prototype) && !isArray (o) && !isRegExp (o)) + , isStringCoercible = x => ((hasProps (x) && x.toString) || isNumber (x)) + +/* ............................................. */ + +const prop = (o, k) => (isObject (o) ? o[k] : undefined) + , prop2 = (o, k1, k2) => (!isObject (o) ? undefined : (((k1 in o) && (o[k1] !== null)) ? o[k1] : o[k2])) + +/* ............................................. */ + +const asFloat = x => ((isNumber (x) || isString (x)) ? parseFloat (x) : NaN) + , asInteger = x => ((isNumber (x) || isString (x)) ? parseInt (x, 10) : NaN) + +/* ............................................. */ + +module.exports = { + + isNumber + , isInteger + , isArray + , isObject + , isString + , isStringCoercible + , isDictionary + + , hasProps + , prop + + , asFloat + , asInteger + + , safeFloat: (o, k, $default, n = asFloat (prop (o, k))) => (isNumber (n) ? n : $default) + , safeInteger: (o, k, $default, n = asInteger (prop (o, k))) => (isNumber (n) ? n : $default) + , safeIntegerProduct: (o, k, $factor, $default, n = asInteger (prop (o, k))) => (isNumber (n) ? parseInt (n * $factor) : $default) + , safeTimestamp: (o, k, $default, n = asFloat (prop (o, k))) => (isNumber (n) ? parseInt (n * 1000) : $default) + , safeValue: (o, k, $default, x = prop (o, k)) => (hasProps (x) ? x : $default) + , safeString: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x) : $default) + , safeStringLower: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x).toLowerCase () : $default) + , safeStringUpper: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x).toUpperCase () : $default) + + // not using safeFloats with an array argument as we're trying to save some cycles here + // we're not using safeFloat3 either because those cases are too rare to deserve their own optimization + + , safeFloat2: (o, k1, k2, $default, n = asFloat (prop2 (o, k1, k2))) => (isNumber (n) ? n : $default) + , safeInteger2: (o, k1, k2, $default, n = asInteger (prop2 (o, k1, k2))) => (isNumber (n) ? n : $default) + , safeIntegerProduct2: (o, k1, k2, $factor, $default, n = asInteger (prop2 (o, k1, k2))) => (isNumber (n) ? parseInt (n * $factor) : $default) + , safeTimestamp2: (o, k1, k2, $default, n = asFloat (prop2 (o, k1, k2))) => (isNumber (n) ? parseInt (n * 1000) : $default) + , safeValue2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (hasProps (x) ? x : $default) + , safeString2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x) : $default) + , safeStringLower2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x).toLowerCase () : $default) + , safeStringUpper2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x).toUpperCase () : $default) + +} + +/* ------------------------------------------------------------------------ */ + +},{}],19:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, OrderNotFound } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bcex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bcex', + 'name': 'BCEX', + 'countries': [ 'CN', 'HK' ], + 'version': '1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': 'emulated', + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + 'fetchTradingLimits': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/77231516-851c6900-6bac-11ea-8fd6-ee5c23eddbd4.jpg', + 'api': 'https://www.bcex.top', + 'www': 'https://www.bcex.top', + 'doc': 'https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Interface', + 'fees': 'https://bcex.udesk.cn/hc/articles/57085', + 'referral': 'https://www.bcex.top/register?invite_code=758978&lang=en', + }, + 'status': { + 'status': 'error', + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + }, + 'api': { + 'public': { + 'get': [ + 'Api_Market/getPriceList', // tickers + 'Api_Order/ticker', // last ohlcv candle (ticker) + 'Api_Order/depth', // orderbook + 'Api_Market/getCoinTrade', // ticker + 'Api_Order/marketOrder', // trades... + ], + 'post': [ + 'Api_Market/getPriceList', // tickers + 'Api_Order/ticker', // last ohlcv candle (ticker) + 'Api_Order/depth', // orderbook + 'Api_Market/getCoinTrade', // ticker + 'Api_Order/marketOrder', // trades... + ], + }, + 'private': { + 'post': [ + 'Api_Order/cancel', + 'Api_Order/coinTrust', // limit order + 'Api_Order/orderList', // open / all orders (my trades?) + 'Api_Order/orderInfo', + 'Api_Order/tradeList', // open / all orders + 'Api_Order/trustList', // ? + 'Api_User/userBalance', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'ckusd': 0.0, + 'other': 0.05 / 100, + }, + 'deposit': {}, + }, + }, + 'exceptions': { + '该币不存在,非法操作': ExchangeError, // { code: 1, msg: "该币不存在,非法操作" } - returned when a required symbol parameter is missing in the request (also, maybe on other types of errors as well) + '公钥不合法': AuthenticationError, // { code: 1, msg: '公钥不合法' } - wrong public key + '您的可用余额不足': InsufficientFunds, // { code: 1, msg: '您的可用余额不足' } - your available balance is insufficient + '您的btc不足': InsufficientFunds, // { code: 1, msg: '您的btc不足' } - your btc is insufficient + '参数非法': InvalidOrder, // {'code': 1, 'msg': '参数非法'} - 'Parameter illegal' + '订单信息不存在': OrderNotFound, // {'code': 1, 'msg': '订单信息不存在'} - 'Order information does not exist' + }, + 'commonCurrencies': { + 'UNI': 'UNI COIN', + 'PNT': 'Penta', + }, + 'options': { + 'limits': { + // hardcoding is deprecated, using these predefined values is not recommended, use loadTradingLimits instead + 'AFC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 6, 'max': 120000 }}}, + 'AFC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 6, 'max': 120000 }}}, + 'AFT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 300000 }}}, + 'AICC/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 5, 'max': 50000 }}}, + 'AIDOC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 100000 }}}, + 'AISI/ETH': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.001, 'max': 500 }}}, + 'AIT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 400000 }}}, + 'ANS/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 500 }}}, + 'ANS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.1, 'max': 1000 }}}, + 'ARC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 60, 'max': 600000 }}}, + 'AXF/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 100, 'max': 1000000 }}}, + 'BASH/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 250, 'max': 3000000 }}}, + 'BATT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 60, 'max': 1500000 }}}, + 'BCD/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.3, 'max': 7000 }}}, + 'BHPC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 70000 }}}, + 'BHPC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 60000 }}}, + 'BOPO/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 2000000 }}}, + 'BOPO/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 100, 'max': 10000000 }}}, + 'BTC/CKUSD': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.001, 'max': 10 }}}, + 'BTC/CNET': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.0005, 'max': 5 }}}, + 'BTC/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.0002, 'max': 4 }}}, + 'BTE/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 25, 'max': 250000 }}}, + 'BU/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 400000 }}}, + 'CIC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 3000, 'max': 30000000 }}}, + 'CIT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 4, 'max': 40000 }}}, + 'CIT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 4, 'max': 40000 }}}, + 'CMT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 2500000 }}}, + 'CNET/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 12, 'max': 120000 }}}, + 'CNMC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 4, 'max': 50000 }}}, + 'CTC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 5, 'max': 550000 }}}, + 'CZR/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 12, 'max': 500000 }}}, + 'DCON/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 8, 'max': 300000 }}}, + 'DCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 40000 }}}, + 'DCT/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 2, 'max': 2000 }}}, + 'DOGE/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 3000, 'max': 14000000 }}}, + 'DOGE/CKUSD': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 500, 'max': 2000000 }}}, + 'DRCT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 16, 'max': 190000 }}}, + 'ELA/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.02, 'max': 500 }}}, + 'ELF/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 100000 }}}, + 'ELF/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.01, 'max': 100000 }}}, + 'EOS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.5, 'max': 5000 }}}, + 'EOS/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 2.5, 'max': 30000 }}}, + 'EOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.18, 'max': 1800 }}}, + 'ETC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2500 }}}, + 'ETC/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2500 }}}, + 'ETF/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 7, 'max': 150000 }}}, + 'ETH/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.015, 'max': 100 }}}, + 'ETH/CKUSD': { 'precision': { 'amount': 4, 'price': 4 }, 'limits': { 'amount': { 'min': 0.005, 'max': 100 }}}, + 'ETH/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.005, 'max': 100 }}}, + 'FCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.24, 'max': 1000 }}}, + 'FCT/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.24, 'max': 1000 }}}, + 'GAME/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 1, 'max': 10000 }}}, + 'GOOC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 200, 'max': 2000000 }}}, + 'GP/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 600, 'max': 6000000 }}}, + 'HSC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 1000, 'max': 20000000 }}}, + 'IFISH/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 300, 'max': 8000000 }}}, + 'IIC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 50, 'max': 4000000 }}}, + 'IMOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 300000 }}}, + 'JC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 300, 'max': 3000000 }}}, + 'LBTC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 3000 }}}, + 'LEC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 500, 'max': 5000000 }}}, + 'LKY/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 10, 'max': 70000 }}}, + 'LKY/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}}, + 'LMC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 25, 'max': 250000 }}}, + 'LSK/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.3, 'max': 3000 }}}, + 'LTC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.01, 'max': 500 }}}, + 'LTC/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.01, 'max': 500 }}}, + 'LTC/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.02, 'max': 450 }}}, + 'MC/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 10000, 'max': 100000000 }}}, + 'MCC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 30, 'max': 350000 }}}, + 'MOC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 25, 'max': 600000 }}}, + 'MRYC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 300, 'max': 3000000 }}}, + 'MT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 200, 'max': 6000000 }}}, + 'MXI/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 5000, 'max': 60000000 }}}, + 'NAI/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}}, + 'NAS/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.2, 'max': 15000 }}}, + 'NAS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.5, 'max': 5000 }}}, + 'NEWOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 65, 'max': 700000 }}}, + 'NKN/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 3, 'max': 350000 }}}, + 'NTK/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 30000 }}}, + 'ONT/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2000 }}}, + 'ONT/ETH': { 'precision': { 'amount': 3, 'price': 8 }, 'limits': { 'amount': { 'min': 0.01, 'max': 1000 }}}, + 'PNT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 80, 'max': 800000 }}}, + 'PST/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 5, 'max': 100000 }}}, + 'PTT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 450, 'max': 10000000 }}}, + 'QTUM/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.4, 'max': 2800 }}}, + 'QTUM/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.1, 'max': 1000 }}}, + 'RATING/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 500, 'max': 10000000 }}}, + 'RHC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 1000, 'max': 10000000 }}}, + 'SDA/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 500000 }}}, + 'SDD/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}}, + 'SHC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 250, 'max': 2500000 }}}, + 'SHE/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 5000000 }}}, + 'SMC/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 1000, 'max': 10000000 }}}, + 'SOP/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 50, 'max': 1000000 }}}, + 'TAC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 35, 'max': 800000 }}}, + 'TIP/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 7, 'max': 200000 }}}, + 'TKT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 40, 'max': 400000 }}}, + 'TLC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 500, 'max': 10000000 }}}, + 'TNC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 110000 }}}, + 'TUB/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 200, 'max': 8000000 }}}, + 'UC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 3000000 }}}, + 'UDB/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 2000, 'max': 40000000 }}}, + 'UIC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 150000 }}}, + 'VAAC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 250000 }}}, + 'VPN/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 200, 'max': 2000000 }}}, + 'VSC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 30, 'max': 650000 }}}, + 'WAVES/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.15, 'max': 1500 }}}, + 'WDNA/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 250000 }}}, + 'WIC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 3, 'max': 30000 }}}, + 'XAS/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 2.5, 'max': 25000 }}}, + 'XLM/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 300000 }}}, + 'XLM/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 1, 'max': 300000 }}}, + 'XLM/USDT': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 150000 }}}, + 'XRP/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 24, 'max': 100000 }}}, + 'XRP/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 5, 'max': 50000 }}}, + 'YBCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 200000 }}}, + 'YBCT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 10, 'max': 200000 }}}, + 'YBY/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 25000, 'max': 250000000 }}}, + 'ZEC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.02, 'max': 100 }}}, + 'ZEC/CKUSD': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.02, 'max': 100 }}}, + }, + }, + }); + } + + async fetchTradingLimits (symbols = undefined, params = {}) { + // this method should not be called directly, use loadTradingLimits () instead + // by default it will try load withdrawal fees of all currencies (with separate requests, sequentially) + // however if you define symbols = [ 'ETH/BTC', 'LTC/BTC' ] in args it will only load those + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const result = {}; + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + result[symbol] = await this.fetchTradingLimitsById (this.marketId (symbol), params); + } + return result; + } + + async fetchTradingLimitsById (id, params = {}) { + const request = { + 'symbol': id, + }; + const response = await this.publicPostApiOrderTicker (this.extend (request, params)); + // + // { code: 0, + // msg: "获取牌价信息成功", + // data: { high: 0.03721392, + // low: 0.03335362, + // buy: "0.03525757", + // sell: "0.03531160", + // last: 0.0352634, + // vol: "184742.4176", + // min_trade: "0.01500000", + // max_trade: "100.00000000", + // number_float: "4", + // price_float: "8" } } } + // + return this.parseTradingLimits (this.safeValue (response, 'data', {})); + } + + parseTradingLimits (limits, symbol = undefined, params = {}) { + // + // { high: 0.03721392, + // low: 0.03335362, + // buy: "0.03525757", + // sell: "0.03531160", + // last: 0.0352634, + // vol: "184742.4176", + // min_trade: "0.01500000", + // max_trade: "100.00000000", + // number_float: "4", + // price_float: "8" } + // + return { + 'info': limits, + 'precision': { + 'amount': this.safeInteger (limits, 'number_float'), + 'price': this.safeInteger (limits, 'price_float'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (limits, 'min_trade'), + 'max': this.safeFloat (limits, 'max_trade'), + }, + }, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetApiMarketGetPriceList (params); + const result = []; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const currentMarketId = keys[i]; + const currentMarkets = response[currentMarketId]; + for (let j = 0; j < currentMarkets.length; j++) { + const market = currentMarkets[j]; + const baseId = this.safeString (market, 'coin_from'); + const quoteId = this.safeString (market, 'coin_to'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const id = baseId + '2' + quoteId; + const symbol = base + '/' + quote; + const active = true; + const defaults = this.safeValue (this.options['limits'], symbol, {}); + result.push (this.extend ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + // overrided by defaults from this.options['limits'] + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + // overrided by defaults from this.options['limits'] + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + 'info': market, + }, defaults)); + } + } + return result; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp2 (trade, 'date', 'created'); + const id = this.safeString (trade, 'tid'); + const orderId = this.safeString (trade, 'order_id'); + const amount = this.safeFloat2 (trade, 'number', 'amount'); + const price = this.safeFloat (trade, 'price'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + let side = this.safeString (trade, 'side'); + if (side === 'sale') { + side = 'sell'; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'order': orderId, + 'fee': undefined, + 'takerOrMaker': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const market = this.market (symbol); + const response = await this.publicPostApiOrderMarketOrder (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostApiUserUserBalance (params); + const data = this.safeValue (response, 'data'); + let keys = Object.keys (data); + const result = { }; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const amount = this.safeFloat (data, key); + const parts = key.split ('_'); + const currencyId = parts[0]; + const lockOrOver = parts[1]; + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + result[code] = this.account (); + } + if (lockOrOver === 'lock') { + result[code]['used'] = parseFloat (amount); + } else { + result[code]['free'] = parseFloat (amount); + } + } + keys = Object.keys (result); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const total = this.sum (result[key]['used'], result[key]['free']); + result[key]['total'] = total; + } + result['info'] = data; + return this.parseBalance (result); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.markets[symbol]; + const request = { + 'part': market['quoteId'], + 'coin': market['baseId'], + }; + const response = await this.publicPostApiMarketGetCoinTrade (this.extend (request, params)); + const timestamp = this.milliseconds (); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (response, 'max'), + 'low': this.safeFloat (response, 'min'), + 'bid': this.safeFloat (response, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (response, 'sale'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': this.safeFloat (response, 'price'), + 'last': this.safeFloat (response, 'price'), + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (response, 'change_24h'), + 'average': undefined, + 'baseVolume': this.safeFloat (response, 'volume_24h'), + 'quoteVolume': undefined, + 'info': response, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + const response = await this.publicPostApiOrderDepth (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const timestamp = this.safeTimestamp (data, 'date'); + return this.parseOrderBook (data, timestamp); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privatePostApiOrderOrderList (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', // partially filled + '2': 'closed', + '3': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a `symbol` argument'); + } + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'trust_id': id, + }; + const response = await this.privatePostApiOrderOrderInfo (this.extend (request, params)); + const order = this.safeValue (response, 'data'); + const timestamp = this.safeTimestamp (order, 'created'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let side = this.safeString (order, 'flag'); + if (side === 'sale') { + side = 'sell'; + } + // Can't use parseOrder because the data format is different btw endpoint for fetchOrder and fetchOrders + return { + 'info': order, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': this.safeFloat (order, 'price'), + 'cost': undefined, + 'average': this.safeFloat (order, 'avg_price'), + 'amount': this.safeFloat (order, 'number'), + 'filled': this.safeFloat (order, 'numberdeal'), + 'remaining': this.safeFloat (order, 'numberover'), + 'status': status, + 'fee': undefined, + 'clientOrderId': undefined, + 'trades': undefined, + }; + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const timestamp = this.safeTimestamp (order, 'datetime'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const type = undefined; + let side = this.safeString (order, 'type'); + if (side === 'sale') { + side = 'sell'; + } + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'avg_price'); + const amount = this.safeFloat (order, 'amount'); + const remaining = this.safeFloat (order, 'amount_outstanding'); + const filled = amount - remaining; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const cost = filled * price; + const fee = undefined; + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + return result; + } + + async fetchOrdersByType (type, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'type': type, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privatePostApiOrderTradeList (this.extend (request, params)); + if ('data' in response) { + return this.parseOrders (response['data'], market, since, limit); + } + return []; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByType ('open', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByType ('all', symbol, since, limit, params); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'type': side, + 'price': this.priceToPrecision (symbol, price), + 'number': this.amountToPrecision (symbol, amount), + }; + const response = await this.privatePostApiOrderCoinTrust (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const id = this.safeString (data, 'order_id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument'); + } + await this.loadMarkets (); + const request = {}; + if (symbol !== undefined) { + request['symbol'] = this.marketId (symbol); + } + if (id !== undefined) { + request['order_id'] = id; + } + return await this.privatePostApiOrderCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + let payload = this.urlencode ({ 'api_key': this.apiKey }); + if (Object.keys (query).length) { + payload += '&' + this.urlencode (this.keysort (query)); + } + const auth = payload + '&secret_key=' + this.secret; + const signature = this.hash (this.encode (auth)); + body = payload + '&sign=' + signature; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const errorCode = this.safeValue (response, 'code'); + if (errorCode !== undefined) { + if (errorCode !== 0) { + // + // { code: 1, msg: "该币不存在,非法操作" } - returned when a required symbol parameter is missing in the request (also, maybe on other types of errors as well) + // { code: 1, msg: '公钥不合法' } - wrong public key + // { code: 1, msg: '价格输入有误,请检查你的数值精度' } - 'The price input is incorrect, please check your numerical accuracy' + // { code: 1, msg: '单笔最小交易数量不能小于0.00100000,请您重新挂单'} - + // 'The minimum number of single transactions cannot be less than 0.00100000. Please re-post the order' + // + const message = this.safeString (response, 'msg'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + if (message.indexOf ('请您重新挂单') >= 0) { // minimum limit + throw new InvalidOrder (feedback); + } else { + throw new ExchangeError (feedback); + } + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],20:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const hitbtc = require ('./hitbtc'); +// --------------------------------------------------------------------------- + +module.exports = class bequant extends hitbtc { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bequant', + 'name': 'Bequant', + 'countries': [ 'MT' ], // Malta + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/55248342-a75dfe00-525a-11e9-8aa2-05e9dca943c6.jpg', + 'api': { + 'public': 'https://api.bequant.io', + 'private': 'https://api.bequant.io', + }, + 'www': 'https://bequant.io', + 'doc': [ + 'https://api.bequant.io/', + ], + 'fees': [ + 'https://bequant.io/fees-and-limits', + ], + 'referral': 'https://bequant.io', + }, + }); + } +}; + +},{"./hitbtc":89}],21:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AccountSuspended, ArgumentsRequired, AuthenticationError, DDoSProtection, ExchangeNotAvailable, InvalidOrder, OrderNotFound, PermissionDenied, InsufficientFunds, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bibox extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bibox', + 'name': 'Bibox', + 'countries': [ 'CN', 'US', 'KR' ], + 'version': 'v1', + 'hostname': 'bibox365.com', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, // or they will return https://github.com/ccxt/ccxt/issues/2338 + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDeposits': true, + 'fetchDepositAddress': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'publicAPI': false, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '12h': '12hour', + '1d': 'day', + '1w': 'week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg', + 'api': 'https://api.{hostname}', + 'www': 'https://www.bibox365.com', + 'doc': [ + 'https://biboxcom.github.io/en/', + ], + 'fees': 'https://bibox.zendesk.com/hc/en-us/articles/360002336133', + 'referral': 'https://w2.bibox365.com/login/register?invite_code=05Kj3I', + }, + 'api': { + 'public': { + 'post': [ + // TODO: rework for full endpoint/cmd paths here + 'mdata', + ], + 'get': [ + 'cquery', + 'mdata', + ], + }, + 'private': { + 'post': [ + 'cquery', + 'ctrade', + 'user', + 'orderpending', + 'transfer', + ], + }, + 'v2private': { + 'post': [ + 'assets/transfer/spot', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.0008, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'exceptions': { + '2011': AccountSuspended, // Account is locked + '2015': AuthenticationError, // Google authenticator is wrong + '2021': InsufficientFunds, // Insufficient balance available for withdrawal + '2027': InsufficientFunds, // Insufficient balance available (for trade) + '2033': OrderNotFound, // operation failed! Orders have been completed or revoked + '2067': InvalidOrder, // Does not support market orders + '2068': InvalidOrder, // The number of orders can not be less than + '2085': InvalidOrder, // Order quantity is too small + '3012': AuthenticationError, // invalid apiKey + '3016': BadSymbol, // Trading pair error + '3024': PermissionDenied, // wrong apikey permissions + '3025': AuthenticationError, // signature failed + '4000': ExchangeNotAvailable, // current network is unstable + '4003': DDoSProtection, // server busy please try again later + }, + 'commonCurrencies': { + 'BOX': 'DefiBox', + 'BPT': 'BlockPool Token', + 'KEY': 'Bihu', + 'MTC': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025 + 'PAI': 'PCHAIN', + }, + }); + } + + async fetchMarkets (params = {}) { + const request = { + 'cmd': 'marketAll', + }; + const response = await this.publicGetMdata (this.extend (request, params)); + // + // { + // "result": [ + // { + // "is_hide":0, + // "high_cny":"1.9478", + // "amount":"272.41", + // "coin_symbol":"BIX", + // "last":"0.00002487", + // "currency_symbol":"BTC", + // "change":"+0.00000073", + // "low_cny":"1.7408", + // "base_last_cny":"1.84538041", + // "area_id":7, + // "percent":"+3.02%", + // "last_cny":"1.8454", + // "high":"0.00002625", + // "low":"0.00002346", + // "pair_type":0, + // "last_usd":"0.2686", + // "vol24H":"10940613", + // "id":1, + // "high_usd":"0.2835", + // "low_usd":"0.2534" + // } + // ], + // "cmd":"marketAll", + // "ver":"1.1" + // } + // + const markets = this.safeValue (response, 'result'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const numericId = this.safeInteger (market, 'id'); + const baseId = this.safeString (market, 'coin_symbol'); + const quoteId = this.safeString (market, 'currency_symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = baseId + '_' + quoteId; + const precision = { + 'amount': 4, + 'price': 8, + }; + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // we don't set values that are not defined by the exchange + const timestamp = this.safeInteger (ticker, 'timestamp'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } else { + const baseId = this.safeString (ticker, 'coin_symbol'); + const quoteId = this.safeString (ticker, 'currency_symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const last = this.safeFloat (ticker, 'last'); + const change = this.safeFloat (ticker, 'change'); + const baseVolume = this.safeFloat2 (ticker, 'vol', 'vol24H'); + let open = undefined; + if ((last !== undefined) && (change !== undefined)) { + open = last - change; + } + let percentage = this.safeString (ticker, 'percent'); + if (percentage !== undefined) { + percentage = percentage.replace ('%', ''); + percentage = parseFloat (percentage); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': this.safeFloat (ticker, 'amount'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'ticker', + 'pair': market['id'], + }; + const response = await this.publicGetMdata (this.extend (request, params)); + return this.parseTicker (response['result'], market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + const ticker = this.parseTicker (rawTickers[i]); + if ((symbols === undefined) || (this.inArray (ticker['symbol'], symbols))) { + tickers.push (ticker); + } + } + return tickers; + } + + async fetchTickers (symbols = undefined, params = {}) { + const request = { + 'cmd': 'marketAll', + }; + const response = await this.publicGetMdata (this.extend (request, params)); + const tickers = this.parseTickers (response['result'], symbols); + const result = this.indexBy (tickers, 'symbol'); + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeInteger2 (trade, 'time', 'createdAt'); + let side = this.safeInteger2 (trade, 'side', 'order_side'); + side = (side === 1) ? 'buy' : 'sell'; + let symbol = undefined; + if (market === undefined) { + let marketId = this.safeString (trade, 'pair'); + if (marketId === undefined) { + const baseId = this.safeString (trade, 'coin_symbol'); + const quoteId = this.safeString (trade, 'currency_symbol'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + marketId = baseId + '_' + quoteId; + } + } + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + let feeCurrency = this.safeString (trade, 'fee_symbol'); + if (feeCurrency !== undefined) { + if (feeCurrency in this.currencies_by_id) { + feeCurrency = this.currencies_by_id[feeCurrency]['code']; + } else { + feeCurrency = this.safeCurrencyCode (feeCurrency); + } + } + const feeRate = undefined; // todo: deduce from market if market is defined + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined && amount !== undefined) { + cost = price * amount; + } + if (feeCost !== undefined) { + fee = { + 'cost': -feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }; + } + const id = this.safeString (trade, 'id'); + return { + 'info': trade, + 'id': id, + 'order': undefined, // Bibox does not have it (documented) yet + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': 'limit', + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'deals', + 'pair': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; // default = 200 + } + const response = await this.publicGetMdata (this.extend (request, params)); + return this.parseTrades (response['result'], market, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'depth', + 'pair': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; // default = 200 + } + const response = await this.publicGetMdata (this.extend (request, params)); + return this.parseOrderBook (response['result'], this.safeFloat (response['result'], 'update_time'), 'bids', 'asks', 'price', 'volume'); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":1591448220000, + // "open":"0.02507029", + // "high":"0.02507029", + // "low":"0.02506349", + // "close":"0.02506349", + // "vol":"5.92000000" + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'vol'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'kline', + 'pair': market['id'], + 'period': this.timeframes[timeframe], + 'size': limit, + }; + const response = await this.publicGetMdata (this.extend (request, params)); + // + // { + // "result":[ + // {"time":1591448220000,"open":"0.02507029","high":"0.02507029","low":"0.02506349","close":"0.02506349","vol":"5.92000000"}, + // {"time":1591448280000,"open":"0.02506449","high":"0.02506975","low":"0.02506108","close":"0.02506843","vol":"5.72000000"}, + // {"time":1591448340000,"open":"0.02506698","high":"0.02506698","low":"0.02506452","close":"0.02506519","vol":"4.86000000"}, + // ], + // "cmd":"kline", + // "ver":"1.1" + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + async fetchCurrencies (params = {}) { + if (!this.apiKey || !this.secret) { + throw new AuthenticationError (this.id + " fetchCurrencies is an authenticated endpoint, therefore it requires 'apiKey' and 'secret' credentials. If you don't need currency details, set exchange.has['fetchCurrencies'] = false before calling its methods."); + } + const request = { + 'cmd': 'transfer/coinList', + 'body': {}, + }; + const response = await this.privatePostTransfer (this.extend (request, params)); + // + // { + // "result":[ + // { + // "result":[ + // { + // "totalBalance":"14.57582269", + // "balance":"14.57582269", + // "freeze":"0.00000000", + // "id":60, + // "symbol":"USDT", + // "icon_url":"/appimg/USDT_icon.png", + // "describe_url":"[{\"lang\":\"zh-cn\",\"link\":\"https://bibox.zendesk.com/hc/zh-cn/articles/115004798234\"},{\"lang\":\"en-ww\",\"link\":\"https://bibox.zendesk.com/hc/en-us/articles/115004798234\"}]", + // "name":"USDT", + // "enable_withdraw":1, + // "enable_deposit":1, + // "enable_transfer":1, + // "confirm_count":2, + // "is_erc20":1, + // "forbid_info":null, + // "describe_summary":"[{\"lang\":\"zh-cn\",\"text\":\"USDT 是 Tether 公司推出的基于稳定价值货币美元(USD)的代币 Tether USD(简称USDT),1USDT=1美元,用户可以随时使用 USDT 与 USD 进行1:1的兑换。\"},{\"lang\":\"en-ww\",\"text\":\"USDT is a cryptocurrency asset issued on the Bitcoin blockchain via the Omni Layer Protocol. Each USDT unit is backed by a U.S Dollar held in the reserves of the Tether Limited and can be redeemed through the Tether Platform.\"}]", + // "total_amount":4776930644, + // "supply_amount":4642367414, + // "price":"--", + // "contract_father":"OMNI", + // "supply_time":"--", + // "comment":null, + // "contract":"31", + // "original_decimals":8, + // "deposit_type":0, + // "hasCobo":0, + // "BTCValue":"0.00126358", + // "CNYValue":"100.93381445", + // "USDValue":"14.57524654", + // "children":[ + // {"type":"OMNI","symbol":"USDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":2}, + // {"type":"TRC20","symbol":"tUSDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":20}, + // {"type":"ERC20","symbol":"eUSDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":25} + // ] + // }, + // ], + // "cmd":"transfer/coinList" + // } + // ] + // } + // + const currencies = this.safeValue (response, 'result'); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'symbol'); + const name = currency['name']; // contains hieroglyphs causing python ASCII bug + const code = this.safeCurrencyCode (id); + const precision = 8; + const deposit = this.safeValue (currency, 'enable_deposit'); + const withdraw = this.safeValue (currency, 'enable_withdraw'); + const active = (deposit && withdraw); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const type = this.safeString (params, 'type', 'assets'); + params = this.omit (params, 'type'); + const request = { + 'cmd': 'transfer/' + type, // assets, mainAssets + 'body': this.extend ({ + 'select': 1, // return full info + }, params), + }; + const response = await this.privatePostTransfer (request); + const balances = this.safeValue (response, 'result'); + const result = { 'info': balances }; + let indexed = undefined; + if ('assets_list' in balances) { + indexed = this.indexBy (balances['assets_list'], 'coin_symbol'); + } else { + indexed = balances; + } + const keys = Object.keys (indexed); + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + let code = id.toUpperCase (); + if (code.indexOf ('TOTAL_') >= 0) { + code = code.slice (6); + } + if (code in this.currencies_by_id) { + code = this.currencies_by_id[code]['code']; + } + const account = this.account (); + let balance = indexed[id]; + if (typeof balance === 'string') { + balance = parseFloat (balance); + account['free'] = balance; + account['used'] = 0.0; + account['total'] = balance; + } else { + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'freeze'); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + 'page': 1, + }; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } else { + request['size'] = 100; + } + const response = await this.privatePostTransfer ({ + 'cmd': 'transfer/transferInList', + 'body': this.extend (request, params), + }); + const deposits = this.safeValue (response['result'], 'items', []); + for (let i = 0; i < deposits.length; i++) { + deposits[i]['type'] = 'deposit'; + } + return this.parseTransactions (deposits, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + 'page': 1, + }; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } else { + request['size'] = 100; + } + const response = await this.privatePostTransfer ({ + 'cmd': 'transfer/transferOutList', + 'body': this.extend (request, params), + }); + const withdrawals = this.safeValue (response['result'], 'items', []); + for (let i = 0; i < withdrawals.length; i++) { + withdrawals[i]['type'] = 'withdrawal'; + } + return this.parseTransactions (withdrawals, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // 'id': 1023291, + // 'coin_symbol': 'ETH', + // 'to_address': '0x7263....', + // 'amount': '0.49170000', + // 'confirmCount': '16', + // 'createdAt': 1553123867000, + // 'status': 2 + // } + // + // fetchWithdrawals + // + // { + // 'id': 521844, + // 'coin_symbol': 'ETH', + // 'to_address': '0xfd4e....', + // 'addr_remark': '', + // 'amount': '0.39452750', + // 'fee': '0.00600000', + // 'createdAt': 1553226906000, + // 'memo': '', + // 'status': 3 + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'to_address'); + const currencyId = this.safeString (transaction, 'coin_symbol'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeString (transaction, 'createdAt'); + let tag = this.safeString (transaction, 'addr_remark'); + const type = this.safeString (transaction, 'type'); + const status = this.parseTransactionStatusByType (this.safeString (transaction, 'status'), type); + const amount = this.safeFloat (transaction, 'amount'); + let feeCost = this.safeFloat (transaction, 'fee'); + if (type === 'deposit') { + feeCost = 0; + tag = undefined; + } + const fee = { + 'cost': feeCost, + 'currency': code, + }; + return { + 'info': transaction, + 'id': id, + 'txid': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTransactionStatusByType (status, type = undefined) { + const statuses = { + 'deposit': { + '1': 'pending', + '2': 'ok', + }, + 'withdrawal': { + '0': 'pending', + '3': 'ok', + }, + }; + return this.safeString (this.safeValue (statuses, type, {}), status, status); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderType = (type === 'limit') ? 2 : 1; + const orderSide = (side === 'buy') ? 1 : 2; + const request = { + 'cmd': 'orderpending/trade', + 'body': this.extend ({ + 'pair': market['id'], + 'account_type': 0, + 'order_type': orderType, + 'order_side': orderSide, + 'pay_bix': 0, + 'amount': amount, + 'price': price, + }, params), + }; + const response = await this.privatePostOrderpending (request); + return { + 'info': response, + 'id': this.safeString (response, 'result'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'cmd': 'orderpending/cancelTrade', + 'body': this.extend ({ + 'orders_id': id, + }, params), + }; + const response = await this.privatePostOrderpending (request); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'cmd': 'orderpending/order', + 'body': this.extend ({ + 'id': id.toString (), + 'account_type': 0, // 0 = spot account + }, params), + }; + const response = await this.privatePostOrderpending (request); + const order = this.safeValue (response, 'result'); + if (this.isEmpty (order)) { + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + return this.parseOrder (order); + } + + parseOrder (order, market = undefined) { + let symbol = undefined; + if (market === undefined) { + let marketId = undefined; + const baseId = this.safeString (order, 'coin_symbol'); + const quoteId = this.safeString (order, 'currency_symbol'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + marketId = baseId + '_' + quoteId; + } + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + const type = (order['order_type'] === 1) ? 'market' : 'limit'; + const timestamp = order['createdAt']; + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'deal_price'); + const filled = this.safeFloat (order, 'deal_amount'); + const amount = this.safeFloat (order, 'amount'); + let cost = this.safeFloat2 (order, 'deal_money', 'money'); + let remaining = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (cost === undefined) { + cost = price * filled; + } + } + const side = (order['order_side'] === 1) ? 'buy' : 'sell'; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString (order, 'id'); + const feeCost = this.safeFloat (order, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + cost = cost ? cost : (parseFloat (price) * filled); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + // original comments from bibox: + '1': 'open', // pending + '2': 'open', // part completed + '3': 'closed', // completed + '4': 'canceled', // part canceled + '5': 'canceled', // canceled + '6': 'canceled', // canceling + }; + return this.safeString (statuses, status, status); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let pair = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + pair = market['id']; + } + const size = limit ? limit : 200; + const request = { + 'cmd': 'orderpending/orderPendingList', + 'body': this.extend ({ + 'pair': pair, + 'account_type': 0, // 0 - regular, 1 - margin + 'page': 1, + 'size': size, + }, params), + }; + const response = await this.privatePostOrderpending (request); + const orders = this.safeValue (response['result'], 'items', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = 200, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'orderpending/pendingHistoryList', + 'body': this.extend ({ + 'pair': market['id'], + 'account_type': 0, // 0 - regular, 1 - margin + 'page': 1, + 'size': limit, + }, params), + }; + const response = await this.privatePostOrderpending (request); + const orders = this.safeValue (response['result'], 'items', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const size = limit ? limit : 200; + const request = { + 'cmd': 'orderpending/orderHistoryList', + 'body': this.extend ({ + 'pair': market['id'], + 'account_type': 0, // 0 - regular, 1 - margin + 'page': 1, + 'size': size, + 'coin_symbol': market['baseId'], + 'currency_symbol': market['quoteId'], + }, params), + }; + const response = await this.privatePostOrderpending (request); + const trades = this.safeValue (response['result'], 'items', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'cmd': 'transfer/transferIn', + 'body': this.extend ({ + 'coin_symbol': currency['id'], + }, params), + }; + const response = await this.privatePostTransfer (request); + // + // { + // "result":"3Jx6RZ9TNMsAoy9NUzBwZf68QBppDruSKW","cmd":"transfer/transferIn" + // } + // + // { + // "result":"{\"account\":\"PERSONALLY OMITTED\",\"memo\":\"PERSONALLY OMITTED\"}","cmd":"transfer/transferIn" + // } + // + const result = this.safeString (response, 'result'); + let address = result; + let tag = undefined; + if (this.isJsonEncodedObject (result)) { + const parsed = JSON.parse (result); + address = this.safeString (parsed, 'account'); + tag = this.safeString (parsed, 'memo'); + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (this.password === undefined) { + if (!('trade_pwd' in params)) { + throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a trade_pwd parameter'); + } + } + if (!('totp_code' in params)) { + throw new ExchangeError (this.id + ' withdraw() requires a totp_code parameter for 2FA authentication'); + } + const request = { + 'trade_pwd': this.password, + 'coin_symbol': currency['id'], + 'amount': amount, + 'addr': address, + }; + if (tag !== undefined) { + request['address_remark'] = tag; + } + const response = await this.privatePostTransfer ({ + 'cmd': 'transfer/transferOut', + 'body': this.extend (request, params), + }); + return { + 'info': response, + 'id': undefined, + }; + } + + async fetchFundingFees (codes = undefined, params = {}) { + // by default it will try load withdrawal fees of all currencies (with separate requests) + // however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those + await this.loadMarkets (); + const withdrawFees = {}; + const info = {}; + if (codes === undefined) { + codes = Object.keys (this.currencies); + } + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const request = { + 'cmd': 'transfer/coinConfig', + 'body': this.extend ({ + 'coin_symbol': currency['id'], + }, params), + }; + const response = await this.privatePostTransfer (request); + info[code] = response; + withdrawFees[code] = this.safeFloat (response['result'], 'withdraw_fee'); + } + return { + 'info': info, + 'withdraw': withdrawFees, + 'deposit': {}, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }) + '/' + this.version + '/' + path; + const cmds = this.json ([ params ]); + if (api === 'public') { + if (method !== 'GET') { + body = { 'cmds': cmds }; + } else if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else if (api === 'v2private') { + this.checkRequiredCredentials (); + url = this.urls['api'] + '/v2/' + path; + const json_params = this.json (params); + body = { + 'body': json_params, + 'apikey': this.apiKey, + 'sign': this.hmac (this.encode (json_params), this.encode (this.secret), 'md5'), + }; + } else { + this.checkRequiredCredentials (); + body = { + 'cmds': cmds, + 'apikey': this.apiKey, + 'sign': this.hmac (this.encode (cmds), this.encode (this.secret), 'md5'), + }; + } + if (body !== undefined) { + body = this.json (body, { 'convertArraysToObjects': true }); + } + headers = { 'Content-Type': 'application/json' }; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if ('error' in response) { + if ('code' in response['error']) { + const code = this.safeString (response['error'], 'code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } + throw new ExchangeError (this.id + ' ' + body); + } + if (!('result' in response)) { + throw new ExchangeError (this.id + ' ' + body); + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (method === 'GET') { + return response; + } else { + return response['result'][0]; + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],22:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, OrderNotFound, InsufficientFunds, PermissionDenied, BadRequest, RateLimitExceeded, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bigone extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bigone', + 'name': 'BigONE', + 'countries': [ 'CN' ], + 'version': 'v3', + 'rateLimit': 1200, // 500 request per 10 minutes + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'min1', + '5m': 'min5', + '15m': 'min15', + '30m': 'min30', + '1h': 'hour1', + '3h': 'hour3', + '4h': 'hour4', + '6h': 'hour6', + '12h': 'hour12', + '1d': 'day1', + '1w': 'week1', + '1M': 'month1', + }, + 'hostname': 'big.one', // or 'bigone.com' + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/69354403-1d532180-0c91-11ea-88ed-44c06cefdf87.jpg', + 'api': { + 'public': 'https://{hostname}/api/v3', + 'private': 'https://{hostname}/api/v3/viewer', + }, + 'www': 'https://big.one', + 'doc': 'https://open.big.one/docs/api.html', + 'fees': 'https://bigone.zendesk.com/hc/en-us/articles/115001933374-BigONE-Fee-Policy', + 'referral': 'https://b1.run/users/new?code=D3LLBVFT', + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'asset_pairs', + 'asset_pairs/{asset_pair_name}/depth', + 'asset_pairs/{asset_pair_name}/trades', + 'asset_pairs/{asset_pair_name}/ticker', + 'asset_pairs/{asset_pair_name}/candles', + 'asset_pairs/tickers', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'assets/{asset_symbol}/address', + 'orders', + 'orders/{id}', + 'orders/multi', + 'trades', + 'withdrawals', + 'deposits', + ], + 'post': [ + 'orders', + 'orders/{id}/cancel', + 'orders/cancel', + 'withdrawals', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + // HARDCODING IS DEPRECATED THE FEES BELOW ARE TO BE REMOVED SOON + 'withdraw': { + 'BTC': 0.001, + 'ETH': 0.005, + 'EOS': 0.01, + 'ZEC': 0.003, + 'LTC': 0.01, + 'QTUM': 0.01, + // 'INK': 0.01 QTUM, + // 'BOT': 0.01 QTUM, + 'ETC': 0.01, + 'GAS': 0.0, + 'BTS': 1.0, + 'GXS': 0.1, + 'BITCNY': 19.0, + }, + }, + }, + 'exceptions': { + 'exact': { + '10001': BadRequest, // syntax error + '10005': ExchangeError, // internal error + "Amount's scale must greater than AssetPair's base scale": InvalidOrder, + '10007': BadRequest, // parameter error, {"code":10007,"message":"Amount's scale must greater than AssetPair's base scale"} + '10011': ExchangeError, // system error + '10013': OrderNotFound, // {"code":10013,"message":"Resource not found"} + '10014': InsufficientFunds, // {"code":10014,"message":"Insufficient funds"} + '10403': PermissionDenied, // permission denied + '10429': RateLimitExceeded, // too many requests + '40004': AuthenticationError, // {"code":40004,"message":"invalid jwt"} + '40103': AuthenticationError, // invalid otp code + '40104': AuthenticationError, // invalid asset pin code + '40301': PermissionDenied, // {"code":40301,"message":"Permission denied withdrawal create"} + '40302': ExchangeError, // already requested + '40601': ExchangeError, // resource is locked + '40602': ExchangeError, // resource is depleted + '40603': InsufficientFunds, // insufficient resource + '40120': InvalidOrder, // Order is in trading + '40121': InvalidOrder, // Order is already cancelled or filled + }, + 'broad': { + }, + }, + 'commonCurrencies': { + 'MBN': 'Mobilian Coin', + 'ONE': 'BigONE Token', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAssetPairs (params); + // + // { + // "code":0, + // "data":[ + // { + // "id":"01e48809-b42f-4a38-96b1-c4c547365db1", + // "name":"PCX-BTC", + // "quote_scale":7, + // "quote_asset":{ + // "id":"0df9c3c3-255a-46d7-ab82-dedae169fba9", + // "symbol":"BTC", + // "name":"Bitcoin", + // }, + // "base_asset":{ + // "id":"405484f7-4b03-4378-a9c1-2bd718ecab51", + // "symbol":"PCX", + // "name":"ChainX", + // }, + // "base_scale":3, + // "min_quote_value":"0.0001", + // }, + // ] + // } + // + const markets = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const uuid = this.safeString (market, 'id'); + const baseAsset = this.safeValue (market, 'base_asset', {}); + const quoteAsset = this.safeValue (market, 'quote_asset', {}); + const baseId = this.safeString (baseAsset, 'symbol'); + const quoteId = this.safeString (quoteAsset, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'base_scale'), + 'price': this.safeInteger (market, 'quote_scale'), + }; + const minCost = this.safeInteger (market, 'min_quote_value'); + const entry = { + 'id': id, + 'uuid': uuid, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }, + 'info': market, + }; + result.push (entry); + } + return result; + } + + async loadMarkets (reload = false, params = {}) { + const markets = await super.loadMarkets (reload, params); + let marketsByUuid = this.safeValue (this.options, 'marketsByUuid'); + if ((marketsByUuid === undefined) || reload) { + marketsByUuid = {}; + for (let i = 0; i < this.symbols.length; i++) { + const symbol = this.symbols[i]; + const market = this.markets[symbol]; + const uuid = this.safeString (market, 'uuid'); + marketsByUuid[uuid] = market; + } + this.options['marketsByUuid'] = marketsByUuid; + } + return markets; + } + + parseTicker (ticker, market = undefined) { + // + // { + // "asset_pair_name":"ETH-BTC", + // "bid":{"price":"0.021593","order_count":1,"quantity":"0.20936"}, + // "ask":{"price":"0.021613","order_count":1,"quantity":"2.87064"}, + // "open":"0.021795", + // "high":"0.021795", + // "low":"0.021471", + // "close":"0.021613", + // "volume":"117078.90431", + // "daily_change":"-0.000182" + // } + // + const marketId = this.safeString (ticker, 'asset_pair_name'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = undefined; + const close = this.safeFloat (ticker, 'close'); + const bid = this.safeValue (ticker, 'bid', {}); + const ask = this.safeValue (ticker, 'ask', {}); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (bid, 'price'), + 'bidVolume': this.safeFloat (bid, 'quantity'), + 'ask': this.safeFloat (ask, 'price'), + 'askVolume': this.safeFloat (ask, 'quantity'), + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'daily_change'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + const response = await this.publicGetAssetPairsAssetPairNameTicker (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "asset_pair_name":"ETH-BTC", + // "bid":{"price":"0.021593","order_count":1,"quantity":"0.20936"}, + // "ask":{"price":"0.021613","order_count":1,"quantity":"2.87064"}, + // "open":"0.021795", + // "high":"0.021795", + // "low":"0.021471", + // "close":"0.021613", + // "volume":"117078.90431", + // "daily_change":"-0.000182" + // } + // } + // + const ticker = this.safeValue (response, 'data', {}); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['pair_names'] = ids.join (','); + } + const response = await this.publicGetAssetPairsTickers (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // { + // "asset_pair_name":"PCX-BTC", + // "bid":{"price":"0.000234","order_count":1,"quantity":"0.518"}, + // "ask":{"price":"0.0002348","order_count":1,"quantity":"2.348"}, + // "open":"0.0002343", + // "high":"0.0002348", + // "low":"0.0002162", + // "close":"0.0002348", + // "volume":"12887.016", + // "daily_change":"0.0000005" + // }, + // { + // "asset_pair_name":"GXC-USDT", + // "bid":{"price":"0.5054","order_count":1,"quantity":"40.53"}, + // "ask":{"price":"0.5055","order_count":1,"quantity":"38.53"}, + // "open":"0.5262", + // "high":"0.5323", + // "low":"0.5055", + // "close":"0.5055", + // "volume":"603963.05", + // "daily_change":"-0.0207" + // } + // ] + // } + // + const tickers = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { + // "data": { + // "timestamp": 1527665262168391000 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'timestamp'); + return parseInt (timestamp / 1000000); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 200 + } + const response = await this.publicGetAssetPairsAssetPairNameDepth (this.extend (request, params)); + // + // { + // "code":0, + // "data": { + // "asset_pair_name": "EOS-BTC", + // "bids": [ + // { "price": "42", "order_count": 4, "quantity": "23.33363711" } + // ], + // "asks": [ + // { "price": "45", "order_count": 2, "quantity": "4193.3283464" } + // ] + // } + // } + // + const orderbook = this.safeValue (response, 'data', {}); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'quantity'); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id": 38199941, + // "price": "3378.67", + // "amount": "0.019812", + // "taker_side": "ASK", + // "created_at": "2019-01-29T06:05:56Z" + // } + // + // fetchMyTrades (private) + // + // { + // "id": 10854280, + // "asset_pair_name": "XIN-USDT", + // "price": "70", + // "amount": "1", + // "taker_side": "ASK", + // "maker_order_id": 58284908, + // "taker_order_id": 58284909, + // "maker_fee": "0.0008", + // "taker_fee": "0.07", + // "side": "SELF_TRADING", + // "inserted_at": "2019-04-16T12:00:01Z" + // }, + // + // { + // "id": 10854263, + // "asset_pair_name": "XIN-USDT", + // "price": "75.7", + // "amount": "12.743149", + // "taker_side": "BID", + // "maker_order_id": null, + // "taker_order_id": 58284888, + // "maker_fee": null, + // "taker_fee": "0.0025486298", + // "side": "BID", + // "inserted_at": "2019-04-15T06:20:57Z" + // } + // + const timestamp = this.parse8601 (this.safeString2 (trade, 'created_at', 'inserted_at')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const marketId = this.safeString (trade, 'asset_pair_name'); + const symbol = this.safeSymbol (marketId, market, '-'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = this.costToPrecision (symbol, price * amount); + } + } + let side = this.safeString (trade, 'side'); + const takerSide = this.safeString (trade, 'taker_side'); + let takerOrMaker = undefined; + if ((takerSide !== undefined) && (side !== undefined) && (side !== 'SELF_TRADING')) { + takerOrMaker = (takerSide === side) ? 'taker' : 'maker'; + } + if (side === undefined) { + // taker side is not related to buy/sell side + // the following code is probably a mistake + side = (takerSide === 'ASK') ? 'sell' : 'buy'; + } else { + if (side === 'BID') { + side = 'buy'; + } else if (side === 'ASK') { + side = 'sell'; + } + } + const makerOrderId = this.safeString (trade, 'maker_order_id'); + const takerOrderId = this.safeString (trade, 'taker_order_id'); + let orderId = undefined; + if (makerOrderId !== undefined) { + if (takerOrderId !== undefined) { + orderId = [ makerOrderId, takerOrderId ]; + } else { + orderId = makerOrderId; + } + } else if (takerOrderId !== undefined) { + orderId = takerOrderId; + } + const id = this.safeString (trade, 'id'); + const result = { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': parseFloat (cost), + 'info': trade, + }; + let makerCurrencyCode = undefined; + let takerCurrencyCode = undefined; + if ((market !== undefined) && (takerOrMaker !== undefined)) { + if (side === 'buy') { + if (takerOrMaker === 'maker') { + makerCurrencyCode = market['base']; + takerCurrencyCode = market['quote']; + } else { + makerCurrencyCode = market['quote']; + takerCurrencyCode = market['base']; + } + } else { + if (takerOrMaker === 'maker') { + makerCurrencyCode = market['quote']; + takerCurrencyCode = market['base']; + } else { + makerCurrencyCode = market['base']; + takerCurrencyCode = market['quote']; + } + } + } else if (side === 'SELF_TRADING') { + if (takerSide === 'BID') { + makerCurrencyCode = market['quote']; + takerCurrencyCode = market['base']; + } else if (takerSide === 'ASK') { + makerCurrencyCode = market['base']; + takerCurrencyCode = market['quote']; + } + } + const makerFeeCost = this.safeFloat (trade, 'maker_fee'); + const takerFeeCost = this.safeFloat (trade, 'taker_fee'); + if (makerFeeCost !== undefined) { + if (takerFeeCost !== undefined) { + result['fees'] = [ + { 'cost': makerFeeCost, 'currency': makerCurrencyCode }, + { 'cost': takerFeeCost, 'currency': takerCurrencyCode }, + ]; + } else { + result['fee'] = { 'cost': makerFeeCost, 'currency': makerCurrencyCode }; + } + } else if (takerFeeCost !== undefined) { + result['fee'] = { 'cost': takerFeeCost, 'currency': takerCurrencyCode }; + } else { + result['fee'] = undefined; + } + return result; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + const response = await this.publicGetAssetPairsAssetPairNameTrades (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "id": 38199941, + // "price": "3378.67", + // "amount": "0.019812", + // "taker_side": "ASK", + // "created_at": "2019-01-29T06:05:56Z" + // }, + // { + // "id": 38199934, + // "price": "3376.14", + // "amount": "0.019384", + // "taker_side": "ASK", + // "created_at": "2019-01-29T06:05:40Z" + // } + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // close: '0.021562', + // high: '0.021563', + // low: '0.02156', + // open: '0.021563', + // time: '2019-11-21T07:54:00Z', + // volume: '59.84376' + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'time')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; // default 100, max 500 + } + const request = { + 'asset_pair_name': market['id'], + 'period': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + // const start = parseInt (since / 1000); + const end = this.sum (since, limit * this.parseTimeframe (timeframe) * 1000); + request['time'] = this.iso8601 (end); + } + const response = await this.publicGetAssetPairsAssetPairNameCandles (this.extend (request, params)); + // + // { + // code: 0, + // data: [ + // { + // close: '0.021656', + // high: '0.021658', + // low: '0.021652', + // open: '0.021652', + // time: '2019-11-21T09:30:00Z', + // volume: '53.08664' + // }, + // { + // close: '0.021652', + // high: '0.021656', + // low: '0.021652', + // open: '0.021656', + // time: '2019-11-21T09:29:00Z', + // volume: '88.39861' + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + // + // { + // "code":0, + // "data":[ + // {"asset_symbol":"NKC","balance":"0","locked_balance":"0"}, + // {"asset_symbol":"UBTC","balance":"0","locked_balance":"0"}, + // {"asset_symbol":"READ","balance":"0","locked_balance":"0"}, + // ], + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const symbol = this.safeString (balance, 'asset_symbol'); + const code = this.safeCurrencyCode (symbol); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": 10, + // "asset_pair_name": "EOS-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "FILLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z", + // } + // + const id = this.safeString (order, 'id'); + const marketId = this.safeString (order, 'asset_pair_name'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled_amount'); + let remaining = undefined; + if (amount !== undefined && filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + const status = this.parseOrderStatus (this.safeString (order, 'state')); + let side = this.safeString (order, 'side'); + if (side === 'BID') { + side = 'buy'; + } else { + side = 'sell'; + } + let cost = undefined; + if (filled !== undefined) { + if (price !== undefined) { + cost = filled * price; + } + } + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'updated_at')); + const average = this.safeFloat (order, 'avg_deal_price'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + side = (side === 'buy') ? 'BID' : 'ASK'; + const uppercaseType = type.toUpperCase (); + const request = { + 'asset_pair_name': market['id'], // asset pair name BTC-USDT, required + 'side': side, // order side one of "ASK"/"BID", required + 'amount': this.amountToPrecision (symbol, amount), // order amount, string, required + // 'price': this.priceToPrecision (symbol, price), // order price, string, required + 'type': uppercaseType, + // 'operator': 'GTE', // stop orders only, GTE greater than and equal, LTE less than and equal + // 'immediate_or_cancel': false, // limit orders only, must be false when post_only is true + // 'post_only': false, // limit orders only, must be false when immediate_or_cancel is true + }; + if (uppercaseType === 'LIMIT') { + request['price'] = this.priceToPrecision (symbol, price); + } else { + const isStopLimit = (uppercaseType === 'STOP_LIMIT'); + const isStopMarket = (uppercaseType === 'STOP_MARKET'); + if (isStopLimit || isStopMarket) { + const stopPrice = this.safeFloat2 (params, 'stop_price', 'stopPrice'); + if (stopPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a stop_price parameter'); + } + request['stop_price'] = this.priceToPrecision (symbol, stopPrice); + params = this.omit (params, [ 'stop_price', 'stopPrice' ]); + } + if (isStopLimit) { + request['price'] = this.priceToPrecision (symbol, price); + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "id": 10, + // "asset_pair_name": "EOS-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "FILLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z" + // } + // + const order = this.safeValue (response, 'data'); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'id': id }; + const response = await this.privatePostOrdersIdCancel (this.extend (request, params)); + // { + // "id": 10, + // "asset_pair_name": "EOS-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "CANCELLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z" + // } + const order = this.safeValue (response, 'data'); + return this.parseOrder (order); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + const response = await this.privatePostOrdersCancel (this.extend (request, params)); + // + // { + // "code":0, + // "data": { + // "cancelled":[ + // 58272370, + // 58272377 + // ], + // "failed": [] + // } + // } + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'id': id }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + const order = this.safeValue (response, 'data', {}); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + // 'page_token': 'dxzef', // request page after this page token + // 'side': 'ASK', // 'ASK' or 'BID', optional + // 'state': 'FILLED', // 'CANCELLED', 'FILLED', 'PENDING' + // 'limit' 20, // default 20, max 200 + }; + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 200 + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // "code":0, + // "data": [ + // { + // "id": 10, + // "asset_pair_name": "ETH-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "FILLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z", + // }, + // ], + // "page_token":"dxzef", + // } + // + const orders = this.safeValue (response, 'data', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument'); + } + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + // 'page_token': 'dxzef', // request page after this page token + }; + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 200 + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "id": 10854280, + // "asset_pair_name": "XIN-USDT", + // "price": "70", + // "amount": "1", + // "taker_side": "ASK", + // "maker_order_id": 58284908, + // "taker_order_id": 58284909, + // "maker_fee": "0.0008", + // "taker_fee": "0.07", + // "side": "SELF_TRADING", + // "inserted_at": "2019-04-16T12:00:01Z" + // }, + // { + // "id": 10854263, + // "asset_pair_name": "XIN-USDT", + // "price": "75.7", + // "amount": "12.743149", + // "taker_side": "BID", + // "maker_order_id": null, + // "taker_order_id": 58284888, + // "maker_fee": null, + // "taker_fee": "0.0025486298", + // "side": "BID", + // "inserted_at": "2019-04-15T06:20:57Z" + // } + // ], + // "page_token":"dxfv" + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'PENDING': 'open', + 'FILLED': 'closed', + 'CANCELLED': 'canceled', + }; + return this.safeString (statuses, status); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'PENDING', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'FILLED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + nonce () { + return this.microseconds () * 1000; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + const baseUrl = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }); + let url = baseUrl + '/' + this.implodeParams (path, params); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const request = { + 'type': 'OpenAPIV2', + 'sub': this.apiKey, + 'nonce': nonce, + // 'recv_window': '30', // default 30 + }; + const jwt = this.jwt (request, this.encode (this.secret)); + headers = { + 'Authorization': 'Bearer ' + jwt, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset_symbol': currency['id'], + }; + const response = await this.privateGetAssetsAssetSymbolAddress (this.extend (request, params)); + // + // the actual response format is not the same as the documented one + // the data key contains an array in the actual response + // + // { + // "code":0, + // "message":"", + // "data":[ + // { + // "id":5521878, + // "chain":"Bitcoin", + // "value":"1GbmyKoikhpiQVZ1C9sbF17mTyvBjeobVe", + // "memo":"" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const dataLength = data.length; + if (dataLength < 1) { + throw new ExchangeError (this.id + 'fetchDepositAddress() returned empty address response'); + } + const firstElement = data[0]; + const address = this.safeString (firstElement, 'value'); + const tag = this.safeString (firstElement, 'memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + // what are other statuses here? + 'WITHHOLD': 'ok', // deposits + 'UNCONFIRMED': 'pending', + 'CONFIRMED': 'ok', // withdrawals + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "amount": "25.0", + // "asset_symbol": "BTS" + // "confirms": 100, + // "id": 5, + // "inserted_at": "2018-02-16T11:39:58.000Z", + // "is_internal": false, + // "kind": "default", + // "memo": "", + // "state": "WITHHOLD", + // "txid": "72e03037d144dae3d32b68b5045462b1049a0755", + // "updated_at": "2018-11-09T10:20:09.000Z", + // } + // + // fetchWithdrawals + // + // { + // "amount": "5", + // "asset_symbol": "ETH", + // "completed_at": "2018-03-15T16:13:45.610463Z", + // "customer_id": "10", + // "id": 10, + // "inserted_at": "2018-03-15T16:13:45.610463Z", + // "is_internal": true, + // "note": "2018-03-15T16:13:45.610463Z", + // "state": "CONFIRMED", + // "target_address": "0x4643bb6b393ac20a6175c713175734a72517c63d6f7" + // "txid": "0x4643bb6b393ac20a6175c713175734a72517c63d6f73a3ca90a15356f2e967da0", + // } + // + // withdraw + // + // { + // "id":1077391, + // "customer_id":1082679, + // "amount":"21.9000000000000000", + // "txid":"", + // "is_internal":false, + // "kind":"on_chain", + // "state":"PENDING", + // "inserted_at":"2020-06-03T00:50:57+00:00", + // "updated_at":"2020-06-03T00:50:57+00:00", + // "memo":"", + // "target_address":"rDYtYT3dBeuw376rvHqoZBKW3UmvguoBAf", + // "fee":"0.1000000000000000", + // "asset_symbol":"XRP" + // } + // + const currencyId = this.safeString (transaction, 'asset_symbol'); + const code = this.safeCurrencyCode (currencyId); + const id = this.safeInteger (transaction, 'id'); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const timestamp = this.parse8601 (this.safeString (transaction, 'inserted_at')); + const updated = this.parse8601 (this.safeString2 (transaction, 'updated_at', 'completed_at')); + const txid = this.safeString (transaction, 'txid'); + const address = this.safeString (transaction, 'target_address'); + const tag = this.safeString (transaction, 'memo'); + const type = ('customer_id' in transaction) ? 'deposit' : 'withdrawal'; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': undefined, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': undefined, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page_token': 'dxzef', // request page after this page token + // 'limit': 50, // optional, default 50 + // 'kind': 'string', // optional - air_drop, big_holder_dividend, default, eosc_to_eos, internal, equally_airdrop, referral_mining, one_holder_dividend, single_customer, snapshotted_airdrop, trade_mining + // 'asset_symbol': 'BTC', // optional + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset_symbol'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50 + } + const response = await this.privateGetDeposits (this.extend (request, params)); + // + // { + // "code": 0, + // "page_token": "NQ==", + // "data": [ + // { + // "id": 5, + // "amount": "25.0", + // "confirms": 100, + // "txid": "72e03037d144dae3d32b68b5045462b1049a0755", + // "is_internal": false, + // "inserted_at": "2018-02-16T11:39:58.000Z", + // "updated_at": "2018-11-09T10:20:09.000Z", + // "kind": "default", + // "memo": "", + // "state": "WITHHOLD", + // "asset_symbol": "BTS" + // } + // ] + // } + // + const deposits = this.safeValue (response, 'data', []); + return this.parseTransactions (deposits, code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page_token': 'dxzef', // request page after this page token + // 'limit': 50, // optional, default 50 + // 'kind': 'string', // optional - air_drop, big_holder_dividend, default, eosc_to_eos, internal, equally_airdrop, referral_mining, one_holder_dividend, single_customer, snapshotted_airdrop, trade_mining + // 'asset_symbol': 'BTC', // optional + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset_symbol'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50 + } + const response = await this.privateGetWithdrawals (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "id": 10, + // "customer_id": "10", + // "asset_symbol": "ETH", + // "amount": "5", + // "state": "CONFIRMED", + // "note": "2018-03-15T16:13:45.610463Z", + // "txid": "0x4643bb6b393ac20a6175c713175734a72517c63d6f73a3ca90a15356f2e967da0", + // "completed_at": "2018-03-15T16:13:45.610463Z", + // "inserted_at": "2018-03-15T16:13:45.610463Z", + // "is_internal": true, + // "target_address": "0x4643bb6b393ac20a6175c713175734a72517c63d6f7" + // } + // ], + // "page_token":"dxvf" + // } + // + const withdrawals = this.safeValue (response, 'data', []); + return this.parseTransactions (withdrawals, code, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'symbol': currency['id'], + 'target_address': address, + 'amount': this.currencyToPrecision (code, amount), + }; + if (tag !== undefined) { + request['memo'] = tag; + } + // requires write permission on the wallet + const response = await this.privatePostWithdrawals (this.extend (request, params)); + // + // { + // "code":0, + // "message":"", + // "data":{ + // "id":1077391, + // "customer_id":1082679, + // "amount":"21.9000000000000000", + // "txid":"", + // "is_internal":false, + // "kind":"on_chain", + // "state":"PENDING", + // "inserted_at":"2020-06-03T00:50:57+00:00", + // "updated_at":"2020-06-03T00:50:57+00:00", + // "memo":"", + // "target_address":"rDYtYT3dBeuw376rvHqoZBKW3UmvguoBAf", + // "fee":"0.1000000000000000", + // "asset_symbol":"XRP" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransaction (data, currency); + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"code":10013,"message":"Resource not found"} + // {"code":40004,"message":"invalid jwt"} + // + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + if (code !== '0') { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],23:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, InvalidAddress, RateLimitExceeded, PermissionDenied, NotSupported, BadRequest, BadSymbol, AccountSuspended, OrderImmediatelyFillable } = require ('./base/errors'); +const { ROUND, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class binance extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'binance', + 'name': 'Binance', + 'countries': [ 'JP', 'MT' ], // Japan, Malta + 'rateLimit': 500, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': 'emulated', + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg', + 'test': { + 'dapiPublic': 'https://testnet.binancefuture.com/dapi/v1', + 'dapiPrivate': 'https://testnet.binancefuture.com/dapi/v1', + 'fapiPublic': 'https://testnet.binancefuture.com/fapi/v1', + 'fapiPrivate': 'https://testnet.binancefuture.com/fapi/v1', + 'fapiPrivateV2': 'https://testnet.binancefuture.com/fapi/v2', + 'public': 'https://testnet.binance.vision/api/v3', + 'private': 'https://testnet.binance.vision/api/v3', + 'v3': 'https://testnet.binance.vision/api/v3', + 'v1': 'https://testnet.binance.vision/api/v1', + }, + 'api': { + 'wapi': 'https://api.binance.com/wapi/v3', + 'sapi': 'https://api.binance.com/sapi/v1', + 'dapiPublic': 'https://dapi.binance.com/dapi/v1', + 'dapiPrivate': 'https://dapi.binance.com/dapi/v1', + 'dapiData': 'https://dapi.binance.com/futures/data', + 'fapiPublic': 'https://fapi.binance.com/fapi/v1', + 'fapiPrivate': 'https://fapi.binance.com/fapi/v1', + 'fapiData': 'https://fapi.binance.com/futures/data', + 'fapiPrivateV2': 'https://fapi.binance.com/fapi/v2', + 'public': 'https://api.binance.com/api/v3', + 'private': 'https://api.binance.com/api/v3', + 'v3': 'https://api.binance.com/api/v3', + 'v1': 'https://api.binance.com/api/v1', + }, + 'www': 'https://www.binance.com', + 'referral': 'https://www.binance.com/?ref=10205187', + 'doc': [ + 'https://binance-docs.github.io/apidocs/spot/en', + ], + 'api_management': 'https://www.binance.com/en/usercenter/settings/api-management', + 'fees': 'https://www.binance.com/en/fee/schedule', + }, + 'api': { + // the API structure below will need 3-layer apidefs + 'sapi': { + 'get': [ + 'accountSnapshot', + // these endpoints require this.apiKey + 'margin/asset', + 'margin/pair', + 'margin/allAssets', + 'margin/allPairs', + 'margin/priceIndex', + // these endpoints require this.apiKey + this.secret + 'asset/assetDividend', + 'margin/loan', + 'margin/repay', + 'margin/account', + 'margin/transfer', + 'margin/interestHistory', + 'margin/forceLiquidationRec', + 'margin/order', + 'margin/openOrders', + 'margin/allOrders', + 'margin/myTrades', + 'margin/maxBorrowable', + 'margin/maxTransferable', + 'margin/isolated/transfer', + 'margin/isolated/account', + 'margin/isolated/pair', + 'margin/isolated/allPairs', + 'futures/transfer', + 'futures/loan/borrow/history', + 'futures/loan/repay/history', + 'futures/loan/wallet', + 'futures/loan/configs', + 'futures/loan/calcAdjustLevel', + 'futures/loan/calcMaxAdjustAmount', + 'futures/loan/adjustCollateral/history', + 'futures/loan/liquidationHistory', + // https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi + 'capital/config/getall', // get networks for withdrawing USDT ERC20 vs USDT Omni + 'capital/deposit/address', + 'capital/deposit/hisrec', + 'capital/deposit/subAddress', + 'capital/deposit/subHisrec', + 'capital/withdraw/history', + 'sub-account/futures/account', + 'sub-account/futures/accountSummary', + 'sub-account/futures/positionRisk', + 'sub-account/futures/internalTransfer', + 'sub-account/margin/account', + 'sub-account/margin/accountSummary', + 'sub-account/spotSummary', + 'sub-account/status', + 'sub-account/transfer/subUserHistory', + // lending endpoints + 'lending/daily/product/list', + 'lending/daily/userLeftQuota', + 'lending/daily/userRedemptionQuota', + 'lending/daily/token/position', + 'lending/union/account', + 'lending/union/purchaseRecord', + 'lending/union/redemptionRecord', + 'lending/union/interestHistory', + 'lending/project/list', + 'lending/project/position/list', + // mining endpoints + 'mining/pub/algoList', + 'mining/pub/coinList', + 'mining/worker/detail', + 'mining/worker/list', + 'mining/payment/list', + 'mining/statistics/user/status', + 'mining/statistics/user/list', + // liquid swap endpoints + 'bswap/pools', + 'bswap/liquidity', + 'bswap/liquidityOps', + 'bswap/quote', + 'bswap/swap', + ], + 'post': [ + 'asset/dust', + 'account/disableFastWithdrawSwitch', + 'account/enableFastWithdrawSwitch', + 'capital/withdraw/apply', + 'margin/transfer', + 'margin/loan', + 'margin/repay', + 'margin/order', + 'margin/isolated/create', + 'margin/isolated/transfer', + 'sub-account/margin/transfer', + 'sub-account/margin/enable', + 'sub-account/margin/enable', + 'sub-account/futures/enable', + 'sub-account/futures/transfer', + 'sub-account/futures/internalTransfer', + 'sub-account/transfer/subToSub', + 'sub-account/transfer/subToMaster', + 'userDataStream', + 'userDataStream/isolated', + 'futures/transfer', + 'futures/loan/borrow', + 'futures/loan/repay', + 'futures/loan/adjustCollateral', + // lending + 'lending/customizedFixed/purchase', + 'lending/daily/purchase', + 'lending/daily/redeem', + // liquid swap endpoints + 'bswap/liquidityAdd', + 'bswap/liquidityRemove', + 'bswap/swap', + ], + 'put': [ + 'userDataStream', + 'userDataStream/isolated', + ], + 'delete': [ + 'margin/order', + 'userDataStream', + 'userDataStream/isolated', + ], + }, + 'wapi': { + 'post': [ + 'withdraw', + 'sub-account/transfer', + ], + 'get': [ + 'depositHistory', + 'withdrawHistory', + 'depositAddress', + 'accountStatus', + 'systemStatus', + 'apiTradingStatus', + 'userAssetDribbletLog', + 'tradeFee', + 'assetDetail', + 'sub-account/list', + 'sub-account/transfer/history', + 'sub-account/assets', + ], + }, + 'dapiPublic': { + 'get': [ + 'ping', + 'time', + 'exchangeInfo', + 'depth', + 'trades', + 'historicalTrades', + 'aggTrades', + 'premiumIndex', + 'fundingRate', + 'klines', + 'continuousKlines', + 'indexPriceKlines', + 'markPriceKlines', + 'ticker/24hr', + 'ticker/price', + 'ticker/bookTicker', + 'allForceOrders', + 'openInterest', + ], + }, + 'dapiData': { + 'get': [ + 'openInterestHist', + 'topLongShortAccountRatio', + 'topLongShortPositionRatio', + 'globalLongShortAccountRatio', + 'takerBuySellVol', + 'basis', + ], + }, + 'dapiPrivate': { + 'get': [ + 'positionSide/dual', + 'order', + 'openOrder', + 'openOrders', + 'allOrders', + 'balance', + 'account', + 'positionMargin/history', + 'positionRisk', + 'userTrades', + 'income', + 'leverageBracket', + 'forceOrders', + 'adlQuantile', + ], + 'post': [ + 'positionSide/dual', + 'order', + 'batchOrders', + 'countdownCancelAll', + 'leverage', + 'marginType', + 'positionMargin', + 'listenKey', + ], + 'put': [ + 'listenKey', + ], + 'delete': [ + 'order', + 'allOpenOrders', + 'batchOrders', + 'listenKey', + ], + }, + 'fapiPublic': { + 'get': [ + 'ping', + 'time', + 'exchangeInfo', + 'depth', + 'trades', + 'historicalTrades', + 'aggTrades', + 'klines', + 'fundingRate', + 'premiumIndex', + 'ticker/24hr', + 'ticker/price', + 'ticker/bookTicker', + 'allForceOrders', + 'openInterest', + 'indexInfo', + ], + }, + 'fapiData': { + 'get': [ + 'openInterestHist', + 'topLongShortAccountRatio', + 'topLongShortPositionRatio', + 'globalLongShortAccountRatio', + 'takerlongshortRatio', + ], + }, + 'fapiPrivate': { + 'get': [ + 'allForceOrders', + 'allOrders', + 'openOrder', + 'openOrders', + 'order', + 'account', + 'balance', + 'leverageBracket', + 'positionMargin/history', + 'positionRisk', + 'positionSide/dual', + 'userTrades', + 'income', + ], + 'post': [ + 'batchOrders', + 'positionSide/dual', + 'positionMargin', + 'marginType', + 'order', + 'leverage', + 'listenKey', + 'countdownCancelAll', + ], + 'put': [ + 'listenKey', + ], + 'delete': [ + 'batchOrders', + 'order', + 'allOpenOrders', + 'listenKey', + ], + }, + 'fapiPrivateV2': { + 'get': [ + 'account', + 'balance', + 'positionRisk', + ], + }, + 'v3': { + 'get': [ + 'ticker/price', + 'ticker/bookTicker', + ], + }, + 'public': { + 'get': [ + 'ping', + 'time', + 'depth', + 'trades', + 'aggTrades', + 'historicalTrades', + 'klines', + 'ticker/24hr', + 'ticker/price', + 'ticker/bookTicker', + 'exchangeInfo', + ], + 'put': [ 'userDataStream' ], + 'post': [ 'userDataStream' ], + 'delete': [ 'userDataStream' ], + }, + 'private': { + 'get': [ + 'allOrderList', // oco + 'openOrderList', // oco + 'orderList', // oco + 'order', + 'openOrders', + 'allOrders', + 'account', + 'myTrades', + ], + 'post': [ + 'order/oco', + 'order', + 'order/test', + ], + 'delete': [ + 'openOrders', // added on 2020-04-25 for canceling all open orders per symbol + 'orderList', // oco + 'order', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + }, + 'commonCurrencies': { + 'BCC': 'BCC', // kept for backward-compatibility https://github.com/ccxt/ccxt/issues/4848 + 'YOYO': 'YOYOW', + }, + // exchange-specific options + 'options': { + // 'fetchTradesMethod': 'publicGetAggTrades', // publicGetTrades, publicGetHistoricalTrades + 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel + 'defaultType': 'spot', // 'spot', 'future', 'margin', 'delivery' + 'hasAlreadyAuthenticatedSuccessfully': false, + 'warnOnFetchOpenOrdersWithoutSymbol': true, + 'recvWindow': 5 * 1000, // 5 sec, binance default + 'timeDifference': 0, // the difference between system clock and Binance clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision + 'newOrderRespType': { + 'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + 'limit': 'RESULT', // we change it from 'ACK' by default to 'RESULT' + }, + 'quoteOrderQty': true, // whether market orders support amounts in quote currency + }, + // https://binance-docs.github.io/apidocs/spot/en/#error-codes-2 + 'exceptions': { + 'API key does not exist': AuthenticationError, + 'Order would trigger immediately.': OrderImmediatelyFillable, + 'Order would immediately match and take.': OrderImmediatelyFillable, // {"code":-2010,"msg":"Order would immediately match and take."} + 'Account has insufficient balance for requested action.': InsufficientFunds, + 'Rest API trading is not enabled.': ExchangeNotAvailable, + "You don't have permission.": PermissionDenied, // {"msg":"You don't have permission.","success":false} + 'Market is closed.': ExchangeNotAvailable, // {"code":-1013,"msg":"Market is closed."} + 'Too many requests.': DDoSProtection, // {"msg":"Too many requests. Please try again later.","success":false} + '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} + '-1001': ExchangeNotAvailable, // 'Internal error; unable to process your request. Please try again.' + '-1002': AuthenticationError, // 'You are not authorized to execute this request.' + '-1003': RateLimitExceeded, // {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."} + '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL + '-1015': RateLimitExceeded, // 'Too many new orders; current limit is %s orders per %s.' + '-1016': ExchangeNotAvailable, // 'This service is no longer available.', + '-1020': BadRequest, // 'This operation is not supported.' + '-1021': InvalidNonce, // 'your time is ahead of server' + '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."} + '-1100': BadRequest, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price' + '-1101': BadRequest, // Too many parameters; expected %s and received %s. + '-1102': BadRequest, // Param %s or %s must be sent, but both were empty + '-1103': BadRequest, // An unknown parameter was sent. + '-1104': BadRequest, // Not all sent parameters were read, read 8 parameters but was sent 9 + '-1105': BadRequest, // Parameter %s was empty. + '-1106': BadRequest, // Parameter %s sent when not required. + '-1111': BadRequest, // Precision is over the maximum defined for this asset. + '-1112': InvalidOrder, // No orders on book for symbol. + '-1114': BadRequest, // TimeInForce parameter sent when not required. + '-1115': BadRequest, // Invalid timeInForce. + '-1116': BadRequest, // Invalid orderType. + '-1117': BadRequest, // Invalid side. + '-1118': BadRequest, // New client order ID was empty. + '-1119': BadRequest, // Original client order ID was empty. + '-1120': BadRequest, // Invalid interval. + '-1121': BadSymbol, // Invalid symbol. + '-1125': AuthenticationError, // This listenKey does not exist. + '-1127': BadRequest, // More than %s hours between startTime and endTime. + '-1128': BadRequest, // {"code":-1128,"msg":"Combination of optional parameters invalid."} + '-1130': BadRequest, // Data sent for paramter %s is not valid. + '-1131': BadRequest, // recvWindow must be less than 60000 + '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc... + '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER' + '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist' + '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." } + '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action." + '-3005': InsufficientFunds, // {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."} + '-3008': InsufficientFunds, // {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."} + '-3010': ExchangeError, // {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."} + '-3022': AccountSuspended, // You account's trading is banned. + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const type = this.safeString2 (this.options, 'fetchTime', 'defaultType', 'spot'); + let method = 'publicGetTime'; + if (type === 'future') { + method = 'fapiPublicGetTime'; + } else if (type === 'delivery') { + method = 'dapiPublicGetTime'; + } + const response = await this[method] (params); + return this.safeInteger (response, 'serverTime'); + } + + async loadTimeDifference (params = {}) { + const serverTime = await this.fetchTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = after - serverTime; + return this.options['timeDifference']; + } + + async fetchMarkets (params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchMarkets', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + if ((type !== 'spot') && (type !== 'future') && (type !== 'margin') && (type !== 'delivery')) { + throw new ExchangeError (this.id + " does not support '" + type + "' type, set exchange.options['defaultType'] to 'spot', 'margin', 'delivery' or 'future'"); // eslint-disable-line quotes + } + let method = 'publicGetExchangeInfo'; + if (type === 'future') { + method = 'fapiPublicGetExchangeInfo'; + } else if (type === 'delivery') { + method = 'dapiPublicGetExchangeInfo'; + } + const response = await this[method] (query); + // + // spot / margin + // + // { + // "timezone":"UTC", + // "serverTime":1575416692969, + // "rateLimits":[ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, + // {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":100}, + // {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":200000} + // ], + // "exchangeFilters":[], + // "symbols":[ + // { + // "symbol":"ETHBTC", + // "status":"TRADING", + // "baseAsset":"ETH", + // "baseAssetPrecision":8, + // "quoteAsset":"BTC", + // "quotePrecision":8, + // "baseCommissionPrecision":8, + // "quoteCommissionPrecision":8, + // "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"], + // "icebergAllowed":true, + // "ocoAllowed":true, + // "quoteOrderQtyMarketAllowed":true, + // "isSpotTradingAllowed":true, + // "isMarginTradingAllowed":true, + // "filters":[ + // {"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"}, + // {"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5}, + // {"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"}, + // {"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5}, + // {"filterType":"ICEBERG_PARTS","limit":10}, + // {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"63100.00000000","stepSize":"0.00000000"}, + // {"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5} + // ] + // }, + // ], + // } + // + // futures/usdt-margined (fapi) + // + // { + // "timezone":"UTC", + // "serverTime":1575417244353, + // "rateLimits":[ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, + // {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200} + // ], + // "exchangeFilters":[], + // "symbols":[ + // { + // "symbol":"BTCUSDT", + // "status":"TRADING", + // "maintMarginPercent":"2.5000", + // "requiredMarginPercent":"5.0000", + // "baseAsset":"BTC", + // "quoteAsset":"USDT", + // "pricePrecision":2, + // "quantityPrecision":3, + // "baseAssetPrecision":8, + // "quotePrecision":8, + // "filters":[ + // {"minPrice":"0.01","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.01"}, + // {"stepSize":"0.001","filterType":"LOT_SIZE","maxQty":"1000","minQty":"0.001"}, + // {"stepSize":"0.001","filterType":"MARKET_LOT_SIZE","maxQty":"1000","minQty":"0.001"}, + // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, + // {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} + // ], + // "orderTypes":["LIMIT","MARKET","STOP"], + // "timeInForce":["GTC","IOC","FOK","GTX"] + // } + // ] + // } + // + // delivery/coin-margined (dapi) + // + // { + // "timezone": "UTC", + // "serverTime": 1597667052958, + // "rateLimits": [ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000}, + // {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":6000} + // ], + // "exchangeFilters": [], + // "symbols": [ + // { + // "symbol": "BTCUSD_200925", + // "pair": "BTCUSD", + // "contractType": "CURRENT_QUARTER", + // "deliveryDate": 1601020800000, + // "onboardDate": 1590739200000, + // "contractStatus": "TRADING", + // "contractSize": 100, + // "marginAsset": "BTC", + // "maintMarginPercent": "2.5000", + // "requiredMarginPercent": "5.0000", + // "baseAsset": "BTC", + // "quoteAsset": "USD", + // "pricePrecision": 1, + // "quantityPrecision": 0, + // "baseAssetPrecision": 8, + // "quotePrecision": 8, + // "equalQtyPrecision": 4, + // "filters": [ + // {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"}, + // {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"stepSize":"0","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, + // {"multiplierDown":"0.9500","multiplierUp":"1.0500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} + // ], + // "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"], + // "timeInForce": ["GTC","IOC","FOK","GTX"] + // }, + // { + // "symbol": "BTCUSD_PERP", + // "pair": "BTCUSD", + // "contractType": "PERPETUAL", + // "deliveryDate": 4133404800000, + // "onboardDate": 1596006000000, + // "contractStatus": "TRADING", + // "contractSize": 100, + // "marginAsset": "BTC", + // "maintMarginPercent": "2.5000", + // "requiredMarginPercent": "5.0000", + // "baseAsset": "BTC", + // "quoteAsset": "USD", + // "pricePrecision": 1, + // "quantityPrecision": 0, + // "baseAssetPrecision": 8, + // "quotePrecision": 8, + // "equalQtyPrecision": 4, + // "filters": [ + // {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"}, + // {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"stepSize":"1","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, + // {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} + // ], + // "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"], + // "timeInForce": ["GTC","IOC","FOK","GTX"] + // } + // ] + // } + // + if (this.options['adjustForTimeDifference']) { + await this.loadTimeDifference (); + } + const markets = this.safeValue (response, 'symbols'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const spot = (type === 'spot'); + const future = (type === 'future'); + const delivery = (type === 'delivery'); + const id = this.safeString (market, 'symbol'); + const lowercaseId = this.safeStringLower (market, 'symbol'); + const baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const parts = id.split ('_'); + const lastPart = this.safeString (parts, 1); + const idSymbol = (delivery) && (lastPart !== 'PERP'); + const symbol = idSymbol ? id : (base + '/' + quote); + const filters = this.safeValue (market, 'filters', []); + const filtersByType = this.indexBy (filters, 'filterType'); + const precision = { + 'base': this.safeInteger (market, 'baseAssetPrecision'), + 'quote': this.safeInteger (market, 'quotePrecision'), + 'amount': this.safeInteger (market, 'baseAssetPrecision'), + 'price': this.safeInteger (market, 'quotePrecision'), + }; + const status = this.safeString2 (market, 'status', 'contractStatus'); + const active = (status === 'TRADING'); + const margin = this.safeValue (market, 'isMarginTradingAllowed', future || delivery); + const entry = { + 'id': id, + 'lowercaseId': lowercaseId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'margin': margin, + 'future': future, + 'delivery': delivery, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + if ('PRICE_FILTER' in filtersByType) { + const filter = this.safeValue (filtersByType, 'PRICE_FILTER', {}); + // PRICE_FILTER reports zero values for maxPrice + // since they updated filter types in November 2018 + // https://github.com/ccxt/ccxt/issues/4286 + // therefore limits['price']['max'] doesn't have any meaningful value except undefined + entry['limits']['price'] = { + 'min': this.safeFloat (filter, 'minPrice'), + 'max': undefined, + }; + const maxPrice = this.safeFloat (filter, 'maxPrice'); + if ((maxPrice !== undefined) && (maxPrice > 0)) { + entry['limits']['price']['max'] = maxPrice; + } + entry['precision']['price'] = this.precisionFromString (filter['tickSize']); + } + if ('LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'LOT_SIZE', {}); + const stepSize = this.safeString (filter, 'stepSize'); + entry['precision']['amount'] = this.precisionFromString (stepSize); + entry['limits']['amount'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MARKET_LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MARKET_LOT_SIZE', {}); + entry['limits']['market'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MIN_NOTIONAL' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MIN_NOTIONAL', {}); + entry['limits']['cost']['min'] = this.safeFloat (filter, 'minNotional'); + } + result.push (entry); + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetAccount'; + if (type === 'future') { + const options = this.safeValue (this.options, 'future', {}); + const fetchBalanceOptions = this.safeValue (options, 'fetchBalance', {}); + method = this.safeString (fetchBalanceOptions, 'method', 'fapiPrivateV2GetAccount'); + } else if (type === 'delivery') { + const options = this.safeValue (this.options, 'delivery', {}); + const fetchBalanceOptions = this.safeValue (options, 'fetchBalance', {}); + method = this.safeString (fetchBalanceOptions, 'method', 'dapiPrivateGetAccount'); + } else if (type === 'margin') { + method = 'sapiGetMarginAccount'; + } + const query = this.omit (params, 'type'); + const response = await this[method] (query); + // + // spot + // + // { + // makerCommission: 10, + // takerCommission: 10, + // buyerCommission: 0, + // sellerCommission: 0, + // canTrade: true, + // canWithdraw: true, + // canDeposit: true, + // updateTime: 1575357359602, + // accountType: "MARGIN", + // balances: [ + // { asset: "BTC", free: "0.00219821", locked: "0.00000000" }, + // ] + // } + // + // margin + // + // { + // "borrowEnabled":true, + // "marginLevel":"999.00000000", + // "totalAssetOfBtc":"0.00000000", + // "totalLiabilityOfBtc":"0.00000000", + // "totalNetAssetOfBtc":"0.00000000", + // "tradeEnabled":true, + // "transferEnabled":true, + // "userAssets":[ + // {"asset":"MATIC","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}, + // {"asset":"VET","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}, + // {"asset":"USDT","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"} + // ], + // } + // + // futures (fapi) + // + // fapiPrivateGetAccount + // + // { + // "feeTier":0, + // "canTrade":true, + // "canDeposit":true, + // "canWithdraw":true, + // "updateTime":0, + // "totalInitialMargin":"0.00000000", + // "totalMaintMargin":"0.00000000", + // "totalWalletBalance":"4.54000000", + // "totalUnrealizedProfit":"0.00000000", + // "totalMarginBalance":"4.54000000", + // "totalPositionInitialMargin":"0.00000000", + // "totalOpenOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"4.54000000", + // "assets":[ + // { + // "asset":"USDT", + // "walletBalance":"4.54000000", + // "unrealizedProfit":"0.00000000", + // "marginBalance":"4.54000000", + // "maintMargin":"0.00000000", + // "initialMargin":"0.00000000", + // "positionInitialMargin":"0.00000000", + // "openOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"4.54000000" + // } + // ], + // "positions":[ + // { + // "symbol":"BTCUSDT", + // "initialMargin":"0.00000", + // "maintMargin":"0.00000", + // "unrealizedProfit":"0.00000000", + // "positionInitialMargin":"0.00000", + // "openOrderInitialMargin":"0.00000" + // } + // ] + // } + // + // fapiPrivateV2GetAccount + // + // { + // "feeTier":0, + // "canTrade":true, + // "canDeposit":true, + // "canWithdraw":true, + // "updateTime":0, + // "totalInitialMargin":"0.00000000", + // "totalMaintMargin":"0.00000000", + // "totalWalletBalance":"0.00000000", + // "totalUnrealizedProfit":"0.00000000", + // "totalMarginBalance":"0.00000000", + // "totalPositionInitialMargin":"0.00000000", + // "totalOpenOrderInitialMargin":"0.00000000", + // "totalCrossWalletBalance":"0.00000000", + // "totalCrossUnPnl":"0.00000000", + // "availableBalance":"0.00000000", + // "maxWithdrawAmount":"0.00000000", + // "assets":[ + // { + // "asset":"BNB", + // "walletBalance":"0.01000000", + // "unrealizedProfit":"0.00000000", + // "marginBalance":"0.01000000", + // "maintMargin":"0.00000000", + // "initialMargin":"0.00000000", + // "positionInitialMargin":"0.00000000", + // "openOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"0.01000000", + // "crossWalletBalance":"0.01000000", + // "crossUnPnl":"0.00000000", + // "availableBalance":"0.01000000" + // } + // ], + // "positions":[ + // { + // "symbol":"BTCUSDT", + // "initialMargin":"0", + // "maintMargin":"0", + // "unrealizedProfit":"0.00000000", + // "positionInitialMargin":"0", + // "openOrderInitialMargin":"0", + // "leverage":"20", + // "isolated":false, + // "entryPrice":"0.00000", + // "maxNotional":"5000000", + // "positionSide":"BOTH" + // }, + // ] + // } + // + // fapiPrivateV2GetBalance + // + // [ + // { + // "accountAlias":"FzFzXquXXqoC", + // "asset":"BNB", + // "balance":"0.01000000", + // "crossWalletBalance":"0.01000000", + // "crossUnPnl":"0.00000000", + // "availableBalance":"0.01000000", + // "maxWithdrawAmount":"0.01000000" + // } + // ] + // + const result = { 'info': response }; + if ((type === 'spot') || (type === 'margin')) { + const balances = this.safeValue2 (response, 'balances', 'userAssets', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + } else { + let balances = response; + if (!Array.isArray (response)) { + balances = this.safeValue (response, 'assets', []); + } + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableBalance'); + account['used'] = this.safeFloat (balance, 'initialMargin'); + account['total'] = this.safeFloat2 (balance, 'marginBalance', 'balance'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 5000, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book + } + let method = 'publicGetDepth'; + if (market['future']) { + method = 'fapiPublicGetDepth'; + } else if (market['delivery']) { + method = 'dapiPublicGetDepth'; + } + const response = await this[method] (this.extend (request, params)); + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // { + // symbol: 'ETHBTC', + // priceChange: '0.00068700', + // priceChangePercent: '2.075', + // weightedAvgPrice: '0.03342681', + // prevClosePrice: '0.03310300', + // lastPrice: '0.03378900', + // lastQty: '0.07700000', + // bidPrice: '0.03378900', + // bidQty: '7.16800000', + // askPrice: '0.03379000', + // askQty: '24.00000000', + // openPrice: '0.03310200', + // highPrice: '0.03388900', + // lowPrice: '0.03306900', + // volume: '205478.41000000', + // quoteVolume: '6868.48826294', + // openTime: 1601469986932, + // closeTime: 1601556386932, + // firstId: 196098772, + // lastId: 196186315, + // count: 87544 + // } + // + const timestamp = this.safeInteger (ticker, 'closeTime'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'lastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), + 'open': this.safeFloat (ticker, 'openPrice'), + 'close': last, + 'last': last, + 'previousClose': this.safeFloat (ticker, 'prevClosePrice'), // previous day close + 'change': this.safeFloat (ticker, 'priceChange'), + 'percentage': this.safeFloat (ticker, 'priceChangePercent'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchStatus (params = {}) { + const response = await this.wapiGetSystemStatus (params); + let status = this.safeValue (response, 'status'); + if (status !== undefined) { + status = (status === 0) ? 'ok' : 'maintenance'; + this.status = this.extend (this.status, { + 'status': status, + 'updated': this.milliseconds (), + }); + } + return this.status; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = 'publicGetTicker24hr'; + if (market['future']) { + method = 'fapiPublicGetTicker24hr'; + } else if (market['delivery']) { + method = 'dapiPublicGetTicker24hr'; + } + const response = await this[method] (this.extend (request, params)); + if (Array.isArray (response)) { + const firstTicker = this.safeValue (response, 0, {}); + return this.parseTicker (firstTicker, market); + } + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchBidsAsks (symbols = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchBidsAsks', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = undefined; + if (type === 'future') { + method = 'fapiPublicGetTickerBookTicker'; + } else if (type === 'delivery') { + method = 'dapiPublicGetTickerBookTicker'; + } else { + method = 'publicGetTickerBookTicker'; + } + const response = await this[method] (query); + return this.parseTickers (response, symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let defaultMethod = undefined; + if (type === 'future') { + defaultMethod = 'fapiPublicGetTicker24hr'; + } else if (type === 'delivery') { + defaultMethod = 'dapiPublicGetTicker24hr'; + } else { + defaultMethod = 'publicGetTicker24hr'; + } + const method = this.safeString (this.options, 'fetchTickersMethod', defaultMethod); + const response = await this[method] (query); + return this.parseTickers (response, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591478520000, + // "0.02501300", + // "0.02501800", + // "0.02500000", + // "0.02500000", + // "22.19000000", + // 1591478579999, + // "0.55490906", + // 40, + // "10.92900000", + // "0.27336462", + // "0" + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default == max == 500 + } + let method = 'publicGetKlines'; + if (market['future']) { + method = 'fapiPublicGetKlines'; + } else if (market['delivery']) { + method = 'dapiPublicGetKlines'; + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"], + // [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"], + // [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + if ('isDustTrade' in trade) { + return this.parseDustTrade (trade, market); + } + // + // aggregate trades + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list + // + // { + // "a": 26129, // Aggregate tradeId + // "p": "0.01633102", // Price + // "q": "4.70443515", // Quantity + // "f": 27781, // First tradeId + // "l": 27781, // Last tradeId + // "T": 1498793709153, // Timestamp + // "m": true, // Was the buyer the maker? + // "M": true // Was the trade the best price match? + // } + // + // recent public trades and old public trades + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data + // + // { + // "id": 28457, + // "price": "4.00000100", + // "qty": "12.00000000", + // "time": 1499865549590, + // "isBuyerMaker": true, + // "isBestMatch": true + // } + // + // private trades + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data + // + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true + // } + // + // futures trades + // https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data + // + // { + // "accountId": 20, + // "buyer": False, + // "commission": "-0.07819010", + // "commissionAsset": "USDT", + // "counterPartyId": 653, + // "id": 698759, + // "maker": False, + // "orderId": 25851813, + // "price": "7819.01", + // "qty": "0.002", + // "quoteQty": "0.01563", + // "realizedPnl": "-0.91539999", + // "side": "SELL", + // "symbol": "BTCUSDT", + // "time": 1569514978020 + // } + // + const timestamp = this.safeInteger2 (trade, 'T', 'time'); + const price = this.safeFloat2 (trade, 'p', 'price'); + const amount = this.safeFloat2 (trade, 'q', 'qty'); + const id = this.safeString2 (trade, 'a', 'id'); + let side = undefined; + const orderId = this.safeString (trade, 'orderId'); + if ('m' in trade) { + side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally + } else if ('isBuyerMaker' in trade) { + side = trade['isBuyerMaker'] ? 'sell' : 'buy'; + } else if ('side' in trade) { + side = this.safeStringLower (trade, 'side'); + } else { + if ('isBuyer' in trade) { + side = trade['isBuyer'] ? 'buy' : 'sell'; // this is a true side + } + } + let fee = undefined; + if ('commission' in trade) { + fee = { + 'cost': this.safeFloat (trade, 'commission'), + 'currency': this.safeCurrencyCode (this.safeString (trade, 'commissionAsset')), + }; + } + let takerOrMaker = undefined; + if ('isMaker' in trade) { + takerOrMaker = trade['isMaker'] ? 'maker' : 'taker'; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'fromId': 123, // ID to get aggregate trades from INCLUSIVE. + // 'startTime': 456, // Timestamp in ms to get aggregate trades from INCLUSIVE. + // 'endTime': 789, // Timestamp in ms to get aggregate trades until INCLUSIVE. + // 'limit': 500, // default = 500, maximum = 1000 + }; + const defaultType = this.safeString2 (this.options, 'fetchTrades', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let defaultMethod = undefined; + if (type === 'future') { + defaultMethod = 'fapiPublicGetAggTrades'; + } else if (type === 'delivery') { + defaultMethod = 'dapiPublicGetAggTrades'; + } else { + defaultMethod = 'publicGetAggTrades'; + } + let method = this.safeString (this.options, 'fetchTradesMethod', defaultMethod); + if (method === 'publicGetAggTrades') { + if (since !== undefined) { + request['startTime'] = since; + // https://github.com/ccxt/ccxt/issues/6400 + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list + request['endTime'] = this.sum (since, 3600000); + } + if (type === 'future') { + method = 'fapiPublicGetAggTrades'; + } else if (type === 'delivery') { + method = 'dapiPublicGetAggTrades'; + } + } else if (method === 'publicGetHistoricalTrades') { + if (type === 'future') { + method = 'fapiPublicGetHistoricalTrades'; + } else if (type === 'delivery') { + method = 'dapiPublicGetHistoricalTrades'; + } + } + if (limit !== undefined) { + request['limit'] = limit; // default = 500, maximum = 1000 + } + // + // Caveats: + // - default limit (500) applies only if no other parameters set, trades up + // to the maximum limit may be returned to satisfy other parameters + // - if both limit and time window is set and time window contains more + // trades than the limit then the last trades from the window are returned + // - 'tradeId' accepted and returned by this method is "aggregate" trade id + // which is different from actual trade id + // - setting both fromId and time window results in error + const response = await this[method] (this.extend (request, query)); + // + // aggregate trades + // + // [ + // { + // "a": 26129, // Aggregate tradeId + // "p": "0.01633102", // Price + // "q": "4.70443515", // Quantity + // "f": 27781, // First tradeId + // "l": 27781, // Last tradeId + // "T": 1498793709153, // Timestamp + // "m": true, // Was the buyer the maker? + // "M": true // Was the trade the best price match? + // } + // ] + // + // recent public trades and historical public trades + // + // [ + // { + // "id": 28457, + // "price": "4.00000100", + // "qty": "12.00000000", + // "time": 1499865549590, + // "isBuyerMaker": true, + // "isBestMatch": true + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'PARTIALLY_FILLED': 'open', + 'FILLED': 'closed', + 'CANCELED': 'canceled', + 'PENDING_CANCEL': 'canceling', // currently unused + 'REJECTED': 'rejected', + 'EXPIRED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // spot + // + // { + // "symbol": "LTCBTC", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "0.0", + // "cummulativeQuoteQty": "0.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "icebergQty": "0.0", + // "time": 1499827319559, + // "updateTime": 1499827319559, + // "isWorking": true + // } + // + // futures + // + // { + // "symbol": "BTCUSDT", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "1.0", + // "cumQuote": "10.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "updateTime": 1499827319559 + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = undefined; + if ('time' in order) { + timestamp = this.safeInteger (order, 'time'); + } else if ('transactTime' in order) { + timestamp = this.safeInteger (order, 'transactTime'); + } + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'origQty'); + const filled = this.safeFloat (order, 'executedQty'); + let remaining = undefined; + // - Spot/Margin market: cummulativeQuoteQty + // - Futures market: cumQuote. + // Note this is not the actual cost, since Binance futures uses leverage to calculate margins. + let cost = this.safeFloat2 (order, 'cummulativeQuoteQty', 'cumQuote'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + if (this.options['parseOrderToPrecision']) { + remaining = parseFloat (this.amountToPrecision (symbol, remaining)); + } + remaining = Math.max (remaining, 0.0); + } + if (price !== undefined) { + if (cost === undefined) { + cost = price * filled; + } + } + } + const id = this.safeString (order, 'orderId'); + let type = this.safeStringLower (order, 'type'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + if (this.options['parseOrderToPrecision']) { + price = parseFloat (this.priceToPrecision (symbol, price)); + } + } + } + } + } else if (type === 'limit_maker') { + type = 'limit'; + } + const side = this.safeStringLower (order, 'side'); + let fee = undefined; + let trades = undefined; + const fills = this.safeValue (order, 'fills'); + if (fills !== undefined) { + trades = this.parseTrades (fills, market); + const numTrades = trades.length; + if (numTrades > 0) { + cost = trades[0]['cost']; + fee = { + 'cost': trades[0]['fee']['cost'], + 'currency': trades[0]['fee']['currency'], + }; + for (let i = 1; i < trades.length; i++) { + cost = this.sum (cost, trades[i]['cost']); + fee['cost'] = this.sum (fee['cost'], trades[i]['fee']['cost']); + } + } + } + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + if (this.options['parseOrderToPrecision']) { + average = parseFloat (this.priceToPrecision (symbol, average)); + } + } + if (this.options['parseOrderToPrecision']) { + cost = parseFloat (this.costToPrecision (symbol, cost)); + } + } + const clientOrderId = this.safeString (order, 'clientOrderId'); + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'createOrder', 'defaultType', market['type']); + const orderType = this.safeString (params, 'type', defaultType); + const clientOrderId = this.safeString2 (params, 'newClientOrderId', 'clientOrderId'); + params = this.omit (params, [ 'type', 'newClientOrderId', 'clientOrderId' ]); + let method = 'privatePostOrder'; + if (orderType === 'future') { + method = 'fapiPrivatePostOrder'; + } else if (orderType === 'delivery') { + method = 'dapiPrivatePostOrder'; + } else if (orderType === 'margin') { + method = 'sapiPostMarginOrder'; + } + // the next 5 lines are added to support for testing orders + if (market['spot']) { + const test = this.safeValue (params, 'test', false); + if (test) { + method += 'Test'; + } + params = this.omit (params, 'test'); + } + const uppercaseType = type.toUpperCase (); + const validOrderTypes = this.safeValue (market['info'], 'orderTypes'); + if (!this.inArray (uppercaseType, validOrderTypes)) { + throw new InvalidOrder (this.id + ' ' + type + ' is not a valid order type in ' + market['type'] + ' market ' + symbol); + } + const request = { + 'symbol': market['id'], + 'type': uppercaseType, + 'side': side.toUpperCase (), + }; + if (clientOrderId !== undefined) { + request['newClientOrderId'] = clientOrderId; + } + if (market['spot']) { + request['newOrderRespType'] = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + } + // additional required fields depending on the order type + let timeInForceIsRequired = false; + let priceIsRequired = false; + let stopPriceIsRequired = false; + let quantityIsRequired = false; + // + // spot/margin + // + // LIMIT timeInForce, quantity, price + // MARKET quantity or quoteOrderQty + // STOP_LOSS quantity, stopPrice + // STOP_LOSS_LIMIT timeInForce, quantity, price, stopPrice + // TAKE_PROFIT quantity, stopPrice + // TAKE_PROFIT_LIMIT timeInForce, quantity, price, stopPrice + // LIMIT_MAKER quantity, price + // + // futures + // + // LIMIT timeInForce, quantity, price + // MARKET quantity + // STOP/TAKE_PROFIT quantity, price, stopPrice + // STOP_MARKET stopPrice + // TAKE_PROFIT_MARKET stopPrice + // TRAILING_STOP_MARKET callbackRate + // + if (uppercaseType === 'MARKET') { + const quoteOrderQty = this.safeValue (this.options, 'quoteOrderQty', false); + if (quoteOrderQty) { + const quoteOrderQty = this.safeFloat (params, 'quoteOrderQty'); + const precision = market['precision']['price']; + if (quoteOrderQty !== undefined) { + request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQty, TRUNCATE, precision, this.precisionMode); + params = this.omit (params, 'quoteOrderQty'); + } else if (price !== undefined) { + request['quoteOrderQty'] = this.decimalToPrecision (amount * price, TRUNCATE, precision, this.precisionMode); + } else { + quantityIsRequired = true; + } + } else { + quantityIsRequired = true; + } + } else if (uppercaseType === 'LIMIT') { + priceIsRequired = true; + timeInForceIsRequired = true; + quantityIsRequired = true; + } else if ((uppercaseType === 'STOP_LOSS') || (uppercaseType === 'TAKE_PROFIT')) { + stopPriceIsRequired = true; + quantityIsRequired = true; + if (market['future']) { + priceIsRequired = true; + } + } else if ((uppercaseType === 'STOP_LOSS_LIMIT') || (uppercaseType === 'TAKE_PROFIT_LIMIT')) { + quantityIsRequired = true; + stopPriceIsRequired = true; + priceIsRequired = true; + timeInForceIsRequired = true; + } else if (uppercaseType === 'LIMIT_MAKER') { + priceIsRequired = true; + quantityIsRequired = true; + } else if (uppercaseType === 'STOP') { + quantityIsRequired = true; + stopPriceIsRequired = true; + priceIsRequired = true; + } else if ((uppercaseType === 'STOP_MARKET') || (uppercaseType === 'TAKE_PROFIT_MARKET')) { + const closePosition = this.safeValue (params, 'closePosition'); + if (closePosition === undefined) { + quantityIsRequired = true; + } + stopPriceIsRequired = true; + } else if (uppercaseType === 'TRAILING_STOP_MARKET') { + quantityIsRequired = true; + const callbackRate = this.safeFloat (params, 'callbackRate'); + if (callbackRate === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a callbackRate extra param for a ' + type + ' order'); + } + } + if (quantityIsRequired) { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (priceIsRequired) { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a price argument for a ' + type + ' order'); + } + request['price'] = this.priceToPrecision (symbol, price); + } + if (timeInForceIsRequired) { + request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel + } + if (stopPriceIsRequired) { + const stopPrice = this.safeFloat (params, 'stopPrice'); + if (stopPrice === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order'); + } else { + params = this.omit (params, 'stopPrice'); + request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); + } + } + const response = await this[method] (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetOrder'; + if (type === 'future') { + method = 'fapiPrivateGetOrder'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetOrder'; + } else if (type === 'margin') { + method = 'sapiGetMarginOrder'; + } + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['origClientOrderId'] = clientOrderId; + } else { + request['orderId'] = parseInt (id); + } + const query = this.omit (params, [ 'type', 'clientOrderId', 'origClientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + return this.parseOrder (response, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOrders', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetAllOrders'; + if (type === 'future') { + method = 'fapiPrivateGetAllOrders'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetAllOrders'; + } else if (type === 'margin') { + method = 'sapiGetMarginAllOrders'; + } + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // "symbol": "LTCBTC", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "0.0", + // "cummulativeQuoteQty": "0.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "icebergQty": "0.0", + // "time": 1499827319559, + // "updateTime": 1499827319559, + // "isWorking": true + // } + // ] + // + // futures + // + // [ + // { + // "symbol": "BTCUSDT", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "1.0", + // "cumQuote": "10.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "updateTime": 1499827319559 + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let query = undefined; + let type = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', market['type']); + type = this.safeString (params, 'type', defaultType); + query = this.omit (params, 'type'); + } else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) { + const symbols = this.symbols; + const numSymbols = symbols.length; + const fetchOpenOrdersRateLimit = parseInt (numSymbols / 2); + throw new ExchangeError (this.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.'); + } else { + const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', 'spot'); + type = this.safeString (params, 'type', defaultType); + query = this.omit (params, 'type'); + } + let method = 'privateGetOpenOrders'; + if (type === 'future') { + method = 'fapiPrivateGetOpenOrders'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetOpenOrders'; + } else if (type === 'margin') { + method = 'sapiGetMarginOpenOrders'; + } + const response = await this[method] (this.extend (request, query)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + // https://github.com/ccxt/ccxt/issues/6507 + const origClientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + const request = { + 'symbol': market['id'], + // 'orderId': parseInt (id), + // 'origClientOrderId': id, + }; + if (origClientOrderId === undefined) { + request['orderId'] = parseInt (id); + } else { + request['origClientOrderId'] = origClientOrderId; + } + let method = 'privateDeleteOrder'; + if (type === 'future') { + method = 'fapiPrivateDeleteOrder'; + } else if (type === 'delivery') { + method = 'dapiPrivateDeleteOrder'; + } else if (type === 'margin') { + method = 'sapiDeleteMarginOrder'; + } + const query = this.omit (params, [ 'type', 'origClientOrderId', 'clientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const defaultType = this.safeString2 (this.options, 'cancelAllOrders', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = 'privateDeleteOpenOrders'; + if (type === 'future') { + method = 'fapiPrivateDeleteAllOpenOrders'; + } else if (type === 'delivery') { + method = 'dapiPrivateDeleteAllOpenOrders'; + } + const response = await this[method] (this.extend (request, query)); + if (Array.isArray (response)) { + return this.parseOrders (response, market); + } else { + return response; + } + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.fetchBalance (params); + const info = this.safeValue (response, 'info', {}); + // + // futures, delivery + // + // { + // "feeTier":0, + // "canTrade":true, + // "canDeposit":true, + // "canWithdraw":true, + // "updateTime":0, + // "assets":[ + // { + // "asset":"ETH", + // "walletBalance":"0.09886711", + // "unrealizedProfit":"0.00000000", + // "marginBalance":"0.09886711", + // "maintMargin":"0.00000000", + // "initialMargin":"0.00000000", + // "positionInitialMargin":"0.00000000", + // "openOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"0.09886711", + // "crossWalletBalance":"0.09886711", + // "crossUnPnl":"0.00000000", + // "availableBalance":"0.09886711" + // } + // ], + // "positions":[ + // { + // "symbol":"BTCUSD_201225", + // "initialMargin":"0", + // "maintMargin":"0", + // "unrealizedProfit":"0.00000000", + // "positionInitialMargin":"0", + // "openOrderInitialMargin":"0", + // "leverage":"20", + // "isolated":false, + // "positionSide":"BOTH", + // "entryPrice":"0.00000000", + // "maxQty":"250", // "maxNotional" on futures + // }, + // ] + // } + // + const positions = this.safeValue2 (info, 'positions', 'userAssets', []); + // todo unify parsePosition/parsePositions + return positions; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeValue (params, 'type', market['type']); + let method = undefined; + if (type === 'spot') { + method = 'privateGetMyTrades'; + } else if (type === 'future') { + method = 'fapiPrivateGetUserTrades'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetUserTrades'; + } + params = this.omit (params, 'type'); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot trade + // + // [ + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true, + // } + // ] + // + // futures trade + // + // [ + // { + // "accountId": 20, + // "buyer": False, + // "commission": "-0.07819010", + // "commissionAsset": "USDT", + // "counterPartyId": 653, + // "id": 698759, + // "maker": False, + // "orderId": 25851813, + // "price": "7819.01", + // "qty": "0.002", + // "quoteQty": "0.01563", + // "realizedPnl": "-0.91539999", + // "side": "SELL", + // "symbol": "BTCUSDT", + // "time": 1569514978020 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchMyDustTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // + // Binance provides an opportunity to trade insignificant (i.e. non-tradable and non-withdrawable) + // token leftovers (of any asset) into `BNB` coin which in turn can be used to pay trading fees with it. + // The corresponding trades history is called the `Dust Log` and can be requested via the following end-point: + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data + // + await this.loadMarkets (); + const response = await this.wapiGetUserAssetDribbletLog (params); + // { success: true, + // results: { total: 1, + // rows: [ { transfered_total: "1.06468458", + // service_charge_total: "0.02172826", + // tran_id: 2701371634, + // logs: [ { tranId: 2701371634, + // serviceChargeAmount: "0.00012819", + // uid: "35103861", + // amount: "0.8012", + // operateTime: "2018-10-07 17:56:07", + // transferedAmount: "0.00628141", + // fromAsset: "ADA" } ], + // operate_time: "2018-10-07 17:56:06" } ] } } + const results = this.safeValue (response, 'results', {}); + const rows = this.safeValue (results, 'rows', []); + const data = []; + for (let i = 0; i < rows.length; i++) { + const logs = rows[i]['logs']; + for (let j = 0; j < logs.length; j++) { + logs[j]['isDustTrade'] = true; + data.push (logs[j]); + } + } + const trades = this.parseTrades (data, undefined, since, limit); + return this.filterBySinceLimit (trades, since, limit); + } + + parseDustTrade (trade, market = undefined) { + // { tranId: 2701371634, + // serviceChargeAmount: "0.00012819", + // uid: "35103861", + // amount: "0.8012", + // operateTime: "2018-10-07 17:56:07", + // transferedAmount: "0.00628141", + // fromAsset: "ADA" }, + const orderId = this.safeString (trade, 'tranId'); + const timestamp = this.parse8601 (this.safeString (trade, 'operateTime')); + const tradedCurrency = this.safeCurrencyCode (this.safeString (trade, 'fromAsset')); + const earnedCurrency = this.currency ('BNB')['code']; + const applicantSymbol = earnedCurrency + '/' + tradedCurrency; + let tradedCurrencyIsQuote = false; + if (applicantSymbol in this.markets) { + tradedCurrencyIsQuote = true; + } + // + // Warning + // Binance dust trade `fee` is already excluded from the `BNB` earning reported in the `Dust Log`. + // So the parser should either set the `fee.cost` to `0` or add it on top of the earned + // BNB `amount` (or `cost` depending on the trade `side`). The second of the above options + // is much more illustrative and therefore preferable. + // + const fee = { + 'currency': earnedCurrency, + 'cost': this.safeFloat (trade, 'serviceChargeAmount'), + }; + let symbol = undefined; + let amount = undefined; + let cost = undefined; + let side = undefined; + if (tradedCurrencyIsQuote) { + symbol = applicantSymbol; + amount = this.sum (this.safeFloat (trade, 'transferedAmount'), fee['cost']); + cost = this.safeFloat (trade, 'amount'); + side = 'buy'; + } else { + symbol = tradedCurrency + '/' + earnedCurrency; + amount = this.safeFloat (trade, 'amount'); + cost = this.sum (this.safeFloat (trade, 'transferedAmount'), fee['cost']); + side = 'sell'; + } + let price = undefined; + if (cost !== undefined) { + if (amount) { + price = cost / amount; + } + } + const id = undefined; + const type = undefined; + const takerOrMaker = undefined; + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'amount': amount, + 'price': price, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + // max 3 months range https://github.com/ccxt/ccxt/issues/6495 + request['endTime'] = this.sum (since, 7776000000); + } + const response = await this.wapiGetDepositHistory (this.extend (request, params)); + // + // { success: true, + // depositList: [ { insertTime: 1517425007000, + // amount: 0.3, + // address: "0x0123456789abcdef", + // addressTag: "", + // txId: "0x0123456789abcdef", + // asset: "ETH", + // status: 1 } ] } + // + return this.parseTransactions (response['depositList'], currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + // max 3 months range https://github.com/ccxt/ccxt/issues/6495 + request['endTime'] = this.sum (since, 7776000000); + } + const response = await this.wapiGetWithdrawHistory (this.extend (request, params)); + // + // { withdrawList: [ { amount: 14, + // address: "0x0123456789abcdef...", + // successTime: 1514489710000, + // transactionFee: 0.01, + // addressTag: "", + // txId: "0x0123456789abcdef...", + // id: "0123456789abcdef...", + // asset: "ETH", + // applyTime: 1514488724000, + // status: 6 }, + // { amount: 7600, + // address: "0x0123456789abcdef...", + // successTime: 1515323226000, + // transactionFee: 0.01, + // addressTag: "", + // txId: "0x0123456789abcdef...", + // id: "0123456789abcdef...", + // asset: "ICN", + // applyTime: 1515322539000, + // status: 6 } ], + // success: true } + // + return this.parseTransactions (response['withdrawList'], currency, since, limit); + } + + parseTransactionStatusByType (status, type = undefined) { + const statusesByType = { + 'deposit': { + '0': 'pending', + '1': 'ok', + }, + 'withdrawal': { + '0': 'pending', // Email Sent + '1': 'canceled', // Cancelled (different from 1 = ok in deposits) + '2': 'pending', // Awaiting Approval + '3': 'failed', // Rejected + '4': 'pending', // Processing + '5': 'failed', // Failure + '6': 'ok', // Completed + }, + }; + const statuses = this.safeValue (statusesByType, type, {}); + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // insertTime: 1517425007000, + // amount: 0.3, + // address: "0x0123456789abcdef", + // addressTag: "", + // txId: "0x0123456789abcdef", + // asset: "ETH", + // status: 1 + // } + // + // fetchWithdrawals + // + // { + // amount: 14, + // address: "0x0123456789abcdef...", + // successTime: 1514489710000, + // transactionFee: 0.01, + // addressTag: "", + // txId: "0x0123456789abcdef...", + // id: "0123456789abcdef...", + // asset: "ETH", + // applyTime: 1514488724000, + // status: 6 + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString (transaction, 'addressTag'); // set but unused + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const txid = this.safeString (transaction, 'txId'); + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + let timestamp = undefined; + const insertTime = this.safeInteger (transaction, 'insertTime'); + const applyTime = this.safeInteger (transaction, 'applyTime'); + let type = this.safeString (transaction, 'type'); + if (type === undefined) { + if ((insertTime !== undefined) && (applyTime === undefined)) { + type = 'deposit'; + timestamp = insertTime; + } else if ((insertTime === undefined) && (applyTime !== undefined)) { + type = 'withdrawal'; + timestamp = applyTime; + } + } + const status = this.parseTransactionStatusByType (this.safeString (transaction, 'status'), type); + const amount = this.safeFloat (transaction, 'amount'); + const feeCost = this.safeFloat (transaction, 'transactionFee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { 'currency': code, 'cost': feeCost }; + } + const updated = this.safeInteger (transaction, 'successTime'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.wapiGetDepositAddress (this.extend (request, params)); + const success = this.safeValue (response, 'success'); + if ((success === undefined) || !success) { + throw new InvalidAddress (this.id + ' fetchDepositAddress returned an empty response – create the deposit address in the user settings first.'); + } + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'addressTag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchFundingFees (codes = undefined, params = {}) { + const response = await this.wapiGetAssetDetail (params); + // + // { + // "success": true, + // "assetDetail": { + // "CTR": { + // "minWithdrawAmount": "70.00000000", //min withdraw amount + // "depositStatus": false,//deposit status + // "withdrawFee": 35, // withdraw fee + // "withdrawStatus": true, //withdraw status + // "depositTip": "Delisted, Deposit Suspended" //reason + // }, + // "SKY": { + // "minWithdrawAmount": "0.02000000", + // "depositStatus": true, + // "withdrawFee": 0.01, + // "withdrawStatus": true + // } + // } + // } + // + const detail = this.safeValue (response, 'assetDetail', {}); + const ids = Object.keys (detail); + const withdrawFees = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + withdrawFees[code] = this.safeFloat (detail[id], 'withdrawFee'); + } + return { + 'withdraw': withdrawFees, + 'deposit': {}, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // name is optional, can be overrided via params + const name = address.slice (0, 20); + const request = { + 'asset': currency['id'], + 'address': address, + 'amount': parseFloat (amount), + 'name': name, // name is optional, can be overrided via params + // https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi + // issue sapiGetCapitalConfigGetall () to get networks for withdrawing USDT ERC20 vs USDT Omni + // 'network': 'ETH', // 'BTC', 'TRX', etc, optional + }; + if (tag !== undefined) { + request['addressTag'] = tag; + } + const response = await this.wapiPostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + parseTradingFee (fee, market = undefined) { + // + // { + // "symbol": "ADABNB", + // "maker": 0.9000, + // "taker": 1.0000 + // } + // + const marketId = this.safeString (fee, 'symbol'); + const symbol = this.safeSymbol (marketId); + return { + 'info': fee, + 'symbol': symbol, + 'maker': this.safeFloat (fee, 'maker'), + 'taker': this.safeFloat (fee, 'taker'), + }; + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.wapiGetTradeFee (this.extend (request, params)); + // + // { + // "tradeFee": [ + // { + // "symbol": "ADABNB", + // "maker": 0.9000, + // "taker": 1.0000 + // } + // ], + // "success": true + // } + // + const tradeFee = this.safeValue (response, 'tradeFee', []); + const first = this.safeValue (tradeFee, 0, {}); + return this.parseTradingFee (first); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.wapiGetTradeFee (params); + // + // { + // "tradeFee": [ + // { + // "symbol": "ADABNB", + // "maker": 0.9000, + // "taker": 1.0000 + // } + // ], + // "success": true + // } + // + const tradeFee = this.safeValue (response, 'tradeFee', []); + const result = {}; + for (let i = 0; i < tradeFee.length; i++) { + const fee = this.parseTradingFee (tradeFee[i]); + const symbol = fee['symbol']; + result[symbol] = fee; + } + return result; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (!(api in this.urls['api'])) { + throw new NotSupported (this.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints'); + } + let url = this.urls['api'][api]; + url += '/' + path; + if (api === 'wapi') { + url += '.html'; + } + const userDataStream = (path === 'userDataStream') || (path === 'listenKey'); + if (path === 'historicalTrades') { + if (this.apiKey) { + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + } else { + throw new AuthenticationError (this.id + ' historicalTrades endpoint requires `apiKey` credential'); + } + } else if (userDataStream) { + if (this.apiKey) { + // v1 special case for userDataStream + body = this.urlencode (params); + headers = { + 'X-MBX-APIKEY': this.apiKey, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } else { + throw new AuthenticationError (this.id + ' userDataStream endpoint requires `apiKey` credential'); + } + } + if ((api === 'private') || (api === 'sapi') || (api === 'wapi' && path !== 'systemStatus') || (api === 'dapiPrivate') || (api === 'fapiPrivate') || (api === 'fapiPrivateV2')) { + this.checkRequiredCredentials (); + let query = undefined; + const recvWindow = this.safeInteger (this.options, 'recvWindow', 5000); + if ((api === 'sapi') && (path === 'asset/dust')) { + query = this.urlencodeWithArrayRepeat (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': recvWindow, + }, params)); + } else if (path === 'batchOrders') { + query = this.rawencode (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': recvWindow, + }, params)); + } else { + query = this.urlencode (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': recvWindow, + }, params)); + } + const signature = this.hmac (this.encode (query), this.encode (this.secret)); + query += '&' + 'signature=' + signature; + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + if ((method === 'GET') || (method === 'DELETE') || (api === 'wapi')) { + url += '?' + query; + } else { + body = query; + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + } else { + // userDataStream endpoints are public, but POST, PUT, DELETE + // therefore they don't accept URL query arguments + // https://github.com/ccxt/ccxt/issues/5224 + if (!userDataStream) { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 418) || (code === 429)) { + throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body); + } + // error response in a form: { "code": -1013, "msg": "Invalid quantity." } + // following block cointains legacy checks against message patterns in "msg" property + // will switch "code" checks eventually, when we know all of them + if (code >= 400) { + if (body.indexOf ('Price * QTY is zero or less') >= 0) { + throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body); + } + if (body.indexOf ('LOT_SIZE') >= 0) { + throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size ' + body); + } + if (body.indexOf ('PRICE_FILTER') >= 0) { + throw new InvalidOrder (this.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use this.priceToPrecision (symbol, amount) ' + body); + } + } + if (response === undefined) { + return; // fallback to default error handler + } + // check success value for wapi endpoints + // response in format {'msg': 'The coin does not exist.', 'success': true/false} + const success = this.safeValue (response, 'success', true); + if (!success) { + const message = this.safeString (response, 'msg'); + let parsedMessage = undefined; + if (message !== undefined) { + try { + parsedMessage = JSON.parse (message); + } catch (e) { + // do nothing + parsedMessage = undefined; + } + if (parsedMessage !== undefined) { + response = parsedMessage; + } + } + } + const message = this.safeString (response, 'msg'); + if (message !== undefined) { + this.throwExactlyMatchedException (this.exceptions, message, this.id + ' ' + message); + } + // checks against error codes + const error = this.safeString (response, 'code'); + if (error !== undefined) { + // https://github.com/ccxt/ccxt/issues/6501 + // https://github.com/ccxt/ccxt/issues/7742 + if ((error === '200') || (error === '0')) { + return; + } + // a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} + // despite that their message is very confusing, it is raised by Binance + // on a temporary ban, the API key is valid, but disabled for a while + if ((error === '-2015') && this.options['hasAlreadyAuthenticatedSuccessfully']) { + throw new DDoSProtection (this.id + ' temporary banned: ' + body); + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, error, feedback); + throw new ExchangeError (feedback); + } + if (!success) { + throw new ExchangeError (this.id + ' ' + body); + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + // a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} + if ((api === 'private') || (api === 'wapi')) { + this.options['hasAlreadyAuthenticatedSuccessfully'] = true; + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],24:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const binance = require ('./binance.js'); + +// --------------------------------------------------------------------------- + +module.exports = class binanceus extends binance { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'binanceus', + 'name': 'Binance US', + 'countries': [ 'US' ], // US + 'certified': false, + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/65177307-217b7c80-da5f-11e9-876e-0b748ba0a358.jpg', + 'api': { + 'web': 'https://www.binance.us', + 'wapi': 'https://api.binance.us/wapi/v3', + 'public': 'https://api.binance.us/api/v1', + 'private': 'https://api.binance.us/api/v3', + 'v3': 'https://api.binance.us/api/v3', + 'v1': 'https://api.binance.us/api/v1', + }, + 'www': 'https://www.binance.us', + 'referral': 'https://www.binance.us/?ref=35005074', + 'doc': 'https://github.com/binance-us/binance-official-api-docs', + 'fees': 'https://www.binance.us/en/fee/schedule', + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1 + 'maker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1 + }, + }, + 'options': { + 'quoteOrderQty': false, + }, + }); + } +}; + + +},{"./binance.js":23}],25:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, InvalidNonce, AuthenticationError, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bit2c extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bit2c', + 'name': 'Bit2C', + 'countries': [ 'IL' ], // Israel + 'rateLimit': 3000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766119-3593220e-5ece-11e7-8b3a-5a041f6bcc3f.jpg', + 'api': 'https://bit2c.co.il', + 'www': 'https://www.bit2c.co.il', + 'referral': 'https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf', + 'doc': [ + 'https://www.bit2c.co.il/home/api', + 'https://github.com/OferE/bit2c', + ], + }, + 'api': { + 'public': { + 'get': [ + 'Exchanges/{pair}/Ticker', + 'Exchanges/{pair}/orderbook', + 'Exchanges/{pair}/trades', + 'Exchanges/{pair}/lasttrades', + ], + }, + 'private': { + 'post': [ + 'Merchant/CreateCheckout', + 'Order/AddCoinFundsRequest', + 'Order/AddFund', + 'Order/AddOrder', + 'Order/AddOrderMarketPriceBuy', + 'Order/AddOrderMarketPriceSell', + 'Order/CancelOrder', + 'Order/AddCoinFundsRequest', + 'Order/AddStopOrder', + 'Payment/GetMyId', + 'Payment/Send', + 'Payment/Pay', + ], + 'get': [ + 'Account/Balance', + 'Account/Balance/v2', + 'Order/MyOrders', + 'Order/GetById', + 'Order/AccountHistory', + 'Order/OrderHistory', + ], + }, + }, + 'markets': { + 'BTC/NIS': { 'id': 'BtcNis', 'symbol': 'BTC/NIS', 'base': 'BTC', 'quote': 'NIS', 'baseId': 'Btc', 'quoteId': 'Nis' }, + 'ETH/NIS': { 'id': 'EthNis', 'symbol': 'ETH/NIS', 'base': 'ETH', 'quote': 'NIS', 'baseId': 'Eth', 'quoteId': 'Nis' }, + 'BCH/NIS': { 'id': 'BchabcNis', 'symbol': 'BCH/NIS', 'base': 'BCH', 'quote': 'NIS', 'baseId': 'Bchabc', 'quoteId': 'Nis' }, + 'LTC/NIS': { 'id': 'LtcNis', 'symbol': 'LTC/NIS', 'base': 'LTC', 'quote': 'NIS', 'baseId': 'Ltc', 'quoteId': 'Nis' }, + 'ETC/NIS': { 'id': 'EtcNis', 'symbol': 'ETC/NIS', 'base': 'ETC', 'quote': 'NIS', 'baseId': 'Etc', 'quoteId': 'Nis' }, + 'BTG/NIS': { 'id': 'BtgNis', 'symbol': 'BTG/NIS', 'base': 'BTG', 'quote': 'NIS', 'baseId': 'Btg', 'quoteId': 'Nis' }, + 'BSV/NIS': { 'id': 'BchsvNis', 'symbol': 'BSV/NIS', 'base': 'BSV', 'quote': 'NIS', 'baseId': 'Bchsv', 'quoteId': 'Nis' }, + 'GRIN/NIS': { 'id': 'GrinNis', 'symbol': 'GRIN/NIS', 'base': 'GRIN', 'quote': 'NIS', 'baseId': 'Grin', 'quoteId': 'Nis' }, + }, + 'fees': { + 'trading': { + 'maker': 0.5 / 100, + 'taker': 0.5 / 100, + }, + }, + 'options': { + 'fetchTradesMethod': 'public_get_exchanges_pair_trades', + }, + 'exceptions': { + 'exact': { + 'Please provide valid APIkey': AuthenticationError, // { "error" : "Please provide valid APIkey" } + }, + 'broad': { + // { "error": "Please provide valid nonce in Request Nonce (1598218490) is not bigger than last nonce (1598218490)."} + // { "error": "Please provide valid nonce in Request UInt64.TryParse failed for nonce :" } + 'Please provide valid nonce': InvalidNonce, + 'please approve new terms of use on site': PermissionDenied, // { "error" : "please approve new terms of use on site." } + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balance = await this.privateGetAccountBalanceV2 (params); + // + // { + // "AVAILABLE_NIS": 0.0, + // "NIS": 0.0, + // "LOCKED_NIS": 0.0, + // "AVAILABLE_BTC": 0.0, + // "BTC": 0.0, + // "LOCKED_BTC": 0.0, + // "AVAILABLE_ETH": 0.0, + // "ETH": 0.0, + // "LOCKED_ETH": 0.0, + // "AVAILABLE_BCHSV": 0.0, + // "BCHSV": 0.0, + // "LOCKED_BCHSV": 0.0, + // "AVAILABLE_BCHABC": 0.0, + // "BCHABC": 0.0, + // "LOCKED_BCHABC": 0.0, + // "AVAILABLE_LTC": 0.0, + // "LTC": 0.0, + // "LOCKED_LTC": 0.0, + // "AVAILABLE_ETC": 0.0, + // "ETC": 0.0, + // "LOCKED_ETC": 0.0, + // "AVAILABLE_BTG": 0.0, + // "BTG": 0.0, + // "LOCKED_BTG": 0.0, + // "AVAILABLE_GRIN": 0.0, + // "GRIN": 0.0, + // "LOCKED_GRIN": 0.0, + // "Fees": { + // "BtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "EthNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "BchabcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "LtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "EtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "BtgNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "LtcBtc": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "BchsvNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "GrinNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 } + // } + // } + // + const result = { 'info': balance }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const account = this.account (); + const currencyId = this.currencyId (code); + const uppercase = currencyId.toUpperCase (); + if (uppercase in balance) { + account['free'] = this.safeFloat (balance, 'AVAILABLE_' + uppercase); + account['total'] = this.safeFloat (balance, uppercase); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const orderbook = await this.publicGetExchangesPairOrderbook (this.extend (request, params)); + return this.parseOrderBook (orderbook); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const ticker = await this.publicGetExchangesPairTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const averagePrice = this.safeFloat (ticker, 'av'); + const baseVolume = this.safeFloat (ticker, 'a'); + let quoteVolume = undefined; + if (baseVolume !== undefined && averagePrice !== undefined) { + quoteVolume = baseVolume * averagePrice; + } + const last = this.safeFloat (ticker, 'll'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'h'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'l'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': averagePrice, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = this.options['fetchTradesMethod']; + const request = { + 'pair': market['id'], + }; + if (since !== undefined) { + request['date'] = parseInt (since); + } + if (limit !== undefined) { + request['limit'] = limit; // max 100000 + } + const response = await this[method] (this.extend (request, params)); + if (typeof response === 'string') { + throw new ExchangeError (response); + } + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let method = 'privatePostOrderAddOrder'; + const request = { + 'Amount': amount, + 'Pair': this.marketId (symbol), + }; + if (type === 'market') { + method += 'MarketPrice' + this.capitalize (side); + } else { + request['Price'] = price; + request['Total'] = amount * price; + request['IsBid'] = (side === 'buy'); + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['NewOrder']['id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privatePostOrderCancelOrder (this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.privateGetOrderMyOrders (this.extend (request, params)); + const orders = this.safeValue (response, market['id'], {}); + const asks = this.safeValue (orders, 'ask', []); + const bids = this.safeValue (orders, 'bid', []); + return this.parseOrders (this.arrayConcat (asks, bids), market, since, limit); + } + + parseOrder (order, market = undefined) { + const timestamp = this.safeInteger (order, 'created'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let side = this.safeValue (order, 'type'); + if (side === 0) { + side = 'buy'; + } else if (side === 1) { + side = 'sell'; + } + const id = this.safeString (order, 'id'); + const status = this.safeString (order, 'status'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': undefined, + 'remaining': undefined, + 'cost': cost, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (limit !== undefined) { + request['take'] = limit; + } + request['take'] = limit; + if (since !== undefined) { + request['toTime'] = this.ymd (this.milliseconds (), '.'); + request['fromTime'] = this.ymd (since, '.'); + } + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privateGetOrderOrderHistory (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + let timestamp = undefined; + let id = undefined; + let price = undefined; + let amount = undefined; + let orderId = undefined; + let feeCost = undefined; + let side = undefined; + const reference = this.safeString (trade, 'reference'); + if (reference !== undefined) { + timestamp = this.safeTimestamp (trade, 'ticks'); + price = this.safeFloat (trade, 'price'); + amount = this.safeFloat (trade, 'firstAmount'); + const reference_parts = reference.split ('|'); // reference contains 'pair|orderId|tradeId' + if (market === undefined) { + const marketId = this.safeString (trade, 'pair'); + if (marketId in this.markets_by_id[marketId]) { + market = this.markets_by_id[marketId]; + } else if (reference_parts[0] in this.markets_by_id) { + market = this.markets_by_id[reference_parts[0]]; + } + } + orderId = reference_parts[1]; + id = reference_parts[2]; + side = this.safeInteger (trade, 'action'); + if (side === 0) { + side = 'buy'; + } else if (side === 1) { + side = 'sell'; + } + feeCost = this.safeFloat (trade, 'feeAmount'); + } else { + timestamp = this.safeTimestamp (trade, 'date'); + id = this.safeString (trade, 'tid'); + price = this.safeFloat (trade, 'price'); + amount = this.safeFloat (trade, 'amount'); + side = this.safeValue (trade, 'isBid'); + if (side !== undefined) { + if (side) { + side = 'buy'; + } else { + side = 'sell'; + } + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': price * amount, + 'fee': { + 'cost': feeCost, + 'currency': 'NIS', + 'rate': undefined, + }, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + if (api === 'public') { + url += '.json'; + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const query = this.extend ({ + 'nonce': nonce, + }, params); + const auth = this.urlencode (query); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + auth; + } + } else { + body = auth; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512', 'base64'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'key': this.apiKey, + 'sign': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { "error" : "please approve new terms of use on site." } + // { "error": "Please provide valid nonce in Request Nonce (1598218490) is not bigger than last nonce (1598218490)."} + // + const error = this.safeString (response, 'error'); + if (error !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + + +},{"./base/Exchange":5,"./base/errors":7}],26:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, InvalidNonce, InsufficientFunds, InvalidOrder, OrderNotFound, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitbank extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitbank', + 'name': 'bitbank', + 'countries': [ 'JP' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '4h': '4hour', + '8h': '8hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/37808081-b87f2d9c-2e59-11e8-894d-c1900b7584fe.jpg', + 'api': { + 'public': 'https://public.bitbank.cc', + 'private': 'https://api.bitbank.cc', + }, + 'www': 'https://bitbank.cc/', + 'doc': 'https://docs.bitbank.cc/', + 'fees': 'https://bitbank.cc/docs/fees/', + }, + 'api': { + 'public': { + 'get': [ + '{pair}/ticker', + '{pair}/depth', + '{pair}/transactions', + '{pair}/transactions/{yyyymmdd}', + '{pair}/candlestick/{candletype}/{yyyymmdd}', + ], + }, + 'private': { + 'get': [ + 'user/assets', + 'user/spot/order', + 'user/spot/active_orders', + 'user/spot/trade_history', + 'user/withdrawal_account', + ], + 'post': [ + 'user/spot/order', + 'user/spot/cancel_order', + 'user/spot/cancel_orders', + 'user/spot/orders_info', + 'user/request_withdrawal', + ], + }, + }, + 'markets': { + 'BCH/BTC': { 'id': 'bcc_btc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bcc', 'quoteId': 'btc' }, + 'BCH/JPY': { 'id': 'bcc_jpy', 'symbol': 'BCH/JPY', 'base': 'BCH', 'quote': 'JPY', 'baseId': 'bcc', 'quoteId': 'jpy' }, + 'MONA/BTC': { 'id': 'mona_btc', 'symbol': 'MONA/BTC', 'base': 'MONA', 'quote': 'BTC', 'baseId': 'mona', 'quoteId': 'btc' }, + 'MONA/JPY': { 'id': 'mona_jpy', 'symbol': 'MONA/JPY', 'base': 'MONA', 'quote': 'JPY', 'baseId': 'mona', 'quoteId': 'jpy' }, + 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc' }, + 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' }, + 'XRP/JPY': { 'id': 'xrp_jpy', 'symbol': 'XRP/JPY', 'base': 'XRP', 'quote': 'JPY', 'baseId': 'xrp', 'quoteId': 'jpy' }, + 'XLM/JPY': { 'id': 'xlm_jpy', 'symbol': 'XLM/JPY', 'base': 'XLM', 'quote': 'JPY', 'baseId': 'xlm', 'quoteId': 'jpy' }, + 'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' }, + 'ETH/JPY': { 'id': 'eth_jpy', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy' }, + 'LTC/JPY': { 'id': 'ltc_jpy', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy' }, + 'XRP/BTC': { 'id': 'xrp_btc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc' }, + }, + 'fees': { + 'trading': { + 'maker': -0.02 / 100, + 'taker': 0.12 / 100, + }, + 'funding': { + 'withdraw': { + // 'JPY': (amount > 30000) ? 756 : 540, + 'BTC': 0.001, + 'LTC': 0.001, + 'XRP': 0.15, + 'ETH': 0.0005, + 'MONA': 0.001, + 'BCC': 0.001, + }, + }, + }, + 'precision': { + 'price': 8, + 'amount': 8, + }, + 'exceptions': { + '20001': AuthenticationError, + '20002': AuthenticationError, + '20003': AuthenticationError, + '20005': AuthenticationError, + '20004': InvalidNonce, + '40020': InvalidOrder, + '40021': InvalidOrder, + '40025': ExchangeError, + '40013': OrderNotFound, + '40014': OrderNotFound, + '50008': PermissionDenied, + '50009': OrderNotFound, + '50010': OrderNotFound, + '60001': InsufficientFunds, + '60005': InvalidOrder, + }, + }); + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTicker (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetPairDepth (this.extend (request, params)); + const orderbook = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeInteger (trade, 'executed_at'); + let symbol = undefined; + let feeCurrency = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const id = this.safeString2 (trade, 'transaction_id', 'trade_id'); + const takerOrMaker = this.safeString (trade, 'maker_taker'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee_amount_quote'); + if (feeCost !== undefined) { + fee = { + 'currency': market['quote'], + 'cost': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'order_id'); + const type = this.safeString (trade, 'type'); + const side = this.safeString (trade, 'side'); + return { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTransactions (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'transactions', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "0.02501786", + // "0.02501786", + // "0.02501786", + // "0.02501786", + // "0.0000", + // 1591488000000 + // ] + // + return [ + this.safeInteger (ohlcv, 5), + this.safeFloat (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let date = this.milliseconds (); + date = this.ymd (date); + date = date.split ('-'); + const request = { + 'pair': market['id'], + 'candletype': this.timeframes[timeframe], + 'yyyymmdd': date.join (''), + }; + const response = await this.publicGetPairCandlestickCandletypeYyyymmdd (this.extend (request, params)); + // + // { + // "success":1, + // "data":{ + // "candlestick":[ + // { + // "type":"5min", + // "ohlcv":[ + // ["0.02501786","0.02501786","0.02501786","0.02501786","0.0000",1591488000000], + // ["0.02501747","0.02501953","0.02501747","0.02501953","0.3017",1591488300000], + // ["0.02501762","0.02501762","0.02500392","0.02500392","0.1500",1591488600000], + // ] + // } + // ], + // "timestamp":1591508668190 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const candlestick = this.safeValue (data, 'candlestick', []); + const first = this.safeValue (candlestick, 0, {}); + const ohlcv = this.safeValue (first, 'ohlcv', []); + return this.parseOHLCVs (ohlcv, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUserAssets (params); + const result = { 'info': response }; + const data = this.safeValue (response, 'data', {}); + const assets = this.safeValue (data, 'assets', []); + for (let i = 0; i < assets.length; i++) { + const balance = assets[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'free_amount'), + 'used': this.safeFloat (balance, 'locked_amount'), + 'total': this.safeFloat (balance, 'onhand_amount'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'UNFILLED': 'open', + 'PARTIALLY_FILLED': 'open', + 'FULLY_FILLED': 'closed', + 'CANCELED_UNFILLED': 'canceled', + 'CANCELED_PARTIALLY_FILLED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'order_id'); + const marketId = this.safeString (order, 'pair'); + let symbol = undefined; + if (marketId && !market && (marketId in this.marketsById)) { + market = this.marketsById[marketId]; + } + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (order, 'ordered_at'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'start_amount'); + const filled = this.safeFloat (order, 'executed_amount'); + const remaining = this.safeFloat (order, 'remaining_amount'); + const average = this.safeFloat (order, 'average_price'); + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = filled * average; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': order, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a price argument for both market and limit orders'); + } + const request = { + 'pair': market['id'], + 'amount': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), + 'side': side, + 'type': type, + }; + const response = await this.privatePostUserSpotOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'pair': market['id'], + }; + const response = await this.privatePostUserSpotCancelOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return data; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'pair': market['id'], + }; + const response = await this.privateGetUserSpotOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privateGetUserSpotActiveOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const orders = this.safeValue (data, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = {}; + if (market !== undefined) { + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['count'] = limit; + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privateGetUserSpotTradeHistory (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privateGetUserWithdrawalAccount (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + // Not sure about this if there could be more than one account... + const accounts = this.safeValue (data, 'accounts', []); + const firstAccount = this.safeValue (accounts, 0, {}); + const address = this.safeString (firstAccount, 'address'); + return { + 'currency': currency, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + if (!('uuid' in params)) { + throw new ExchangeError (this.id + ' uuid is required for withdrawal'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + 'amount': amount, + }; + const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const txid = this.safeString (data, 'txid'); + return { + 'info': response, + 'id': txid, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'][api] + '/'; + if (api === 'public') { + url += this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let auth = nonce; + url += this.version + '/' + this.implodeParams (path, params); + if (method === 'POST') { + body = this.json (query); + auth += body; + } else { + auth += '/' + this.version + '/' + path; + if (Object.keys (query).length) { + query = this.urlencode (query); + url += '?' + query; + auth += '?' + query; + } + } + headers = { + 'Content-Type': 'application/json', + 'ACCESS-KEY': this.apiKey, + 'ACCESS-NONCE': nonce, + 'ACCESS-SIGNATURE': this.hmac (this.encode (auth), this.encode (this.secret)), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const success = this.safeInteger (response, 'success'); + const data = this.safeValue (response, 'data'); + if (!success || !data) { + const errorMessages = { + '10000': 'URL does not exist', + '10001': 'A system error occurred. Please contact support', + '10002': 'Invalid JSON format. Please check the contents of transmission', + '10003': 'A system error occurred. Please contact support', + '10005': 'A timeout error occurred. Please wait for a while and try again', + '20001': 'API authentication failed', + '20002': 'Illegal API key', + '20003': 'API key does not exist', + '20004': 'API Nonce does not exist', + '20005': 'API signature does not exist', + '20011': 'Two-step verification failed', + '20014': 'SMS authentication failed', + '30001': 'Please specify the order quantity', + '30006': 'Please specify the order ID', + '30007': 'Please specify the order ID array', + '30009': 'Please specify the stock', + '30012': 'Please specify the order price', + '30013': 'Trade Please specify either', + '30015': 'Please specify the order type', + '30016': 'Please specify asset name', + '30019': 'Please specify uuid', + '30039': 'Please specify the amount to be withdrawn', + '40001': 'The order quantity is invalid', + '40006': 'Count value is invalid', + '40007': 'End time is invalid', + '40008': 'end_id Value is invalid', + '40009': 'The from_id value is invalid', + '40013': 'The order ID is invalid', + '40014': 'The order ID array is invalid', + '40015': 'Too many specified orders', + '40017': 'Incorrect issue name', + '40020': 'The order price is invalid', + '40021': 'The trading classification is invalid', + '40022': 'Start date is invalid', + '40024': 'The order type is invalid', + '40025': 'Incorrect asset name', + '40028': 'uuid is invalid', + '40048': 'The amount of withdrawal is illegal', + '50003': 'Currently, this account is in a state where you can not perform the operation you specified. Please contact support', + '50004': 'Currently, this account is temporarily registered. Please try again after registering your account', + '50005': 'Currently, this account is locked. Please contact support', + '50006': 'Currently, this account is locked. Please contact support', + '50008': 'User identification has not been completed', + '50009': 'Your order does not exist', + '50010': 'Can not cancel specified order', + '50011': 'API not found', + '60001': 'The number of possessions is insufficient', + '60002': 'It exceeds the quantity upper limit of the tender buying order', + '60003': 'The specified quantity exceeds the limit', + '60004': 'The specified quantity is below the threshold', + '60005': 'The specified price is above the limit', + '60006': 'The specified price is below the lower limit', + '70001': 'A system error occurred. Please contact support', + '70002': 'A system error occurred. Please contact support', + '70003': 'A system error occurred. Please contact support', + '70004': 'We are unable to accept orders as the transaction is currently suspended', + '70005': 'Order can not be accepted because purchase order is currently suspended', + '70006': 'We can not accept orders because we are currently unsubscribed ', + '70009': 'We are currently temporarily restricting orders to be carried out. Please use the limit order.', + '70010': 'We are temporarily raising the minimum order quantity as the system load is now rising.', + }; + const errorClasses = this.exceptions; + const code = this.safeString (data, 'code'); + const message = this.safeString (errorMessages, code, 'Error'); + const ErrorClass = this.safeValue (errorClasses, code); + if (ErrorClass !== undefined) { + throw new ErrorClass (message); + } else { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],27:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InvalidNonce, InsufficientFunds, AuthenticationError, InvalidOrder, ExchangeError, OrderNotFound, AccountSuspended, BadSymbol, OrderImmediatelyFillable, RateLimitExceeded, OnMaintenance, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitbay extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitbay', + 'name': 'BitBay', + 'countries': [ 'MT', 'EU' ], // Malta + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '3d': '259200', + '1w': '604800', + }, + 'urls': { + 'referral': 'https://auth.bitbay.net/ref/jHlbB4mIkdS1', + 'logo': 'https://user-images.githubusercontent.com/1294454/27766132-978a7bd8-5ece-11e7-9540-bc96d1e9bbb8.jpg', + 'www': 'https://bitbay.net', + 'api': { + 'public': 'https://bitbay.net/API/Public', + 'private': 'https://bitbay.net/API/Trading/tradingApi.php', + 'v1_01Public': 'https://api.bitbay.net/rest', + 'v1_01Private': 'https://api.bitbay.net/rest', + }, + 'doc': [ + 'https://bitbay.net/public-api', + 'https://bitbay.net/en/private-api', + 'https://bitbay.net/account/tab-api', + 'https://github.com/BitBayNet/API', + 'https://docs.bitbay.net/v1.0.1-en/reference', + ], + 'support': 'https://support.bitbay.net', + 'fees': 'https://bitbay.net/en/fees', + }, + 'api': { + 'public': { + 'get': [ + '{id}/all', + '{id}/market', + '{id}/orderbook', + '{id}/ticker', + '{id}/trades', + ], + }, + 'private': { + 'post': [ + 'info', + 'trade', + 'cancel', + 'orderbook', + 'orders', + 'transfer', + 'withdraw', + 'history', + 'transactions', + ], + }, + 'v1_01Public': { + 'get': [ + 'trading/ticker', + 'trading/ticker/{symbol}', + 'trading/stats', + 'trading/orderbook/{symbol}', + 'trading/transactions/{symbol}', + 'trading/candle/history/{symbol}/{resolution}', + ], + }, + 'v1_01Private': { + 'get': [ + 'payments/withdrawal/{detailId}', + 'payments/deposit/{detailId}', + 'trading/offer', + 'trading/config/{symbol}', + 'trading/history/transactions', + 'balances/BITBAY/history', + 'balances/BITBAY/balance', + 'fiat_cantor/rate/{baseId}/{quoteId}', + 'fiat_cantor/history', + ], + 'post': [ + 'trading/offer/{symbol}', + 'trading/config/{symbol}', + 'balances/BITBAY/balance', + 'balances/BITBAY/balance/transfer/{source}/{destination}', + 'fiat_cantor/exchange', + ], + 'delete': [ + 'trading/offer/{symbol}/{id}/{side}/{price}', + ], + 'put': [ + 'balances/BITBAY/balance/{id}', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.0, + 'taker': 0.1 / 100, + 'percentage': true, + 'tierBased': false, + }, + 'fiat': { + 'maker': 0.30 / 100, + 'taker': 0.43 / 100, + 'percentage': true, + 'tierBased': true, + 'tiers': { + 'taker': [ + [ 0.0043, 0 ], + [ 0.0042, 1250 ], + [ 0.0041, 3750 ], + [ 0.0040, 7500 ], + [ 0.0039, 10000 ], + [ 0.0038, 15000 ], + [ 0.0037, 20000 ], + [ 0.0036, 25000 ], + [ 0.0035, 37500 ], + [ 0.0034, 50000 ], + [ 0.0033, 75000 ], + [ 0.0032, 100000 ], + [ 0.0031, 150000 ], + [ 0.0030, 200000 ], + [ 0.0029, 250000 ], + [ 0.0028, 375000 ], + [ 0.0027, 500000 ], + [ 0.0026, 625000 ], + [ 0.0025, 875000 ], + ], + 'maker': [ + [ 0.0030, 0 ], + [ 0.0029, 1250 ], + [ 0.0028, 3750 ], + [ 0.0028, 7500 ], + [ 0.0027, 10000 ], + [ 0.0026, 15000 ], + [ 0.0025, 20000 ], + [ 0.0025, 25000 ], + [ 0.0024, 37500 ], + [ 0.0023, 50000 ], + [ 0.0023, 75000 ], + [ 0.0022, 100000 ], + [ 0.0021, 150000 ], + [ 0.0021, 200000 ], + [ 0.0020, 250000 ], + [ 0.0019, 375000 ], + [ 0.0018, 500000 ], + [ 0.0018, 625000 ], + [ 0.0017, 875000 ], + ], + }, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.0009, + 'LTC': 0.005, + 'ETH': 0.00126, + 'LSK': 0.2, + 'BCH': 0.0006, + 'GAME': 0.005, + 'DASH': 0.001, + 'BTG': 0.0008, + 'PLN': 4, + 'EUR': 1.5, + }, + }, + }, + 'options': { + 'fiatCurrencies': [ 'EUR', 'USD', 'GBP', 'PLN' ], + }, + 'exceptions': { + '400': ExchangeError, // At least one parameter wasn't set + '401': InvalidOrder, // Invalid order type + '402': InvalidOrder, // No orders with specified currencies + '403': InvalidOrder, // Invalid payment currency name + '404': InvalidOrder, // Error. Wrong transaction type + '405': InvalidOrder, // Order with this id doesn't exist + '406': InsufficientFunds, // No enough money or crypto + // code 407 not specified are not specified in their docs + '408': InvalidOrder, // Invalid currency name + '501': AuthenticationError, // Invalid public key + '502': AuthenticationError, // Invalid sign + '503': InvalidNonce, // Invalid moment parameter. Request time doesn't match current server time + '504': ExchangeError, // Invalid method + '505': AuthenticationError, // Key has no permission for this action + '506': AccountSuspended, // Account locked. Please contact with customer service + // codes 507 and 508 are not specified in their docs + '509': ExchangeError, // The BIC/SWIFT is required for this currency + '510': BadSymbol, // Invalid market name + 'FUNDS_NOT_SUFFICIENT': InsufficientFunds, + 'OFFER_FUNDS_NOT_EXCEEDING_MINIMUMS': InvalidOrder, + 'OFFER_NOT_FOUND': OrderNotFound, + 'OFFER_WOULD_HAVE_BEEN_PARTIALLY_FILLED': OrderImmediatelyFillable, + 'ACTION_LIMIT_EXCEEDED': RateLimitExceeded, + 'UNDER_MAINTENANCE': OnMaintenance, + 'REQUEST_TIMESTAMP_TOO_OLD': InvalidNonce, + 'PERMISSIONS_NOT_SUFFICIENT': PermissionDenied, + }, + 'commonCurrencies': { + 'GGC': 'Global Game Coin', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.v1_01PublicGetTradingTicker (params); + const fiatCurrencies = this.safeValue (this.options, 'fiatCurrencies', []); + // + // { + // status: 'Ok', + // items: { + // 'BSV-USD': { + // market: { + // code: 'BSV-USD', + // first: { currency: 'BSV', minOffer: '0.00035', scale: 8 }, + // second: { currency: 'USD', minOffer: '5', scale: 2 } + // }, + // time: '1557569762154', + // highestBid: '52.31', + // lowestAsk: '62.99', + // rate: '63', + // previousRate: '51.21', + // }, + // }, + // } + // + const result = []; + const items = this.safeValue (response, 'items'); + const keys = Object.keys (items); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const item = items[key]; + const market = this.safeValue (item, 'market', {}); + const first = this.safeValue (market, 'first', {}); + const second = this.safeValue (market, 'second', {}); + const baseId = this.safeString (first, 'currency'); + const quoteId = this.safeString (second, 'currency'); + const id = baseId + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (first, 'scale'), + 'price': this.safeInteger (second, 'scale'), + }; + let fees = this.safeValue (this.fees, 'trading', {}); + if (this.inArray (base, fiatCurrencies) || this.inArray (quote, fiatCurrencies)) { + fees = this.safeValue (this.fees, 'fiat', {}); + } + const maker = this.safeFloat (fees, 'maker'); + const taker = this.safeFloat (fees, 'taker'); + // todo: check that the limits have ben interpreted correctly + // todo: parse the fees page + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'active': undefined, + 'maker': maker, + 'taker': taker, + 'limits': { + 'amount': { + 'min': this.safeFloat (first, 'minOffer'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (second, 'minOffer'), + 'max': undefined, + }, + }, + 'info': item, + }); + } + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const response = await this.v1_01PrivateGetTradingOffer (this.extend (request, params)); + const items = this.safeValue (response, 'items', []); + return this.parseOrders (items, undefined, since, limit, { 'status': 'open' }); + } + + parseOrder (order, market = undefined) { + // + // { + // market: 'ETH-EUR', + // offerType: 'Sell', + // id: '93d3657b-d616-11e9-9248-0242ac110005', + // currentAmount: '0.04', + // lockedAmount: '0.04', + // rate: '280', + // startAmount: '0.04', + // time: '1568372806924', + // postOnly: false, + // hidden: false, + // mode: 'limit', + // receivedAmount: '0.0', + // firstBalanceId: '5b816c3e-437c-4e43-9bef-47814ae7ebfc', + // secondBalanceId: 'ab43023b-4079-414c-b340-056e3430a3af' + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.safeInteger (order, 'time'); + const amount = this.safeFloat (order, 'startAmount'); + const remaining = this.safeFloat (order, 'currentAmount'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = Math.max (0, amount - remaining); + } + } + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': undefined, + 'symbol': symbol, + 'type': this.safeString (order, 'mode'), + 'timeInForce': undefined, + 'side': this.safeStringLower (order, 'offerType'), + 'price': this.safeFloat (order, 'rate'), + 'stopPrice': undefined, + 'amount': amount, + 'cost': undefined, + 'filled': filled, + 'remaining': remaining, + 'average': undefined, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbol) { + const markets = [ this.marketId (symbol) ]; + request['markets'] = markets; + } + const query = { 'query': this.json (this.extend (request, params)) }; + const response = await this.v1_01PrivateGetTradingHistoryTransactions (query); + // + // { + // status: 'Ok', + // totalRows: '67', + // items: [ + // { + // id: 'b54659a0-51b5-42a0-80eb-2ac5357ccee2', + // market: 'BTC-EUR', + // time: '1541697096247', + // amount: '0.00003', + // rate: '4341.44', + // initializedBy: 'Sell', + // wasTaker: false, + // userAction: 'Buy', + // offerId: 'bd19804a-6f89-4a69-adb8-eb078900d006', + // commissionValue: null + // }, + // ] + // } + // + const items = this.safeValue (response, 'items'); + const result = this.parseTrades (items, undefined, since, limit); + if (symbol === undefined) { + return result; + } + return this.filterBySymbol (result, symbol); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.v1_01PrivateGetBalancesBITBAYBalance (params); + const balances = this.safeValue (response, 'balances'); + if (balances === undefined) { + throw new ExchangeError (this.id + ' empty balance response ' + this.json (response)); + } + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'lockedFunds'); + account['free'] = this.safeFloat (balance, 'availableFunds'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const orderbook = await this.publicGetIdOrderbook (this.extend (request, params)); + return this.parseOrderBook (orderbook); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const ticker = await this.publicGetIdTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const baseVolume = this.safeFloat (ticker, 'volume'); + const vwap = this.safeFloat (ticker, 'vwap'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max'), + 'low': this.safeFloat (ticker, 'min'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'average'), + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + const balanceCurrencies = []; + if (code !== undefined) { + const currency = this.currency (code); + balanceCurrencies.push (currency['id']); + } + let request = { + 'balanceCurrencies': balanceCurrencies, + }; + if (since !== undefined) { + request['fromTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + request = this.extend (request, params); + const response = await this.v1_01PrivateGetBalancesBITBAYHistory ({ 'query': this.json (request) }); + const items = response['items']; + return this.parseLedger (items, undefined, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // FUNDS_MIGRATION + // { + // "historyId": "84ea7a29-7da5-4de5-b0c0-871e83cad765", + // "balance": { + // "id": "821ec166-cb88-4521-916c-f4eb44db98df", + // "currency": "LTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "LTC" + // }, + // "detailId": null, + // "time": 1506128252968, + // "type": "FUNDS_MIGRATION", + // "value": 0.0009957, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 0.0009957, "available": 0.0009957, "locked": 0 }, + // "change": { "total": 0.0009957, "available": 0.0009957, "locked": 0 } + // } + // + // CREATE_BALANCE + // { + // "historyId": "d0fabd8d-9107-4b5e-b9a6-3cab8af70d49", + // "balance": { + // "id": "653ffcf2-3037-4ebe-8e13-d5ea1a01d60d", + // "currency": "BTG", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTG" + // }, + // "detailId": null, + // "time": 1508895244751, + // "type": "CREATE_BALANCE", + // "value": 0, + // "fundsBefore": { "total": null, "available": null, "locked": null }, + // "fundsAfter": { "total": 0, "available": 0, "locked": 0 }, + // "change": { "total": 0, "available": 0, "locked": 0 } + // } + // + // BITCOIN_GOLD_FORK + // { + // "historyId": "2b4d52d3-611c-473d-b92c-8a8d87a24e41", + // "balance": { + // "id": "653ffcf2-3037-4ebe-8e13-d5ea1a01d60d", + // "currency": "BTG", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTG" + // }, + // "detailId": null, + // "time": 1508895244778, + // "type": "BITCOIN_GOLD_FORK", + // "value": 0.00453512, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 0.00453512, "available": 0.00453512, "locked": 0 }, + // "change": { "total": 0.00453512, "available": 0.00453512, "locked": 0 } + // } + // + // ADD_FUNDS + // { + // "historyId": "3158236d-dae5-4a5d-81af-c1fa4af340fb", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "8e83a960-e737-4380-b8bb-259d6e236faa", + // "time": 1520631178816, + // "type": "ADD_FUNDS", + // "value": 0.628405, + // "fundsBefore": { "total": 0.00453512, "available": 0.00453512, "locked": 0 }, + // "fundsAfter": { "total": 0.63294012, "available": 0.63294012, "locked": 0 }, + // "change": { "total": 0.628405, "available": 0.628405, "locked": 0 } + // } + // + // TRANSACTION_PRE_LOCKING + // { + // "historyId": "e7d19e0f-03b3-46a8-bc72-dde72cc24ead", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": null, + // "time": 1520706403868, + // "type": "TRANSACTION_PRE_LOCKING", + // "value": -0.1, + // "fundsBefore": { "total": 0.63294012, "available": 0.63294012, "locked": 0 }, + // "fundsAfter": { "total": 0.63294012, "available": 0.53294012, "locked": 0.1 }, + // "change": { "total": 0, "available": -0.1, "locked": 0.1 } + // } + // + // TRANSACTION_POST_OUTCOME + // { + // "historyId": "c4010825-231d-4a9c-8e46-37cde1f7b63c", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "bf2876bc-b545-4503-96c8-ef4de8233876", + // "time": 1520706404032, + // "type": "TRANSACTION_POST_OUTCOME", + // "value": -0.01771415, + // "fundsBefore": { "total": 0.63294012, "available": 0.53294012, "locked": 0.1 }, + // "fundsAfter": { "total": 0.61522597, "available": 0.53294012, "locked": 0.08228585 }, + // "change": { "total": -0.01771415, "available": 0, "locked": -0.01771415 } + // } + // + // TRANSACTION_POST_INCOME + // { + // "historyId": "7f18b7af-b676-4125-84fd-042e683046f6", + // "balance": { + // "id": "ab43023b-4079-414c-b340-056e3430a3af", + // "currency": "EUR", + // "type": "FIAT", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "EUR" + // }, + // "detailId": "f5fcb274-0cc7-4385-b2d3-bae2756e701f", + // "time": 1520706404035, + // "type": "TRANSACTION_POST_INCOME", + // "value": 628.78, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 628.78, "available": 628.78, "locked": 0 }, + // "change": { "total": 628.78, "available": 628.78, "locked": 0 } + // } + // + // TRANSACTION_COMMISSION_OUTCOME + // { + // "historyId": "843177fa-61bc-4cbf-8be5-b029d856c93b", + // "balance": { + // "id": "ab43023b-4079-414c-b340-056e3430a3af", + // "currency": "EUR", + // "type": "FIAT", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "EUR" + // }, + // "detailId": "f5fcb274-0cc7-4385-b2d3-bae2756e701f", + // "time": 1520706404050, + // "type": "TRANSACTION_COMMISSION_OUTCOME", + // "value": -2.71, + // "fundsBefore": { "total": 766.06, "available": 766.06, "locked": 0 }, + // "fundsAfter": { "total": 763.35,"available": 763.35, "locked": 0 }, + // "change": { "total": -2.71, "available": -2.71, "locked": 0 } + // } + // + // TRANSACTION_OFFER_COMPLETED_RETURN + // { + // "historyId": "cac69b04-c518-4dc5-9d86-e76e91f2e1d2", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": null, + // "time": 1520714886425, + // "type": "TRANSACTION_OFFER_COMPLETED_RETURN", + // "value": 0.00000196, + // "fundsBefore": { "total": 0.00941208, "available": 0.00941012, "locked": 0.00000196 }, + // "fundsAfter": { "total": 0.00941208, "available": 0.00941208, "locked": 0 }, + // "change": { "total": 0, "available": 0.00000196, "locked": -0.00000196 } + // } + // + // WITHDRAWAL_LOCK_FUNDS + // { + // "historyId": "03de2271-66ab-4960-a786-87ab9551fc14", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "6ad3dc72-1d6d-4ec2-8436-ca43f85a38a6", + // "time": 1522245654481, + // "type": "WITHDRAWAL_LOCK_FUNDS", + // "value": -0.8, + // "fundsBefore": { "total": 0.8, "available": 0.8, "locked": 0 }, + // "fundsAfter": { "total": 0.8, "available": 0, "locked": 0.8 }, + // "change": { "total": 0, "available": -0.8, "locked": 0.8 } + // } + // + // WITHDRAWAL_SUBTRACT_FUNDS + // { + // "historyId": "b0308c89-5288-438d-a306-c6448b1a266d", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "6ad3dc72-1d6d-4ec2-8436-ca43f85a38a6", + // "time": 1522246526186, + // "type": "WITHDRAWAL_SUBTRACT_FUNDS", + // "value": -0.8, + // "fundsBefore": { "total": 0.8, "available": 0, "locked": 0.8 }, + // "fundsAfter": { "total": 0, "available": 0, "locked": 0 }, + // "change": { "total": -0.8, "available": 0, "locked": -0.8 } + // } + // + // TRANSACTION_OFFER_ABORTED_RETURN + // { + // "historyId": "b1a3c075-d403-4e05-8f32-40512cdd88c0", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": null, + // "time": 1522512298662, + // "type": "TRANSACTION_OFFER_ABORTED_RETURN", + // "value": 0.0564931, + // "fundsBefore": { "total": 0.44951311, "available": 0.39302001, "locked": 0.0564931 }, + // "fundsAfter": { "total": 0.44951311, "available": 0.44951311, "locked": 0 }, + // "change": { "total": 0, "available": 0.0564931, "locked": -0.0564931 } + // } + // + // WITHDRAWAL_UNLOCK_FUNDS + // { + // "historyId": "0ed569a2-c330-482e-bb89-4cb553fb5b11", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "0c7be256-c336-4111-bee7-4eb22e339700", + // "time": 1527866360785, + // "type": "WITHDRAWAL_UNLOCK_FUNDS", + // "value": 0.05045, + // "fundsBefore": { "total": 0.86001578, "available": 0.80956578, "locked": 0.05045 }, + // "fundsAfter": { "total": 0.86001578, "available": 0.86001578, "locked": 0 }, + // "change": { "total": 0, "available": 0.05045, "locked": -0.05045 } + // } + // + // TRANSACTION_COMMISSION_RETURN + // { + // "historyId": "07c89c27-46f1-4d7a-8518-b73798bf168a", + // "balance": { + // "id": "ab43023b-4079-414c-b340-056e3430a3af", + // "currency": "EUR", + // "type": "FIAT", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "EUR" + // }, + // "detailId": null, + // "time": 1528304043063, + // "type": "TRANSACTION_COMMISSION_RETURN", + // "value": 0.6, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 0.6, "available": 0.6, "locked": 0 }, + // "change": { "total": 0.6, "available": 0.6, "locked": 0 } + // } + // + const timestamp = this.safeInteger (item, 'time'); + const balance = this.safeValue (item, 'balance', {}); + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const change = this.safeValue (item, 'change', {}); + let amount = this.safeFloat (change, 'total'); + let direction = 'in'; + if (amount < 0) { + direction = 'out'; + amount = -amount; + } + const id = this.safeString (item, 'historyId'); + // there are 2 undocumented api calls: (v1_01PrivateGetPaymentsDepositDetailId and v1_01PrivateGetPaymentsWithdrawalDetailId) + // that can be used to enrich the transfers with txid, address etc (you need to use info.detailId as a parameter) + const referenceId = this.safeString (item, 'detailId'); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const fundsBefore = this.safeValue (item, 'fundsBefore', {}); + const before = this.safeFloat (fundsBefore, 'total'); + const fundsAfter = this.safeValue (item, 'fundsAfter', {}); + const after = this.safeFloat (fundsAfter, 'total'); + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': undefined, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': 'ok', + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'ADD_FUNDS': 'transaction', + 'BITCOIN_GOLD_FORK': 'transaction', + 'CREATE_BALANCE': 'transaction', + 'FUNDS_MIGRATION': 'transaction', + 'WITHDRAWAL_LOCK_FUNDS': 'transaction', + 'WITHDRAWAL_SUBTRACT_FUNDS': 'transaction', + 'WITHDRAWAL_UNLOCK_FUNDS': 'transaction', + 'TRANSACTION_COMMISSION_OUTCOME': 'fee', + 'TRANSACTION_COMMISSION_RETURN': 'fee', + 'TRANSACTION_OFFER_ABORTED_RETURN': 'trade', + 'TRANSACTION_OFFER_COMPLETED_RETURN': 'trade', + 'TRANSACTION_POST_INCOME': 'trade', + 'TRANSACTION_POST_OUTCOME': 'trade', + 'TRANSACTION_PRE_LOCKING': 'trade', + }; + return this.safeString (types, type, type); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // '1582399800000', + // { + // o: '0.0001428', + // c: '0.0001428', + // h: '0.0001428', + // l: '0.0001428', + // v: '4', + // co: '1' + // } + // ] + // + const first = this.safeValue (ohlcv, 1, {}); + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (first, 'o'), + this.safeFloat (first, 'h'), + this.safeFloat (first, 'l'), + this.safeFloat (first, 'c'), + this.safeFloat (first, 'v'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + 'resolution': this.timeframes[timeframe], + // 'from': 1574709092000, // unix timestamp in milliseconds, required + // 'to': 1574709092000, // unix timestamp in milliseconds, required + }; + if (limit === undefined) { + limit = 100; + } + const duration = this.parseTimeframe (timeframe); + const timerange = limit * duration * 1000; + if (since === undefined) { + request['to'] = this.milliseconds (); + request['from'] = request['to'] - timerange; + } else { + request['from'] = parseInt (since); + request['to'] = this.sum (request['from'], timerange); + } + const response = await this.v1_01PublicGetTradingCandleHistorySymbolResolution (this.extend (request, params)); + // + // { + // "status":"Ok", + // "items":[ + // ["1591503060000",{"o":"0.02509572","c":"0.02509438","h":"0.02509664","l":"0.02509438","v":"0.02082165","co":"17"}], + // ["1591503120000",{"o":"0.02509606","c":"0.02509515","h":"0.02509606","l":"0.02509487","v":"0.04971703","co":"13"}], + // ["1591503180000",{"o":"0.02509532","c":"0.02509589","h":"0.02509589","l":"0.02509454","v":"0.01332236","co":"7"}], + // ] + // } + // + const items = this.safeValue (response, 'items', []); + return this.parseOHLCVs (items, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // createOrder trades + // + // { + // "rate": "0.02195928", + // "amount": "0.00167952" + // } + // + // fetchMyTrades (private) + // + // { + // amount: "0.29285199", + // commissionValue: "0.00125927", + // id: "11c8203a-a267-11e9-b698-0242ac110007", + // initializedBy: "Buy", + // market: "ETH-EUR", + // offerId: "11c82038-a267-11e9-b698-0242ac110007", + // rate: "277", + // time: "1562689917517", + // userAction: "Buy", + // wasTaker: true, + // } + // + // fetchTrades (public) + // + // { + // id: 'df00b0da-e5e0-11e9-8c19-0242ac11000a', + // t: '1570108958831', + // a: '0.04776653', + // r: '0.02145854', + // ty: 'Sell' + // } + // + const timestamp = this.safeInteger2 (trade, 'time', 't'); + const userAction = this.safeString (trade, 'userAction'); + const side = (userAction === 'Buy') ? 'buy' : 'sell'; + const wasTaker = this.safeValue (trade, 'wasTaker'); + let takerOrMaker = undefined; + if (wasTaker !== undefined) { + takerOrMaker = wasTaker ? 'taker' : 'maker'; + } + const price = this.safeFloat2 (trade, 'rate', 'r'); + const amount = this.safeFloat2 (trade, 'amount', 'a'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const feeCost = this.safeFloat (trade, 'commissionValue'); + const marketId = this.safeString (trade, 'market'); + market = this.safeMarket (marketId, market, '-'); + const symbol = market['symbol']; + let fee = undefined; + if (feeCost !== undefined) { + const feeCcy = (side === 'buy') ? market['base'] : market['quote']; + fee = { + 'currency': feeCcy, + 'cost': feeCost, + }; + } + const order = this.safeString (trade, 'offerId'); + // todo: check this logic + let type = undefined; + if (order !== undefined) { + type = order ? 'limit' : 'market'; + } + return { + 'id': this.safeString (trade, 'id'), + 'order': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + }; + if (since !== undefined) { + request['fromTime'] = since - 1; // result does not include exactly `since` time therefore decrease by 1 + } + if (limit !== undefined) { + request['limit'] = limit; // default - 10, max - 300 + } + const response = await this.v1_01PublicGetTradingTransactionsSymbol (this.extend (request, params)); + const items = this.safeValue (response, 'items'); + return this.parseTrades (items, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + 'offerType': side, + 'amount': amount, + 'mode': type, + }; + if (type === 'limit') { + request['rate'] = price; + price = parseFloat (price); + } + amount = parseFloat (amount); + const response = await this.v1_01PrivatePostTradingOfferSymbol (this.extend (request, params)); + // + // unfilled (open order) + // + // { + // status: 'Ok', + // completed: false, // can deduce status from here + // offerId: 'ce9cc72e-d61c-11e9-9248-0242ac110005', + // transactions: [], // can deduce order info from here + // } + // + // filled (closed order) + // + // { + // "status": "Ok", + // "offerId": "942a4a3e-e922-11e9-8c19-0242ac11000a", + // "completed": true, + // "transactions": [ + // { + // "rate": "0.02195928", + // "amount": "0.00167952" + // }, + // { + // "rate": "0.02195928", + // "amount": "0.00167952" + // }, + // { + // "rate": "0.02196207", + // "amount": "0.27704177" + // } + // ] + // } + // + // partially-filled (open order) + // + // { + // "status": "Ok", + // "offerId": "d0ebefab-f4d7-11e9-8c19-0242ac11000a", + // "completed": false, + // "transactions": [ + // { + // "rate": "0.02106404", + // "amount": "0.0019625" + // }, + // { + // "rate": "0.02106404", + // "amount": "0.0019625" + // }, + // { + // "rate": "0.02105901", + // "amount": "0.00975256" + // } + // ] + // } + // + const timestamp = this.milliseconds (); // the real timestamp is missing in the response + const id = this.safeString (response, 'offerId'); + const completed = this.safeValue (response, 'completed', false); + const status = completed ? 'closed' : 'open'; + let filled = 0; + let cost = undefined; + const transactions = this.safeValue (response, 'transactions'); + let trades = undefined; + if (transactions !== undefined) { + trades = this.parseTrades (transactions, market, undefined, undefined, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'side': side, + 'type': type, + 'orderId': id, + }); + cost = 0; + for (let i = 0; i < trades.length; i++) { + filled = this.sum (filled, trades[i]['amount']); + cost = this.sum (cost, trades[i]['cost']); + } + } + const remaining = amount - filled; + return { + 'id': id, + 'info': response, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'average': undefined, + 'fee': undefined, + 'trades': trades, + 'clientOrderId': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const side = this.safeString (params, 'side'); + if (side === undefined) { + throw new ExchangeError (this.id + ' cancelOrder() requires a `side` parameter ("buy" or "sell")'); + } + const price = this.safeValue (params, 'price'); + if (price === undefined) { + throw new ExchangeError (this.id + ' cancelOrder() requires a `price` parameter (float or string)'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + 'id': id, + 'side': side, + 'price': price, + }; + // { status: 'Fail', errors: [ 'NOT_RECOGNIZED_OFFER_TYPE' ] } -- if required params are missing + // { status: 'Ok', errors: [] } + return this.v1_01PrivateDeleteTradingOfferSymbolIdSidePrice (this.extend (request, params)); + } + + isFiat (currency) { + const fiatCurrencies = { + 'USD': true, + 'EUR': true, + 'PLN': true, + }; + return this.safeValue (fiatCurrencies, currency, false); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + let method = undefined; + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'quantity': amount, + }; + if (this.isFiat (code)) { + method = 'privatePostWithdraw'; + // request['account'] = params['account']; // they demand an account number + // request['express'] = params['express']; // whatever it means, they don't explain + // request['bic'] = ''; + } else { + method = 'privatePostTransfer'; + if (tag !== undefined) { + address += '?dt=' + tag.toString (); + } + request['address'] = address; + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api === 'public') { + const query = this.omit (params, this.extractParams (path)); + url += '/' + this.implodeParams (path, params) + '.json'; + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'v1_01Public') { + const query = this.omit (params, this.extractParams (path)); + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'v1_01Private') { + this.checkRequiredCredentials (); + const query = this.omit (params, this.extractParams (path)); + url += '/' + this.implodeParams (path, params); + const nonce = this.milliseconds ().toString (); + let payload = undefined; + if (method !== 'POST') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + payload = this.apiKey + nonce; + } else if (body === undefined) { + body = this.json (query); + payload = this.apiKey + nonce + body; + } + headers = { + 'Request-Timestamp': nonce, + 'Operation-Id': this.uuid (), + 'API-Key': this.apiKey, + 'API-Hash': this.hmac (this.encode (payload), this.encode (this.secret), 'sha512'), + 'Content-Type': 'application/json', + }; + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'method': path, + 'moment': this.nonce (), + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'API-Key': this.apiKey, + 'API-Hash': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('code' in response) { + // + // bitbay returns the integer 'success': 1 key from their private API + // or an integer 'code' value from 0 to 510 and an error message + // + // { 'success': 1, ... } + // { 'code': 502, 'message': 'Invalid sign' } + // { 'code': 0, 'message': 'offer funds not exceeding minimums' } + // + // 400 At least one parameter wasn't set + // 401 Invalid order type + // 402 No orders with specified currencies + // 403 Invalid payment currency name + // 404 Error. Wrong transaction type + // 405 Order with this id doesn't exist + // 406 No enough money or crypto + // 408 Invalid currency name + // 501 Invalid public key + // 502 Invalid sign + // 503 Invalid moment parameter. Request time doesn't match current server time + // 504 Invalid method + // 505 Key has no permission for this action + // 506 Account locked. Please contact with customer service + // 509 The BIC/SWIFT is required for this currency + // 510 Invalid market name + // + const code = this.safeString (response, 'code'); // always an integer + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } else if ('status' in response) { + // + // {"status":"Fail","errors":["OFFER_FUNDS_NOT_EXCEEDING_MINIMUMS"]} + // + const status = this.safeString (response, 'status'); + if (status === 'Fail') { + const errors = this.safeValue (response, 'errors'); + const feedback = this.id + ' ' + body; + for (let i = 0; i < errors.length; i++) { + const error = errors[i]; + this.throwExactlyMatchedException (this.exceptions, error, feedback); + } + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],28:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const hitbtc = require ('./hitbtc.js'); + +// --------------------------------------------------------------------------- + +module.exports = class bitcoincom extends hitbtc { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitcoincom', + 'name': 'bitcoin.com', + 'countries': [ 'KN' ], + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/97296144-514fa300-1861-11eb-952b-3d55d492200b.jpg', + 'api': { + 'public': 'https://api.exchange.bitcoin.com', + 'private': 'https://api.exchange.bitcoin.com', + }, + 'www': 'https://exchange.bitcoin.com', + 'doc': 'https://api.exchange.bitcoin.com/api/2/explore', + 'fees': 'https://exchange.bitcoin.com/fees-and-limits', + 'referral': 'https://exchange.bitcoin.com/referral/da948b21d6c92d69', + }, + 'fees': { + 'trading': { + 'maker': 0.15 / 100, + 'taker': 0.2 / 100, + }, + }, + }); + } +}; + +},{"./hitbtc.js":89}],29:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { NotSupported, RateLimitExceeded, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, InvalidNonce, BadSymbol } = require ('./base/errors'); +const { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitfinex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitfinex', + 'name': 'Bitfinex', + 'countries': [ 'VG' ], + 'version': 'v1', + 'rateLimit': 1500, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'deposit': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': false, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': true, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '3h': '3h', + '6h': '6h', + '12h': '12h', + '1d': '1D', + '1w': '7D', + '2w': '14D', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg', + 'api': { + 'v2': 'https://api-pub.bitfinex.com', // https://github.com/ccxt/ccxt/issues/5109 + 'public': 'https://api.bitfinex.com', + 'private': 'https://api.bitfinex.com', + }, + 'www': 'https://www.bitfinex.com', + 'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL', + 'doc': [ + 'https://docs.bitfinex.com/v1/docs', + 'https://github.com/bitfinexcom/bitfinex-api-node', + ], + }, + 'api': { + // v2 symbol ids require a 't' prefix + // just the public part of it (use bitfinex2 for everything else) + 'v2': { + 'get': [ + 'platform/status', + 'tickers', + 'ticker/{symbol}', + 'trades/{symbol}/hist', + 'book/{symbol}/{precision}', + 'book/{symbol}/P0', + 'book/{symbol}/P1', + 'book/{symbol}/P2', + 'book/{symbol}/P3', + 'book/{symbol}/R0', + 'stats1/{key}:{size}:{symbol}:{side}/{section}', + 'stats1/{key}:{size}:{symbol}/{section}', + 'stats1/{key}:{size}:{symbol}:long/last', + 'stats1/{key}:{size}:{symbol}:long/hist', + 'stats1/{key}:{size}:{symbol}:short/last', + 'stats1/{key}:{size}:{symbol}:short/hist', + 'candles/trade:{timeframe}:{symbol}/{section}', + 'candles/trade:{timeframe}:{symbol}/last', + 'candles/trade:{timeframe}:{symbol}/hist', + ], + }, + 'public': { + 'get': [ + 'book/{symbol}', + // 'candles/{symbol}', + 'lendbook/{currency}', + 'lends/{currency}', + 'pubticker/{symbol}', + 'stats/{symbol}', + 'symbols', + 'symbols_details', + 'tickers', + 'trades/{symbol}', + ], + }, + 'private': { + 'post': [ + 'account_fees', + 'account_infos', + 'balances', + 'basket_manage', + 'credits', + 'deposit/new', + 'funding/close', + 'history', + 'history/movements', + 'key_info', + 'margin_infos', + 'mytrades', + 'mytrades_funding', + 'offer/cancel', + 'offer/new', + 'offer/status', + 'offers', + 'offers/hist', + 'order/cancel', + 'order/cancel/all', + 'order/cancel/multi', + 'order/cancel/replace', + 'order/new', + 'order/new/multi', + 'order/status', + 'orders', + 'orders/hist', + 'position/claim', + 'position/close', + 'positions', + 'summary', + 'taken_funds', + 'total_taken_funds', + 'transfer', + 'unused_taken_funds', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + 'tiers': { + 'taker': [ + [0, 0.2 / 100], + [500000, 0.2 / 100], + [1000000, 0.2 / 100], + [2500000, 0.2 / 100], + [5000000, 0.2 / 100], + [7500000, 0.2 / 100], + [10000000, 0.18 / 100], + [15000000, 0.16 / 100], + [20000000, 0.14 / 100], + [25000000, 0.12 / 100], + [30000000, 0.1 / 100], + ], + 'maker': [ + [0, 0.1 / 100], + [500000, 0.08 / 100], + [1000000, 0.06 / 100], + [2500000, 0.04 / 100], + [5000000, 0.02 / 100], + [7500000, 0], + [10000000, 0], + [15000000, 0], + [20000000, 0], + [25000000, 0], + [30000000, 0], + ], + }, + }, + 'funding': { + 'tierBased': false, // true for tier-based/progressive + 'percentage': false, // fixed commission + // Actually deposit fees are free for larger deposits (> $1000 USD equivalent) + // these values below are deprecated, we should not hardcode fees and limits anymore + // to be reimplemented with bitfinex funding fees from their API or web endpoints + 'deposit': { + 'BTC': 0.0004, + 'IOTA': 0.5, + 'ETH': 0.0027, + 'BCH': 0.0001, + 'LTC': 0.001, + 'EOS': 0.24279, + 'XMR': 0.04, + 'SAN': 0.99269, + 'DASH': 0.01, + 'ETC': 0.01, + 'XRP': 0.02, + 'YYW': 16.915, + 'NEO': 0, + 'ZEC': 0.001, + 'BTG': 0, + 'OMG': 0.14026, + 'DATA': 20.773, + 'QASH': 1.9858, + 'ETP': 0.01, + 'QTUM': 0.01, + 'EDO': 0.95001, + 'AVT': 1.3045, + 'USDT': 0, + 'TRX': 28.184, + 'ZRX': 1.9947, + 'RCN': 10.793, + 'TNB': 31.915, + 'SNT': 14.976, + 'RLC': 1.414, + 'GNT': 5.8952, + 'SPK': 10.893, + 'REP': 0.041168, + 'BAT': 6.1546, + 'ELF': 1.8753, + 'FUN': 32.336, + 'SNG': 18.622, + 'AID': 8.08, + 'MNA': 16.617, + 'NEC': 1.6504, + 'XTZ': 0.2, + }, + 'withdraw': { + 'BTC': 0.0004, + 'IOTA': 0.5, + 'ETH': 0.0027, + 'BCH': 0.0001, + 'LTC': 0.001, + 'EOS': 0.24279, + 'XMR': 0.04, + 'SAN': 0.99269, + 'DASH': 0.01, + 'ETC': 0.01, + 'XRP': 0.02, + 'YYW': 16.915, + 'NEO': 0, + 'ZEC': 0.001, + 'BTG': 0, + 'OMG': 0.14026, + 'DATA': 20.773, + 'QASH': 1.9858, + 'ETP': 0.01, + 'QTUM': 0.01, + 'EDO': 0.95001, + 'AVT': 1.3045, + 'USDT': 20, + 'TRX': 28.184, + 'ZRX': 1.9947, + 'RCN': 10.793, + 'TNB': 31.915, + 'SNT': 14.976, + 'RLC': 1.414, + 'GNT': 5.8952, + 'SPK': 10.893, + 'REP': 0.041168, + 'BAT': 6.1546, + 'ELF': 1.8753, + 'FUN': 32.336, + 'SNG': 18.622, + 'AID': 8.08, + 'MNA': 16.617, + 'NEC': 1.6504, + 'XTZ': 0.2, + }, + }, + }, + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + 'commonCurrencies': { + 'ABS': 'ABYSS', + 'AIO': 'AION', + 'ALG': 'ALGO', // https://github.com/ccxt/ccxt/issues/6034 + 'AMP': 'AMPL', + 'ATM': 'ATMI', + 'ATO': 'ATOM', // https://github.com/ccxt/ccxt/issues/5118 + 'BAB': 'BCH', + 'CTX': 'CTXC', + 'DAD': 'DADI', + 'DAT': 'DATA', + 'DSH': 'DASH', + 'DRK': 'DRK', + // https://github.com/ccxt/ccxt/issues/7399 + // https://coinmarketcap.com/currencies/pnetwork/ + // https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ + 'EDO': 'PNT', + 'GSD': 'GUSD', + 'HOT': 'Hydro Protocol', + 'IOS': 'IOST', + 'IOT': 'IOTA', + 'IQX': 'IQ', + 'MIT': 'MITH', + 'MNA': 'MANA', + 'NCA': 'NCASH', + 'ORS': 'ORS Group', // conflict with Origin Sport #3230 + 'POY': 'POLY', + 'QSH': 'QASH', + 'QTM': 'QTUM', + 'RBT': 'RBTC', + 'SEE': 'SEER', + 'SNG': 'SNGLS', + 'SPK': 'SPANK', + 'STJ': 'STORJ', + 'TRI': 'TRIO', + 'TSD': 'TUSD', + 'YYW': 'YOYOW', + 'UDC': 'USDC', + 'UST': 'USDT', + 'UTN': 'UTNP', + 'VSY': 'VSYS', + 'WAX': 'WAXP', + 'XCH': 'XCHF', + 'ZBT': 'ZB', + }, + 'exceptions': { + 'exact': { + 'temporarily_unavailable': ExchangeNotAvailable, // Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info. + 'Order could not be cancelled.': OrderNotFound, // non-existent order + 'No such order found.': OrderNotFound, // ? + 'Order price must be positive.': InvalidOrder, // on price <= 0 + 'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError, + 'Key price should be a decimal number, e.g. "123.456"': InvalidOrder, // on isNaN (price) + 'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder, // on isNaN (amount) + 'ERR_RATE_LIMIT': RateLimitExceeded, + 'Ratelimit': RateLimitExceeded, + 'Nonce is too small.': InvalidNonce, + 'No summary found.': ExchangeError, // fetchTradingFees (summary) endpoint can give this vague error message + 'Cannot evaluate your available balance, please try again': ExchangeNotAvailable, + 'Unknown symbol': BadSymbol, + }, + 'broad': { + 'Invalid X-BFX-SIGNATURE': AuthenticationError, + 'This API key does not have permission': PermissionDenied, // authenticated but not authorized + 'not enough exchange balance for ': InsufficientFunds, // when buying cost is greater than the available quote currency + 'minimum size for ': InvalidOrder, // when amount below limits.amount.min + 'Invalid order': InvalidOrder, // ? + 'The available balance is only': InsufficientFunds, // {"status":"error","message":"Cannot withdraw 1.0027 ETH from your exchange wallet. The available balance is only 0.0 ETH. If you have limit orders, open positions, unused or active margin funding, this will decrease your available balance. To increase it, you can cancel limit orders or reduce/close your positions.","withdrawal_id":0,"fees":"0.0027"} + }, + }, + 'precisionMode': SIGNIFICANT_DIGITS, + 'options': { + 'currencyNames': { + 'AGI': 'agi', + 'AID': 'aid', + 'AIO': 'aio', + 'ANT': 'ant', + 'AVT': 'aventus', // #1811 + 'BAT': 'bat', + // https://github.com/ccxt/ccxt/issues/5833 + 'BCH': 'bab', // undocumented + // 'BCH': 'bcash', // undocumented + 'BCI': 'bci', + 'BFT': 'bft', + 'BSV': 'bsv', + 'BTC': 'bitcoin', + 'BTG': 'bgold', + 'CFI': 'cfi', + 'COMP': 'comp', + 'DAI': 'dai', + 'DADI': 'dad', + 'DASH': 'dash', + 'DATA': 'datacoin', + 'DTH': 'dth', + 'EDO': 'eidoo', // #1811 + 'ELF': 'elf', + 'EOS': 'eos', + 'ETC': 'ethereumc', + 'ETH': 'ethereum', + 'ETP': 'metaverse', + 'FUN': 'fun', + 'GNT': 'golem', + 'IOST': 'ios', + 'IOTA': 'iota', + // https://github.com/ccxt/ccxt/issues/5833 + 'LEO': 'let', // ETH chain + // 'LEO': 'les', // EOS chain + 'LINK': 'link', + 'LRC': 'lrc', + 'LTC': 'litecoin', + 'LYM': 'lym', + 'MANA': 'mna', + 'MIT': 'mit', + 'MKR': 'mkr', + 'MTN': 'mtn', + 'NEO': 'neo', + 'ODE': 'ode', + 'OMG': 'omisego', + 'OMNI': 'mastercoin', + 'QASH': 'qash', + 'QTUM': 'qtum', // #1811 + 'RCN': 'rcn', + 'RDN': 'rdn', + 'REP': 'rep', + 'REQ': 'req', + 'RLC': 'rlc', + 'SAN': 'santiment', + 'SNGLS': 'sng', + 'SNT': 'status', + 'SPANK': 'spk', + 'STORJ': 'stj', + 'TNB': 'tnb', + 'TRX': 'trx', + 'TUSD': 'tsd', + 'USD': 'wire', + 'USDC': 'udc', // https://github.com/ccxt/ccxt/issues/5833 + 'UTK': 'utk', + 'USDT': 'tetheruso', // Tether on Omni + // 'USDT': 'tetheruse', // Tether on ERC20 + // 'USDT': 'tetherusl', // Tether on Liquid + // 'USDT': 'tetherusx', // Tether on Tron + // 'USDT': 'tetheruss', // Tether on EOS + 'VEE': 'vee', + 'WAX': 'wax', + 'XLM': 'xlm', + 'XMR': 'monero', + 'XRP': 'ripple', + 'XVG': 'xvg', + 'YOYOW': 'yoyow', + 'ZEC': 'zcash', + 'ZRX': 'zrx', + 'XTZ': 'xtz', + }, + 'orderTypes': { + 'limit': 'exchange limit', + 'market': 'exchange market', + }, + }, + }); + } + + async fetchFundingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostAccountFees (params); + const fees = response['withdraw']; + const withdraw = {}; + const ids = Object.keys (fees); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + withdraw[code] = this.safeFloat (fees, id); + } + return { + 'info': response, + 'withdraw': withdraw, + 'deposit': withdraw, // only for deposits of less than $1000 + }; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostSummary (params); + // + // { + // time: '2019-02-20T15:50:19.152000Z', + // trade_vol_30d: [ + // { + // curr: 'Total (USD)', + // vol: 0, + // vol_maker: 0, + // vol_BFX: 0, + // vol_BFX_maker: 0, + // vol_ETHFX: 0, + // vol_ETHFX_maker: 0 + // } + // ], + // fees_funding_30d: {}, + // fees_funding_total_30d: 0, + // fees_trading_30d: {}, + // fees_trading_total_30d: 0, + // maker_fee: 0.001, + // taker_fee: 0.002 + // } + // + return { + 'info': response, + 'maker': this.safeFloat (response, 'maker_fee'), + 'taker': this.safeFloat (response, 'taker_fee'), + }; + } + + async fetchMarkets (params = {}) { + const ids = await this.publicGetSymbols (); + const details = await this.publicGetSymbolsDetails (); + const result = []; + for (let i = 0; i < details.length; i++) { + const market = details[i]; + let id = this.safeString (market, 'pair'); + if (!this.inArray (id, ids)) { + continue; + } + id = id.toUpperCase (); + let baseId = undefined; + let quoteId = undefined; + if (id.indexOf (':') >= 0) { + const parts = id.split (':'); + baseId = parts[0]; + quoteId = parts[1]; + } else { + baseId = id.slice (0, 3); + quoteId = id.slice (3, 6); + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'price_precision'), + // https://docs.bitfinex.com/docs/introduction#amount-precision + // The amount field allows up to 8 decimals. + // Anything exceeding this will be rounded to the 8th decimal. + 'amount': 8, + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_order_size'), + 'max': this.safeFloat (market, 'maximum_order_size'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + }; + limits['cost'] = { + 'min': limits['amount']['min'] * limits['price']['min'], + 'max': undefined, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + amountToPrecision (symbol, amount) { + // https://docs.bitfinex.com/docs/introduction#amount-precision + // The amount field allows up to 8 decimals. + // Anything exceeding this will be rounded to the 8th decimal. + return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); + } + + priceToPrecision (symbol, price) { + price = this.decimalToPrecision (price, ROUND, this.markets[symbol]['precision']['price'], this.precisionMode); + // https://docs.bitfinex.com/docs/introduction#price-precision + // The precision level of all trading prices is based on significant figures. + // All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals (e.g. 1.2345, 123.45, 1234.5, 0.00012345). + // Prices submit with a precision larger than 5 will be cut by the API. + return this.decimalToPrecision (price, TRUNCATE, 8, DECIMAL_PLACES); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let key = 'quote'; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + const code = market[key]; + const currency = this.safeValue (this.currencies, code); + if (currency !== undefined) { + const precision = this.safeInteger (currency, 'precision'); + if (precision !== undefined) { + cost = parseFloat (this.currencyToPrecision (code, cost)); + } + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balanceType = this.safeString (params, 'type', 'exchange'); + const query = this.omit (params, 'type'); + const response = await this.privatePostBalances (query); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + if (balance['type'] === balanceType) { + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + // bitfinex had BCH previously, now it's BAB, but the old + // BCH symbol is kept for backward-compatibility + // we need a workaround here so that the old BCH balance + // would not override the new BAB balance (BAB is unified to BCH) + // https://github.com/ccxt/ccxt/issues/4989 + if (!(code in result)) { + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'amount'); + result[code] = account; + } + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit_bids'] = limit; + request['limit_asks'] = limit; + } + const response = await this.publicGetBookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const ticker = await this.publicGetPubtickerSymbol (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + let timestamp = this.safeFloat (ticker, 'timestamp'); + if (timestamp !== undefined) { + timestamp *= 1000; + } + timestamp = parseInt (timestamp); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } else if ('pair' in ticker) { + const marketId = this.safeString (ticker, 'pair'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + const baseId = marketId.slice (0, 3); + const quoteId = marketId.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + } + const last = this.safeFloat (ticker, 'last_price'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'mid'), + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market) { + const id = this.safeString (trade, 'tid'); + let timestamp = this.safeFloat (trade, 'timestamp'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp) * 1000; + } + const type = undefined; + const side = this.safeStringLower (trade, 'type'); + const orderId = this.safeString (trade, 'order_id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + if ('fee_amount' in trade) { + const feeCost = -this.safeFloat (trade, 'fee_amount'); + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'type': type, + 'order': orderId, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'limit_trades': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.publicGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit_trades'] = limit; + } + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.privatePostMytrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'side': side, + 'amount': this.amountToPrecision (symbol, amount), + 'type': this.safeString (this.options['orderTypes'], type, type), + 'ocoorder': false, + 'buy_price_oco': 0, + 'sell_price_oco': 0, + }; + if (type === 'market') { + request['price'] = this.nonce ().toString (); + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrderNew (this.extend (request, params)); + return this.parseOrder (response); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const order = { + 'order_id': parseInt (id), + }; + if (price !== undefined) { + order['price'] = this.priceToPrecision (symbol, price); + } + if (amount !== undefined) { + order['amount'] = this.numberToString (amount); + } + if (symbol !== undefined) { + order['symbol'] = this.marketId (symbol); + } + if (side !== undefined) { + order['side'] = side; + } + if (type !== undefined) { + order['type'] = this.safeString (this.options['orderTypes'], type, type); + } + const response = await this.privatePostOrderCancelReplace (this.extend (order, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + return await this.privatePostOrderCancel (this.extend (request, params)); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + return await this.privatePostOrderCancelAll (params); + } + + parseOrder (order, market = undefined) { + const side = this.safeString (order, 'side'); + const open = this.safeValue (order, 'is_live'); + const canceled = this.safeValue (order, 'is_cancelled'); + let status = undefined; + if (open) { + status = 'open'; + } else if (canceled) { + status = 'canceled'; + } else { + status = 'closed'; + } + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeStringUpper (order, 'symbol'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let orderType = order['type']; + const exchange = orderType.indexOf ('exchange ') >= 0; + if (exchange) { + const parts = order['type'].split (' '); + orderType = parts[1]; + } + let timestamp = this.safeFloat (order, 'timestamp'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp) * 1000; + } + const id = this.safeString (order, 'id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'average': this.safeFloat (order, 'avg_execution_price'), + 'amount': this.safeFloat (order, 'original_amount'), + 'remaining': this.safeFloat (order, 'remaining_amount'), + 'filled': this.safeFloat (order, 'executed_amount'), + 'status': status, + 'fee': undefined, + 'cost': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbol !== undefined) { + if (!(symbol in this.markets)) { + throw new ExchangeError (this.id + ' has no symbol ' + symbol); + } + } + const response = await this.privatePostOrders (params); + let orders = this.parseOrders (response, undefined, since, limit); + if (symbol !== undefined) { + orders = this.filterBy (orders, 'symbol', symbol); + } + return orders; + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostOrdersHist (this.extend (request, params)); + let orders = this.parseOrders (response, undefined, since, limit); + if (symbol !== undefined) { + orders = this.filterBy (orders, 'symbol', symbol); + } + orders = this.filterByArray (orders, 'status', [ 'closed', 'canceled' ], false); + return orders; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1457539800000, + // 0.02594, + // 0.02594, + // 0.02594, + // 0.02594, + // 0.1 + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const market = this.market (symbol); + const v2id = 't' + market['id']; + const request = { + 'symbol': v2id, + 'timeframe': this.timeframes[timeframe], + 'sort': 1, + 'limit': limit, + }; + if (since !== undefined) { + request['start'] = since; + } + const response = await this.v2GetCandlesTradeTimeframeSymbolHist (this.extend (request, params)); + // + // [ + // [1457539800000,0.02594,0.02594,0.02594,0.02594,0.1], + // [1457547300000,0.02577,0.02577,0.02577,0.02577,0.01], + // [1457550240000,0.0255,0.0253,0.0255,0.0252,3.2640000000000002], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + getCurrencyName (code) { + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + if (code in this.options['currencyNames']) { + return this.options['currencyNames'][code]; + } + throw new NotSupported (this.id + ' ' + code + ' not supported for withdrawal'); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const request = { + 'renew': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + return response; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'method': name, + 'wallet_name': 'exchange', + 'renew': 0, // a value of 1 will generate a new address + }; + const response = await this.privatePostDepositNew (this.extend (request, params)); + let address = this.safeValue (response, 'address'); + let tag = undefined; + if ('address_pool' in response) { + tag = address; + address = response['address_pool']; + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currencyId = this.safeString (params, 'currency'); + const query = this.omit (params, 'currency'); + let currency = undefined; + if (currencyId === undefined) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument or a `currency` parameter'); + } else { + currency = this.currency (code); + currencyId = currency['id']; + } + } + query['currency'] = currencyId; + if (since !== undefined) { + query['since'] = parseInt (since / 1000); + } + const response = await this.privatePostHistoryMovements (this.extend (query, params)); + // + // [ + // { + // "id":581183, + // "txid": 123456, + // "currency":"BTC", + // "method":"BITCOIN", + // "type":"WITHDRAWAL", + // "amount":".01", + // "description":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ, offchain transfer ", + // "address":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ", + // "status":"COMPLETED", + // "timestamp":"1443833327.0", + // "timestamp_created": "1443833327.1", + // "fee": 0.1, + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // crypto + // + // { + // "id": 12042490, + // "fee": "-0.02", + // "txid": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D", + // "type": "DEPOSIT", + // "amount": "2099.849999", + // "method": "RIPPLE", + // "status": "COMPLETED", + // "address": "2505189261", + // "currency": "XRP", + // "timestamp": "1551730524.0", + // "description": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D", + // "timestamp_created": "1551730523.0" + // } + // + // fiat + // + // { + // "id": 12725095, + // "fee": "-60.0", + // "txid": null, + // "type": "WITHDRAWAL", + // "amount": "9943.0", + // "method": "WIRE", + // "status": "SENDING", + // "address": null, + // "currency": "EUR", + // "timestamp": "1561802484.0", + // "description": "Name: bob, AccountAddress: some address, Account: someaccountno, Bank: bank address, SWIFT: foo, Country: UK, Details of Payment: withdrawal name, Intermediary Bank Name: , Intermediary Bank Address: , Intermediary Bank City: , Intermediary Bank Country: , Intermediary Bank Account: , Intermediary Bank SWIFT: , Fee: -60.0", + // "timestamp_created": "1561716066.0" + // } + // + let timestamp = this.safeFloat (transaction, 'timestamp_created'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp * 1000); + } + let updated = this.safeFloat (transaction, 'timestamp'); + if (updated !== undefined) { + updated = parseInt (updated * 1000); + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeStringLower (transaction, 'type'); // DEPOSIT or WITHDRAWAL + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + let feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'txid'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'address'), // todo: this is actually the tag for XRP transfers (the address is missing) + 'tag': undefined, // refix it properly for the tag from description + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'currency': code, + 'cost': feeCost, + 'rate': undefined, + }, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'SENDING': 'pending', + 'CANCELED': 'canceled', + 'ZEROCONFIRMED': 'failed', // ZEROCONFIRMED happens e.g. in a double spend attempt (I had one in my movements!) + 'COMPLETED': 'ok', + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'withdraw_type': name, + 'walletselected': 'exchange', + 'amount': this.numberToString (amount), + 'address': address, + }; + if (tag !== undefined) { + request['payment_id'] = tag; + } + const responses = await this.privatePostWithdraw (this.extend (request, params)); + const response = responses[0]; + const id = this.safeString (response, 'withdrawal_id'); + const message = this.safeString (response, 'message'); + const errorMessage = this.findBroadlyMatchedKey (this.exceptions['broad'], message); + if (id === 0) { + if (errorMessage !== undefined) { + const ExceptionClass = this.exceptions['broad'][errorMessage]; + throw new ExceptionClass (this.id + ' ' + message); + } + throw new ExchangeError (this.id + ' withdraw returned an id of zero: ' + this.json (response)); + } + return { + 'info': response, + 'id': id, + }; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostPositions (params); + // + // [ + // { + // "id":943715, + // "symbol":"btcusd", + // "status":"ACTIVE", + // "base":"246.94", + // "amount":"1.0", + // "timestamp":"1444141857.0", + // "swap":"0.0", + // "pl":"-2.22042" + // } + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + if (api === 'v2') { + request = '/' + api + request; + } else { + request = '/' + this.version + request; + } + let query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'][api] + request; + if ((api === 'public') || (path.indexOf ('/hist') >= 0)) { + if (Object.keys (query).length) { + const suffix = '?' + this.urlencode (query); + url += suffix; + request += suffix; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + query = this.extend ({ + 'nonce': nonce.toString (), + 'request': request, + }, query); + body = this.json (query); + const payload = this.stringToBase64 (body); + const secret = this.encode (this.secret); + const signature = this.hmac (payload, secret, 'sha384'); + headers = { + 'X-BFX-APIKEY': this.apiKey, + 'X-BFX-PAYLOAD': this.decode (payload), + 'X-BFX-SIGNATURE': signature, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code >= 400) { + if (body[0] === '{') { + const feedback = this.id + ' ' + body; + const message = this.safeString2 (response, 'message', 'error'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],30:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const bitfinex = require ('./bitfinex.js'); +const { ExchangeError, InvalidAddress, ArgumentsRequired, InsufficientFunds, AuthenticationError, OrderNotFound, InvalidOrder, BadRequest, InvalidNonce, BadSymbol, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitfinex2 extends bitfinex { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitfinex2', + 'name': 'Bitfinex', + 'countries': [ 'VG' ], + 'version': 'v2', + 'certified': false, + 'pro': false, + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createDepositAddress': true, + 'createLimitOrder': true, + 'createMarketOrder': true, + 'createOrder': true, + 'deposit': false, + 'editOrder': false, + 'fetchBalance': true, + 'fetchClosedOrder': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchFundingFees': false, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrder': true, + 'fetchOpenOrders': true, + 'fetchOrder': false, + 'fetchOrderTrades': true, + 'fetchStatus': true, + 'fetchTickers': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTransactions': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '3h': '3h', + '6h': '6h', + '12h': '12h', + '1d': '1D', + '1w': '7D', + '2w': '14D', + '1M': '1M', + }, + 'rateLimit': 1500, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg', + 'api': { + 'v1': 'https://api.bitfinex.com', + 'public': 'https://api-pub.bitfinex.com', + 'private': 'https://api.bitfinex.com', + }, + 'www': 'https://www.bitfinex.com', + 'doc': [ + 'https://docs.bitfinex.com/v2/docs/', + 'https://github.com/bitfinexcom/bitfinex-api-node', + ], + 'fees': 'https://www.bitfinex.com/fees', + }, + 'api': { + 'v1': { + 'get': [ + 'symbols', + 'symbols_details', + ], + }, + 'public': { + 'get': [ + 'conf/{config}', + 'conf/pub:{action}:{object}', + 'conf/pub:{action}:{object}:{detail}', + 'conf/pub:map:{object}', + 'conf/pub:map:{object}:{detail}', + 'conf/pub:map:currency:{detail}', + 'conf/pub:map:currency:sym', // maps symbols to their API symbols, BAB > BCH + 'conf/pub:map:currency:label', // verbose friendly names, BNT > Bancor + 'conf/pub:map:currency:unit', // maps symbols to unit of measure where applicable + 'conf/pub:map:currency:undl', // maps derivatives symbols to their underlying currency + 'conf/pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on + 'conf/pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs + 'conf/pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745 + 'conf/pub:map:tx:method', + 'conf/pub:list:{object}', + 'conf/pub:list:{object}:{detail}', + 'conf/pub:list:currency', + 'conf/pub:list:pair:exchange', + 'conf/pub:list:pair:margin', + 'conf/pub:list:pair:futures', + 'conf/pub:list:competitions', + 'conf/pub:info:{object}', + 'conf/pub:info:{object}:{detail}', + 'conf/pub:info:pair', + 'conf/pub:info:tx:status', // [ deposit, withdrawal ] statuses 1 = active, 0 = maintenance + 'conf/pub:fees', + 'platform/status', + 'tickers', + 'ticker/{symbol}', + 'trades/{symbol}/hist', + 'book/{symbol}/{precision}', + 'book/{symbol}/P0', + 'book/{symbol}/P1', + 'book/{symbol}/P2', + 'book/{symbol}/P3', + 'book/{symbol}/R0', + 'stats1/{key}:{size}:{symbol}:{side}/{section}', + 'stats1/{key}:{size}:{symbol}:{side}/last', + 'stats1/{key}:{size}:{symbol}:{side}/hist', + 'stats1/{key}:{size}:{symbol}/{section}', + 'stats1/{key}:{size}:{symbol}/last', + 'stats1/{key}:{size}:{symbol}/hist', + 'stats1/{key}:{size}:{symbol}:long/last', + 'stats1/{key}:{size}:{symbol}:long/hist', + 'stats1/{key}:{size}:{symbol}:short/last', + 'stats1/{key}:{size}:{symbol}:short/hist', + 'candles/trade:{timeframe}:{symbol}/{section}', + 'candles/trade:{timeframe}:{symbol}/last', + 'candles/trade:{timeframe}:{symbol}/hist', + 'status/{type}', + 'status/deriv', + 'liquidations/hist', + 'rankings/{key}:{timeframe}:{symbol}/{section}', + 'rankings/{key}:{timeframe}:{symbol}/hist', + ], + 'post': [ + 'calc/trade/avg', + 'calc/fx', + ], + }, + 'private': { + 'post': [ + // 'auth/r/orders/{symbol}/new', // outdated + // 'auth/r/stats/perf:{timeframe}/hist', // outdated + 'auth/r/wallets', + 'auth/r/wallets/hist', + 'auth/r/orders', + 'auth/r/orders/{symbol}', + 'auth/w/order/submit', + 'auth/w/order/update', + 'auth/w/order/cancel', + 'auth/w/order/multi', + 'auth/w/order/cancel/multi', + 'auth/r/orders/{symbol}/hist', + 'auth/r/orders/hist', + 'auth/r/order/{symbol}:{id}/trades', + 'auth/r/trades/{symbol}/hist', + 'auth/r/trades/hist', + 'auth/r/ledgers/{currency}/hist', + 'auth/r/ledgers/hist', + 'auth/r/info/margin/{key}', + 'auth/r/info/margin/base', + 'auth/r/info/margin/sym_all', + 'auth/r/positions', + 'auth/w/position/claim', + 'auth/r/positions/hist', + 'auth/r/positions/audit', + 'auth/w/deriv/collateral/set', + 'auth/r/funding/offers', + 'auth/r/funding/offers/{symbol}', + 'auth/w/funding/offer/submit', + 'auth/w/funding/offer/cancel', + 'auth/w/funding/offer/cancel/all', + 'auth/w/funding/close', + 'auth/w/funding/auto', + 'auth/w/funding/keep', + 'auth/r/funding/offers/{symbol}/hist', + 'auth/r/funding/offers/hist', + 'auth/r/funding/loans', + 'auth/r/funding/loans/hist', + 'auth/r/funding/loans/{symbol}', + 'auth/r/funding/loans/{symbol}/hist', + 'auth/r/funding/credits', + 'auth/r/funding/credits/hist', + 'auth/r/funding/credits/{symbol}', + 'auth/r/funding/credits/{symbol}/hist', + 'auth/r/funding/trades/{symbol}/hist', + 'auth/r/funding/trades/hist', + 'auth/r/info/funding/{key}', + 'auth/r/info/user', + 'auth/r/logins/hist', + 'auth/w/transfer', + 'auth/w/deposit/address', + 'auth/w/deposit/invoice', + 'auth/w/withdraw', + 'auth/r/movements/{currency}/hist', + 'auth/r/movements/hist', + 'auth/r/alerts', + 'auth/w/alert/set', + 'auth/w/alert/price:{symbol}:{price}/del', + 'auth/w/alert/{type}:{symbol}:{price}/del', + 'auth/calc/order/avail', + 'auth/w/settings/set', + 'auth/r/settings', + 'auth/w/settings/del', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.0004, + 'BCH': 0.0001, + 'ETH': 0.00135, + 'EOS': 0.0, + 'LTC': 0.001, + 'OMG': 0.15097, + 'IOT': 0.0, + 'NEO': 0.0, + 'ETC': 0.01, + 'XRP': 0.02, + 'ETP': 0.01, + 'ZEC': 0.001, + 'BTG': 0.0, + 'DASH': 0.01, + 'XMR': 0.0001, + 'QTM': 0.01, + 'EDO': 0.23687, + 'DAT': 9.8858, + 'AVT': 1.1251, + 'SAN': 0.35977, + 'USDT': 5.0, + 'SPK': 16.971, + 'BAT': 1.1209, + 'GNT': 2.8789, + 'SNT': 9.0848, + 'QASH': 1.726, + 'YYW': 7.9464, + }, + }, + }, + 'options': { + 'precision': 'R0', // P0, P1, P2, P3, P4, R0 + // convert 'EXCHANGE MARKET' to lowercase 'market' + // convert 'EXCHANGE LIMIT' to lowercase 'limit' + // everything else remains uppercase + 'exchangeTypes': { + // 'MARKET': undefined, + 'EXCHANGE MARKET': 'market', + // 'LIMIT': undefined, + 'EXCHANGE LIMIT': 'limit', + // 'STOP': undefined, + // 'EXCHANGE STOP': undefined, + // 'TRAILING STOP': undefined, + // 'EXCHANGE TRAILING STOP': undefined, + // 'FOK': undefined, + // 'EXCHANGE FOK': undefined, + // 'STOP LIMIT': undefined, + // 'EXCHANGE STOP LIMIT': undefined, + // 'IOC': undefined, + // 'EXCHANGE IOC': undefined, + }, + // convert 'market' to 'EXCHANGE MARKET' + // convert 'limit' 'EXCHANGE LIMIT' + // everything else remains as is + 'orderTypes': { + 'market': 'EXCHANGE MARKET', + 'limit': 'EXCHANGE LIMIT', + }, + 'fiat': { + 'USD': 'USD', + 'EUR': 'EUR', + 'JPY': 'JPY', + 'GBP': 'GBP', + }, + }, + 'exceptions': { + 'exact': { + '10020': BadRequest, + '10100': AuthenticationError, + '10114': InvalidNonce, + '20060': OnMaintenance, + }, + 'broad': { + 'address': InvalidAddress, + 'available balance is only': InsufficientFunds, + 'not enough exchange balance': InsufficientFunds, + 'Order not found': OrderNotFound, + 'symbol: invalid': BadSymbol, + 'Invalid order': InvalidOrder, + }, + }, + }); + } + + isFiat (code) { + return (code in this.options['fiat']); + } + + getCurrencyId (code) { + return 'f' + code; + } + + async fetchStatus (params = {}) { + // + // [1] // operative + // [0] // maintenance + // + const response = await this.publicGetPlatformStatus (params); + const status = this.safeValue (response, 0); + const formattedStatus = (status === 1) ? 'ok' : 'maintenance'; + this.status = this.extend (this.status, { + 'status': formattedStatus, + 'updated': this.milliseconds (), + }); + return this.status; + } + + async fetchMarkets (params = {}) { + // todo drop v1 in favor of v2 configs + // pub:list:pair:exchange,pub:list:pair:margin,pub:list:pair:futures,pub:info:pair + const v2response = await this.publicGetConfPubListPairFutures (params); + const v1response = await this.v1GetSymbolsDetails (params); + const futuresMarketIds = this.safeValue (v2response, 0, []); + const result = []; + for (let i = 0; i < v1response.length; i++) { + const market = v1response[i]; + let id = this.safeStringUpper (market, 'pair'); + let spot = true; + if (this.inArray (id, futuresMarketIds)) { + spot = false; + } + const futures = !spot; + const type = spot ? 'spot' : 'futures'; + let baseId = undefined; + let quoteId = undefined; + if (id.indexOf (':') >= 0) { + const parts = id.split (':'); + baseId = parts[0]; + quoteId = parts[1]; + } else { + baseId = id.slice (0, 3); + quoteId = id.slice (3, 6); + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + id = 't' + id; + baseId = this.getCurrencyId (baseId); + quoteId = this.getCurrencyId (quoteId); + const precision = { + 'price': this.safeInteger (market, 'price_precision'), + 'amount': 8, // https://github.com/ccxt/ccxt/issues/7310 + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_order_size'), + 'max': this.safeFloat (market, 'maximum_order_size'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + }; + limits['cost'] = { + 'min': limits['amount']['min'] * limits['price']['min'], + 'max': undefined, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + 'type': type, + 'swap': false, + 'spot': spot, + 'futures': futures, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const labels = [ + 'pub:list:currency', + 'pub:map:currency:sym', // maps symbols to their API symbols, BAB > BCH + 'pub:map:currency:label', // verbose friendly names, BNT > Bancor + 'pub:map:currency:unit', // maps symbols to unit of measure where applicable + 'pub:map:currency:undl', // maps derivatives symbols to their underlying currency + 'pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on + 'pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs + 'pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745 + ]; + const config = labels.join (','); + const request = { + 'config': config, + }; + const response = await this.publicGetConfConfig (this.extend (request, params)); + // + // [ + // + // a list of symbols + // ["AAA","ABS","ADA"], + // + // // sym + // // maps symbols to their API symbols, BAB > BCH + // [ + // [ 'BAB', 'BCH' ], + // [ 'CNHT', 'CNHt' ], + // [ 'DSH', 'DASH' ], + // [ 'IOT', 'IOTA' ], + // [ 'LES', 'LEO-EOS' ], + // [ 'LET', 'LEO-ERC20' ], + // [ 'STJ', 'STORJ' ], + // [ 'TSD', 'TUSD' ], + // [ 'UDC', 'USDC' ], + // [ 'USK', 'USDK' ], + // [ 'UST', 'USDt' ], + // [ 'USTF0', 'USDt0' ], + // [ 'XCH', 'XCHF' ], + // [ 'YYW', 'YOYOW' ], + // // ... + // ], + // // label + // // verbose friendly names, BNT > Bancor + // [ + // [ 'BAB', 'Bitcoin Cash' ], + // [ 'BCH', 'Bitcoin Cash' ], + // [ 'LEO', 'Unus Sed LEO' ], + // [ 'LES', 'Unus Sed LEO (EOS)' ], + // [ 'LET', 'Unus Sed LEO (ERC20)' ], + // // ... + // ], + // // unit + // // maps symbols to unit of measure where applicable + // [ + // [ 'IOT', 'Mi|MegaIOTA' ], + // ], + // // undl + // // maps derivatives symbols to their underlying currency + // [ + // [ 'USTF0', 'UST' ], + // [ 'BTCF0', 'BTC' ], + // [ 'ETHF0', 'ETH' ], + // ], + // // pool + // // maps symbols to underlying network/protocol they operate on + // [ + // [ 'SAN', 'ETH' ], [ 'OMG', 'ETH' ], [ 'AVT', 'ETH' ], [ 'EDO', 'ETH' ], + // [ 'ESS', 'ETH' ], [ 'ATD', 'EOS' ], [ 'ADD', 'EOS' ], [ 'MTO', 'EOS' ], + // [ 'PNK', 'ETH' ], [ 'BAB', 'BCH' ], [ 'WLO', 'XLM' ], [ 'VLD', 'ETH' ], + // [ 'BTT', 'TRX' ], [ 'IMP', 'ETH' ], [ 'SCR', 'ETH' ], [ 'GNO', 'ETH' ], + // // ... + // ], + // // explorer + // // maps symbols to their recognised block explorer URLs + // [ + // [ + // 'AIO', + // [ + // "https://mainnet.aion.network", + // "https://mainnet.aion.network/#/account/VAL", + // "https://mainnet.aion.network/#/transaction/VAL" + // ] + // ], + // // ... + // ], + // // fee + // // maps currencies to their withdrawal fees + // [ + // ["AAA",[0,0]], + // ["ABS",[0,131.3]], + // ["ADA",[0,0.3]], + // ], + // ] + // + const indexed = { + 'sym': this.indexBy (this.safeValue (response, 1, []), 0), + 'label': this.indexBy (this.safeValue (response, 2, []), 0), + 'unit': this.indexBy (this.safeValue (response, 3, []), 0), + 'undl': this.indexBy (this.safeValue (response, 4, []), 0), + 'pool': this.indexBy (this.safeValue (response, 5, []), 0), + 'explorer': this.indexBy (this.safeValue (response, 6, []), 0), + 'fees': this.indexBy (this.safeValue (response, 7, []), 0), + }; + const ids = this.safeValue (response, 0, []); + const result = {}; + for (let i = 0; i < ids.length; i++) { + let id = ids[i]; + const code = this.safeCurrencyCode (id); + const label = this.safeValue (indexed['label'], id, []); + const name = this.safeString (label, 1); + const pool = this.safeValue (indexed['pool'], id, []); + const type = this.safeString (pool, 1); + const feeValues = this.safeValue (indexed['fees'], id, []); + const fees = this.safeValue (feeValues, 1, []); + const fee = this.safeFloat (fees, 1); + const precision = 8; // default precision, todo: fix "magic constants" + id = 'f' + id; + result[code] = { + 'id': id, + 'code': code, + 'info': [ id, label, pool, feeValues ], + 'type': type, + 'name': name, + 'active': true, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'price': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': fee, + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + // this api call does not return the 'used' amount - use the v1 version instead (which also returns zero balances) + await this.loadMarkets (); + const response = await this.privatePostAuthRWallets (params); + const balanceType = this.safeString (params, 'type', 'exchange'); + const result = { 'info': response }; + for (let b = 0; b < response.length; b++) { + const balance = response[b]; + const accountType = balance[0]; + let currency = balance[1]; + const total = balance[2]; + const available = balance[4]; + if (accountType === balanceType) { + if (currency[0] === 't') { + currency = currency.slice (1); + } + const code = this.safeCurrencyCode (currency); + const account = this.account (); + // do not fill in zeroes and missing values in the parser + // rewrite and unify the following to use the unified parseBalance + account['total'] = total; + if (!available) { + if (available === 0) { + account['free'] = 0; + account['used'] = total; + } else { + account['free'] = total; + } + } else { + account['free'] = available; + account['used'] = account['total'] - account['free']; + } + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const precision = this.safeValue (this.options, 'precision', 'R0'); + const request = { + 'symbol': this.marketId (symbol), + 'precision': precision, + }; + if (limit !== undefined) { + request['len'] = limit; // 25 or 100 + } + const fullRequest = this.extend (request, params); + const orderbook = await this.publicGetBookSymbolPrecision (fullRequest); + const timestamp = this.milliseconds (); + const result = { + 'bids': [], + 'asks': [], + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + const priceIndex = (fullRequest['precision'] === 'R0') ? 1 : 0; + for (let i = 0; i < orderbook.length; i++) { + const order = orderbook[i]; + const price = order[priceIndex]; + const amount = Math.abs (order[2]); + const side = (order[2] > 0) ? 'bids' : 'asks'; + result[side].push ([ price, amount ]); + } + result['bids'] = this.sortBy (result['bids'], 0, true); + result['asks'] = this.sortBy (result['asks'], 0); + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const length = ticker.length; + const last = ticker[length - 4]; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': ticker[length - 2], + 'low': ticker[length - 1], + 'bid': ticker[length - 10], + 'bidVolume': undefined, + 'ask': ticker[length - 8], + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': ticker[length - 6], + 'percentage': ticker[length - 5] * 100, + 'average': undefined, + 'baseVolume': ticker[length - 3], + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['symbols'] = ids.join (','); + } else { + request['symbols'] = 'ALL'; + } + const tickers = await this.publicGetTickers (this.extend (request, params)); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const id = ticker[0]; + if (id in this.markets_by_id) { + const market = this.markets_by_id[id]; + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const ticker = await this.publicGetTickerSymbol (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseSymbol (marketId) { + if (marketId === undefined) { + return marketId; + } + marketId = marketId.replace ('t', ''); + let baseId = undefined; + let quoteId = undefined; + if (marketId.indexOf (':') >= 0) { + const parts = marketId.split (':'); + baseId = parts[0]; + quoteId = parts[1]; + } else { + baseId = marketId.slice (0, 3); + quoteId = marketId.slice (3, 6); + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + return base + '/' + quote; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // ID, + // MTS, // timestamp + // AMOUNT, + // PRICE + // ] + // + // fetchMyTrades (private) + // + // [ + // ID, + // PAIR, + // MTS_CREATE, + // ORDER_ID, + // EXEC_AMOUNT, + // EXEC_PRICE, + // ORDER_TYPE, + // ORDER_PRICE, + // MAKER, + // FEE, + // FEE_CURRENCY, + // ... + // ] + // + const tradeLength = trade.length; + const isPrivate = (tradeLength > 5); + const id = trade[0].toString (); + const amountIndex = isPrivate ? 4 : 2; + let amount = trade[amountIndex]; + let cost = undefined; + const priceIndex = isPrivate ? 5 : 3; + const price = trade[priceIndex]; + let side = undefined; + let orderId = undefined; + let takerOrMaker = undefined; + let type = undefined; + let fee = undefined; + let symbol = undefined; + const timestampIndex = isPrivate ? 2 : 1; + const timestamp = trade[timestampIndex]; + if (isPrivate) { + const marketId = trade[1]; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + symbol = this.parseSymbol (marketId); + } + orderId = trade[3].toString (); + takerOrMaker = (trade[8] === 1) ? 'maker' : 'taker'; + let feeCost = trade[9]; + const feeCurrency = this.safeCurrencyCode (trade[10]); + if (feeCost !== undefined) { + feeCost = -feeCost; + if (symbol in this.markets) { + feeCost = this.feeToPrecision (symbol, feeCost); + } else { + const currencyId = 'f' + feeCurrency; + if (currencyId in this.currencies_by_id) { + const currency = this.currencies_by_id[currencyId]; + feeCost = this.currencyToPrecision (currency['code'], feeCost); + } + } + fee = { + 'cost': parseFloat (feeCost), + 'currency': feeCurrency, + }; + } + const orderType = trade[6]; + type = this.safeString (this.options['exchangeTypes'], orderType); + } + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } + } + if (amount !== undefined) { + side = (amount < 0) ? 'sell' : 'buy'; + amount = Math.abs (amount); + if (cost === undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'side': side, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let sort = '-1'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + sort = '1'; + } + if (limit !== undefined) { + request['limit'] = limit; // default 120, max 5000 + } + request['sort'] = sort; + const response = await this.publicGetTradesSymbolHist (this.extend (request, params)); + // + // [ + // [ + // ID, + // MTS, // timestamp + // AMOUNT, + // PRICE + // ] + // ] + // + const trades = this.sortBy (response, 1); + return this.parseTrades (trades, market, undefined, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 100, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; // default 100, max 5000 + } + if (since === undefined) { + since = this.milliseconds () - this.parseTimeframe (timeframe) * limit * 1000; + } + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + 'sort': 1, + 'start': since, + 'limit': limit, + }; + const response = await this.publicGetCandlesTradeTimeframeSymbolHist (this.extend (request, params)); + // + // [ + // [1591503840000,0.025069,0.025068,0.025069,0.025068,1.97828998], + // [1591504500000,0.025065,0.025065,0.025065,0.025065,1.0164], + // [1591504620000,0.025062,0.025062,0.025062,0.025062,0.5], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + if (status === undefined) { + return status; + } + const parts = status.split (' '); + const state = this.safeString (parts, 0); + const statuses = { + 'ACTIVE': 'open', + 'PARTIALLY': 'open', + 'EXECUTED': 'closed', + 'CANCELED': 'canceled', + 'INSUFFICIENT': 'canceled', + 'RSN_DUST': 'rejected', + 'RSN_PAUSE': 'rejected', + }; + return this.safeString (statuses, state, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 0); + let symbol = undefined; + const marketId = this.safeString (order, 3); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + symbol = this.parseSymbol (marketId); + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + // https://github.com/ccxt/ccxt/issues/6686 + // const timestamp = this.safeTimestamp (order, 5); + const timestamp = this.safeInteger (order, 5); + const remaining = Math.abs (this.safeFloat (order, 6)); + const amount = Math.abs (this.safeFloat (order, 7)); + const filled = amount - remaining; + const side = (order[7] < 0) ? 'sell' : 'buy'; + const orderType = this.safeString (order, 8); + const type = this.safeString (this.safeValue (this.options, 'exchangeTypes'), orderType); + let status = undefined; + const statusString = this.safeString (order, 13); + if (statusString !== undefined) { + const parts = statusString.split (' @ '); + status = this.parseOrderStatus (this.safeString (parts, 0)); + } + const price = this.safeFloat (order, 16); + const average = this.safeFloat (order, 17); + const cost = price * filled; + const clientOrderId = this.safeString (order, 2); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderTypes = this.safeValue (this.options, 'orderTypes', {}); + const orderType = this.safeString (orderTypes, type, type); + amount = (side === 'sell') ? -amount : amount; + const request = { + 'symbol': market['id'], + 'type': orderType, + 'amount': this.numberToString (amount), + }; + if (type !== 'market') { + request['price'] = this.numberToString (price); + } + const clientOrderId = this.safeValue2 (params, 'cid', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['cid'] = clientOrderId; + params = this.omit (params, [ 'cid', 'clientOrderId' ]); + } + const response = await this.privatePostAuthWOrderSubmit (this.extend (request, params)); + // + // [ + // 1578784364.748, // Millisecond Time Stamp of the update + // "on-req", // Purpose of notification ('on-req', 'oc-req', 'uca', 'fon-req', 'foc-req') + // null, // Unique ID of the message + // null, // Ignore + // [ + // [ + // 37271830598, // Order ID + // null, // Group ID + // 1578784364748, // Client Order ID + // "tBTCUST", // Pair + // 1578784364748, // Millisecond timestamp of creation + // 1578784364748, // Millisecond timestamp of update + // -0.005, // Positive means buy, negative means sell + // -0.005, // Original amount + // "EXCHANGE LIMIT", // Order type (LIMIT, MARKET, STOP, TRAILING STOP, EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK, IOC, EXCHANGE IOC) + // null, // Previous order type + // null, // Millisecond timestamp of Time-In-Force: automatic order cancellation + // null, // Ignore + // 0, // Flags (see https://docs.bitfinex.com/docs/flag-values) + // "ACTIVE", // Order Status + // null, // Ignore + // null, // Ignore + // 20000, // Price + // 0, // Average price + // 0, // The trailing price + // 0, // Auxiliary Limit price (for STOP LIMIT) + // null, // Ignore + // null, // Ignore + // null, // Ignore + // 0, // 1 - hidden order + // null, // If another order caused this order to be placed (OCO) this will be that other order's ID + // null, // Ignore + // null, // Ignore + // null, // Ignore + // "API>BFX", // Origin of action: BFX, ETHFX, API>BFX, API>ETHFX + // null, // Ignore + // null, // Ignore + // null // Meta + // ] + // ], + // null, // Error code + // "SUCCESS", // Status (SUCCESS, ERROR, FAILURE, ...) + // "Submitting 1 orders." // Text of the notification + // ] + // + const status = this.safeString (response, 6); + if (status !== 'SUCCESS') { + const errorCode = response[5]; + const errorText = response[7]; + throw new ExchangeError (this.id + ' ' + response[6] + ': ' + errorText + ' (#' + errorCode + ')'); + } + const orders = this.safeValue (response, 4, []); + const order = this.safeValue (orders, 0); + return this.parseOrder (order, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const request = { + 'all': 1, + }; + const response = await this.privatePostAuthWOrderCancelMulti (this.extend (request, params)); + const orders = this.safeValue (response, 4, []); + return this.parseOrders (orders); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const cid = this.safeValue2 (params, 'cid', 'clientOrderId'); // client order id + let request = undefined; + if (cid !== undefined) { + const cidDate = this.safeValue (params, 'cidDate'); // client order id date + if (cidDate === undefined) { + throw new InvalidOrder (this.id + " canceling an order by clientOrderId ('cid') requires both 'cid' and 'cid_date' ('YYYY-MM-DD')"); + } + request = { + 'cid': cid, + 'cid_date': cidDate, + }; + params = this.omit (params, [ 'cid', 'clientOrderId' ]); + } else { + request = { + 'id': parseInt (id), + }; + } + const response = await this.privatePostAuthWOrderCancel (this.extend (request, params)); + const order = this.safeValue (response, 4); + return this.parseOrder (order); + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': [ parseInt (id) ], + }; + const orders = await this.fetchOpenOrders (symbol, undefined, undefined, this.extend (request, params)); + const order = this.safeValue (orders, 0); + if (order === undefined) { + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + return order; + } + + async fetchClosedOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': [ parseInt (id) ], + }; + const orders = await this.fetchClosedOrders (symbol, undefined, undefined, this.extend (request, params)); + const order = this.safeValue (orders, 0); + if (order === undefined) { + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + return order; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + let response = undefined; + if (symbol === undefined) { + response = await this.privatePostAuthROrders (this.extend (request, params)); + } else { + market = this.market (symbol); + request['symbol'] = market['id']; + response = await this.privatePostAuthROrdersSymbol (this.extend (request, params)); + } + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // returns the most recent closed or canceled orders up to circa two weeks ago + await this.loadMarkets (); + const request = {}; + let market = undefined; + let response = undefined; + if (symbol === undefined) { + response = await this.privatePostAuthROrdersHist (this.extend (request, params)); + } else { + market = this.market (symbol); + request['symbol'] = market['id']; + response = await this.privatePostAuthROrdersSymbolHist (this.extend (request, params)); + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 25, max 2500 + } + return this.parseOrders (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrderTrades() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const orderId = parseInt (id); + const request = { + 'id': orderId, + 'symbol': market['id'], + }; + // valid for trades upto 10 days old + const response = await this.privatePostAuthROrderSymbolIdTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + 'end': this.milliseconds (), + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 25, max 1000 + } + let method = 'privatePostAuthRTradesHist'; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + method = 'privatePostAuthRTradesSymbolHist'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const request = { + 'op_renew': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + return response; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'method': name, + 'wallet': 'exchange', // 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively + 'op_renew': 0, // a value of 1 will generate a new address + }; + const response = await this.privatePostAuthWDepositAddress (this.extend (request, params)); + // + // [ + // 1582269616687, // MTS Millisecond Time Stamp of the update + // 'acc_dep', // TYPE Purpose of notification 'acc_dep' for account deposit + // null, // MESSAGE_ID unique ID of the message + // null, // not documented + // [ + // null, // PLACEHOLDER + // 'BITCOIN', // METHOD Method of deposit + // 'BTC', // CURRENCY_CODE Currency code of new address + // null, // PLACEHOLDER + // '1BC9PZqpUmjyEB54uggn8TFKj49zSDYzqG', // ADDRESS + // null, // POOL_ADDRESS + // ], + // null, // CODE null or integer work in progress + // 'SUCCESS', // STATUS Status of the notification, SUCCESS, ERROR, FAILURE + // 'success', // TEXT Text of the notification + // ] + // + const result = this.safeValue (response, 4, []); + const poolAddress = this.safeString (result, 5); + const address = (poolAddress === undefined) ? this.safeString (result, 4) : poolAddress; + const tag = (poolAddress === undefined) ? undefined : this.safeString (result, 4); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'SUCCESS': 'ok', + 'ERROR': 'failed', + 'FAILURE': 'failed', + 'CANCELED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // [ + // 1582271520931, // MTS Millisecond Time Stamp of the update + // "acc_wd-req", // TYPE Purpose of notification 'acc_wd-req' account withdrawal request + // null, // MESSAGE_ID unique ID of the message + // null, // not documented + // [ + // 0, // WITHDRAWAL_ID Unique Withdrawal ID + // null, // PLACEHOLDER + // "bitcoin", // METHOD Method of withdrawal + // null, // PAYMENT_ID Payment ID if relevant + // "exchange", // WALLET Sending wallet + // 1, // AMOUNT Amount of Withdrawal less fee + // null, // PLACEHOLDER + // null, // PLACEHOLDER + // 0.0004, // WITHDRAWAL_FEE Fee on withdrawal + // ], + // null, // CODE null or integer Work in progress + // "SUCCESS", // STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE + // "Invalid bitcoin address (abcdef)", // TEXT Text of the notification + // ] + // + // fetchTransactions + // + // [ + // 13293039, // ID + // 'ETH', // CURRENCY + // 'ETHEREUM', // CURRENCY_NAME + // null, + // null, + // 1574175052000, // MTS_STARTED + // 1574181326000, // MTS_UPDATED + // null, + // null, + // 'CANCELED', // STATUS + // null, + // null, + // -0.24, // AMOUNT, negative for withdrawals + // -0.00135, // FEES + // null, + // null, + // 'DESTINATION_ADDRESS', + // null, + // null, + // null, + // 'TRANSACTION_ID', + // "Purchase of 100 pizzas", // WITHDRAW_TRANSACTION_NOTE + // ] + // + const transactionLength = transaction.length; + let timestamp = undefined; + let updated = undefined; + let code = undefined; + let amount = undefined; + let id = undefined; + let status = undefined; + let tag = undefined; + let type = undefined; + let feeCost = undefined; + let txid = undefined; + let addressTo = undefined; + if (transactionLength < 9) { + const data = this.safeValue (transaction, 4, []); + timestamp = this.safeInteger (transaction, 0); + if (currency !== undefined) { + code = currency['code']; + } + feeCost = this.safeFloat (data, 8); + if (feeCost !== undefined) { + feeCost = -feeCost; + } + amount = this.safeFloat (data, 5); + id = this.safeValue (data, 0); + status = 'ok'; + if (id === 0) { + id = undefined; + status = 'failed'; + } + tag = this.safeString (data, 3); + type = 'withdrawal'; + } else { + id = this.safeString (transaction, 0); + timestamp = this.safeInteger (transaction, 5); + updated = this.safeInteger (transaction, 6); + status = this.parseTransactionStatus (this.safeString (transaction, 9)); + amount = this.safeFloat (transaction, 12); + if (amount !== undefined) { + if (amount < 0) { + type = 'withdrawal'; + } else { + type = 'deposit'; + } + } + feeCost = this.safeFloat (transaction, 13); + if (feeCost !== undefined) { + feeCost = -feeCost; + } + addressTo = this.safeString (transaction, 16); + txid = this.safeString (transaction, 20); + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': addressTo, // this is actually the tag for XRP transfers (the address is missing) + 'addressTo': addressTo, + 'tagFrom': undefined, + 'tag': tag, // refix it properly for the tag from description + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'currency': code, + 'cost': feeCost, + 'rate': undefined, + }, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + let method = 'privatePostAuthRMovementsHist'; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + method = 'privatePostAuthRMovementsCurrencyHist'; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // max 1000 + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // [ + // 13293039, // ID + // 'ETH', // CURRENCY + // 'ETHEREUM', // CURRENCY_NAME + // null, + // null, + // 1574175052000, // MTS_STARTED + // 1574181326000, // MTS_UPDATED + // null, + // null, + // 'CANCELED', // STATUS + // null, + // null, + // -0.24, // AMOUNT, negative for withdrawals + // -0.00135, // FEES + // null, + // null, + // 'DESTINATION_ADDRESS', + // null, + // null, + // null, + // 'TRANSACTION_ID', + // "Purchase of 100 pizzas", // WITHDRAW_TRANSACTION_NOTE + // ] + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'method': name, + 'wallet': 'exchange', // 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively + 'amount': this.numberToString (amount), + 'address': address, + }; + if (tag !== undefined) { + request['payment_id'] = tag; + } + const response = await this.privatePostAuthWWithdraw (this.extend (request, params)); + // + // [ + // 1582271520931, // MTS Millisecond Time Stamp of the update + // "acc_wd-req", // TYPE Purpose of notification 'acc_wd-req' account withdrawal request + // null, // MESSAGE_ID unique ID of the message + // null, // not documented + // [ + // 0, // WITHDRAWAL_ID Unique Withdrawal ID + // null, // PLACEHOLDER + // "bitcoin", // METHOD Method of withdrawal + // null, // PAYMENT_ID Payment ID if relevant + // "exchange", // WALLET Sending wallet + // 1, // AMOUNT Amount of Withdrawal less fee + // null, // PLACEHOLDER + // null, // PLACEHOLDER + // 0.0004, // WITHDRAWAL_FEE Fee on withdrawal + // ], + // null, // CODE null or integer Work in progress + // "SUCCESS", // STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE + // "Invalid bitcoin address (abcdef)", // TEXT Text of the notification + // ] + // + const text = this.safeString (response, 7); + if (text !== 'success') { + this.throwBroadlyMatchedException (this.exceptions['broad'], text, text); + } + const transaction = this.parseTransaction (response, currency); + return this.extend (transaction, { + 'address': address, + }); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostPositions (params); + // + // [ + // [ + // "tBTCUSD", // SYMBOL + // "ACTIVE", // STATUS + // 0.0195, // AMOUNT + // 8565.0267019, // BASE_PRICE + // 0, // MARGIN_FUNDING + // 0, // MARGIN_FUNDING_TYPE + // -0.33455568705000516, // PL + // -0.0003117550117425625, // PL_PERC + // 7045.876419249083, // PRICE_LIQ + // 3.0673001895895604, // LEVERAGE + // null, // _PLACEHOLDER + // 142355652, // POSITION_ID + // 1574002216000, // MTS_CREATE + // 1574002216000, // MTS_UPDATE + // null, // _PLACEHOLDER + // 0, // TYPE + // null, // _PLACEHOLDER + // 0, // COLLATERAL + // 0, // COLLATERAL_MIN + // // META + // { + // "reason":"TRADE", + // "order_id":34271018124, + // "liq_stage":null, + // "trade_price":"8565.0267019", + // "trade_amount":"0.0195", + // "order_id_oppo":34277498022 + // } + // ] + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'v1') { + request = api + request; + } else { + request = this.version + request; + } + let url = this.urls['api'][api] + '/' + request; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + body = this.json (query); + const auth = '/api/' + request + nonce + body; + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha384'); + headers = { + 'bfx-nonce': nonce, + 'bfx-apikey': this.apiKey, + 'bfx-signature': signature, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (response) { + if ('message' in response) { + if (response['message'].indexOf ('not enough exchange balance') >= 0) { + throw new InsufficientFunds (this.id + ' ' + this.json (response)); + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } else if (response === '') { + throw new ExchangeError (this.id + ' returned empty response'); + } + return response; + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + if (statusCode === 500) { + // See https://docs.bitfinex.com/docs/abbreviations-glossary#section-errorinfo-codes + const errorCode = this.numberToString (response[1]); + const errorText = response[2]; + const feedback = this.id + ' ' + errorText; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorText, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorText, feedback); + throw new ExchangeError (this.id + ' ' + errorText + ' (#' + errorCode + ')'); + } + } +}; + +},{"./base/errors":7,"./bitfinex.js":29}],31:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, OrderNotFound } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitflyer extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitflyer', + 'name': 'bitFlyer', + 'countries': [ 'JP' ], + 'version': 'v1', + 'rateLimit': 1000, // their nonce-timestamp is in seconds... + 'hostname': 'bitflyer.com', // or bitflyer.com + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': 'emulated', + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': 'emulated', + 'fetchOrder': 'emulated', + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg', + 'api': 'https://api.{hostname}', + 'www': 'https://bitflyer.com', + 'doc': 'https://lightning.bitflyer.com/docs?lang=en', + }, + 'api': { + 'public': { + 'get': [ + 'getmarkets/usa', // new (wip) + 'getmarkets/eu', // new (wip) + 'getmarkets', // or 'markets' + 'getboard', // ... + 'getticker', + 'getexecutions', + 'gethealth', + 'getboardstate', + 'getchats', + ], + }, + 'private': { + 'get': [ + 'getpermissions', + 'getbalance', + 'getbalancehistory', + 'getcollateral', + 'getcollateralhistory', + 'getcollateralaccounts', + 'getaddresses', + 'getcoinins', + 'getcoinouts', + 'getbankaccounts', + 'getdeposits', + 'getwithdrawals', + 'getchildorders', + 'getparentorders', + 'getparentorder', + 'getexecutions', + 'getpositions', + 'gettradingcommission', + ], + 'post': [ + 'sendcoin', + 'withdraw', + 'sendchildorder', + 'cancelchildorder', + 'sendparentorder', + 'cancelparentorder', + 'cancelallchildorders', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'BTC/JPY': { + 'maker': 0.15 / 100, + 'taker': 0.15 / 100, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const jp_markets = await this.publicGetGetmarkets (params); + const us_markets = await this.publicGetGetmarketsUsa (params); + const eu_markets = await this.publicGetGetmarketsEu (params); + let markets = this.arrayConcat (jp_markets, us_markets); + markets = this.arrayConcat (markets, eu_markets); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'product_code'); + const currencies = id.split ('_'); + let baseId = undefined; + let quoteId = undefined; + let base = undefined; + let quote = undefined; + const numCurrencies = currencies.length; + if (numCurrencies === 1) { + baseId = id.slice (0, 3); + quoteId = id.slice (3, 6); + } else if (numCurrencies === 2) { + baseId = currencies[0]; + quoteId = currencies[1]; + } else { + baseId = currencies[1]; + quoteId = currencies[2]; + } + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + const symbol = (numCurrencies === 2) ? (base + '/' + quote) : id; + const fees = this.safeValue (this.fees, symbol, this.fees['trading']); + let maker = this.safeValue (fees, 'maker', this.fees['trading']['maker']); + let taker = this.safeValue (fees, 'taker', this.fees['trading']['taker']); + let spot = true; + let future = false; + let type = 'spot'; + if (('alias' in market) || (currencies[0] === 'FX')) { + type = 'future'; + future = true; + spot = false; + maker = 0.0; + taker = 0.0; + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'maker': maker, + 'taker': taker, + 'type': type, + 'spot': spot, + 'future': future, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetGetbalance (params); + // + // [ + // { + // "currency_code": "JPY", + // "amount": 1024078, + // "available": 508000 + // }, + // { + // "currency_code": "BTC", + // "amount": 10.24, + // "available": 4.12 + // }, + // { + // "currency_code": "ETH", + // "amount": 20.48, + // "available": 16.38 + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'amount'); + account['free'] = this.safeFloat (balance, 'available'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + }; + const orderbook = await this.publicGetGetboard (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'size'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + }; + const ticker = await this.publicGetGetticker (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const last = this.safeFloat (ticker, 'ltp'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume_by_product'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + let side = this.safeStringLower (trade, 'side'); + if (side !== undefined) { + if (side.length < 1) { + side = undefined; + } + } + let order = undefined; + if (side !== undefined) { + const id = side + '_child_order_acceptance_id'; + if (id in trade) { + order = trade[id]; + } + } + if (order === undefined) { + order = this.safeString (trade, 'child_order_acceptance_id'); + } + const timestamp = this.parse8601 (this.safeString (trade, 'exec_date')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const id = this.safeString (trade, 'id'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': order, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_code': market['id'], + }; + const response = await this.publicGetGetexecutions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + 'child_order_type': type.toUpperCase (), + 'side': side.toUpperCase (), + 'price': price, + 'size': amount, + }; + const result = await this.privatePostSendchildorder (this.extend (request, params)); + // { "status": - 200, "error_message": "Insufficient funds", "data": null } + const id = this.safeString (result, 'child_order_acceptance_id'); + return { + 'info': result, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder() requires a `symbol` argument'); + } + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + 'child_order_acceptance_id': id, + }; + return await this.privatePostCancelchildorder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'ACTIVE': 'open', + 'COMPLETED': 'closed', + 'CANCELED': 'canceled', + 'EXPIRED': 'canceled', + 'REJECTED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const timestamp = this.parse8601 (this.safeString (order, 'child_order_date')); + const amount = this.safeFloat (order, 'size'); + const remaining = this.safeFloat (order, 'outstanding_size'); + const filled = this.safeFloat (order, 'executed_size'); + const price = this.safeFloat (order, 'price'); + const cost = price * filled; + const status = this.parseOrderStatus (this.safeString (order, 'child_order_state')); + const type = this.safeStringLower (order, 'child_order_type'); + const side = this.safeStringLower (order, 'side'); + const marketId = this.safeString (order, 'product_code'); + const symbol = this.safeSymbol (marketId, market); + let fee = undefined; + const feeCost = this.safeFloat (order, 'total_commission'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + 'rate': undefined, + }; + } + const id = this.safeString (order, 'child_order_acceptance_id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = 100, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_code': market['id'], + 'count': limit, + }; + const response = await this.privateGetGetchildorders (this.extend (request, params)); + let orders = this.parseOrders (response, market, since, limit); + if (symbol !== undefined) { + orders = this.filterBy (orders, 'symbol', symbol); + } + return orders; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = 100, params = {}) { + const request = { + 'child_order_state': 'ACTIVE', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = 100, params = {}) { + const request = { + 'child_order_state': 'COMPLETED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a `symbol` argument'); + } + const orders = await this.fetchOrders (symbol); + const ordersById = this.indexBy (orders, 'id'); + if (id in ordersById) { + return ordersById[id]; + } + throw new OrderNotFound (this.id + ' No order found with id ' + id); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_code': market['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetexecutions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + if (symbols === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPositions requires a `symbols` argument, exactly one symbol in an array'); + } + await this.loadMarkets (); + const request = { + 'product_code': this.marketIds (symbols), + }; + const response = await this.privateGetpositions (this.extend (request, params)); + // + // [ + // { + // "product_code": "FX_BTC_JPY", + // "side": "BUY", + // "price": 36000, + // "size": 10, + // "commission": 0, + // "swap_point_accumulate": -35, + // "require_collateral": 120000, + // "open_date": "2015-11-03T10:04:45.011", + // "leverage": 3, + // "pnl": 965, + // "sfd": -0.5 + // } + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + if (code !== 'JPY' && code !== 'USD' && code !== 'EUR') { + throw new ExchangeError (this.id + ' allows withdrawing JPY, USD, EUR only, ' + code + ' is not supported'); + } + const currency = this.currency (code); + const request = { + 'currency_code': currency['id'], + 'amount': amount, + // 'bank_account_id': 1234, + }; + const response = await this.privatePostWithdraw (this.extend (request, params)); + const id = this.safeString (response, 'message_id'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.version + '/'; + if (api === 'private') { + request += 'me/'; + } + request += path; + if (method === 'GET') { + if (Object.keys (params).length) { + request += '?' + this.urlencode (params); + } + } + const baseUrl = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }); + const url = baseUrl + request; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let auth = [ nonce, method, request ].join (''); + if (Object.keys (params).length) { + if (method !== 'GET') { + body = this.json (params); + auth += body; + } + } + headers = { + 'ACCESS-KEY': this.apiKey, + 'ACCESS-TIMESTAMP': nonce, + 'ACCESS-SIGN': this.hmac (this.encode (auth), this.encode (this.secret)), + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],32:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, OrderNotFound, InsufficientFunds, DDoSProtection, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitforex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitforex', + 'name': 'Bitforex', + 'countries': [ 'CN' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + '1M': '1month', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295553-1160ec00-c50e-11ea-8ea0-df79276a9646.jpg', + 'api': 'https://api.bitforex.com', + 'www': 'https://www.bitforex.com', + 'doc': 'https://github.com/githubdev2020/API_Doc_en/wiki', + 'fees': 'https://help.bitforex.com/en_us/?cat=13', + 'referral': 'https://www.bitforex.com/en/invitationRegister?inviterId=1867438', + }, + 'api': { + 'public': { + 'get': [ + 'api/v1/market/symbols', + 'api/v1/market/ticker', + 'api/v1/market/depth', + 'api/v1/market/trades', + 'api/v1/market/kline', + ], + }, + 'private': { + 'post': [ + 'api/v1/fund/mainAccount', + 'api/v1/fund/allAccount', + 'api/v1/trade/placeOrder', + 'api/v1/trade/placeMultiOrder', + 'api/v1/trade/cancelOrder', + 'api/v1/trade/orderInfo', + 'api/v1/trade/orderInfos', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': true, + 'deposit': {}, + 'withdraw': { + 'BTC': 0.0005, + 'ETH': 0.01, + 'BCH': 0.0001, + 'LTC': 0.001, + 'ETC': 0.005, + 'USDT': 5, + 'CMCT': 30, + 'AION': 3, + 'LVT': 0, + 'DATA': 40, + 'RHP': 50, + 'NEO': 0, + 'AIDOC': 10, + 'BQT': 2, + 'R': 2, + 'DPY': 0.8, + 'GTC': 40, + 'AGI': 30, + 'DENT': 100, + 'SAN': 1, + 'SPANK': 8, + 'AID': 5, + 'OMG': 0.1, + 'BFT': 5, + 'SHOW': 150, + 'TRX': 20, + 'ABYSS': 10, + 'THM': 25, + 'ZIL': 20, + 'PPT': 0.2, + 'WTC': 0.4, + 'LRC': 7, + 'BNT': 1, + 'CTXC': 1, + 'MITH': 20, + 'TRUE': 4, + 'LYM': 10, + 'VEE': 100, + 'AUTO': 200, + 'REN': 50, + 'TIO': 2.5, + 'NGC': 1.5, + 'PST': 10, + 'CRE': 200, + 'IPC': 5, + 'PTT': 1000, + 'XMCT': 20, + 'ATMI': 40, + 'TERN': 40, + 'XLM': 0.01, + 'ODE': 15, + 'FTM': 100, + 'RTE': 100, + 'DCC': 100, + 'IMT': 500, + 'GOT': 3, + 'EGT': 500, + 'DACC': 1000, + 'UBEX': 500, + 'ABL': 100, + 'OLT': 100, + 'DAV': 40, + 'THRT': 10, + 'RMESH': 3, + 'UPP': 20, + 'SDT': 0, + 'SHR': 10, + 'MTV': 3, + 'ESS': 100, + 'MET': 3, + 'TTC': 20, + 'LXT': 10, + 'XCLP': 100, + 'LUK': 100, + 'UBC': 100, + 'DTX': 10, + 'BEAT': 20, + 'DEED': 2, + 'BGX': 3000, + 'PRL': 20, + 'ELY': 50, + 'CARD': 300, + 'SQR': 15, + 'VRA': 400, + 'BWX': 3500, + 'MAS': 75, + 'FLP': 0.6, + 'UNC': 300, + 'CRNC': 15, + 'MFG': 70, + 'ZXC': 70, + 'TRT': 30, + 'ZIX': 35, + 'XRA': 10, + 'AMO': 1600, + 'IPG': 3, + 'uDoo': 50, + 'URB': 30, + 'ARCONA': 3, + 'CRAD': 5, + 'NOBS': 1000, + 'ADF': 2, + 'ELF': 5, + 'LX': 20, + 'PATH': 15, + 'SILK': 120, + 'SKYFT': 50, + 'EDN': 50, + 'ADE': 50, + 'EDR': 10, + 'TIME': 0.25, + 'SPRK': 20, + 'QTUM': 0.01, + 'BF': 5, + 'ZPR': 100, + 'HYB': 10, + 'CAN': 30, + 'CEL': 10, + 'ATS': 50, + 'KCASH': 1, + 'ACT': 0.01, + 'MT': 300, + 'DXT': 30, + 'WAB': 4000, + 'HYDRO': 400, + 'LQD': 5, + 'OPTC': 200, + 'EQUAD': 80, + 'LATX': 50, + 'LEDU': 100, + 'RIT': 70, + 'ACDC': 500, + 'FSN': 2, + }, + }, + }, + 'commonCurrencies': { + 'CREDIT': 'TerraCredit', + 'IQ': 'IQ.Cash', + 'UOS': 'UOS Network', + }, + 'exceptions': { + '4004': OrderNotFound, + '1013': AuthenticationError, + '1016': AuthenticationError, + '1017': PermissionDenied, // {"code":"1017","success":false,"time":1602670594367,"message":"IP not allow"} + '3002': InsufficientFunds, + '10204': DDoSProtection, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetApiV1MarketSymbols (params); + const data = response['data']; + const result = []; + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const id = this.safeString (market, 'symbol'); + const symbolParts = id.split ('-'); + const baseId = symbolParts[2]; + const quoteId = symbolParts[1]; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = true; + const precision = { + 'amount': this.safeInteger (market, 'amountPrecision'), + 'price': this.safeInteger (market, 'pricePrecision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minOrderAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (trade, 'time'); + const id = this.safeString (trade, 'tid'); + const orderId = undefined; + const amount = this.safeFloat (trade, 'amount'); + const price = this.safeFloat (trade, 'price'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const sideId = this.safeInteger (trade, 'direction'); + const side = this.parseSide (sideId); + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'order': orderId, + 'fee': undefined, + 'takerOrMaker': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['size'] = limit; + } + const market = this.market (symbol); + const response = await this.publicGetApiV1MarketTrades (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostApiV1FundAllAccount (params); + const data = response['data']; + const result = { 'info': response }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'frozen'); + account['free'] = this.safeFloat (balance, 'active'); + account['total'] = this.safeFloat (balance, 'fix'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.markets[symbol]; + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetApiV1MarketTicker (this.extend (request, params)); + const data = response['data']; + const timestamp = this.safeInteger (data, 'date'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (data, 'high'), + 'low': this.safeFloat (data, 'low'), + 'bid': this.safeFloat (data, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (data, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': this.safeFloat (data, 'last'), + 'last': this.safeFloat (data, 'last'), + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (data, 'vol'), + 'quoteVolume': undefined, + 'info': response, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "close":0.02505143, + // "currencyVol":0, + // "high":0.02506422, + // "low":0.02505143, + // "open":0.02506095, + // "time":1591508940000, + // "vol":51.1869 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'vol'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'ktype': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['size'] = limit; // default 1, max 600 + } + const response = await this.publicGetApiV1MarketKline (this.extend (request, params)); + // + // { + // "data":[ + // {"close":0.02505143,"currencyVol":0,"high":0.02506422,"low":0.02505143,"open":0.02506095,"time":1591508940000,"vol":51.1869}, + // {"close":0.02503914,"currencyVol":0,"high":0.02506687,"low":0.02503914,"open":0.02505358,"time":1591509000000,"vol":9.1082}, + // {"close":0.02505172,"currencyVol":0,"high":0.02507466,"low":0.02503895,"open":0.02506371,"time":1591509060000,"vol":63.7431}, + // ], + // "success":true, + // "time":1591509427131 + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.publicGetApiV1MarketDepth (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const timestamp = this.safeInteger (response, 'time'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', + '2': 'closed', + '3': 'canceled', + '4': 'canceled', + }; + return (status in statuses) ? statuses[status] : status; + } + + parseSide (sideId) { + if (sideId === 1) { + return 'buy'; + } else if (sideId === 2) { + return 'sell'; + } else { + return undefined; + } + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'orderId'); + const timestamp = this.safeFloat (order, 'createTime'); + const lastTradeTimestamp = this.safeFloat (order, 'lastTime'); + const symbol = market['symbol']; + const sideId = this.safeInteger (order, 'tradeType'); + const side = this.parseSide (sideId); + const type = undefined; + const price = this.safeFloat (order, 'orderPrice'); + const average = this.safeFloat (order, 'avgPrice'); + const amount = this.safeFloat (order, 'orderAmount'); + const filled = this.safeFloat (order, 'dealAmount'); + const remaining = amount - filled; + const status = this.parseOrderStatus (this.safeString (order, 'orderState')); + const cost = filled * price; + const feeSide = (side === 'buy') ? 'base' : 'quote'; + const feeCurrency = market[feeSide]; + const fee = { + 'cost': this.safeFloat (order, 'tradeFee'), + 'currency': feeCurrency, + }; + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': this.marketId (symbol), + 'orderId': id, + }; + const response = await this.privatePostApiV1TradeOrderInfo (this.extend (request, params)); + const order = this.parseOrder (response['data'], market); + return order; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': this.marketId (symbol), + 'state': 0, + }; + const response = await this.privatePostApiV1TradeOrderInfos (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': this.marketId (symbol), + 'state': 1, + }; + const response = await this.privatePostApiV1TradeOrderInfos (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let sideId = undefined; + if (side === 'buy') { + sideId = 1; + } else if (side === 'sell') { + sideId = 2; + } + const request = { + 'symbol': this.marketId (symbol), + 'price': price, + 'amount': amount, + 'tradeType': sideId, + }; + const response = await this.privatePostApiV1TradePlaceOrder (this.extend (request, params)); + const data = response['data']; + return { + 'info': response, + 'id': this.safeString (data, 'orderId'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + if (symbol !== undefined) { + request['symbol'] = this.marketId (symbol); + } + const results = await this.privatePostApiV1TradeCancelOrder (this.extend (request, params)); + const success = results['success']; + const returnVal = { 'info': results, 'success': success }; + return returnVal; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + let payload = this.urlencode ({ 'accessKey': this.apiKey }); + query['nonce'] = this.milliseconds (); + if (Object.keys (query).length) { + payload += '&' + this.urlencode (this.keysort (query)); + } + // let message = '/' + 'api/' + this.version + '/' + path + '?' + payload; + const message = '/' + path + '?' + payload; + const signature = this.hmac (this.encode (message), this.encode (this.secret)); + body = payload + '&signData=' + signature; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (typeof body !== 'string') { + return; // fallback to default error handler + } + if ((body[0] === '{') || (body[0] === '[')) { + const feedback = this.id + ' ' + body; + const success = this.safeValue (response, 'success'); + if (success !== undefined) { + if (!success) { + const code = this.safeString (response, 'code'); + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],33:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, OnMaintenance, ArgumentsRequired, BadRequest, AccountSuspended, InvalidAddress, PermissionDenied, DDoSProtection, InsufficientFunds, InvalidNonce, CancelPending, InvalidOrder, OrderNotFound, AuthenticationError, RequestTimeout, NotSupported, BadSymbol, RateLimitExceeded } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitget extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitget', + 'name': 'Bitget', + 'countries': [ 'SG' ], + 'version': 'v3', + 'rateLimit': 1000, // up to 3000 requests per 5 minutes ≈ 600 requests per minute ≈ 10 requests per second ≈ 100 ms + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + }, + 'hostname': 'bitget.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/88317935-a8a21c80-cd22-11ea-8e2b-4b9fac5975eb.jpg', + 'api': { + 'data': 'https://api.{hostname}', + 'api': 'https://api.{hostname}', + 'capi': 'https://capi.{hostname}', + 'swap': 'https://capi.{hostname}', + }, + 'www': 'https://www.bitget.com', + 'doc': [ + 'https://bitgetlimited.github.io/apidoc/en/swap', + 'https://bitgetlimited.github.io/apidoc/en/spot', + ], + 'fees': 'https://www.bitget.cc/zh-CN/rate?tab=1', + 'test': { + 'rest': 'https://testnet.bitget.com', + }, + 'referral': 'https://www.bitget.com/expressly?languageType=0&channelCode=ccxt&vipCode=tg9j', + }, + 'api': { + 'data': { + 'get': [ + 'market/history/kline', // Kline data + 'market/detail/merged', // Get aggregated ticker + 'market/tickers', // Get all trading tickers + 'market/allticker', // Get all trading market method 2 + 'market/depth', // Get Market Depth Data + 'market/trade', // Get Trade Detail Data + 'market/history/trade', // Get record of trading + 'market/detail', // Get Market Detail 24h Volume + 'common/symbols', // Query all trading pairs and accuracy supported in the station + 'common/currencys', // Query all currencies supported in the station + 'common/timestamp', // Query system current time + ], + }, + 'api': { + 'get': [ + 'account/accounts', // Get all accounts of current user(即account_id)。 + 'accounts/{account_id}/balance', // Get the balance of the specified account + 'order/orders', // Query order, deprecated + 'order/orders/openOrders', + 'order/orders/history', + 'order/deposit_withdraw', // Query assets history + ], + 'post': [ + 'order/orders/place', // Place order + 'order/orders/{order_id}/submitcancel', // Request to cancel an order request + 'order/orders/batchcancel', // Bulk order cancellation + 'order/orders/{order_id}', // Query an order details + 'order/orders/{order_id}/matchresults', // Query the transaction details of an order + 'order/matchresults', // Query current order, order history + ], + }, + 'capi': { + 'get': [ + 'market/time', + 'market/contracts', + 'market/depth', + 'market/tickers', + 'market/ticker', + 'market/trades', + 'market/candles', + 'market/index', + 'market/open_count', + 'market/open_interest', + 'market/price_limit', + 'market/funding_time', + 'market/mark_price', + 'market/open_count', + 'market/historyFundRate', + ], + }, + 'swap': { + 'get': [ + 'account/accounts', + 'account/account', + 'account/settings', + 'position/allPosition', + 'position/singlePosition', + 'position/holds', + 'order/detail', + 'order/orders', + 'order/fills', + 'order/current', + 'order/currentPlan', // conditional + 'order/history', + 'order/historyPlan', // conditional + 'trace/closeTrack', + 'trace/currentTrack', + 'trace/historyTrack', + ], + 'post': [ + 'account/leverage', + 'account/adjustMargin', + 'account/modifyAutoAppendMargin', + 'order/placeOrder', + 'order/batchOrders', + 'order/cancel_order', + 'order/cancel_batch_orders', + 'order/plan_order', + 'order/cancel_plan', + 'position/changeHoldModel', + ], + }, + }, + 'fees': { + 'spot': { + 'taker': 0.002, + 'maker': 0.002, + }, + 'swap': { + 'taker': 0.0006, + 'maker': 0.0004, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'exceptions': { + // http error codes + // 400 Bad Request — Invalid request format + // 401 Unauthorized — Invalid API Key + // 403 Forbidden — You do not have access to the requested resource + // 404 Not Found + // 500 Internal Server Error — We had a problem with our server + 'exact': { + '1': ExchangeError, // { "code": 1, "message": "System error" } + // undocumented + 'failure to get a peer from the ring-balancer': ExchangeNotAvailable, // { "message": "failure to get a peer from the ring-balancer" } + '4010': PermissionDenied, // { "code": 4010, "message": "For the security of your funds, withdrawals are not permitted within 24 hours after changing fund password / mobile number / Google Authenticator settings " } + // common + // '0': ExchangeError, // 200 successful,when the order placement / cancellation / operation is successful + '4001': ExchangeError, // no data received in 30s + '4002': ExchangeError, // Buffer full. cannot write data + // -------------------------------------------------------- + '30001': AuthenticationError, // { "code": 30001, "message": 'request header "OK_ACCESS_KEY" cannot be blank'} + '30002': AuthenticationError, // { "code": 30002, "message": 'request header "OK_ACCESS_SIGN" cannot be blank'} + '30003': AuthenticationError, // { "code": 30003, "message": 'request header "OK_ACCESS_TIMESTAMP" cannot be blank'} + '30004': AuthenticationError, // { "code": 30004, "message": 'request header "OK_ACCESS_PASSPHRASE" cannot be blank'} + '30005': InvalidNonce, // { "code": 30005, "message": "invalid OK_ACCESS_TIMESTAMP" } + '30006': AuthenticationError, // { "code": 30006, "message": "invalid OK_ACCESS_KEY" } + '30007': BadRequest, // { "code": 30007, "message": 'invalid Content_Type, please use "application/json" format'} + '30008': RequestTimeout, // { "code": 30008, "message": "timestamp request expired" } + '30009': ExchangeError, // { "code": 30009, "message": "system error" } + '30010': AuthenticationError, // { "code": 30010, "message": "API validation failed" } + '30011': PermissionDenied, // { "code": 30011, "message": "invalid IP" } + '30012': AuthenticationError, // { "code": 30012, "message": "invalid authorization" } + '30013': AuthenticationError, // { "code": 30013, "message": "invalid sign" } + '30014': DDoSProtection, // { "code": 30014, "message": "request too frequent" } + '30015': AuthenticationError, // { "code": 30015, "message": 'request header "OK_ACCESS_PASSPHRASE" incorrect'} + '30016': ExchangeError, // { "code": 30015, "message": "you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey" } + '30017': ExchangeError, // { "code": 30017, "message": "apikey's broker id does not match" } + '30018': ExchangeError, // { "code": 30018, "message": "apikey's domain does not match" } + '30019': ExchangeNotAvailable, // { "code": 30019, "message": "Api is offline or unavailable" } + '30020': BadRequest, // { "code": 30020, "message": "body cannot be blank" } + '30021': BadRequest, // { "code": 30021, "message": "Json data format error" }, { "code": 30021, "message": "json data format error" } + '30022': PermissionDenied, // { "code": 30022, "message": "Api has been frozen" } + '30023': BadRequest, // { "code": 30023, "message": "{0} parameter cannot be blank" } + '30024': BadSymbol, // {"code":30024,"message":"\"instrument_id\" is an invalid parameter"} + '30025': BadRequest, // { "code": 30025, "message": "{0} parameter category error" } + '30026': DDoSProtection, // { "code": 30026, "message": "requested too frequent" } + '30027': AuthenticationError, // { "code": 30027, "message": "login failure" } + '30028': PermissionDenied, // { "code": 30028, "message": "unauthorized execution" } + '30029': AccountSuspended, // { "code": 30029, "message": "account suspended" } + '30030': ExchangeError, // { "code": 30030, "message": "endpoint request failed. Please try again" } + '30031': BadRequest, // { "code": 30031, "message": "token does not exist" } + '30032': BadSymbol, // { "code": 30032, "message": "pair does not exist" } + '30033': BadRequest, // { "code": 30033, "message": "exchange domain does not exist" } + '30034': ExchangeError, // { "code": 30034, "message": "exchange ID does not exist" } + '30035': ExchangeError, // { "code": 30035, "message": "trading is not supported in this website" } + '30036': ExchangeError, // { "code": 30036, "message": "no relevant data" } + '30037': ExchangeNotAvailable, // { "code": 30037, "message": "endpoint is offline or unavailable" } + // '30038': AuthenticationError, // { "code": 30038, "message": "user does not exist" } + '30038': OnMaintenance, // {"client_oid":"","code":"30038","error_code":"30038","error_message":"Matching engine is being upgraded. Please try in about 1 minute.","message":"Matching engine is being upgraded. Please try in about 1 minute.","order_id":"-1","result":false} + // futures + '32001': AccountSuspended, // { "code": 32001, "message": "futures account suspended" } + '32002': PermissionDenied, // { "code": 32002, "message": "futures account does not exist" } + '32003': CancelPending, // { "code": 32003, "message": "canceling, please wait" } + '32004': ExchangeError, // { "code": 32004, "message": "you have no unfilled orders" } + '32005': InvalidOrder, // { "code": 32005, "message": "max order quantity" } + '32006': InvalidOrder, // { "code": 32006, "message": "the order price or trigger price exceeds USD 1 million" } + '32007': InvalidOrder, // { "code": 32007, "message": "leverage level must be the same for orders on the same side of the contract" } + '32008': InvalidOrder, // { "code": 32008, "message": "Max. positions to open (cross margin)" } + '32009': InvalidOrder, // { "code": 32009, "message": "Max. positions to open (fixed margin)" } + '32010': ExchangeError, // { "code": 32010, "message": "leverage cannot be changed with open positions" } + '32011': ExchangeError, // { "code": 32011, "message": "futures status error" } + '32012': ExchangeError, // { "code": 32012, "message": "futures order update error" } + '32013': ExchangeError, // { "code": 32013, "message": "token type is blank" } + '32014': ExchangeError, // { "code": 32014, "message": "your number of contracts closing is larger than the number of contracts available" } + '32015': ExchangeError, // { "code": 32015, "message": "margin ratio is lower than 100% before opening positions" } + '32016': ExchangeError, // { "code": 32016, "message": "margin ratio is lower than 100% after opening position" } + '32017': ExchangeError, // { "code": 32017, "message": "no BBO" } + '32018': ExchangeError, // { "code": 32018, "message": "the order quantity is less than 1, please try again" } + '32019': ExchangeError, // { "code": 32019, "message": "the order price deviates from the price of the previous minute by more than 3%" } + '32020': ExchangeError, // { "code": 32020, "message": "the price is not in the range of the price limit" } + '32021': ExchangeError, // { "code": 32021, "message": "leverage error" } + '32022': ExchangeError, // { "code": 32022, "message": "this function is not supported in your country or region according to the regulations" } + '32023': ExchangeError, // { "code": 32023, "message": "this account has outstanding loan" } + '32024': ExchangeError, // { "code": 32024, "message": "order cannot be placed during delivery" } + '32025': ExchangeError, // { "code": 32025, "message": "order cannot be placed during settlement" } + '32026': ExchangeError, // { "code": 32026, "message": "your account is restricted from opening positions" } + '32027': ExchangeError, // { "code": 32027, "message": "cancelled over 20 orders" } + '32028': AccountSuspended, // { "code": 32028, "message": "account is suspended and liquidated" } + '32029': ExchangeError, // { "code": 32029, "message": "order info does not exist" } + '32030': InvalidOrder, // The order cannot be cancelled + '32031': ArgumentsRequired, // client_oid or order_id is required. + '32038': AuthenticationError, // User does not exist + '32040': ExchangeError, // User have open contract orders or position + '32044': ExchangeError, // { "code": 32044, "message": "The margin ratio after submitting this order is lower than the minimum requirement ({0}) for your tier." } + '32045': ExchangeError, // String of commission over 1 million + '32046': ExchangeError, // Each user can hold up to 10 trade plans at the same time + '32047': ExchangeError, // system error + '32048': InvalidOrder, // Order strategy track range error + '32049': ExchangeError, // Each user can hold up to 10 track plans at the same time + '32050': InvalidOrder, // Order strategy rang error + '32051': InvalidOrder, // Order strategy ice depth error + '32052': ExchangeError, // String of commission over 100 thousand + '32053': ExchangeError, // Each user can hold up to 6 ice plans at the same time + '32057': ExchangeError, // The order price is zero. Market-close-all function cannot be executed + '32054': ExchangeError, // Trade not allow + '32055': InvalidOrder, // cancel order error + '32056': ExchangeError, // iceberg per order average should between {0}-{1} contracts + '32058': ExchangeError, // Each user can hold up to 6 initiative plans at the same time + '32059': InvalidOrder, // Total amount should exceed per order amount + '32060': InvalidOrder, // Order strategy type error + '32061': InvalidOrder, // Order strategy initiative limit error + '32062': InvalidOrder, // Order strategy initiative range error + '32063': InvalidOrder, // Order strategy initiative rate error + '32064': ExchangeError, // Time Stringerval of orders should set between 5-120s + '32065': ExchangeError, // Close amount exceeds the limit of Market-close-all (999 for BTC, and 9999 for the rest tokens) + '32066': ExchangeError, // You have open orders. Please cancel all open orders before changing your leverage level. + '32067': ExchangeError, // Account equity < required margin in this setting. Please adjust your leverage level again. + '32068': ExchangeError, // The margin for this position will fall short of the required margin in this setting. Please adjust your leverage level or increase your margin to proceed. + '32069': ExchangeError, // Target leverage level too low. Your account balance is insufficient to cover the margin required. Please adjust the leverage level again. + '32070': ExchangeError, // Please check open position or unfilled order + '32071': ExchangeError, // Your current liquidation mode does not support this action. + '32072': ExchangeError, // The highest available margin for your order’s tier is {0}. Please edit your margin and place a new order. + '32073': ExchangeError, // The action does not apply to the token + '32074': ExchangeError, // The number of contracts of your position, open orders, and the current order has exceeded the maximum order limit of this asset. + '32075': ExchangeError, // Account risk rate breach + '32076': ExchangeError, // Liquidation of the holding position(s) at market price will require cancellation of all pending close orders of the contracts. + '32077': ExchangeError, // Your margin for this asset in futures account is insufficient and the position has been taken over for liquidation. (You will not be able to place orders, close positions, transfer funds, or add margin during this period of time. Your account will be restored after the liquidation is complete.) + '32078': ExchangeError, // Please cancel all open orders before switching the liquidation mode(Please cancel all open orders before switching the liquidation mode) + '32079': ExchangeError, // Your open positions are at high risk.(Please add margin or reduce positions before switching the mode) + '32080': ExchangeError, // Funds cannot be transferred out within 30 minutes after futures settlement + '32083': ExchangeError, // The number of contracts should be a positive multiple of %%. Please place your order again + // token and margin trading + '33001': PermissionDenied, // { "code": 33001, "message": "margin account for this pair is not enabled yet" } + '33002': AccountSuspended, // { "code": 33002, "message": "margin account for this pair is suspended" } + '33003': InsufficientFunds, // { "code": 33003, "message": "no loan balance" } + '33004': ExchangeError, // { "code": 33004, "message": "loan amount cannot be smaller than the minimum limit" } + '33005': ExchangeError, // { "code": 33005, "message": "repayment amount must exceed 0" } + '33006': ExchangeError, // { "code": 33006, "message": "loan order not found" } + '33007': ExchangeError, // { "code": 33007, "message": "status not found" } + '33008': InsufficientFunds, // { "code": 33008, "message": "loan amount cannot exceed the maximum limit" } + '33009': ExchangeError, // { "code": 33009, "message": "user ID is blank" } + '33010': ExchangeError, // { "code": 33010, "message": "you cannot cancel an order during session 2 of call auction" } + '33011': ExchangeError, // { "code": 33011, "message": "no new market data" } + '33012': ExchangeError, // { "code": 33012, "message": "order cancellation failed" } + '33013': InvalidOrder, // { "code": 33013, "message": "order placement failed" } + '33014': OrderNotFound, // { "code": 33014, "message": "order does not exist" } + '33015': InvalidOrder, // { "code": 33015, "message": "exceeded maximum limit" } + '33016': ExchangeError, // { "code": 33016, "message": "margin trading is not open for this token" } + '33017': InsufficientFunds, // { "code": 33017, "message": "insufficient balance" } + '33018': ExchangeError, // { "code": 33018, "message": "this parameter must be smaller than 1" } + '33020': ExchangeError, // { "code": 33020, "message": "request not supported" } + '33021': BadRequest, // { "code": 33021, "message": "token and the pair do not match" } + '33022': InvalidOrder, // { "code": 33022, "message": "pair and the order do not match" } + '33023': ExchangeError, // { "code": 33023, "message": "you can only place market orders during call auction" } + '33024': InvalidOrder, // { "code": 33024, "message": "trading amount too small" } + '33025': InvalidOrder, // { "code": 33025, "message": "base token amount is blank" } + '33026': ExchangeError, // { "code": 33026, "message": "transaction completed" } + '33027': InvalidOrder, // { "code": 33027, "message": "cancelled order or order cancelling" } + '33028': InvalidOrder, // { "code": 33028, "message": "the decimal places of the trading price exceeded the limit" } + '33029': InvalidOrder, // { "code": 33029, "message": "the decimal places of the trading size exceeded the limit" } + '33034': ExchangeError, // { "code": 33034, "message": "You can only place limit order after Call Auction has started" } + '33035': ExchangeError, // This type of order cannot be canceled(This type of order cannot be canceled) + '33036': ExchangeError, // Exceeding the limit of entrust order + '33037': ExchangeError, // The buy order price should be lower than 130% of the trigger price + '33038': ExchangeError, // The sell order price should be higher than 70% of the trigger price + '33039': ExchangeError, // The limit of callback rate is 0 < x <= 5% + '33040': ExchangeError, // The trigger price of a buy order should be lower than the latest transaction price + '33041': ExchangeError, // The trigger price of a sell order should be higher than the latest transaction price + '33042': ExchangeError, // The limit of price variance is 0 < x <= 1% + '33043': ExchangeError, // The total amount must be larger than 0 + '33044': ExchangeError, // The average amount should be 1/1000 * total amount <= x <= total amount + '33045': ExchangeError, // The price should not be 0, including trigger price, order price, and price limit + '33046': ExchangeError, // Price variance should be 0 < x <= 1% + '33047': ExchangeError, // Sweep ratio should be 0 < x <= 100% + '33048': ExchangeError, // Per order limit: Total amount/1000 < x <= Total amount + '33049': ExchangeError, // Total amount should be X > 0 + '33050': ExchangeError, // Time interval should be 5 <= x <= 120s + '33051': ExchangeError, // cancel order number not higher limit: plan and track entrust no more than 10, ice and time entrust no more than 6 + '33059': BadRequest, // { "code": 33059, "message": "client_oid or order_id is required" } + '33060': BadRequest, // { "code": 33060, "message": "Only fill in either parameter client_oid or order_id" } + '33061': ExchangeError, // Value of a single market price order cannot exceed 100,000 USD + '33062': ExchangeError, // The leverage ratio is too high. The borrowed position has exceeded the maximum position of this leverage ratio. Please readjust the leverage ratio + '33063': ExchangeError, // Leverage multiple is too low, there is insufficient margin in the account, please readjust the leverage ratio + '33064': ExchangeError, // The setting of the leverage ratio cannot be less than 2, please readjust the leverage ratio + '33065': ExchangeError, // Leverage ratio exceeds maximum leverage ratio, please readjust leverage ratio + // account + '21009': ExchangeError, // Funds cannot be transferred out within 30 minutes after swap settlement(Funds cannot be transferred out within 30 minutes after swap settlement) + '34001': PermissionDenied, // { "code": 34001, "message": "withdrawal suspended" } + '34002': InvalidAddress, // { "code": 34002, "message": "please add a withdrawal address" } + '34003': ExchangeError, // { "code": 34003, "message": "sorry, this token cannot be withdrawn to xx at the moment" } + '34004': ExchangeError, // { "code": 34004, "message": "withdrawal fee is smaller than minimum limit" } + '34005': ExchangeError, // { "code": 34005, "message": "withdrawal fee exceeds the maximum limit" } + '34006': ExchangeError, // { "code": 34006, "message": "withdrawal amount is lower than the minimum limit" } + '34007': ExchangeError, // { "code": 34007, "message": "withdrawal amount exceeds the maximum limit" } + '34008': InsufficientFunds, // { "code": 34008, "message": "insufficient balance" } + '34009': ExchangeError, // { "code": 34009, "message": "your withdrawal amount exceeds the daily limit" } + '34010': ExchangeError, // { "code": 34010, "message": "transfer amount must be larger than 0" } + '34011': ExchangeError, // { "code": 34011, "message": "conditions not met" } + '34012': ExchangeError, // { "code": 34012, "message": "the minimum withdrawal amount for NEO is 1, and the amount must be an integer" } + '34013': ExchangeError, // { "code": 34013, "message": "please transfer" } + '34014': ExchangeError, // { "code": 34014, "message": "transfer limited" } + '34015': ExchangeError, // { "code": 34015, "message": "subaccount does not exist" } + '34016': PermissionDenied, // { "code": 34016, "message": "transfer suspended" } + '34017': AccountSuspended, // { "code": 34017, "message": "account suspended" } + '34018': AuthenticationError, // { "code": 34018, "message": "incorrect trades password" } + '34019': PermissionDenied, // { "code": 34019, "message": "please bind your email before withdrawal" } + '34020': PermissionDenied, // { "code": 34020, "message": "please bind your funds password before withdrawal" } + '34021': InvalidAddress, // { "code": 34021, "message": "Not verified address" } + '34022': ExchangeError, // { "code": 34022, "message": "Withdrawals are not available for sub accounts" } + '34023': PermissionDenied, // { "code": 34023, "message": "Please enable futures trading before transferring your funds" } + '34026': ExchangeError, // transfer too frequently(transfer too frequently) + '34036': ExchangeError, // Parameter is incorrect, please refer to API documentation + '34037': ExchangeError, // Get the sub-account balance interface, account type is not supported + '34038': ExchangeError, // Since your C2C transaction is unusual, you are restricted from fund transfer. Please contact our customer support to cancel the restriction + '34039': ExchangeError, // You are now restricted from transferring out your funds due to abnormal trades on C2C Market. Please transfer your fund on our website or app instead to verify your identity + // swap + '35001': ExchangeError, // { "code": 35001, "message": "Contract does not exist" } + '35002': ExchangeError, // { "code": 35002, "message": "Contract settling" } + '35003': ExchangeError, // { "code": 35003, "message": "Contract paused" } + '35004': ExchangeError, // { "code": 35004, "message": "Contract pending settlement" } + '35005': AuthenticationError, // { "code": 35005, "message": "User does not exist" } + '35008': InvalidOrder, // { "code": 35008, "message": "Risk ratio too high" } + '35010': InvalidOrder, // { "code": 35010, "message": "Position closing too large" } + '35012': InvalidOrder, // { "code": 35012, "message": "Incorrect order size" } + '35014': InvalidOrder, // { "code": 35014, "message": "Order price is not within limit" } + '35015': InvalidOrder, // { "code": 35015, "message": "Invalid leverage level" } + '35017': ExchangeError, // { "code": 35017, "message": "Open orders exist" } + '35019': InvalidOrder, // { "code": 35019, "message": "Order size too large" } + '35020': InvalidOrder, // { "code": 35020, "message": "Order price too high" } + '35021': InvalidOrder, // { "code": 35021, "message": "Order size exceeded current tier limit" } + '35022': ExchangeError, // { "code": 35022, "message": "Contract status error" } + '35024': ExchangeError, // { "code": 35024, "message": "Contract not initialized" } + '35025': InsufficientFunds, // { "code": 35025, "message": "No account balance" } + '35026': ExchangeError, // { "code": 35026, "message": "Contract settings not initialized" } + '35029': OrderNotFound, // { "code": 35029, "message": "Order does not exist" } + '35030': InvalidOrder, // { "code": 35030, "message": "Order size too large" } + '35031': InvalidOrder, // { "code": 35031, "message": "Cancel order size too large" } + '35032': ExchangeError, // { "code": 35032, "message": "Invalid user status" } + '35037': ExchangeError, // No last traded price in cache + '35039': ExchangeError, // { "code": 35039, "message": "Open order quantity exceeds limit" } + '35040': InvalidOrder, // {"error_message":"Invalid order type","result":"true","error_code":"35040","order_id":"-1"} + '35044': ExchangeError, // { "code": 35044, "message": "Invalid order status" } + '35046': InsufficientFunds, // { "code": 35046, "message": "Negative account balance" } + '35047': InsufficientFunds, // { "code": 35047, "message": "Insufficient account balance" } + '35048': ExchangeError, // { "code": 35048, "message": "User contract is frozen and liquidating" } + '35049': InvalidOrder, // { "code": 35049, "message": "Invalid order type" } + '35050': InvalidOrder, // { "code": 35050, "message": "Position settings are blank" } + '35052': InsufficientFunds, // { "code": 35052, "message": "Insufficient cross margin" } + '35053': ExchangeError, // { "code": 35053, "message": "Account risk too high" } + '35055': InsufficientFunds, // { "code": 35055, "message": "Insufficient account balance" } + '35057': ExchangeError, // { "code": 35057, "message": "No last traded price" } + '35058': ExchangeError, // { "code": 35058, "message": "No limit" } + '35059': BadRequest, // { "code": 35059, "message": "client_oid or order_id is required" } + '35060': BadRequest, // { "code": 35060, "message": "Only fill in either parameter client_oid or order_id" } + '35061': BadRequest, // { "code": 35061, "message": "Invalid instrument_id" } + '35062': InvalidOrder, // { "code": 35062, "message": "Invalid match_price" } + '35063': InvalidOrder, // { "code": 35063, "message": "Invalid order_size" } + '35064': InvalidOrder, // { "code": 35064, "message": "Invalid client_oid" } + '35066': InvalidOrder, // Order interval error + '35067': InvalidOrder, // Time-weighted order ratio error + '35068': InvalidOrder, // Time-weighted order range error + '35069': InvalidOrder, // Time-weighted single transaction limit error + '35070': InvalidOrder, // Algo order type error + '35071': InvalidOrder, // Order total must be larger than single order limit + '35072': InvalidOrder, // Maximum 6 unfulfilled time-weighted orders can be held at the same time + '35073': InvalidOrder, // Order price is 0. Market-close-all not available + '35074': InvalidOrder, // Iceberg order single transaction average error + '35075': InvalidOrder, // Failed to cancel order + '35076': InvalidOrder, // LTC 20x leverage. Not allowed to open position + '35077': InvalidOrder, // Maximum 6 unfulfilled iceberg orders can be held at the same time + '35078': InvalidOrder, // Order amount exceeded 100,000 + '35079': InvalidOrder, // Iceberg order price variance error + '35080': InvalidOrder, // Callback rate error + '35081': InvalidOrder, // Maximum 10 unfulfilled trail orders can be held at the same time + '35082': InvalidOrder, // Trail order callback rate error + '35083': InvalidOrder, // Each user can only hold a maximum of 10 unfulfilled stop-limit orders at the same time + '35084': InvalidOrder, // Order amount exceeded 1 million + '35085': InvalidOrder, // Order amount is not in the correct range + '35086': InvalidOrder, // Price exceeds 100 thousand + '35087': InvalidOrder, // Price exceeds 100 thousand + '35088': InvalidOrder, // Average amount error + '35089': InvalidOrder, // Price exceeds 100 thousand + '35090': ExchangeError, // No stop-limit orders available for cancelation + '35091': ExchangeError, // No trail orders available for cancellation + '35092': ExchangeError, // No iceberg orders available for cancellation + '35093': ExchangeError, // No trail orders available for cancellation + '35094': ExchangeError, // Stop-limit order last traded price error + '35095': BadRequest, // Instrument_id error + '35096': ExchangeError, // Algo order status error + '35097': ExchangeError, // Order status and order ID cannot exist at the same time + '35098': ExchangeError, // An order status or order ID must exist + '35099': ExchangeError, // Algo order ID error + // option + '36001': BadRequest, // Invalid underlying index. + '36002': BadRequest, // Instrument does not exist. + '36005': ExchangeError, // Instrument status is invalid. + '36101': AuthenticationError, // Account does not exist. + '36102': PermissionDenied, // Account status is invalid. + '36103': AccountSuspended, // Account is suspended due to ongoing liquidation. + '36104': PermissionDenied, // Account is not enabled for options trading. + '36105': PermissionDenied, // Please enable the account for option contract. + '36106': AccountSuspended, // Funds cannot be transferred in or out, as account is suspended. + '36107': PermissionDenied, // Funds cannot be transferred out within 30 minutes after option exercising or settlement. + '36108': InsufficientFunds, // Funds cannot be transferred in or out, as equity of the account is less than zero. + '36109': PermissionDenied, // Funds cannot be transferred in or out during option exercising or settlement. + '36201': PermissionDenied, // New order function is blocked. + '36202': PermissionDenied, // Account does not have permission to short option. + '36203': InvalidOrder, // Invalid format for client_oid. + '36204': ExchangeError, // Invalid format for request_id. + '36205': BadRequest, // Instrument id does not match underlying index. + '36206': BadRequest, // Order_id and client_oid can not be used at the same time. + '36207': InvalidOrder, // Either order price or fartouch price must be present. + '36208': InvalidOrder, // Either order price or size must be present. + '36209': InvalidOrder, // Either order_id or client_oid must be present. + '36210': InvalidOrder, // Either order_ids or client_oids must be present. + '36211': InvalidOrder, // Exceeding max batch size for order submission. + '36212': InvalidOrder, // Exceeding max batch size for oder cancellation. + '36213': InvalidOrder, // Exceeding max batch size for order amendment. + '36214': ExchangeError, // Instrument does not have valid bid/ask quote. + '36216': OrderNotFound, // Order does not exist. + '36217': InvalidOrder, // Order submission failed. + '36218': InvalidOrder, // Order cancellation failed. + '36219': InvalidOrder, // Order amendment failed. + '36220': InvalidOrder, // Order is pending cancel. + '36221': InvalidOrder, // Order qty is not valid multiple of lot size. + '36222': InvalidOrder, // Order price is breaching highest buy limit. + '36223': InvalidOrder, // Order price is breaching lowest sell limit. + '36224': InvalidOrder, // Exceeding max order size. + '36225': InvalidOrder, // Exceeding max open order count for instrument. + '36226': InvalidOrder, // Exceeding max open order count for underlying. + '36227': InvalidOrder, // Exceeding max open size across all orders for underlying + '36228': InvalidOrder, // Exceeding max available qty for instrument. + '36229': InvalidOrder, // Exceeding max available qty for underlying. + '36230': InvalidOrder, // Exceeding max position limit for underlying. + // -------------------------------------------------------- + // swap + '400': BadRequest, // Bad Request + '401': AuthenticationError, // Unauthorized access + '403': PermissionDenied, // Access prohibited + '404': BadRequest, // Request address does not exist + '405': BadRequest, // The HTTP Method is not supported + '415': BadRequest, // The current media type is not supported + '429': DDoSProtection, // Too many requests + '500': ExchangeNotAvailable, // System busy + '1001': RateLimitExceeded, // The request is too frequent and has been throttled + '1002': ExchangeError, // {0} verifications within 24 hours + '1003': ExchangeError, // You failed more than {0} times today, the current operation is locked, please try again in 24 hours + // '00000': ExchangeError, // success + '40001': AuthenticationError, // ACCESS_KEY cannot be empty + '40002': AuthenticationError, // SECRET_KEY cannot be empty + '40003': AuthenticationError, // Signature cannot be empty + '40004': InvalidNonce, // Request timestamp expired + '40005': InvalidNonce, // Invalid ACCESS_TIMESTAMP + '40006': AuthenticationError, // Invalid ACCESS_KEY + '40007': BadRequest, // Invalid Content_Type + '40008': InvalidNonce, // Request timestamp expired + '40009': AuthenticationError, // sign signature error + '40010': AuthenticationError, // sign signature error + '40011': AuthenticationError, // ACCESS_PASSPHRASE cannot be empty + '40012': AuthenticationError, // apikey/password is incorrect + '40013': ExchangeError, // User status is abnormal + '40014': PermissionDenied, // Incorrect permissions + '40015': ExchangeError, // System is abnormal, please try again later + '40016': PermissionDenied, // The user must bind the phone or Google + '40017': ExchangeError, // Parameter verification failed + '40018': PermissionDenied, // Invalid IP + '40102': BadRequest, // Contract configuration does not exist, please check the parameters + '40103': BadRequest, // Request method cannot be empty + '40104': ExchangeError, // Lever adjustment failure + '40105': ExchangeError, // Abnormal access to current price limit data + '40106': ExchangeError, // Abnormal get next settlement time + '40107': ExchangeError, // Abnormal access to index price data + '40108': InvalidOrder, // Wrong order quantity + '40109': OrderNotFound, // The data of the order cannot be found, please confirm the order number + '40200': OnMaintenance, // Server upgrade, please try again later + '40201': InvalidOrder, // Order number cannot be empty + '40202': ExchangeError, // User information cannot be empty + '40203': BadRequest, // The amount of adjustment margin cannot be empty or negative + '40204': BadRequest, // Adjustment margin type cannot be empty + '40205': BadRequest, // Adjusted margin type data is wrong + '40206': BadRequest, // The direction of the adjustment margin cannot be empty + '40207': BadRequest, // The adjustment margin data is wrong + '40208': BadRequest, // The accuracy of the adjustment margin amount is incorrect + '40209': BadRequest, // The current page number is wrong, please confirm + '40300': ExchangeError, // User does not exist + '40301': PermissionDenied, // Permission has not been obtained yet. If you need to use it, please contact customer service + '40302': BadRequest, // Parameter abnormality + '40303': BadRequest, // Can only query up to 20,000 data + '40304': BadRequest, // Parameter type is abnormal + '40305': BadRequest, // Client_oid length is not greater than 50, and cannot be Martian characters + '40306': ExchangeError, // Batch processing orders can only process up to 20 + '40308': OnMaintenance, // The contract is being temporarily maintained + '40309': BadSymbol, // The contract has been removed + '40400': ExchangeError, // Status check abnormal + '40401': ExchangeError, // The operation cannot be performed + '40402': BadRequest, // The opening direction cannot be empty + '40403': BadRequest, // Wrong opening direction format + '40404': BadRequest, // Whether to enable automatic margin call parameters cannot be empty + '40405': BadRequest, // Whether to enable the automatic margin call parameter type is wrong + '40406': BadRequest, // Whether to enable automatic margin call parameters is of unknown type + '40407': ExchangeError, // The query direction is not the direction entrusted by the plan + '40408': ExchangeError, // Wrong time range + '40409': ExchangeError, // Time format error + '40500': InvalidOrder, // Client_oid check error + '40501': ExchangeError, // Channel name error + '40502': ExchangeError, // If it is a copy user, you must pass the copy to whom + '40503': ExchangeError, // With the single type + '40504': ExchangeError, // Platform code must pass + '40505': ExchangeError, // Not the same as single type + '40506': AuthenticationError, // Platform signature error + '40507': AuthenticationError, // Api signature error + '40508': ExchangeError, // KOL is not authorized + '40509': ExchangeError, // Abnormal copy end + '40600': ExchangeError, // Copy function suspended + '40601': ExchangeError, // Followers cannot be KOL + '40602': ExchangeError, // The number of copies has reached the limit and cannot process the request + '40603': ExchangeError, // Abnormal copy end + '40604': ExchangeNotAvailable, // Server is busy, please try again later + '40605': ExchangeError, // Copy type, the copy number must be passed + '40606': ExchangeError, // The type of document number is wrong + '40607': ExchangeError, // Document number must be passed + '40608': ExchangeError, // No documented products currently supported + '40609': ExchangeError, // The contract product does not support copying + '40700': BadRequest, // Cursor parameters are incorrect + '40701': ExchangeError, // KOL is not authorized + '40702': ExchangeError, // Unauthorized copying user + '40703': ExchangeError, // Bill inquiry start and end time cannot be empty + '40704': ExchangeError, // Can only check the data of the last three months + '40705': BadRequest, // The start and end time cannot exceed 90 days + '40706': InvalidOrder, // Wrong order price + '40707': BadRequest, // Start time is greater than end time + '40708': BadRequest, // Parameter verification is abnormal + '40709': ExchangeError, // There is no position in this position, and no automatic margin call can be set + '40710': ExchangeError, // Abnormal account status + '40711': InsufficientFunds, // Insufficient contract account balance + '40712': InsufficientFunds, // Insufficient margin + '40713': ExchangeError, // Cannot exceed the maximum transferable margin amount + '40714': ExchangeError, // No direct margin call is allowed + // spot + 'invalid sign': AuthenticationError, + 'invalid currency': BadSymbol, // invalid trading pair + 'invalid symbol': BadSymbol, + 'invalid period': BadRequest, // invalid Kline type + 'invalid user': ExchangeError, + 'invalid amount': InvalidOrder, + 'invalid type': InvalidOrder, // {"status":"error","ts":1595700344504,"err_code":"invalid-parameter","err_msg":"invalid type"} + 'invalid orderId': InvalidOrder, + 'invalid record': ExchangeError, + 'invalid accountId': BadRequest, + 'invalid address': BadRequest, + 'accesskey not null': AuthenticationError, // {"status":"error","ts":1595704360508,"err_code":"invalid-parameter","err_msg":"accesskey not null"} + 'illegal accesskey': AuthenticationError, + 'sign not null': AuthenticationError, + 'req_time is too much difference from server time': InvalidNonce, + 'permissions not right': PermissionDenied, // {"status":"error","ts":1595704490084,"err_code":"invalid-parameter","err_msg":"permissions not right"} + 'illegal sign invalid': AuthenticationError, // {"status":"error","ts":1595684716042,"err_code":"invalid-parameter","err_msg":"illegal sign invalid"} + 'user locked': AccountSuspended, + 'Request Frequency Is Too High': RateLimitExceeded, + 'more than a daily rate of cash': BadRequest, + 'more than the maximum daily withdrawal amount': BadRequest, + 'need to bind email or mobile': ExchangeError, + 'user forbid': PermissionDenied, + 'User Prohibited Cash Withdrawal': PermissionDenied, + 'Cash Withdrawal Is Less Than The Minimum Value': BadRequest, + 'Cash Withdrawal Is More Than The Maximum Value': BadRequest, + 'the account with in 24 hours ban coin': PermissionDenied, + 'order cancel fail': BadRequest, // {"status":"error","ts":1595703343035,"err_code":"bad-request","err_msg":"order cancel fail"} + 'base symbol error': BadSymbol, + 'base date error': ExchangeError, + 'api signature not valid': AuthenticationError, + 'gateway internal error': ExchangeError, + 'audit failed': ExchangeError, + 'order queryorder invalid': BadRequest, + 'market no need price': InvalidOrder, + 'limit need price': InvalidOrder, + 'userid not equal to account_id': ExchangeError, + 'your balance is low': InsufficientFunds, // {"status":"error","ts":1595594160149,"err_code":"invalid-parameter","err_msg":"invalid size, valid range: [1,2000]"} + 'address invalid cointype': ExchangeError, + 'system exception': ExchangeError, // {"status":"error","ts":1595711862763,"err_code":"system exception","err_msg":"system exception"} + '50003': ExchangeError, // No record + '50004': BadSymbol, // The transaction pair is currently not supported or has been suspended + '50006': PermissionDenied, // The account is forbidden to withdraw. If you have any questions, please contact customer service. + '50007': PermissionDenied, // The account is forbidden to withdraw within 24 hours. If you have any questions, please contact customer service. + '50008': RequestTimeout, // network timeout + '50009': RateLimitExceeded, // The operation is too frequent, please try again later + '50010': ExchangeError, // The account is abnormally frozen. If you have any questions, please contact customer service. + '50014': InvalidOrder, // The transaction amount under minimum limits + '50015': InvalidOrder, // The transaction amount exceed maximum limits + '50016': InvalidOrder, // The price can't be higher than the current price + '50017': InvalidOrder, // Price under minimum limits + '50018': InvalidOrder, // The price exceed maximum limits + '50019': InvalidOrder, // The amount under minimum limits + '50020': InsufficientFunds, // Insufficient balance + '50021': InvalidOrder, // Price is under minimum limits + '50026': InvalidOrder, // Market price parameter error + 'invalid order query time': ExchangeError, // start time is greater than end time; or the time interval between start time and end time is greater than 48 hours + 'invalid start time': BadRequest, // start time is a date 30 days ago; or start time is a date in the future + 'invalid end time': BadRequest, // end time is a date 30 days ago; or end time is a date in the future + '20003': ExchangeError, // operation failed, {"status":"error","ts":1595730308979,"err_code":"bad-request","err_msg":"20003"} + '01001': ExchangeError, // order failed, {"status":"fail","err_code":"01001","err_msg":"系统异常,请稍后重试"} + }, + 'broad': { + 'invalid size, valid range': ExchangeError, + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarkets': [ + 'spot', + 'swap', + ], + 'parseOHLCV': { + 'volume': { + 'spot': 'amount', + 'swap': 5, + }, + }, + 'defaultType': 'spot', // 'spot', 'swap' + 'accountId': undefined, // '1012838157', + 'timeframes': { + 'spot': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '60min', + '2h': '120min', + '4h': '240min', + '6h': '360min', + '12h': '720min', + '1d': '1day', + '1w': '1week', + }, + 'swap': { + '1m': '60', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '604800', + }, + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.dataGetCommonTimestamp (params); + // + // { + // "status":"ok", + // "data":"1595525139400" + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchMarkets (params = {}) { + let types = this.safeValue (this.options, 'fetchMarkets'); + if (!types.length) { + types = [ + this.options['defaultType'], + ]; + } + let result = []; + for (let i = 0; i < types.length; i++) { + const markets = await this.fetchMarketsByType (types[i], params); + result = this.arrayConcat (result, markets); + } + return result; + } + + parseMarkets (markets) { + const result = []; + for (let i = 0; i < markets.length; i++) { + result.push (this.parseMarket (markets[i])); + } + return result; + } + + parseMarket (market) { + // + // spot + // + // { + // "base_currency":"btc", + // "quote_currency":"usdt", + // "symbol":"btc_usdt", + // "tick_size":"2", + // "size_increment":"4", + // "status":"1", + // "base_asset_precision":"8" + // } + // + // + // swap + // + // { + // "symbol":"btcusd", + // "underlying_index":"BTC", + // "quote_currency":"USD", + // "coin":"BTC", + // "contract_val":"1", + // "listing":null, + // "delivery":["07:00:00","15:00:00","23:00:00"], + // "size_increment":"0", + // "tick_size":"1", + // "forwardContractFlag":false, + // "priceEndStep":5 + // } + // + const id = this.safeString (market, 'symbol'); + let marketType = 'spot'; + let spot = true; + let swap = false; + const baseId = this.safeString2 (market, 'base_currency', 'coin'); + const quoteId = this.safeString (market, 'quote_currency'); + const contractVal = this.safeFloat (market, 'contract_val'); + if (contractVal !== undefined) { + marketType = 'swap'; + spot = false; + swap = true; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = id.toUpperCase (); + if (spot) { + symbol = base + '/' + quote; + } + const tickSize = this.safeString (market, 'tick_size'); + const sizeIncrement = this.safeString (market, 'size_increment'); + const precision = { + 'amount': parseFloat ('1e-' + sizeIncrement), + 'price': parseFloat ('1e-' + tickSize), + }; + const minAmount = this.safeFloat2 (market, 'min_size', 'base_min_size'); + const status = this.safeString (market, 'status'); + let active = undefined; + if (status !== undefined) { + active = (status === '1'); + } + const fees = this.safeValue2 (this.fees, marketType, 'trading', {}); + return this.extend (fees, { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': marketType, + 'spot': spot, + 'swap': swap, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': precision['price'], + 'max': undefined, + }, + 'cost': { + 'min': precision['price'], + 'max': undefined, + }, + }, + }); + } + + async fetchMarketsByType (type, params = {}) { + if (type === 'spot') { + const response = await this.dataGetCommonSymbols (params); + // + // { + // "status":"ok", + // "ts":1595526622408, + // "data":[ + // { + // "base_currency":"btc", + // "quote_currency":"usdt", + // "symbol":"btc_usdt", + // "tick_size":"2", + // "size_increment":"4", + // "status":"1", + // "base_asset_precision":"8" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseMarkets (data); + } else if (type === 'swap') { + const response = await this.capiGetMarketContracts (params); + // + // { + // "data":{ + // "contractApis":[ + // { + // "instrument_id":"btcusd", + // "underlying_index":"BTC", + // "quote_currency":"USD", + // "coin":"BTC", + // "contract_val":"1", + // "delivery":["07:00:00","15:00:00","23:00:00"], + // "size_increment":"0", + // "tick_size":"1", + // "forwardContractFlag":false, + // "priceEndStep":"5" + // }, + // ] + // }, + // "status":"ok", + // "err_code":"00000" + // } + // + return this.parseMarkets (response); + } else { + throw new NotSupported (this.id + ' fetchMarketsByType does not support market type ' + type); + } + } + + async fetchCurrencies (params = {}) { + const response = await this.dataGetCommonCurrencys (params); + // + // { + // "status":"ok", + // "ts":1595537740466, + // "data":[ + // "btc", + // "bft", + // "usdt", + // "usdt-omni", + // "usdt-erc20" + // ] + // } + // + const result = {}; + const data = this.safeValue (response, 'data', []); + for (let i = 0; i < data.length; i++) { + const id = data[i]; + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'info': id, + 'type': undefined, + 'name': undefined, + 'active': undefined, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (market['spot']) { + method = 'dataGetMarketDepth'; + request['type'] = 'step0'; // step0, step1, step2, step3, step4, step5, do not merge depth if step0 + } else if (market['swap']) { + method = 'capiGetMarketDepth'; + request['limit'] = (limit === undefined) ? 100 : limit; // max 100 + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.btc_usdt.depth.step0", + // "ts":1595607628197, + // "data":{ + // "id":"1595607628197", + // "ts":"1595607628197", + // "bids":[ + // ["9534.99","15.36160000000000000000"], + // ["9534.85","0.14580000000000000000"], + // ["9534.73","0.02100000000000000000"], + // ], + // "asks":[ + // ["9535.02","7.37160000000000000000"], + // ["9535.03","0.09040000000000000000"], + // ["9535.05","0.02180000000000000000"], + // ] + // } + // } + // + // swap + // + // { + // "asks":[ + // ["9579.0","119865",1], + // ["9579.5","90069",1], + // ["9580.0","256673",1], + // ], + // "bids":[ + // ["9578.5","2417",1], + // ["9577.5","3024",1], + // ["9577.0","21548",1], + // ], + // "timestamp":"1595664767349" + // } + // + const data = this.safeValue (response, 'data', response); + const timestamp = this.safeInteger2 (data, 'timestamp', 'ts'); + const nonce = this.safeInteger (data, 'id'); + const orderbook = this.parseOrderBook (data, timestamp); + orderbook['nonce'] = nonce; + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // spot + // + // fetchTicker + // + // { + // "id":"1595538241113", + // "bid":["0.028474000000","1.139400000000"], + // "ask":["0.028482000000","0.353100000000"], + // "amount":"2850.6649", + // "count":"818", + // "open":"0.02821", + // "close":"0.028474", + // "low":"0.02821", + // "high":"0.029091", + // "vol":"79.4548693404" + // } + // + // fetchTickers + // + // { + // "amount":"30086.8095", + // "count":"22450", + // "open":"9525.11", + // "close":"9591.81", + // "low":"9510.68", + // "high":"9659.7", + // "vol":"286239092.250461", + // "symbol":"btc_usdt" + // } + // + // swap + // + // { + // "instrument_id":"btcusd", + // "last":"9574.5", + // "best_ask":"9575.0", + // "best_bid":"9574.0", + // "high_24h":"9672", + // "low_24h":"9512", + // "volume_24h":"567697050", + // "timestamp":"1595538450096" + // } + // + const timestamp = this.safeInteger2 (ticker, 'timestamp', 'id'); + let symbol = undefined; + const marketId = this.safeString2 (ticker, 'instrument_id', 'symbol'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else if (marketId !== undefined) { + const parts = marketId.split ('_'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const last = this.safeFloat2 (ticker, 'last', 'close'); + const open = this.safeFloat (ticker, 'open'); + let bidVolume = undefined; + let askVolume = undefined; + let bid = this.safeValue (ticker, 'bid'); + if (bid === undefined) { + bid = this.safeFloat (ticker, 'best_bid'); + } else { + bidVolume = this.safeFloat (bid, 1); + bid = this.safeFloat (bid, 0); + } + let ask = this.safeValue (ticker, 'ask'); + if (ask === undefined) { + ask = this.safeFloat (ticker, 'best_ask'); + } else { + askVolume = this.safeFloat (ask, 1); + ask = this.safeFloat (ask, 0); + } + const baseVolume = this.safeFloat2 (ticker, 'amount', 'volume_24h'); + const quoteVolume = this.safeFloat (ticker, 'vol'); + const vwap = this.vwap (baseVolume, quoteVolume); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + percentage = change / open * 100; + average = this.sum (open, last) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (ticker, 'high', 'high_24h'), + 'low': this.safeFloat2 (ticker, 'low', 'low_24h'), + 'bid': bid, + 'bidVolume': bidVolume, + 'ask': ask, + 'askVolume': askVolume, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + if (market['spot']) { + method = 'dataGetMarketDetailMerged'; + } else if (market['swap']) { + method = 'capiGetMarketTicker'; + } + const request = { + 'symbol': market['id'], + }; + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.eth_btc.detail.merged", + // "ts":1595538241474, + // "data":{ + // "id":"1595538241113", + // "bid":["0.028474000000","1.139400000000"], + // "ask":["0.028482000000","0.353100000000"], + // "amount":"2850.6649", + // "count":"818", + // "open":"0.02821", + // "close":"0.028474", + // "low":"0.02821", + // "high":"0.029091", + // "vol":"79.4548693404" + // } + // } + // + // swap + // + // { + // "symbol":"btcusd", + // "last":"9575.5", + // "best_ask":"9576.0", + // "best_bid":"9575.0", + // "high_24h":"9646", + // "low_24h":"9516", + // "volume_24h":"516656839", + // "timestamp":"1595664217405" + // } + // + const data = this.safeValue (response, 'data', response); + return this.parseTicker (data, market); + } + + async fetchTickersByType (type, symbols = undefined, params = {}) { + await this.loadMarkets (); + let method = undefined; + if (type === 'spot') { + method = 'dataGetMarketTickers'; + } else if (type === 'swap') { + method = 'capiGetMarketTickers'; + } + const response = await this[method] (params); + // + // spot + // + // { + // "status":"ok", + // "ts":1595542893250, + // "data":[ + // { + // "amount":"30086.8095", + // "count":"22450", + // "open":"9525.11", + // "close":"9591.81", + // "low":"9510.68", + // "high":"9659.7", + // "vol":"286239092.250461", + // "symbol":"btc_usdt" + // } + // ] + // } + // + // swap + // + // [ + // { + // "symbol":"btcusd", + // "last":"9572", + // "best_ask":"9571.5", + // "best_bid":"9570.5", + // "high_24h":"9646", + // "low_24h":"9516", + // "volume_24h":"515401635", + // "timestamp":"1595664479952" + // } + // ] + // + const data = this.safeValue (response, 'data', response); + let timestamp = undefined; + if (!Array.isArray (response)) { + timestamp = this.safeInteger (response, 'ts'); + } + const result = {}; + for (let i = 0; i < data.length; i++) { + const ticker = this.parseTicker (this.extend ({ + 'timestamp': timestamp, + }, data[i])); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + return await this.fetchTickersByType (type, symbols, this.omit (params, 'type')); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // spot + // + // { + // "id":"1", + // "price":"9533.81", + // "amount":"0.7326", + // "direction":"sell", + // "ts":"1595604964000" + // } + // + // swap + // + // { + // "trade_id":"670581881367954915", + // "price":"9553.00", + // "size":"20", + // "side":"sell", + // "timestamp":"1595605100004", + // "symbol":"btcusd" + // } + // + // spot fetchMyTrades (private) + // + // { + // "id": 29555, + // "order_id": 59378, + // "match_id": 59335, + // "symbol": "eth_usdt", + // "type": "buy-limit", + // "source": "api", + // "price": "100.1000000000", + // "filled_amount": "0.9845000000", + // "filled_fees": "0.0019690000", + // "created_at": 1494901400487 + // } + // + // fetchOrderTrades (private) + // + // spot + // + // { + // "id":"614164775", + // "created_at":"1596298860602", + // "filled_amount":"0.0417000000000000", + // "filled_fees":"0.0000834000000000", + // "match_id":"673491702661292033", + // "order_id":"673491720340279296", + // "price":"359.240000000000", + // "source":"接口", + // "symbol":"eth_usdt", + // "type":"buy-market" + // } + // + // swap + // + // { + // "trade_id":"6667390", + // "symbol":"cmt_btcusdt", + // "order_id":"525946425993854915", + // "price":"9839.00", + // "order_qty":"3466", + // "fee":"-0.0000528407360000", + // "timestamp":"1561121514442", + // "exec_type":"M", + // "side":"3" + // } + // + let symbol = undefined; + const marketId = this.safeString (trade, 'symbol'); + let base = undefined; + let quote = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } else if (marketId !== undefined) { + const parts = marketId.split ('_'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId.toUpperCase (); + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let timestamp = this.safeInteger (trade, 'created_at'); + timestamp = this.safeInteger2 (trade, 'timestamp', 'ts', timestamp); + const price = this.safeFloat (trade, 'price'); + let amount = this.safeFloat2 (trade, 'filled_amount', 'order_qty'); + amount = this.safeFloat2 (trade, 'size', 'amount', amount); + let takerOrMaker = this.safeString2 (trade, 'exec_type', 'liquidity'); + if (takerOrMaker === 'M') { + takerOrMaker = 'maker'; + } else if (takerOrMaker === 'T') { + takerOrMaker = 'taker'; + } + const orderType = this.safeString (trade, 'type'); + let side = undefined; + let type = undefined; + if (orderType !== undefined) { + side = this.safeString (trade, 'type'); + type = this.parseOrderType (side); + side = this.parseOrderSide (side); + } else { + side = this.safeString2 (trade, 'side', 'direction'); + type = this.parseOrderType (side); + side = this.parseOrderSide (side); + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let feeCost = this.safeFloat (trade, 'fee'); + if (feeCost === undefined) { + feeCost = this.safeFloat (trade, 'filled_fees'); + } else { + feeCost = -feeCost; + } + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = (side === 'buy') ? base : quote; + fee = { + // fee is either a positive number (invitation rebate) + // or a negative number (transaction fee deduction) + // therefore we need to invert the fee + // more about it https://github.com/ccxt/ccxt/issues/5909 + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'order_id'); + const id = this.safeString2 (trade, 'trade_id', 'id'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, limit = undefined, since = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (market['spot']) { + method = 'dataGetMarketHistoryTrade'; + } else if (market['swap']) { + method = 'capiGetMarketTrades'; + } + if (market['spot']) { + if (limit !== undefined) { + request['size'] = limit; // default 1, max 2000 + } + } else if (market['swap']) { + if (limit === undefined) { + limit = 100; // default 20, max 100 + } + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.btc_usdt.trade.detail", + // "ts":1595604968430, + // "data":{ + // "ts":"1595604964000", + // "data":[ + // {"id":"1","price":"9533.81","amount":"0.7326","direction":"sell","ts":"1595604964000"}, + // {"id":"2","price":"9533.67","amount":"1.1591","direction":"buy","ts":"1595604961000"}, + // {"id":"3","price":"9533.67","amount":"1.5022","direction":"sell","ts":"1595604959000"}, + // ] + // } + // } + // + // swap + // + // [ + // {"trade_id":"670833198971748613","price":"9578.50","size":"5412","side":"sell","timestamp":"1595665018790","symbol":"btcusd"}, + // {"trade_id":"670833194240574915","price":"9579.00","size":"3972","side":"buy","timestamp":"1595665017662","symbol":"btcusd"}, + // {"trade_id":"670833194240573915","price":"9579.00","size":"1227","side":"buy","timestamp":"1595665017662","symbol":"btcusd"}, + // ] + // + let trades = undefined; + if (Array.isArray (response)) { + trades = response; + } else { + const data = this.safeValue (response, 'data', {}); + trades = this.safeValue2 (data, 'data', []); + } + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined, timeframe = '1m') { + // + // spot + // + // { + // "id":"1594694700000", + // "amount":"283.6811", + // "count":"234", + // "open":"9230.00", + // "close":"9227.15", + // "low":"9206.66", + // "high":"9232.33", + // "vol":"2618015.032504000000" + // } + // + // swap + // + // [ + // "1594693800000", + // "9240", + // "9241", + // "9222", + // "9228.5", + // "3913370", + // "424.003616350563" + // ] + // + const options = this.safeValue (this.options, 'parseOHLCV', {}); + const volume = this.safeValue (options, 'volume', {}); + if (Array.isArray (ohlcv)) { + const volumeIndex = this.safeString (volume, market['type'], 'amount'); + return [ + this.safeInteger (ohlcv, 0), // timestamp + this.safeFloat (ohlcv, 1), // Open + this.safeFloat (ohlcv, 2), // High + this.safeFloat (ohlcv, 3), // Low + this.safeFloat (ohlcv, 4), // Close + // this.safeFloat (ohlcv, 5), // Quote Volume + // this.safeFloat (ohlcv, 6), // Base Volume + this.safeFloat (ohlcv, volumeIndex), // Volume, bitget will return base volume in the 7th element for future markets + ]; + } else { + const volumeIndex = this.safeValue (volume, market['type'], 6); + return [ + this.safeInteger (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), // Open + this.safeFloat (ohlcv, 'high'), // High + this.safeFloat (ohlcv, 'low'), // Low + this.safeFloat (ohlcv, 'close'), // Close + this.safeFloat (ohlcv, volumeIndex), // Base Volume + ]; + } + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + const type = market['type']; + const options = this.safeValue (this.options, 'timeframes', {}); + const intervals = this.safeValue (options, type, {}); + const interval = this.safeValue (intervals, this.timeframes[timeframe]); + if (market['spot']) { + method = 'dataGetMarketHistoryKline'; + request['period'] = interval; + if (limit !== undefined) { + request['size'] = limit; // default 150, max 1000 + } + } else if (market['swap']) { + const duration = this.parseTimeframe (timeframe); + method = 'capiGetMarketCandles'; + request['granularity'] = interval; + const now = this.milliseconds (); + if (since === undefined) { + if (limit === undefined) { + limit = 1000; + } + request['start'] = this.iso8601 (now - limit * duration * 1000); + request['end'] = this.iso8601 (now); + } else { + request['start'] = this.iso8601 (since); + if (limit === undefined) { + request['end'] = this.iso8601 (now); + } else { + request['end'] = this.iso8601 (this.sum (since, limit * duration * 1000)); + } + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.btc_usdt.kline.15min", + // "ts":1595594183874, + // "data":[ + // {"id":"1594694700000","amount":"283.6811","count":"234","open":"9230.00","close":"9227.15","low":"9206.66","high":"9232.33","vol":"2618015.032504000000"}, + // {"id":"1594695600000","amount":"457.2904","count":"238","open":"9227.15","close":"9229.46","low":"9223.80","high":"9235.14","vol":"4220734.684570000000"}, + // {"id":"1594696500000","amount":"501.2353","count":"255","open":"9229.46","close":"9227.78","low":"9222.69","high":"9230.74","vol":"4625779.185006000000"}, + // ] + // } + // + // swap + // + // [ + // ["1594764900000","9255.5","9261","9251","9255.5","3958946","427.742307964305"], + // ["1594765800000","9255.5","9264","9252","9258","3609496","389.832756058107"], + // ["1594766700000","9258","9260","9244.5","9250.5","3738600","403.97870345085"], + // ] + // + let candles = response; + if (!Array.isArray (response)) { + candles = this.safeValue (response, 'data', []); + } + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + parseSpotBalance (response) { + // + // { + // "status":"ok", + // "ts":1595681450932, + // "data":{ + // "list":[ + // {"balance":"0.0000000000000000","currency":"BTC","type":"trade"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"frozen"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"lock"}, + // ], + // "id":"7420922606", + // "type":"spot", + // "state":"working" + // } + // } + // + const result = { 'info': response }; + const data = this.safeValue (response, 'data'); + const balances = this.safeValue (data, 'list'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + const account = this.account (); + result[code] = account; + } + const type = this.safeValue (balance, 'type'); + if (type === 'trade') { + result[code]['free'] = this.safeFloat (balance, 'balance'); + } else if ((type === 'frozen') || (type === 'lock')) { + const used = this.safeFloat (result[code], 'used'); + result[code]['used'] = this.sum (used, this.safeFloat (balance, 'balance')); + } + } + return this.parseBalance (result); + } + + parseSwapBalance (response) { + // + // swap + // + // [ + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"bchusd","margin_frozen":"0","timestamp":"1595673431547","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"ethusd","margin_frozen":"0","timestamp":"1595673431573","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"cmt_btcsusdt","margin_frozen":"0","timestamp":"1595673431577","margin_mode":"fixed","forwardContractFlag":true}, + // ] + // + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const marketId = this.safeString (balance, 'symbol'); + let symbol = marketId; + if (marketId in this.markets_by_id) { + symbol = this.markets_by_id[marketId]['symbol']; + } + const account = this.account (); + // it may be incorrect to use total, free and used for swap accounts + account['total'] = this.safeFloat (balance, 'equity'); + account['free'] = this.safeFloat (balance, 'total_avail_balance'); + result[symbol] = account; + } + return this.parseBalance (result); + } + + async fetchAccounts (params = {}) { + const request = { + 'method': 'accounts', + }; + const response = await this.apiGetAccountAccounts (this.extend (request, params)); + // + // { + // "status":"ok", + // "ts":1595679591824, + // "data":[ + // {"id":"7420922606","type":"spot","state":"working"} + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const accountId = this.safeString (account, 'id'); + const type = this.safeStringLower (account, 'type'); + result.push ({ + 'id': accountId, + 'type': type, + 'currency': undefined, + 'info': account, + }); + } + return result; + } + + async findAccountByType (type) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountsByType = this.groupBy (this.accounts, 'type'); + const accounts = this.safeValue (accountsByType, type); + if (accounts === undefined) { + throw new ExchangeError (this.id + " findAccountByType() could not find an accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + const numAccounts = accounts.length; + if (numAccounts > 1) { + throw new ExchangeError (this.id + " findAccountByType() found more than one accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + return accounts[0]; + } + + async getAccountId (params) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountId = this.safeString (this.options, 'accountId'); + const accountId = this.safeString (params, 'accountId', defaultAccountId); + if (accountId !== undefined) { + return accountId; + } + const defaultType = this.safeString (this.options, 'defaultType', 'margin'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " requires an 'accountId' parameter"); + } + const account = await this.findAccountByType (type); + return account['id']; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchBalance requires a 'type' parameter, one of 'spot', 'swap'"); + } + let method = undefined; + const query = this.omit (params, 'type'); + if (type === 'spot') { + const accountId = await this.getAccountId (params); + method = 'apiGetAccountsAccountIdBalance'; + query['account_id'] = accountId; + query['method'] = 'balance'; + } else if (type === 'swap') { + method = 'swapGetAccountAccounts'; + } + const response = await this[method] (query); + // + // spot + // + // { + // "status":"ok", + // "ts":1595681450932, + // "data":{ + // "list":[ + // {"balance":"0.0000000000000000","currency":"BTC","type":"trade"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"frozen"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"lock"}, + // ], + // "id":"7420922606", + // "type":"spot", + // "state":"working" + // } + // } + // + // swap + // + // [ + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"bchusd","margin_frozen":"0","timestamp":"1595673431547","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"ethusd","margin_frozen":"0","timestamp":"1595673431573","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"cmt_btcsusdt","margin_frozen":"0","timestamp":"1595673431577","margin_mode":"fixed","forwardContractFlag":true}, + // ] + // + return this.parseBalanceByType (type, response); + } + + parseBalanceByType (type, response) { + if (type === 'spot') { + return this.parseSpotBalance (response); + } else if (type === 'swap') { + return this.parseSwapBalance (response); + } + throw new NotSupported (this.id + " fetchBalance does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + + parseOrderStatus (status) { + const statuses = { + 'submitted': 'open', + 'partial-filled': 'open', + 'partial-canceled': 'canceled', + 'filled': 'closed', + 'canceled': 'canceled', + '-2': 'failed', + '-1': 'canceled', + '0': 'open', + '1': 'open', + '2': 'closed', + '3': 'open', + '4': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrderSide (side) { + const sides = { + 'buy-market': 'buy', + 'sell-market': 'sell', + 'buy-limit': 'buy', + 'sell-limit': 'sell', + '1': 'long', // open long + '2': 'short', // open short + '3': 'long', // close long + '4': 'short', // close short + }; + return this.safeString (sides, side, side); + } + + parseOrderType (type) { + const types = { + 'buy-market': 'market', + 'sell-market': 'market', + 'buy-limit': 'limit', + 'sell-limit': 'limit', + '1': 'open', // open long + '2': 'open', // open short + '3': 'close', // close long + '4': 'close', // close short + }; + return this.safeString (types, type, type); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // spot + // + // { + // "status":"ok", + // "ts":1595792596056, + // "data":671368296142774272 + // } + // + // swap + // + // { + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "order_id":"671757564085534713" + // } + // + // cancelOrder + // + // spot + // + // { + // "status": "ok", + // "ts": 1595818631279, + // "data": 671368296142774272 + // } + // + // swap + // + // { + // "order_id":"671757564085534713", + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "symbol":"cmt_ethusdt", + // "result":true, + // "err_code":null, + // "err_msg":null + // } + // + // fetchOpenOrders, fetchClosedOrders, fetchOrder + // + // spot + // + // { + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595872129618", + // "created_at":"1595872089525", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595872129618", + // "id":"671701716584665088", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // + // swap + // + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595885546770", + // "client_oid":"f3aa81d6-9a4c-4eab-bebe-ebc19da21cf2", + // "createTime":"1595885521200", + // "filled_qty":"0", + // "fee":"0.00000000", + // "order_id":"671758053112020913", + // "price":"150.00", + // "price_avg":"0.00", + // "status":"0", + // "type":"1", + // "order_type":"0", + // "totalProfits":null + // } + // + let id = this.safeString (order, 'order_id'); + id = this.safeString2 (order, 'id', 'data', id); + const timestamp = this.safeInteger2 (order, 'created_at', 'createTime'); + let type = this.safeString (order, 'type'); + const side = this.parseOrderSide (type); + type = this.parseOrderType (type); + // if ((side !== 'buy') && (side !== 'sell')) { + // side = this.parseOrderSide (type); + // } + // if ((type !== 'limit') && (type !== 'market')) { + // if ('pnl' in order) { + // type = 'futures'; + // } else { + // type = 'swap'; + // } + // } + let symbol = undefined; + const marketId = this.safeString (order, 'symbol'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + symbol = marketId.toUpperCase (); + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let amount = this.safeFloat2 (order, 'amount', 'size'); + const filled = this.safeFloat2 (order, 'filled_amount', 'filled_qty'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + amount = Math.max (amount, filled); + remaining = Math.max (0, amount - filled); + } + } + if (type === 'market') { + remaining = 0; + } + const cost = this.safeFloat (order, 'filled_cash_amount'); + const price = this.safeFloat (order, 'price'); + let average = this.safeFloat (order, 'price_avg'); + if ((average === undefined) && (filled !== undefined) && (cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + const status = this.parseOrderStatus (this.safeString2 (order, 'state', 'status')); + const feeCost = this.safeFloat2 (order, 'filled_fees', 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const clientOrderId = this.safeString (order, 'client_oid'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + // + // spot + // + // account_id true string Account ID, obtained using the accounts method. Currency transactions use the accountid of the'spot' account; for loan asset transactions, please use the accountid of the'margin' account + // amount true string A limit order indicates the quantity of the order, when a market price buy order indicates how much money to buy, and when a market price sell order indicates how much currency to sell + // price false string Order price, market order does not pass this parameter + // source false string Order source api + // symbol true string Trading pair btc_usdt, eth_btc ... + // type true string Order Type buy-market: buy at market price, sell-market: sell at market price, buy-limit: buy at limit price, sell-limit: sell at limit price + // + // swap + // + // symbol String Yes Contract ID + // client_oid String Yes customize order IDs to identify your orders. (Less than 50 characters without special characters, + // size String Yes Quantity to buy or sell (value not equal to 0 or negative) + // type String Yes 1 Open long 2Open short 3 Close long 4 Close short + // order_type String Yes 0: Normal order (Unfilled and 0 imply normal limit order) 1: Post only 2: Fill or Kill 3: Immediate Or Cancel + // match_price String Yes 0 Limit price 1 market price + // price String No Price of each contract + // + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId', this.uuid ()); + params = this.omit (params, [ 'client_oid', 'clientOrderId' ]); + let method = undefined; + if (market['spot']) { + const accountId = await this.getAccountId ({ + 'type': market['type'], + }); + method = 'apiPostOrderOrdersPlace'; + request['account_id'] = accountId; + request['method'] = 'place'; + request['type'] = side + '-' + type; + if (type === 'limit') { + request['amount'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let cost = this.safeFloat (params, 'amount'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (cost === undefined) { + cost = amount * price; + } + } else if (cost === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'amount' extra parameter (the exchange-specific behaviour)"); + } + } else { + cost = (cost === undefined) ? amount : cost; + } + request['amount'] = this.costToPrecision (symbol, cost); + } else if (side === 'sell') { + request['amount'] = this.amountToPrecision (symbol, amount); + } + } + // ... + } else if (market['swap']) { + request['order_type'] = '0'; // '0' = Normal order, undefined and 0 imply a normal limit order, '1' = Post only, '2' = Fill or Kill, '3' = Immediate Or Cancel + request['client_oid'] = clientOrderId; + const orderType = this.safeString (params, 'type'); + if (orderType === undefined) { + throw new ArgumentsRequired (this.id + " createOrder requires a type parameter, '1' = open long, '2' = open short, '3' = close long, '4' = close short for " + market['type'] + ' orders'); + } + request['size'] = this.amountToPrecision (symbol, amount); + request['type'] = orderType; + // if match_price is set to '1', the price parameter will be ignored for market orders + if (type === 'limit') { + request['match_price'] = '0'; + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + request['match_price'] = '1'; + } + method = 'swapPostOrderPlaceOrder'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ts":1595792596056, + // "data":"671368296142774272" + // } + // + // swap + // + // { + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "order_id":"671757564085534713" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let type = undefined; + if (symbol === undefined) { + const defaultType = this.safeString2 (this.options, 'cancelOrder', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if (type === 'spot') { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument for spot orders'); + } + } + } else { + market = this.market (symbol); + type = market['type']; + } + const query = this.omit (params, 'type'); + let method = undefined; + const request = {}; + if (type === 'spot') { + method = 'apiPostOrderOrdersOrderIdSubmitcancel'; + request['order_id'] = id; + request['method'] = 'submitcancel'; + } else if (type === 'swap') { + method = 'swapPostOrderCancelOrder'; + request['orderId'] = id; + request['symbol'] = market['id']; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { "status": "ok", "ts": 1595818631279, "data": 671368296142774272 } + // + // swap + // + // { + // "order_id":"671757564085534713", + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "symbol":"cmt_ethusdt", + // "result":true, + // "err_code":null, + // "err_msg":null + // } + // + return this.parseOrder (response, market); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " cancelOrders requires a type parameter (one of 'spot', 'swap')."); + } + const request = {}; + let method = undefined; + if (type === 'spot') { + method = 'apiPostOrderOrdersBatchcancel'; + request['method'] = 'batchcancel'; + const jsonIds = this.json (ids); + const parts = jsonIds.split ('"'); + request['order_ids'] = parts.join (''); + } else if (type === 'swap') { + method = 'swapPostOrderCancelBatchOrders'; + request['symbol'] = market['id']; + request['ids'] = ids; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status": "ok", + // "data": { + // "success": [ + // "673451224205135872", + // ], + // "failed": [ + // { + // "err-msg": "invalid record", + // "order-id": "673451224205135873", + // "err-code": "base record invalid" + // } + // ] + // } + // } + // + // swap + // + // { + // "result":true, + // "symbol":"cmt_btcusdt", + // "order_ids":[ + // "258414711", + // "478585558" + // ], + // "fail_infos":[ + // { + // "order_id":"258414711", + // "err_code":"401", + // "err_msg":"" + // } + // ] + // } + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'swap')."); + } + let method = undefined; + const request = {}; + if (type === 'spot') { + const clientOid = this.safeString (params, 'client_oid'); + if (clientOid !== undefined) { + method = 'apiPostOrderOrdersClientOid'; + request['client_oid'] = clientOid; + } else { + method = 'apiPostOrderOrdersOrderId'; + request['order_id'] = id; + } + request['method'] = 'getOrder'; + } else if (type === 'swap') { + method = 'swapGetOrderDetail'; + request['symbol'] = market['id']; + request['orderId'] = id; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // "status":"ok", + // "ts":1595897886717, + // "data":{ + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595818631541", + // "created_at":"1595792595897", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595818631541", + // "id":"671368296142774272", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // } + // + // + // swap + // + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595896459890", + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "createTime":"1595885404607", + // "filled_qty":"0", + // "fee":"0", + // "order_id":"671757564085534713", + // "price":"150", + // "price_avg":"0", + // "status":"-1", + // "type":"1", + // "order_type":"0", + // "totalProfits":"0" + // } + // + const data = this.safeValue (response, 'data', response); + return this.parseOrder (data, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (type === 'spot') { + method = 'apiGetOrderOrdersOpenOrders'; + // request['from'] = this.safeString (params, 'from'); // order id + // request['direct'] = 'next'; // or 'prev' + request['method'] = 'openOrders'; + if (limit === undefined) { + request['size'] = limit; // default 100, max 1000 + } + } else if (type === 'swap') { + method = 'swapGetOrderOrders'; + request['status'] = '3'; // 0 Failed, 1 Partially Filled, 2 Fully Filled 3 = Open + Partially Filled, 4 Canceling + request['from'] = '1'; + request['to'] = '1'; + if (limit === undefined) { + request['limit'] = 100; // default 100, max 100 + } + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // + // { + // "status":"ok", + // "ts":1595875165865, + // "data":[ + // { + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595872129618", + // "created_at":"1595872089525", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595872129618", + // "id":"671701716584665088", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // ] + // } + // + // swap + // + // [ + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595885546770", + // "client_oid":"f3aa81d6-9a4c-4eab-bebe-ebc19da21cf2", + // "createTime":"1595885521200", + // "filled_qty":"0", + // "fee":"0.00000000", + // "order_id":"671758053112020913", + // "price":"150.00", + // "price_avg":"0.00", + // "status":"0", + // "type":"1", + // "order_type":"0", + // "totalProfits":null + // } + // ] + // + let data = response; + if (!Array.isArray (response)) { + data = this.safeValue (response, 'data', []); + } + return this.parseOrders (data, market, undefined, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (type === 'spot') { + method = 'apiGetOrderOrdersHistory'; + // Value range [((end_time) – 48h), (end_time)] + // the query window is 48 hours at most + // the window shift range is the last 30 days + if (since !== undefined) { + request['start_time'] = since; + } + // request['end_time'] = this.safeInteger (params, 'end_time'); + // request['from'] = this.safeString (params, 'from'); // order id + // request['direct'] = 'next'; // or 'prev' + request['method'] = 'openOrders'; + if (limit === undefined) { + request['size'] = limit; // default 100, max 1000 + } + } else if (type === 'swap') { + method = 'swapGetOrderOrders'; + request['status'] = '2'; // 0 Failed, 1 Partially Filled, 2 Fully Filled 3 = Open + Partially Filled, 4 Canceling + request['from'] = '1'; + request['to'] = '1'; + if (limit === undefined) { + request['limit'] = 100; // default 100, max 100 + } + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // + // { + // "status":"ok", + // "ts":1595875165865, + // "data":[ + // { + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595872129618", + // "created_at":"1595872089525", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595872129618", + // "id":"671701716584665088", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // ] + // } + // + // swap + // + // [ + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595885546770", + // "client_oid":"f3aa81d6-9a4c-4eab-bebe-ebc19da21cf2", + // "createTime":"1595885521200", + // "filled_qty":"0", + // "fee":"0.00000000", + // "order_id":"671758053112020913", + // "price":"150.00", + // "price_avg":"0.00", + // "status":"0", + // "type":"1", + // "order_type":"0", + // "totalProfits":null + // } + // ] + // + let data = response; + if (!Array.isArray (response)) { + data = this.safeValue (response, 'data', []); + } + return this.parseOrders (data, market, undefined, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'method': 'deposit_withdraw', + 'type': 'deposit', + 'size': 12, + }; + const response = await this.apiGetOrderDepositWithdraw (this.extend (request, params)); + // + // { + // "status": "ok", + // "data": [ + // { + // "id": 1171, + // "type": "deposit", + // "currency": "usdt", + // "tx_hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "address_tag": "100040", + // "fee": 0, + // "state": "safe", + // "created_at": 1510912472199, + // "updated_at": 1511145876575 + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'method': 'deposit_withdraw', + 'type': 'withdraw', + 'size': 12, + }; + const response = await this.apiGetOrderDepositWithdraw (this.extend (request, params)); + // + // { + // "status": "ok", + // "data": [ + // { + // "id": 1171, + // "type": "withdraw", + // "currency": "usdt", + // "tx_hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "address_tag": "100040", + // "fee": 0, + // "state": "safe", + // "created_at": 1510912472199, + // "updated_at": 1511145876575 + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + // withdrawals + 'WaitForOperation': 'pending', // 等待提现 + 'OperationLock': 'pending', // 初审锁定成功 + 'OperationSuccess': 'ok', // 提现成功 + 'Cancel': 'canceled', // 用户撤销 + 'Sure': 'ok', // 复审锁定成功 + 'Fail': 'failed', // 出币异常 + 'WaitForChainSure': 'ok', // 等待链上确认 + // deposits + 'WAIT_0': 'pending', // 待确认 + 'WAIT_1': 'pending', // 待确认 + 'DATA_CHANGE': 'pending', // 待确认中 + 'SUCCESS': 'ok', // 充值成功 + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchWithdrawals + // + // { + // "id": 1171, + // "type": "withdraw", + // "currency": "usdt", + // "tx_hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "address_tag": "100040", + // "fee": 0, + // "state": "safe", + // "created_at": 1510912472199, + // "updated_at": 1511145876575 + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'address_tag'); + const tagFrom = undefined; + const tagTo = tag; + const addressFrom = undefined; + const addressTo = address; + let type = this.safeString (transaction, 'type'); + if (type === 'withdraw') { + type = 'withdrawal'; + } else if (type === 'deposit') { + type = 'deposit'; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const txid = this.safeString (transaction, 'tx_hash'); + const timestamp = this.safeInteger (transaction, 'created_at'); + const updated = this.safeInteger (transaction, 'updated_at'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'currency': code, + 'cost': feeCost, + }; + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': tagFrom, + 'tagTo': tagTo, + 'tag': tag, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const query = this.omit (params, 'type'); + if (type === 'swap') { + throw new ArgumentsRequired (this.id + ' fetchMyTrades is not supported for ' + type + ' type'); + } + // + // spot + // + // POST /api/v1/order/matchresults Query current order, order history + // symbol true string trading pair btc_usdt, eth_btc ... + // types false string Query order type combination buy-market, sell-market, buy-limit, sell-limit + // start_date false string Query start date, date format yyyy-mm-dd -61 days [-61day, end-date] + // end_date false string Query end date, date format yyyy-mm-dd Now [start-date, now] + // from false string Query start ID order record id + // direct false string Query direction ‘next’ is default , the transaction record ID is sorted from large to small prev,next + // size false string Query record size 100 <=100 + // + const request = { + 'symbol': market['id'], + 'method': 'matchresults', + // 'types': 'buy-market,sell-market,buy-limit,sell-limit', + // 'start_date': this.ymd (since), + // 'end_date': this.ymd (this.milliseconds ()), + // 'size': 100, + // 'direct': 'next', + }; + if (since !== undefined) { + request['start_date'] = this.ymd (since); + const end = this.sum (since, 2 * 24 * 60 * 60 * 1000); + request['end_date'] = this.ymd (end); + } + if (limit !== undefined) { + request['size'] = limit; // default 100, max 100 + } + const response = await this.apiPostOrderMatchresults (this.extend (request, query)); + // + // { + // "status": "ok", + // "data": [ + // { + // "id": 29555, + // "order_id": 59378, + // "match_id": 59335, + // "symbol": "eth_usdt", + // "type": "buy-limit", + // "source": "api", + // "price": "100.1000000000", + // "filled_amount": "0.9845000000", + // "filled_fees": "0.0019690000", + // "created_at": 1494901400487 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrderTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const query = this.omit (params, 'type'); + let method = undefined; + const request = {}; + if (type === 'spot') { + request['order_id'] = id; + request['method'] = 'matchresults'; + method = 'apiPostOrderOrdersOrderIdMatchresults'; + } else if (type === 'swap') { + request['orderId'] = id; + request['symbol'] = market['id']; + method = 'swapGetOrderFills'; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // "status":"ok", + // "ts":1596298917277, + // "data":[ + // { + // "id":"614164775", + // "created_at":"1596298860602", + // "filled_amount":"0.0417000000000000", + // "filled_fees":"0.0000834000000000", + // "match_id":"673491702661292033", + // "order_id":"673491720340279296", + // "price":"359.240000000000", + // "source":"接口", + // "symbol":"eth_usdt", + // "type":"buy-market" + // } + // ] + // } + // + // swap + // + // + // [ + // { + // "trade_id":"6667390", + // "symbol":"cmt_btcusdt", + // "order_id":"525946425993854915", + // "price":"9839.00", + // "order_qty":"3466", + // "fee":"-0.0000528407360000", + // "timestamp":"1561121514442", + // "exec_type":"M", + // "side":"3" + // } + // ] + // + let data = response; + if (!Array.isArray (data)) { + data = this.safeValue (response, 'data', []); + } + return await this.parseTrades (data, market, since, limit); + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.swapGetPositionSinglePosition (this.extend (request, params)); + // + // { + // "margin_mode":"fixed", // Margin mode: crossed / fixed + // "holding":[ + // { + // "symbol":"cmt_btcusdt", // Contract name + // "liquidation_price":"0.00", // Estimated liquidation price + // "position":"0", // Position Margin, the margin for holding current positions + // "avail_position":"0", // Available position + // "avg_cost":"0.00", // Transaction average price + // "leverage":"2", // Leverage + // "realized_pnl":"0.00000000", // Realized Profit and loss + // "keepMarginRate":"0.005", // Maintenance margin rate + // "side":"1", // Position Direction Long or short, Mark obsolete + // "holdSide":"1", // Position Direction Long or short + // "timestamp":"1557571623963", // System timestamp + // "margin":"0.0000000000000000", // Used margin + // "unrealized_pnl":"0.00000000", // Unrealized profit and loss + // } + // ] + // } + return response; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.swapGetPositionAllPosition (params); + // + // [ + // { + // "margin_mode":"fixed", + // "holding":[ + // { + // "liquidation_price":"0.00", + // "position":"0", + // "avail_position":"0", + // "avg_cost":"0.00", + // "symbol":"btcusd", + // "leverage":"20", + // "keepMarginRate":"0.005", + // "realized_pnl":"0.00000000", + // "unrealized_pnl":"0", + // "side":"long", + // "holdSide":"1", + // "timestamp":"1595698564915", + // "margin":"0.0000000000000000" + // }, + // ] + // }, + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + if ((api === 'capi') || (api === 'swap')) { + request = '/api/swap/' + this.version + request; + } else { + request = '/' + api + '/v1' + request; + } + let query = this.omit (params, this.extractParams (path)); + let url = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }) + request; + if ((api === 'data') || (api === 'capi')) { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'swap') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + let auth = timestamp + method + request; + if (method === 'POST') { + body = this.json (params); + auth += body; + } else { + if (Object.keys (params).length) { + const query = this.urlencode (this.keysort (params)); + url += '?' + query; + auth += '?' + query; + } + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); + headers = { + 'ACCESS-KEY': this.apiKey, + 'ACCESS-SIGN': signature, + 'ACCESS-TIMESTAMP': timestamp, + 'ACCESS-PASSPHRASE': this.password, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + } + } else if (api === 'api') { + const timestamp = this.milliseconds ().toString (); + let auth = ''; + query = this.keysort (query); + auth = this.rawencode (query); + const hash = this.hash (this.encode (this.secret), 'sha1'); + let signed = auth; + const signature = this.hmac (this.encode (auth), this.encode (hash), 'md5'); + if (auth.length > 0) { + signed += '&'; + } + signed += 'sign=' + signature + '&req_time=' + timestamp + '&accesskey=' + this.apiKey; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + signed; + } + } else if (method === 'POST') { + url += '?sign=' + signature + '&req_time=' + timestamp + '&accesskey=' + this.apiKey; + body = auth; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + // + // spot + // + // {"status":"fail","err_code":"01001","err_msg":"系统异常,请稍后重试"} + // {"status":"error","ts":1595594160149,"err_code":"invalid-parameter","err_msg":"invalid size, valid range: [1,2000]"} + // {"status":"error","ts":1595684716042,"err_code":"invalid-parameter","err_msg":"illegal sign invalid"} + // {"status":"error","ts":1595700216275,"err_code":"bad-request","err_msg":"your balance is low!"} + // {"status":"error","ts":1595700344504,"err_code":"invalid-parameter","err_msg":"invalid type"} + // {"status":"error","ts":1595703343035,"err_code":"bad-request","err_msg":"order cancel fail"} + // {"status":"error","ts":1595704360508,"err_code":"invalid-parameter","err_msg":"accesskey not null"} + // {"status":"error","ts":1595704490084,"err_code":"invalid-parameter","err_msg":"permissions not right"} + // {"status":"error","ts":1595711862763,"err_code":"system exception","err_msg":"system exception"} + // {"status":"error","ts":1595730308979,"err_code":"bad-request","err_msg":"20003"} + // + // swap + // + // {"code":"40015","msg":"","requestTime":1595698564931,"data":null} + // {"code":"40017","msg":"Order id must not be blank","requestTime":1595702477835,"data":null} + // {"code":"40017","msg":"Order Type must not be blank","requestTime":1595698516162,"data":null} + // {"code":"40301","msg":"","requestTime":1595667662503,"data":null} + // {"code":"40017","msg":"Contract code must not be blank","requestTime":1595703151651,"data":null} + // {"code":"40108","msg":"","requestTime":1595885064600,"data":null} + // {"order_id":"513468410013679613","client_oid":null,"symbol":"ethusd","result":false,"err_code":"order_no_exist_error","err_msg":"订单不存在!"} + // + const message = this.safeString (response, 'err_msg'); + const errorCode = this.safeString2 (response, 'code', 'err_code'); + const feedback = this.id + ' ' + body; + const nonEmptyMessage = ((message !== undefined) && (message !== '')); + if (nonEmptyMessage) { + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + const nonZeroErrorCode = (errorCode !== undefined) && (errorCode !== '00000'); + if (nonZeroErrorCode) { + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + if (nonZeroErrorCode || nonEmptyMessage) { + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],34:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, AuthenticationError, BadRequest, PermissionDenied, InvalidAddress, ArgumentsRequired, InvalidOrder } = require ('./base/errors'); +const { DECIMAL_PLACES, SIGNIFICANT_DIGITS, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bithumb extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bithumb', + 'name': 'Bithumb', + 'countries': [ 'KR' ], // South Korea + 'rateLimit': 500, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg', + 'api': { + 'public': 'https://api.bithumb.com/public', + 'private': 'https://api.bithumb.com', + }, + 'www': 'https://www.bithumb.com', + 'doc': 'https://apidocs.bithumb.com', + 'fees': 'https://en.bithumb.com/customer_support/info_fee', + }, + 'api': { + 'public': { + 'get': [ + 'ticker/{currency}', + 'ticker/all', + 'orderbook/{currency}', + 'orderbook/all', + 'transaction_history/{currency}', + 'transaction_history/all', + ], + }, + 'private': { + 'post': [ + 'info/account', + 'info/balance', + 'info/wallet_address', + 'info/ticker', + 'info/orders', + 'info/user_transactions', + 'info/order_detail', + 'trade/place', + 'trade/cancel', + 'trade/btc_withdrawal', + 'trade/krw_deposit', + 'trade/krw_withdrawal', + 'trade/market_buy', + 'trade/market_sell', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.25 / 100, + 'taker': 0.25 / 100, + }, + }, + 'precisionMode': SIGNIFICANT_DIGITS, + 'exceptions': { + 'Bad Request(SSL)': BadRequest, + 'Bad Request(Bad Method)': BadRequest, + 'Bad Request.(Auth Data)': AuthenticationError, // { "status": "5100", "message": "Bad Request.(Auth Data)" } + 'Not Member': AuthenticationError, + 'Invalid Apikey': AuthenticationError, // {"status":"5300","message":"Invalid Apikey"} + 'Method Not Allowed.(Access IP)': PermissionDenied, + 'Method Not Allowed.(BTC Adress)': InvalidAddress, + 'Method Not Allowed.(Access)': PermissionDenied, + 'Database Fail': ExchangeNotAvailable, + 'Invalid Parameter': BadRequest, + '5600': ExchangeError, + 'Unknown Error': ExchangeError, + 'After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions': ExchangeError, // {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"} + }, + }); + } + + amountToPrecision (symbol, amount) { + return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTickerAll (params); + const data = this.safeValue (response, 'data'); + const currencyIds = Object.keys (data); + const result = []; + const quote = this.safeCurrencyCode ('KRW'); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + if (currencyId === 'date') { + continue; + } + const market = data[currencyId]; + const base = this.safeCurrencyCode (currencyId); + const symbol = currencyId + '/' + quote; + let active = true; + if (Array.isArray (market)) { + const numElements = market.length; + if (numElements === 0) { + active = false; + } + } + result.push ({ + 'id': currencyId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'info': market, + 'active': active, + 'precision': { + 'amount': 4, + 'price': 4, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': 500, + 'max': 5000000000, + }, + }, + 'baseId': undefined, + 'quoteId': undefined, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'currency': 'ALL', + }; + const response = await this.privatePostInfoBalance (this.extend (request, params)); + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const account = this.account (); + const currency = this.currency (code); + const lowerCurrencyId = this.safeStringLower (currency, 'id'); + account['total'] = this.safeFloat (balances, 'total_' + lowerCurrencyId); + account['used'] = this.safeFloat (balances, 'in_use_' + lowerCurrencyId); + account['free'] = this.safeFloat (balances, 'available_' + lowerCurrencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['base'], + }; + if (limit !== undefined) { + request['count'] = limit; // default 30, max 30 + } + const response = await this.publicGetOrderbookCurrency (this.extend (request, params)); + // + // { + // "status":"0000", + // "data":{ + // "timestamp":"1587621553942", + // "payment_currency":"KRW", + // "order_currency":"BTC", + // "bids":[ + // {"price":"8652000","quantity":"0.0043"}, + // {"price":"8651000","quantity":"0.0049"}, + // {"price":"8650000","quantity":"8.4791"}, + // ], + // "asks":[ + // {"price":"8654000","quantity":"0.119"}, + // {"price":"8655000","quantity":"0.254"}, + // {"price":"8658000","quantity":"0.119"}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'timestamp'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks', 'price', 'quantity'); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "opening_price":"227100", + // "closing_price":"228400", + // "min_price":"222300", + // "max_price":"230000", + // "units_traded":"82618.56075337", + // "acc_trade_value":"18767376138.6031", + // "prev_closing_price":"227100", + // "units_traded_24H":"151871.13484676", + // "acc_trade_value_24H":"34247610416.8974", + // "fluctate_24H":"8700", + // "fluctate_rate_24H":"3.96", + // "date":"1587710327264", // fetchTickers inject this + // } + // + const timestamp = this.safeInteger (ticker, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const open = this.safeFloat (ticker, 'opening_price'); + const close = this.safeFloat (ticker, 'closing_price'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((close !== undefined) && (open !== undefined)) { + change = close - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, close) / 2; + } + const baseVolume = this.safeFloat (ticker, 'units_traded_24H'); + const quoteVolume = this.safeFloat (ticker, 'acc_trade_value_24H'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max_price'), + 'low': this.safeFloat (ticker, 'min_price'), + 'bid': this.safeFloat (ticker, 'buy_price'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell_price'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickerAll (params); + // + // { + // "status":"0000", + // "data":{ + // "BTC":{ + // "opening_price":"9045000", + // "closing_price":"9132000", + // "min_price":"8938000", + // "max_price":"9168000", + // "units_traded":"4619.79967497", + // "acc_trade_value":"42021363832.5187", + // "prev_closing_price":"9041000", + // "units_traded_24H":"8793.5045804", + // "acc_trade_value_24H":"78933458515.4962", + // "fluctate_24H":"530000", + // "fluctate_rate_24H":"6.16" + // }, + // "date":"1587710878669" + // } + // } + // + const result = {}; + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'date'); + const tickers = this.omit (data, 'date'); + const ids = Object.keys (tickers); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } + const ticker = tickers[id]; + const isArray = Array.isArray (ticker); + if (!isArray) { + ticker['date'] = timestamp; + result[symbol] = this.parseTicker (ticker, market); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['base'], + }; + const response = await this.publicGetTickerCurrency (this.extend (request, params)); + // + // { + // "status":"0000", + // "data":{ + // "opening_price":"227100", + // "closing_price":"228400", + // "min_price":"222300", + // "max_price":"230000", + // "units_traded":"82618.56075337", + // "acc_trade_value":"18767376138.6031", + // "prev_closing_price":"227100", + // "units_traded_24H":"151871.13484676", + // "acc_trade_value_24H":"34247610416.8974", + // "fluctate_24H":"8700", + // "fluctate_rate_24H":"3.96", + // "date":"1587710327264" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "transaction_date":"2020-04-23 22:21:46", + // "type":"ask", + // "units_traded":"0.0125", + // "price":"8667000", + // "total":"108337" + // } + // + // fetchOrder (private) + // + // { + // "transaction_date": "1572497603902030", + // "price": "8601000", + // "units": "0.005", + // "fee_currency": "KRW", + // "fee": "107.51", + // "total": "43005" + // } + // + // a workaround for their bug in date format, hours are not 0-padded + let timestamp = undefined; + const transactionDatetime = this.safeString (trade, 'transaction_date'); + if (transactionDatetime !== undefined) { + const parts = transactionDatetime.split (' '); + const numParts = parts.length; + if (numParts > 1) { + const transactionDate = parts[0]; + let transactionTime = parts[1]; + if (transactionTime.length < 8) { + transactionTime = '0' + transactionTime; + } + timestamp = this.parse8601 (transactionDate + ' ' + transactionTime); + } else { + timestamp = this.safeIntegerProduct (trade, 'transaction_date', 0.001); + } + } + if (timestamp !== undefined) { + timestamp -= 9 * 3600000; // they report UTC + 9 hours, server in Korean timezone + } + const type = undefined; + let side = this.safeString (trade, 'type'); + side = (side === 'ask') ? 'sell' : 'buy'; + const id = this.safeString (trade, 'cont_no'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'units_traded', 'units'); + let cost = this.safeFloat (trade, 'total'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.commonCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['base'], + }; + if (limit === undefined) { + request['count'] = limit; // default 20, max 100 + } + const response = await this.publicGetTransactionHistoryCurrency (this.extend (request, params)); + // + // { + // "status":"0000", + // "data":[ + // { + // "transaction_date":"2020-04-23 22:21:46", + // "type":"ask", + // "units_traded":"0.0125", + // "price":"8667000", + // "total":"108337" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_currency': market['id'], + 'Payment_currency': market['quote'], + 'units': amount, + }; + let method = 'privatePostTradePlace'; + if (type === 'limit') { + request['price'] = price; + request['type'] = (side === 'buy') ? 'bid' : 'ask'; + } else { + method = 'privatePostTradeMarket' + this.capitalize (side); + } + const response = await this[method] (this.extend (request, params)); + const id = this.safeString (response, 'order_id'); + if (id === undefined) { + throw new InvalidOrder (this.id + ' createOrder did not return an order id'); + } + return { + 'info': response, + 'symbol': symbol, + 'type': type, + 'side': side, + 'id': id, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'count': 1, + 'order_currency': market['base'], + 'payment_currency': market['quote'], + }; + const response = await this.privatePostInfoOrderDetail (this.extend (request, params)); + // + // { + // "status": "0000", + // "data": { + // order_date: '1603161798539254', + // type: 'ask', + // order_status: 'Cancel', + // order_currency: 'BTC', + // payment_currency: 'KRW', + // watch_price: '0', + // order_price: '13344000', + // order_qty: '0.0125', + // cancel_date: '1603161803809993', + // cancel_type: '사용자취소', + // contract: [ + // { + // transaction_date: '1603161799976383', + // price: '13344000', + // units: '0.0015', + // fee_currency: 'KRW', + // fee: '0', + // total: '20016' + // } + // ], + // } + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (this.extend (data, { 'order_id': id }), market); + } + + parseOrderStatus (status) { + const statuses = { + 'Pending': 'open', + 'Completed': 'closed', + 'Cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // + // fetchOrder + // + // { + // "transaction_date": "1572497603668315", + // "type": "bid", + // "order_status": "Completed", + // "order_currency": "BTC", + // "payment_currency": "KRW", + // "order_price": "8601000", + // "order_qty": "0.007", + // "cancel_date": "", + // "cancel_type": "", + // "contract": [ + // { + // "transaction_date": "1572497603902030", + // "price": "8601000", + // "units": "0.005", + // "fee_currency": "KRW", + // "fee": "107.51", + // "total": "43005" + // }, + // ] + // } + // + // { + // order_date: '1603161798539254', + // type: 'ask', + // order_status: 'Cancel', + // order_currency: 'BTC', + // payment_currency: 'KRW', + // watch_price: '0', + // order_price: '13344000', + // order_qty: '0.0125', + // cancel_date: '1603161803809993', + // cancel_type: '사용자취소', + // contract: [ + // { + // transaction_date: '1603161799976383', + // price: '13344000', + // units: '0.0015', + // fee_currency: 'KRW', + // fee: '0', + // total: '20016' + // } + // ], + // } + // + // fetchOpenOrders + // + // { + // "order_currency": "BTC", + // "payment_currency": "KRW", + // "order_id": "C0101000007408440032", + // "order_date": "1571728739360570", + // "type": "bid", + // "units": "5.0", + // "units_remaining": "5.0", + // "price": "501000", + // } + // + const timestamp = this.safeIntegerProduct (order, 'order_date', 0.001); + const sideProperty = this.safeValue2 (order, 'type', 'side'); + const side = (sideProperty === 'bid') ? 'buy' : 'sell'; + const status = this.parseOrderStatus (this.safeString (order, 'order_status')); + let price = this.safeFloat2 (order, 'order_price', 'price'); + let type = 'limit'; + if (price === 0) { + price = undefined; + type = 'market'; + } + const amount = this.safeFloat2 (order, 'order_qty', 'units'); + let remaining = this.safeFloat (order, 'units_remaining'); + if (remaining === undefined) { + if (status === 'closed') { + remaining = 0; + } else if (status !== 'canceled') { + remaining = amount; + } + } + let symbol = undefined; + const baseId = this.safeString (order, 'order_currency'); + const quoteId = this.safeString (order, 'payment_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + if ((base !== undefined) && (quote !== undefined)) { + symbol = base + '/' + quote; + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let filled = undefined; + let cost = undefined; + let average = undefined; + const id = this.safeString (order, 'order_id'); + const rawTrades = this.safeValue (order, 'contract'); + let trades = undefined; + let fee = undefined; + let fees = undefined; + let feesByCurrency = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'side': side, + 'symbol': symbol, + 'order': id, + }); + filled = 0; + feesByCurrency = {}; + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + filled = this.sum (filled, trade['amount']); + cost = this.sum (cost, trade['cost']); + const tradeFee = trade['fee']; + const feeCurrency = tradeFee['currency']; + if (feeCurrency in feesByCurrency) { + feesByCurrency[feeCurrency] = { + 'currency': feeCurrency, + 'cost': this.sum (feesByCurrency[feeCurrency]['cost'], tradeFee['cost']), + }; + } else { + feesByCurrency[feeCurrency] = { + 'currency': feeCurrency, + 'cost': tradeFee['cost'], + }; + } + } + const feeCurrencies = Object.keys (feesByCurrency); + const feeCurrenciesLength = feeCurrencies.length; + if (feeCurrenciesLength > 1) { + fees = []; + for (let i = 0; i < feeCurrencies.length; i++) { + const feeCurrency = feeCurrencies[i]; + fees.push (feesByCurrency[feeCurrency]); + } + } else { + fee = this.safeValue (feesByCurrency, feeCurrencies[0]); + } + if (filled !== 0) { + average = cost / filled; + } + } + if (amount !== undefined) { + if ((filled === undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + if ((remaining === undefined) && (filled !== undefined)) { + remaining = Math.max (0, amount - filled); + } + } + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': trades, + }; + if (fee !== undefined) { + result['fee'] = fee; + } else if (fees !== undefined) { + result['fees'] = fees; + } + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; + } + const request = { + 'count': limit, + 'order_currency': market['base'], + 'payment_currency': market['quote'], + }; + if (since !== undefined) { + request['after'] = since; + } + const response = await this.privatePostInfoOrders (this.extend (request, params)); + // + // { + // "status": "0000", + // "data": [ + // { + // "order_currency": "BTC", + // "payment_currency": "KRW", + // "order_id": "C0101000007408440032", + // "order_date": "1571728739360570", + // "type": "bid", + // "units": "5.0", + // "units_remaining": "5.0", + // "price": "501000", + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const side_in_params = ('side' in params); + if (!side_in_params) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument and a `side` parameter (sell or buy)'); + } + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument and a `side` parameter (sell or buy)'); + } + const market = this.market (symbol); + const side = (params['side'] === 'buy') ? 'bid' : 'ask'; + params = this.omit (params, [ 'side', 'currency' ]); + // https://github.com/ccxt/ccxt/issues/6771 + const request = { + 'order_id': id, + 'type': side, + 'order_currency': market['base'], + 'payment_currency': market['quote'], + }; + return await this.privatePostTradeCancel (this.extend (request, params)); + } + + cancelUnifiedOrder (order, params = {}) { + const request = { + 'side': order['side'], + }; + return this.cancelOrder (order['id'], order['symbol'], this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'units': amount, + 'address': address, + 'currency': currency['id'], + }; + if (currency === 'XRP' || currency === 'XMR') { + const destination = this.safeString (params, 'destination'); + if ((tag === undefined) && (destination === undefined)) { + throw new ArgumentsRequired (this.id + ' ' + code + ' withdraw() requires a tag argument or an extra destination param'); + } else if (tag !== undefined) { + request['destination'] = tag; + } + } + const response = await this.privatePostTradeBtcWithdrawal (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const endpoint = '/' + this.implodeParams (path, params); + let url = this.urls['api'][api] + endpoint; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'endpoint': endpoint, + }, query)); + const nonce = this.nonce ().toString (); + const auth = endpoint + "\0" + body + "\0" + nonce; // eslint-disable-line quotes + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'); + const signature64 = this.decode (this.stringToBase64 (signature)); + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Api-Key': this.apiKey, + 'Api-Sign': signature64, + 'Api-Nonce': nonce, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('status' in response) { + // + // {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"} + // + const status = this.safeString (response, 'status'); + const message = this.safeString (response, 'message'); + if (status !== undefined) { + if (status === '0000') { + return; // no error + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, status, feedback); + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('status' in response) { + if (response['status'] === '0000') { + return response; + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],35:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const zb = require ('./zb.js'); + +// --------------------------------------------------------------------------- + +module.exports = class bitkk extends zb { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitkk', + 'name': 'bitkk', + 'comment': 'a Chinese ZB clone', + 'urls': { + 'api': { + 'public': 'http://api.bitkk.com/data', // no https for public API + 'private': 'https://trade.bitkk.com/api', + }, + 'www': 'https://www.bitkk.com', + 'doc': 'https://www.bitkk.com/i/developer', + 'fees': 'https://www.bitkk.com/i/rate', + }, + }); + } +}; + +},{"./zb.js":179}],36:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeNotAvailable, AccountSuspended, PermissionDenied, RateLimitExceeded, InvalidNonce, InvalidAddress, ArgumentsRequired, ExchangeError, InvalidOrder, InsufficientFunds, BadRequest, OrderNotFound, BadSymbol, NotSupported } = require ('./base/errors'); +const { ROUND, TICK_SIZE, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitmart extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitmart', + 'name': 'BitMart', + 'countries': [ 'US', 'CN', 'HK', 'KR' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'cancelOrders': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCanceledOrders': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchStatus': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'hostname': 'bitmart.com', // bitmart.info for Hong Kong users + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/61835713-a2662f80-ae85-11e9-9d00-6442919701fd.jpg', + 'api': 'https://api-cloud.{hostname}', // bitmart.info for Hong Kong users + 'www': 'https://www.bitmart.com/', + 'doc': 'https://github.com/bitmartexchange/bitmart-official-api-docs', + 'referral': 'http://www.bitmart.com/?r=rQCFLh', + 'fees': 'https://www.bitmart.com/fee/en', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'system': { + 'get': [ + 'time', // https://api-cloud.bitmart.com/system/time + 'service', // https://api-cloud.bitmart.com/system/service + ], + }, + 'account': { + 'get': [ + 'currencies', // https://api-cloud.bitmart.com/account/v1/currencies + ], + }, + 'spot': { + 'get': [ + 'currencies', + 'symbols', + 'symbols/details', + 'ticker', // ?symbol=BTC_USDT + 'steps', // ?symbol=BMX_ETH + 'symbols/kline', // ?symbol=BMX_ETH&step=15&from=1525760116&to=1525769116 + 'symbols/book', // ?symbol=BMX_ETH&precision=6 + 'symbols/trades', // ?symbol=BMX_ETH + ], + }, + 'contract': { + 'get': [ + 'contracts', // https://api-cloud.bitmart.com/contract/v1/ifcontract/contracts + 'pnls', + 'indexes', + 'tickers', + 'quote', + 'indexquote', + 'trades', + 'depth', + 'fundingrate', + ], + }, + }, + 'private': { + 'account': { + 'get': [ + 'wallet', // ?account_type=1 + 'deposit/address', // ?currency=USDT-TRC20 + 'withdraw/charge', // ?currency=BTC + 'deposit-withdraw/history', // ?limit=10&offset=1&operationType=withdraw + 'deposit-withdraw/detail', // ?id=1679952 + ], + 'post': [ + 'withdraw/apply', + ], + }, + 'spot': { + 'get': [ + 'wallet', + 'order_detail', + 'orders', + 'trades', + ], + 'post': [ + 'submit_order', // https://api-cloud.bitmart.com/spot/v1/submit_order + 'cancel_order', // https://api-cloud.bitmart.com/spot/v2/cancel_order + 'cancel_orders', + ], + }, + 'contract': { + 'get': [ + 'userOrders', + 'userOrderInfo', + 'userTrades', + 'orderTrades', + 'accounts', + 'userPositions', + 'userLiqRecords', + 'positionFee', + ], + 'post': [ + 'batchOrders', + 'submitOrder', + 'cancelOrders', + 'marginOper', + ], + }, + }, + }, + 'timeframes': { + '1m': 1, + '3m': 3, + '5m': 5, + '15m': 15, + '30m': 30, + '45m': 45, + '1h': 60, + '2h': 120, + '3h': 180, + '4h': 240, + '1d': 1440, + '1w': 10080, + '1M': 43200, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.001, + 'tiers': { + 'taker': [ + [0, 0.20 / 100], + [10, 0.18 / 100], + [50, 0.16 / 100], + [250, 0.14 / 100], + [1000, 0.12 / 100], + [5000, 0.10 / 100], + [25000, 0.08 / 100], + [50000, 0.06 / 100], + ], + 'maker': [ + [0, 0.1 / 100], + [10, 0.09 / 100], + [50, 0.08 / 100], + [250, 0.07 / 100], + [1000, 0.06 / 100], + [5000, 0.05 / 100], + [25000, 0.04 / 100], + [50000, 0.03 / 100], + ], + }, + }, + }, + 'precisionMode': TICK_SIZE, + 'exceptions': { + 'exact': { + // general errors + '30000': ExchangeError, // 404, Not found + '30001': AuthenticationError, // 401, Header X-BM-KEY is empty + '30002': AuthenticationError, // 401, Header X-BM-KEY not found + '30003': AccountSuspended, // 401, Header X-BM-KEY has frozen + '30004': AuthenticationError, // 401, Header X-BM-SIGN is empty + '30005': AuthenticationError, // 401, Header X-BM-SIGN is wrong + '30006': AuthenticationError, // 401, Header X-BM-TIMESTAMP is empty + '30007': AuthenticationError, // 401, Header X-BM-TIMESTAMP range. Within a minute + '30008': AuthenticationError, // 401, Header X-BM-TIMESTAMP invalid format + '30010': PermissionDenied, // 403, IP is forbidden. We recommend enabling IP whitelist for API trading. After that reauth your account + '30011': AuthenticationError, // 403, Header X-BM-KEY over expire time + '30012': AuthenticationError, // 403, Header X-BM-KEY is forbidden to request it + '30013': RateLimitExceeded, // 429, Request too many requests + '30014': ExchangeNotAvailable, // 503, Service unavailable + // funding account errors + '60000': BadRequest, // 400, Invalid request (maybe the body is empty, or the int parameter passes string data) + '60001': BadRequest, // 400, Asset account type does not exist + '60002': BadRequest, // 400, currency does not exist + '60003': ExchangeError, // 400, Currency has been closed recharge channel, if there is any problem, please consult customer service + '60004': ExchangeError, // 400, Currency has been closed withdraw channel, if there is any problem, please consult customer service + '60005': ExchangeError, // 400, Minimum amount is %s + '60006': ExchangeError, // 400, Maximum withdraw precision is %d + '60007': InvalidAddress, // 400, Only withdrawals from added addresses are allowed + '60008': InsufficientFunds, // 400, Balance not enough + '60009': ExchangeError, // 400, Beyond the limit + '60010': ExchangeError, // 400, Withdraw id or deposit id not found + '60011': InvalidAddress, // 400, Address is not valid + '60012': ExchangeError, // 400, This action is not supported in this currency(If IOTA, HLX recharge and withdraw calls are prohibited) + '60020': PermissionDenied, // 403, Your account is not allowed to recharge + '60021': PermissionDenied, // 403, Your account is not allowed to withdraw + '60022': PermissionDenied, // 403, No withdrawals for 24 hours + '60030': BadRequest, // 405, Method Not Allowed + '60031': BadRequest, // 415, Unsupported Media Type + '60050': ExchangeError, // 500, User account not found + '60051': ExchangeError, // 500, Internal Server Error + // spot errors + '50000': BadRequest, // 400, Bad Request + '50001': BadSymbol, // 400, Symbol not found + '50002': BadRequest, // 400, From Or To format error + '50003': BadRequest, // 400, Step format error + '50004': BadRequest, // 400, Kline size over 500 + '50005': OrderNotFound, // 400, Order Id not found + '50006': InvalidOrder, // 400, Minimum size is %s + '50007': InvalidOrder, // 400, Maximum size is %s + '50008': InvalidOrder, // 400, Minimum price is %s + '50009': InvalidOrder, // 400, Minimum count*price is %s + '50010': InvalidOrder, // 400, RequestParam size is required + '50011': InvalidOrder, // 400, RequestParam price is required + '50012': InvalidOrder, // 400, RequestParam notional is required + '50013': InvalidOrder, // 400, Maximum limit*offset is %d + '50014': BadRequest, // 400, RequestParam limit is required + '50015': BadRequest, // 400, Minimum limit is 1 + '50016': BadRequest, // 400, Maximum limit is %d + '50017': BadRequest, // 400, RequestParam offset is required + '50018': BadRequest, // 400, Minimum offset is 1 + '50019': BadRequest, // 400, Maximum price is %s + // '50019': ExchangeError, // 400, Invalid status. validate status is [1=Failed, 2=Success, 3=Frozen Failed, 4=Frozen Success, 5=Partially Filled, 6=Fully Fulled, 7=Canceling, 8=Canceled + '50020': InsufficientFunds, // 400, Balance not enough + '50021': BadRequest, // 400, Invalid %s + '50022': ExchangeNotAvailable, // 400, Service unavailable + '50023': BadSymbol, // 400, This Symbol can't place order by api + '53000': AccountSuspended, // 403, Your account is frozen due to security policies. Please contact customer service + '57001': BadRequest, // 405, Method Not Allowed + '58001': BadRequest, // 415, Unsupported Media Type + '59001': ExchangeError, // 500, User account not found + '59002': ExchangeError, // 500, Internal Server Error + // contract errors + '40001': ExchangeError, // 400, Cloud account not found + '40002': ExchangeError, // 400, out_trade_no not found + '40003': ExchangeError, // 400, out_trade_no already existed + '40004': ExchangeError, // 400, Cloud account count limit + '40005': ExchangeError, // 400, Transfer vol precision error + '40006': PermissionDenied, // 400, Invalid ip error + '40007': BadRequest, // 400, Parse parameter error + '40008': InvalidNonce, // 400, Check nonce error + '40009': BadRequest, // 400, Check ver error + '40010': BadRequest, // 400, Not found func error + '40011': BadRequest, // 400, Invalid request + '40012': ExchangeError, // 500, System error + '40013': ExchangeError, // 400, Access too often" CLIENT_TIME_INVALID, "Please check your system time. + '40014': BadSymbol, // 400, This contract is offline + '40015': BadSymbol, // 400, This contract's exchange has been paused + '40016': InvalidOrder, // 400, This order would trigger user position liquidate + '40017': InvalidOrder, // 400, It is not possible to open and close simultaneously in the same position + '40018': InvalidOrder, // 400, Your position is closed + '40019': ExchangeError, // 400, Your position is in liquidation delegating + '40020': InvalidOrder, // 400, Your position volume is not enough + '40021': ExchangeError, // 400, The position is not exsit + '40022': ExchangeError, // 400, The position is not isolated + '40023': ExchangeError, // 400, The position would liquidate when sub margin + '40024': ExchangeError, // 400, The position would be warnning of liquidation when sub margin + '40025': ExchangeError, // 400, The position’s margin shouldn’t be lower than the base limit + '40026': ExchangeError, // 400, You cross margin position is in liquidation delegating + '40027': InsufficientFunds, // 400, You contract account available balance not enough + '40028': PermissionDenied, // 400, Your plan order's count is more than system maximum limit. + '40029': InvalidOrder, // 400, The order's leverage is too large. + '40030': InvalidOrder, // 400, The order's leverage is too small. + '40031': InvalidOrder, // 400, The deviation between current price and trigger price is too large. + '40032': InvalidOrder, // 400, The plan order's life cycle is too long. + '40033': InvalidOrder, // 400, The plan order's life cycle is too short. + '40034': BadSymbol, // 400, This contract is not found + }, + 'broad': {}, + }, + 'commonCurrencies': { + 'ONE': 'Menlo One', + 'PLA': 'Plair', + }, + 'options': { + 'defaultType': 'spot', // 'spot', 'swap' + 'fetchBalance': { + 'type': 'spot', // 'spot', 'swap', 'contract', 'account' + }, + 'createMarketBuyOrderRequiresPrice': true, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicSystemGetTime (params); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"c4e5e5b7-fe9f-4191-89f7-53f6c5bf9030", + // "data":{ + // "server_time":1599843709578 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.safeInteger (data, 'server_time'); + } + + async fetchStatus (params = {}) { + const options = this.safeValue (this.options, 'fetchBalance', {}); + const defaultType = this.safeString (this.options, 'defaultType'); + let type = this.safeString (options, 'type', defaultType); + type = this.safeString (params, 'type', type); + params = this.omit (params, 'type'); + const response = await this.publicSystemGetService (params); + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "serivce":[ + // { + // "title": "Spot API Stop", + // "service_type": "spot", + // "status": "2", + // "start_time": 1527777538000, + // "end_time": 1527777538000 + // }, + // { + // "title": "Contract API Stop", + // "service_type": "contract", + // "status": "2", + // "start_time": 1527777538000, + // "end_time": 1527777538000 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const services = this.safeValue (data, 'service', []); + const servicesByType = this.indexBy (services, 'service_type'); + if ((type === 'swap') || (type === 'future')) { + type = 'contract'; + } + const service = this.safeValue (servicesByType, type); + let status = undefined; + let eta = undefined; + if (service !== undefined) { + const statusCode = this.safeInteger (service, 'status'); + if (statusCode === 2) { + status = 'ok'; + } else { + status = 'maintenance'; + eta = this.safeInteger (service, 'end_time'); + } + } + this.status = this.extend (this.status, { + 'status': status, + 'updated': this.milliseconds (), + 'eta': eta, + }); + return this.status; + } + + async fetchSpotMarkets (params = {}) { + const response = await this.publicSpotGetSymbolsDetails (params); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a67c9146-086d-4d3f-9897-5636a9bb26e1", + // "data":{ + // "symbols":[ + // { + // "symbol":"PRQ_BTC", + // "symbol_id":1232, + // "base_currency":"PRQ", + // "quote_currency":"BTC", + // "quote_increment":"1.0000000000", + // "base_min_size":"1.0000000000", + // "base_max_size":"10000000.0000000000", + // "price_min_precision":8, + // "price_max_precision":10, + // "expiration":"NA", + // "min_buy_amount":"0.0001000000", + // "min_sell_amount":"0.0001000000" + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const symbols = this.safeValue (data, 'symbols', []); + const result = []; + for (let i = 0; i < symbols.length; i++) { + const market = symbols[i]; + const id = this.safeString (market, 'symbol'); + const numericId = this.safeInteger (market, 'symbol_id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + // + // https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details + // from the above API doc: + // quote_increment Minimum order price as well as the price increment + // price_min_precision Minimum price precision (digit) used to query price and kline + // price_max_precision Maximum price precision (digit) used to query price and kline + // + // the docs are wrong: https://github.com/ccxt/ccxt/issues/5612 + // + const pricePrecision = this.safeInteger (market, 'price_max_precision'); + const precision = { + 'amount': this.safeFloat (market, 'base_min_size'), + 'price': parseFloat (this.decimalToPrecision (Math.pow (10, -pricePrecision), ROUND, 10)), + }; + const minBuyCost = this.safeFloat (market, 'min_buy_amount'); + const minSellCost = this.safeFloat (market, 'min_sell_amount'); + const minCost = Math.max (minBuyCost, minSellCost); + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'base_min_size'), + 'max': this.safeFloat (market, 'base_max_size'), + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': 'spot', + 'spot': true, + 'future': false, + 'swap': false, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': undefined, + }); + } + return result; + } + + async fetchContractMarkets (params = {}) { + const response = await this.publicContractGetContracts (params); + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"7fcedfb5-a660-4780-8a7a-b36a9e2159f7", + // "data":{ + // "contracts":[ + // { + // "contract":{ + // "contract_id":1, + // "index_id":1, + // "name":"BTCUSDT", + // "display_name":"BTCUSDT永续合约", + // "display_name_en":"BTCUSDT_SWAP", + // "contract_type":1, + // "base_coin":"BTC", + // "quote_coin":"USDT", + // "price_coin":"BTC", + // "exchange":"*", + // "contract_size":"0.0001", + // "begin_at":"2018-08-17T04:00:00Z", + // "delive_at":"2020-08-15T12:00:00Z", + // "delivery_cycle":28800, + // "min_leverage":"1", + // "max_leverage":"100", + // "price_unit":"0.1", + // "vol_unit":"1", + // "value_unit":"0.0001", + // "min_vol":"1", + // "max_vol":"300000", + // "liquidation_warn_ratio":"0.85", + // "fast_liquidation_ratio":"0.8", + // "settgle_type":1, + // "open_type":3, + // "compensate_type":1, + // "status":3, + // "block":1, + // "rank":1, + // "created_at":"2018-07-12T19:16:57Z", + // "depth_bord":"1.001", + // "base_coin_zh":"比特币", + // "base_coin_en":"Bitcoin", + // "max_rate":"0.00375", + // "min_rate":"-0.00375" + // }, + // "risk_limit":{"contract_id":1,"base_limit":"1000000","step":"500000","maintenance_margin":"0.005","initial_margin":"0.01"}, + // "fee_config":{"contract_id":1,"maker_fee":"-0.0003","taker_fee":"0.001","settlement_fee":"0","created_at":"2018-07-12T20:47:22Z"}, + // "plan_order_config":{"contract_id":0,"min_scope":"0.001","max_scope":"2","max_count":10,"min_life_cycle":24,"max_life_cycle":168} + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const contracts = this.safeValue (data, 'contracts', []); + const result = []; + for (let i = 0; i < contracts.length; i++) { + const market = contracts[i]; + const contract = this.safeValue (market, 'contract', {}); + const id = this.safeString (contract, 'contract_id'); + const numericId = this.safeInteger (contract, 'contract_id'); + const baseId = this.safeString (contract, 'base_coin'); + const quoteId = this.safeString (contract, 'quote_coin'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = this.safeString (contract, 'name'); + // + // https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details + // from the above API doc: + // quote_increment Minimum order price as well as the price increment + // price_min_precision Minimum price precision (digit) used to query price and kline + // price_max_precision Maximum price precision (digit) used to query price and kline + // + // the docs are wrong: https://github.com/ccxt/ccxt/issues/5612 + // + const amountPrecision = this.safeFloat (contract, 'vol_unit'); + const pricePrecision = this.safeFloat (contract, 'price_unit'); + const precision = { + 'amount': amountPrecision, + 'price': pricePrecision, + }; + const limits = { + 'amount': { + 'min': this.safeFloat (contract, 'min_vol'), + 'max': this.safeFloat (contract, 'max_vol'), + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + const contractType = this.safeValue (contract, 'contract_type'); + let future = false; + let swap = false; + let type = 'contract'; + if (contractType === 1) { + type = 'swap'; + swap = true; + } else if (contractType === 2) { + type = 'future'; + future = true; + } + const feeConfig = this.safeValue (market, 'fee_config', {}); + const maker = this.safeFloat (feeConfig, 'maker_fee'); + const taker = this.safeFloat (feeConfig, 'taker_fee'); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'maker': maker, + 'taker': taker, + 'type': type, + 'spot': false, + 'future': future, + 'swap': swap, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': undefined, + }); + } + return result; + } + + async fetchMarkets (params = {}) { + const spotMarkets = await this.fetchSpotMarkets (); + const contractMarkets = await this.fetchContractMarkets (); + const allMarkets = this.arrayConcat (spotMarkets, contractMarkets); + return allMarkets; + } + + parseTicker (ticker, market = undefined) { + // + // spot + // + // { + // "symbol":"ETH_BTC", + // "last_price":"0.036037", + // "quote_volume_24h":"4380.6660000000", + // "base_volume_24h":"159.3582006712", + // "high_24h":"0.036972", + // "low_24h":"0.035524", + // "open_24h":"0.036561", + // "close_24h":"0.036037", + // "best_ask":"0.036077", + // "best_ask_size":"9.9500", + // "best_bid":"0.035983", + // "best_bid_size":"4.2792", + // "fluctuation":"-0.0143", + // "url":"https://www.bitmart.com/trade?symbol=ETH_BTC" + // } + // + // contract + // + // { + // "last_price":"422.2", + // "open":"430.5", + // "close":"422.2", + // "low":"421.9", + // "high":"436.9", + // "avg_price":"430.8569900089815372072", + // "volume":"2720", + // "total_volume":"18912248", + // "timestamp":1597631495, + // "rise_fall_rate":"-0.0192799070847851336", + // "rise_fall_value":"-8.3", + // "contract_id":2, + // "position_size":"3067404", + // "volume_day":"9557384", + // "amount24":"80995537.0919999999999974153", + // "base_coin_volume":"189122.48", + // "quote_coin_volume":"81484742.475833810590837937856", + // "pps":"1274350547", + // "index_price":"422.135", + // "fair_price":"422.147253318507", + // "depth_price":{"bid_price":"421.9","ask_price":"422","mid_price":"421.95"}, + // "fair_basis":"0.000029027013", + // "fair_value":"0.012253318507", + // "rate":{"quote_rate":"0.0006","base_rate":"0.0003","interest_rate":"0.000099999999"}, + // "premium_index":"0.000045851604", + // "funding_rate":"0.000158", + // "next_funding_rate":"0.000099999999", + // "next_funding_at":"2020-08-17T04:00:00Z" + // } + // + const timestamp = this.safeTimestamp (ticker, 'timestamp', this.milliseconds ()); + const marketId = this.safeString2 (ticker, 'symbol', 'contract_id'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat2 (ticker, 'close_24h', 'last_price'); + let percentage = this.safeFloat (ticker, 'fluctuation', 'rise_fall_rate'); + if (percentage !== undefined) { + percentage *= 100; + } + const baseVolume = this.safeFloat2 (ticker, 'base_volume_24h', 'base_coin_volume'); + const quoteVolume = this.safeFloat2 (ticker, 'quote_volume_24h', 'quote_coin_volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + const open = this.safeFloat2 (ticker, 'open_24h', 'open'); + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + average = this.sum (last, open) / 2; + } + average = this.safeFloat (ticker, 'avg_price', average); + const price = this.safeValue (ticker, 'depth_price', ticker); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (ticker, 'high', 'high_24h'), + 'low': this.safeFloat2 (ticker, 'low', 'low_24h'), + 'bid': this.safeFloat (price, 'best_bid', 'bid_price'), + 'bidVolume': this.safeFloat (ticker, 'best_bid_size'), + 'ask': this.safeFloat (price, 'best_ask', 'ask_price'), + 'askVolume': this.safeFloat (ticker, 'best_ask_size'), + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open_24h'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['swap'] || market['future']) { + method = 'publicContractGetTickers'; + request['contractID'] = market['id']; + } else if (market['spot']) { + method = 'publicSpotGetTicker'; + request['symbol'] = market['id']; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"6aa5b923-2f57-46e3-876d-feca190e0b82", + // "data":{ + // "tickers":[ + // { + // "symbol":"ETH_BTC", + // "last_price":"0.036037", + // "quote_volume_24h":"4380.6660000000", + // "base_volume_24h":"159.3582006712", + // "high_24h":"0.036972", + // "low_24h":"0.035524", + // "open_24h":"0.036561", + // "close_24h":"0.036037", + // "best_ask":"0.036077", + // "best_ask_size":"9.9500", + // "best_bid":"0.035983", + // "best_bid_size":"4.2792", + // "fluctuation":"-0.0143", + // "url":"https://www.bitmart.com/trade?symbol=ETH_BTC" + // } + // ] + // } + // } + // + // contract + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"d09b57c4-d99b-4a13-91a8-2df98f889909", + // "data":{ + // "tickers":[ + // { + // "last_price":"422.2", + // "open":"430.5", + // "close":"422.2", + // "low":"421.9", + // "high":"436.9", + // "avg_price":"430.8569900089815372072", + // "volume":"2720", + // "total_volume":"18912248", + // "timestamp":1597631495, + // "rise_fall_rate":"-0.0192799070847851336", + // "rise_fall_value":"-8.3", + // "contract_id":2, + // "position_size":"3067404", + // "volume_day":"9557384", + // "amount24":"80995537.0919999999999974153", + // "base_coin_volume":"189122.48", + // "quote_coin_volume":"81484742.475833810590837937856", + // "pps":"1274350547", + // "index_price":"422.135", + // "fair_price":"422.147253318507", + // "depth_price":{"bid_price":"421.9","ask_price":"422","mid_price":"421.95"}, + // "fair_basis":"0.000029027013", + // "fair_value":"0.012253318507", + // "rate":{"quote_rate":"0.0006","base_rate":"0.0003","interest_rate":"0.000099999999"}, + // "premium_index":"0.000045851604", + // "funding_rate":"0.000158", + // "next_funding_rate":"0.000099999999", + // "next_funding_at":"2020-08-17T04:00:00Z" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'tickers', []); + const tickersById = this.indexBy (tickers, 'symbol'); + const ticker = this.safeValue (tickersById, market['id']); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + let method = undefined; + if ((type === 'swap') || (type === 'future')) { + method = 'publicContractGetTickers'; + } else if (type === 'spot') { + method = 'publicSpotGetTicker'; + } + const response = await this[method] (params); + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'tickers', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicAccountGetCurrencies (params); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"8c768b3c-025f-413f-bec5-6d6411d46883", + // "data":{ + // "currencies":[ + // {"currency":"MATIC","name":"Matic Network","withdraw_enabled":true,"deposit_enabled":true}, + // {"currency":"KTN","name":"Kasoutuuka News","withdraw_enabled":true,"deposit_enabled":false}, + // {"currency":"BRT","name":"Berith","withdraw_enabled":true,"deposit_enabled":true}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const currencies = this.safeValue (data, 'currencies', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + const withdrawEnabled = this.safeValue (currency, 'withdraw_enabled'); + const depositEnabled = this.safeValue (currency, 'deposit_enabled'); + const active = withdrawEnabled && depositEnabled; + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'info': currency, // the original payload + 'active': active, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + method = 'publicSpotGetSymbolsBook'; + request['symbol'] = market['id']; + // request['precision'] = 4; // optional price precision / depth level whose range is defined in symbol details + } else if (market['swap'] || market['future']) { + method = 'publicContractGetDepth'; + request['contractID'] = market['id']; + if (limit !== undefined) { + request['count'] = limit; // returns all records if size is omitted + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"8254f8fc-431d-404f-ad9a-e716339f66c7", + // "data":{ + // "buys":[ + // {"amount":"4.7091","total":"4.71","price":"0.034047","count":"1"}, + // {"amount":"5.7439","total":"10.45","price":"0.034039","count":"1"}, + // {"amount":"2.5249","total":"12.98","price":"0.032937","count":"1"}, + // ], + // "sells":[ + // {"amount":"41.4365","total":"41.44","price":"0.034174","count":"1"}, + // {"amount":"4.2317","total":"45.67","price":"0.034183","count":"1"}, + // {"amount":"0.3000","total":"45.97","price":"0.034240","count":"1"}, + // ] + // } + // } + // + // contract + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"c330dfca-ca5b-4f15-b350-9fef3f049b4f", + // "data":{ + // "sells":[ + // {"price":"347.6","vol":"6678"}, + // {"price":"347.7","vol":"3452"}, + // {"price":"347.8","vol":"6331"}, + // ], + // "buys":[ + // {"price":"347.5","vol":"6222"}, + // {"price":"347.4","vol":"20979"}, + // {"price":"347.3","vol":"15179"}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + if (market['spot']) { + return this.parseOrderBook (data, undefined, 'buys', 'sells', 'price', 'amount'); + } else if (market['swap'] || market['future']) { + return this.parseOrderBook (data, undefined, 'buys', 'sells', 'price', 'vol'); + } + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades spot + // + // { + // "amount":"0.005703", + // "order_time":1599652045394, + // "price":"0.034029", + // "count":"0.1676", + // "type":"sell" + // } + // + // public fetchTrades contract, private fetchMyTrades contract + // + // { + // "order_id":109159616160, + // "trade_id":109159616197, + // "contract_id":2, + // "deal_price":"347.6", + // "deal_vol":"5623", + // "make_fee":"-5.8636644", + // "take_fee":"9.772774", + // "created_at":"2020-09-09T11:49:50.749170536Z", + // "way":1, + // "fluctuation":"0" + // } + // + // private fetchMyTrades spot + // + // { + // "detail_id":256348632, + // "order_id":2147484350, + // "symbol":"BTC_USDT", + // "create_time":1590462303000, + // "side":"buy", + // "fees":"0.00001350", + // "fee_coin_name":"BTC", + // "notional":"88.00000000", + // "price_avg":"8800.00", + // "size":"0.01000", + // "exec_type":"M" + // } + // + const id = this.safeString2 (trade, 'trade_id', 'detail_id'); + let timestamp = this.safeInteger2 (trade, 'order_time', 'create_time'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + const type = undefined; + const way = this.safeInteger (trade, 'way'); + let side = this.safeStringLower2 (trade, 'type', 'side'); + if ((side === undefined) && (way !== undefined)) { + if (way < 5) { + side = 'buy'; + } else { + side = 'sell'; + } + } + let takerOrMaker = undefined; + const execType = this.safeString (trade, 'exec_type'); + if (execType !== undefined) { + takerOrMaker = (execType === 'M') ? 'maker' : 'taker'; + } + let price = this.safeFloat2 (trade, 'price', 'deal_price'); + price = this.safeFloat (trade, 'price_avg', price); + let amount = this.safeFloat2 (trade, 'amount', 'deal_vol'); + amount = this.safeFloat (trade, 'size', amount); + let cost = this.safeFloat2 (trade, 'count', 'notional'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = amount * price; + } + const orderId = this.safeInteger (trade, 'order_id'); + const marketId = this.safeString2 (trade, 'contract_id', 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const feeCost = this.safeFloat (trade, 'fees'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_coin_name'); + let feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + if ((feeCurrencyCode === undefined) && (market !== undefined)) { + feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (market['spot']) { + request['symbol'] = market['id']; + method = 'publicSpotGetSymbolsTrades'; + } else if (market['swap'] || market['future']) { + method = 'publicContractGetTrades'; + request['contractID'] = market['id']; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"222d74c0-8f6d-49d9-8e1b-98118c50eeba", + // "data":{ + // "trades":[ + // { + // "amount":"0.005703", + // "order_time":1599652045394, + // "price":"0.034029", + // "count":"0.1676", + // "type":"sell" + // }, + // ] + // } + // } + // + // contract + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"782bc746-b86e-43bf-8d1a-c68b479c9bdd", + // "data":{ + // "trades":[ + // { + // "order_id":109159616160, + // "trade_id":109159616197, + // "contract_id":2, + // "deal_price":"347.6", + // "deal_vol":"5623", + // "make_fee":"-5.8636644", + // "take_fee":"9.772774", + // "created_at":"2020-09-09T11:49:50.749170536Z", + // "way":1, + // "fluctuation":"0" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // spot + // + // { + // "last_price":"0.034987", + // "timestamp":1598787420, + // "volume":"1.0198", + // "open":"0.035007", + // "close":"0.034987", + // "high":"0.035007", + // "low":"0.034986" + // } + // + // contract + // + // { + // "low":"404.4", + // "high":"404.4", + // "open":"404.4", + // "close":"404.4", + // "last_price":"404.4", + // "avg_price":"404.4", + // "volume":"7670", + // "timestamp":1598758441, + // "rise_fall_rate":"0", + // "rise_fall_value":"0", + // "base_coin_volume":"76.7", + // "quote_coin_volume":"31017.48" + // } + // + return [ + this.safeTimestamp (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const type = market['type']; + let method = undefined; + const request = {}; + const duration = this.parseTimeframe (timeframe); + if (type === 'spot') { + method = 'publicSpotGetSymbolsKline'; + request['symbol'] = market['id']; + request['step'] = this.timeframes[timeframe]; + // the exchange will return an empty array if more than 500 candles is requested + const maxLimit = 500; + if (limit === undefined) { + limit = maxLimit; + } + limit = Math.min (maxLimit, limit); + if (since === undefined) { + const end = parseInt (this.milliseconds () / 1000); + const start = end - limit * duration; + request['from'] = start; + request['to'] = end; + } else { + const start = parseInt (since / 1000); + const end = this.sum (start, limit * duration); + request['from'] = start; + request['to'] = end; + } + } else if ((type === 'swap') || (type === 'future')) { + method = 'publicContractGetQuote'; + request['contractID'] = market['id']; + const defaultLimit = 500; + if (limit === undefined) { + limit = defaultLimit; + } + if (since === undefined) { + const end = parseInt (this.milliseconds () / 1000); + const start = end - limit * duration; + request['startTime'] = start; + request['endTime'] = end; + } else { + const start = parseInt (since / 1000); + const end = this.sum (start, limit * duration); + request['startTime'] = start; + request['endTime'] = end; + } + request['unit'] = this.timeframes[timeframe]; + request['resolution'] = 'M'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"80d86378-ab4e-4c70-819e-b42146cf87ad", + // "data":{ + // "klines":[ + // {"last_price":"0.034987","timestamp":1598787420,"volume":"1.0198","open":"0.035007","close":"0.034987","high":"0.035007","low":"0.034986"}, + // {"last_price":"0.034986","timestamp":1598787480,"volume":"0.3959","open":"0.034982","close":"0.034986","high":"0.034986","low":"0.034980"}, + // {"last_price":"0.034978","timestamp":1598787540,"volume":"0.3259","open":"0.034987","close":"0.034978","high":"0.034987","low":"0.034977"}, + // ] + // } + // } + // + // swap + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"32965074-5804-4655-b693-e953e36026a0", + // "data":[ + // {"low":"404.4","high":"404.4","open":"404.4","close":"404.4","last_price":"404.4","avg_price":"404.4","volume":"7670","timestamp":1598758441,"rise_fall_rate":"0","rise_fall_value":"0","base_coin_volume":"76.7","quote_coin_volume":"31017.48"}, + // {"low":"404.1","high":"404.4","open":"404.4","close":"404.1","last_price":"404.1","avg_price":"404.15881086","volume":"12076","timestamp":1598758501,"rise_fall_rate":"-0.000741839762611276","rise_fall_value":"-0.3","base_coin_volume":"120.76","quote_coin_volume":"48806.2179994536"}, + // {"low":"404","high":"404.3","open":"404.1","close":"404","last_price":"404","avg_price":"404.08918918","volume":"740","timestamp":1598758561,"rise_fall_rate":"-0.000247463499133878","rise_fall_value":"-0.1","base_coin_volume":"7.4","quote_coin_volume":"2990.259999932"}, + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + if (Array.isArray (data)) { + return this.parseOHLCVs (data, market, timeframe, since, limit); + } else { + const klines = this.safeValue (data, 'klines', []); + return this.parseOHLCVs (klines, market, timeframe, since, limit); + } + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + const request = {}; + if (market['spot']) { + request['symbol'] = market['id']; + request['offset'] = 1; + if (limit === undefined) { + limit = 100; // max 100 + } + request['limit'] = limit; + method = 'privateSpotGetTrades'; + } else if (market['swap'] || market['future']) { + request['contractID'] = market['id']; + // request['offset'] = 1; + if (limit !== undefined) { + request['size'] = limit; // max 60 + } + method = 'privateContractGetUserTrades'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a06a5c53-8e6f-42d6-8082-2ff4718d221c", + // "data":{ + // "current_page":1, + // "trades":[ + // { + // "detail_id":256348632, + // "order_id":2147484350, + // "symbol":"BTC_USDT", + // "create_time":1590462303000, + // "side":"buy", + // "fees":"0.00001350", + // "fee_coin_name":"BTC", + // "notional":"88.00000000", + // "price_avg":"8800.00", + // "size":"0.01000", + // "exec_type":"M" + // }, + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "trades": [ + // { + // "order_id": 10116361, + // "trade_id": 10116363, + // "contract_id": 1, + // "deal_price": "16", + // "deal_vol": "10", + // "make_fee": "0.04", + // "take_fee": "0.12", + // "created_at": null, + // "way": 5, + // "fluctuation": "0" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrderTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + const request = {}; + if (market['spot']) { + request['symbol'] = market['id']; + request['order_id'] = id; + method = 'privateSpotGetTrades'; + } else if (market['swap'] || market['future']) { + request['contractID'] = market['id']; + request['orderID'] = id; + method = 'privateContractGetOrderTrades'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a06a5c53-8e6f-42d6-8082-2ff4718d221c", + // "data":{ + // "current_page":1, + // "trades":[ + // { + // "detail_id":256348632, + // "order_id":2147484350, + // "symbol":"BTC_USDT", + // "create_time":1590462303000, + // "side":"buy", + // "fees":"0.00001350", + // "fee_coin_name":"BTC", + // "notional":"88.00000000", + // "price_avg":"8800.00", + // "size":"0.01000", + // "exec_type":"M" + // }, + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "trades": [ + // { + // "order_id": 10116361, + // "trade_id": 10116363, + // "contract_id": 1, + // "deal_price": "16", + // "deal_vol": "10", + // "make_fee": "0.04", + // "take_fee": "0.12", + // "created_at": null, + // "way": 5, + // "fluctuation": "0" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + let method = undefined; + const options = this.safeValue (this.options, 'fetchBalance', {}); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + let type = this.safeString (options, 'type', defaultType); + type = this.safeString (params, 'type', type); + params = this.omit (params, 'type'); + if (type === 'spot') { + method = 'privateSpotGetWallet'; + } else if (type === 'account') { + method = 'privateAccountGetWallet'; + } else if ((type === 'swap') || (type === 'future') || (type === 'contract')) { + method = 'privateContractGetAccounts'; + } + const response = await this[method] (params); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"39069916-72f9-44c7-acde-2ad5afd21cad", + // "data":{ + // "wallet":[ + // {"id":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"}, + // {"id":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"}, + // {"id":"BMX","name":"BitMart Token","available":"0.00000000","frozen":"0.00000000"} + // ] + // } + // } + // + // account + // + // { + // "message":"OK", + // "code":1000, + // "trace":"5c3b7fc7-93b2-49ef-bb59-7fdc56915b59", + // "data":{ + // "wallet":[ + // {"currency":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"}, + // {"currency":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"} + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "accounts": [ + // { + // "account_id": 10, + // "coin_code": "USDT", + // "freeze_vol": "1201.8", + // "available_vol": "8397.65", + // "cash_vol": "0", + // "realised_vol": "-0.5", + // "unrealised_vol": "-0.5", + // "earnings_vol": "-0.5", + // "created_at": "2018-07-13T16:48:49+08:00", + // "updated_at": "2018-07-13T18:34:45.900387+08:00" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const wallet = this.safeValue2 (data, 'wallet', 'accounts', []); + const result = { 'info': response }; + for (let i = 0; i < wallet.length; i++) { + const balance = wallet[i]; + let currencyId = this.safeString2 (balance, 'id', 'currency'); + currencyId = this.safeString (balance, 'coind_code', currencyId); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat2 (balance, 'available', 'available_vol'); + account['used'] = this.safeFloat2 (balance, 'frozen', 'freeze_vol'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "order_id": 2707217580 + // } + // + // cancelOrder + // + // '2707217580' // order id + // + // spot fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders + // + // { + // "order_id":1736871726781, + // "symbol":"BTC_USDT", + // "create_time":1591096004000, + // "side":"sell", + // "type":"market", + // "price":"0.00", + // "price_avg":"0.00", + // "size":"0.02000", + // "notional":"0.00000000", + // "filled_notional":"0.00000000", + // "filled_size":"0.00000", + // "status":"8" + // } + // + // contract fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders, fetchOrders + // + // { + // "order_id": 10539098, + // "contract_id": 1, + // "position_id": 10539088, + // "account_id": 10, + // "price": "16", + // "vol": "1", + // "done_avg_price": "16", + // "done_vol": "1", + // "way": 3, + // "category": 1, + // "open_type": 2, + // "make_fee": "0.00025", + // "take_fee": "0.012", + // "origin": "", + // "created_at": "2018-07-23T11:55:56.715305Z", + // "finished_at": "2018-07-23T11:55:56.763941Z", + // "status": 4, + // "errno": 0 + // } + // + let id = undefined; + if (typeof order === 'string') { + id = order; + order = {}; + } + id = this.safeString (order, 'order_id', id); + let timestamp = this.parse8601 (this.safeString (order, 'created_at')); + timestamp = this.safeInteger (order, 'create_time', timestamp); + const marketId = this.safeString2 (order, 'symbol', 'contract_id'); + const symbol = this.safeSymbol (marketId, market, '_'); + let status = undefined; + if (market !== undefined) { + status = this.parseOrderStatusByType (market['type'], this.safeString (order, 'status')); + } + let price = this.safeFloat (order, 'price'); + let average = this.safeFloat2 (order, 'price_avg', 'done_avg_price'); + const amount = this.safeFloat2 (order, 'size', 'vol'); + let cost = undefined; + let filled = this.safeFloat2 (order, 'filled_size', 'done_vol'); + let remaining = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + if (filled === undefined) { + filled = Math.max (0, amount - remaining); + } + } + if (filled !== undefined) { + if (remaining === undefined) { + remaining = Math.max (0, amount - filled); + } + if (cost === undefined) { + if (average !== undefined) { + cost = average * filled; + } + } + } + } + let side = this.safeString (order, 'side'); + // 1 = Open long + // 2 = Close short + // 3 = Close long + // 4 = Open short + side = this.safeString (order, 'way', side); + const category = this.safeInteger (order, 'category'); + let type = this.safeString (order, 'type'); + if (category === 1) { + type = 'limit'; + } else if (category === 2) { + type = 'market'; + } + if (type === 'market') { + if (price === 0.0) { + price = undefined; + } + if (average === 0.0) { + average = undefined; + } + } + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrderStatusByType (type, status) { + const statusesByType = { + 'spot': { + '1': 'failed', // Order failure + '2': 'open', // Placing order + '3': 'failed', // Order failure, Freeze failure + '4': 'open', // Order success, Pending for fulfilment + '5': 'open', // Partially filled + '6': 'closed', // Fully filled + '7': 'canceling', // Canceling + '8': 'canceled', // Canceled + }, + 'swap': { + '1': 'open', // Submitting + '2': 'open', // Commissioned + '4': 'closed', // Completed + }, + }; + const statuses = this.safeValue (statusesByType, type, {}); + return this.safeString (statuses, status, status); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + request['symbol'] = market['id']; + request['side'] = side; + request['type'] = type; + method = 'privateSpotPostSubmitOrder'; + if (type === 'limit') { + request['size'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let notional = this.safeFloat (params, 'notional'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (notional === undefined) { + notional = amount * price; + } + } else if (notional === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'notional' extra parameter (the exchange-specific behaviour)"); + } + } else { + notional = (notional === undefined) ? amount : notional; + } + const precision = market['precision']['price']; + request['notional'] = this.decimalToPrecision (notional, TRUNCATE, precision, this.precisionMode); + } else if (side === 'sell') { + request['size'] = this.amountToPrecision (symbol, amount); + } + } + } else if (market['swap'] || market['future']) { + method = 'privateContractPostSubmitOrder'; + request['contractID'] = market['id']; + if (type === 'limit') { + request['category'] = 1; + } else if (type === 'market') { + request['category'] = 2; + } + request['way'] = side; // 1 = open long, 2 = close short, 3 = close long, 4 = open short + request['custom_id'] = this.nonce (); + request['open_type'] = 1; // 1 = cross margin, 2 = fixed margin + request['leverage'] = 1; // must meet the effective range of leverage configured in the contract + request['price'] = this.priceToPrecision (symbol, price); + request['vol'] = this.amountToPrecision (symbol, amount); + } + const response = await this[method] (this.extend (request, params)); + // + // spot and contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "order_id": 2707217580 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + method = 'privateSpotPostCancelOrder'; + request['order_id'] = parseInt (id); + request['symbol'] = market['id']; + } else if (market['swap'] || market['future']) { + method = 'privateContractPostCancelOrders'; + request['contractID'] = market['id']; + request['orders'] = [ parseInt (id) ]; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "result": true + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "succeed": [ + // 2707219612 + // ], + // "failed": [] + // } + // } + // + const data = this.safeValue (response, 'data'); + const succeeded = this.safeValue (data, 'succeed'); + if (succeeded !== undefined) { + id = this.safeString (succeeded, 0); + if (id === undefined) { + throw new InvalidOrder (this.id + ' cancelOrder failed to cancel ' + symbol + ' order id ' + id); + } + } else { + const result = this.safeValue (data, 'result'); + if (!result) { + throw new InvalidOrder (this.id + ' cancelOrder ' + symbol + ' order id ' + id + ' is filled or canceled'); + } + } + const order = this.parseOrder (id, market); + return this.extend (order, { 'id': id }); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + const side = this.safeString (params, 'side'); + if (side === undefined) { + throw new ArgumentsRequired (this.id + " cancelAllOrders requires a `side` parameter ('buy' or 'sell')"); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (!market['spot']) { + throw new NotSupported (this.id + ' cancelAllOrders does not support ' + market['type'] + ' orders, only spot orders are accepted'); + } + const request = { + 'symbol': market['id'], + 'side': side, // 'buy' or 'sell' + }; + const response = await this.privateSpotPostCancelOrders (this.extend (request, params)); + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": {} + // } + // + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' canelOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (!market['spot']) { + throw new NotSupported (this.id + ' cancelOrders does not support ' + market['type'] + ' orders, only contract orders are accepted'); + } + const orders = []; + for (let i = 0; i < ids.length; i++) { + orders.push (parseInt (ids[i])); + } + const request = { + 'orders': orders, + }; + const response = await this.privateContractPostCancelOrders (this.extend (request, params)); + // + // spot + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "result": true + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "succeed": [ + // 2707219612 + // ], + // "failed": [] + // } + // } + // + return response; + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrdersByStatus requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + method = 'privateSpotGetOrders'; + request['symbol'] = market['id']; + request['offset'] = 1; // max offset * limit < 500 + request['limit'] = 100; // max limit is 100 + // 1 = Order failure + // 2 = Placing order + // 3 = Order failure, Freeze failure + // 4 = Order success, Pending for fulfilment + // 5 = Partially filled + // 6 = Fully filled + // 7 = Canceling + // 8 = Canceled + // 9 = Outstanding (4 and 5) + // 10 = 6 and 8 + if (status === 'open') { + request['status'] = 9; + } else if (status === 'closed') { + request['status'] = 6; + } else { + request['status'] = status; + } + } else if (market['swap'] || market['future']) { + method = 'privateContractGetUserOrders'; + request['contractID'] = market['id']; + // request['offset'] = 1; + if (limit !== undefined) { + request['size'] = limit; // max 60 + } + // 0 = All + // 1 = Submitting + // 2 = Commissioned + // 3 = 1 and 2 + // 4 = Completed + if (status === 'open') { + request['status'] = 3; + } else if (status === 'closed') { + request['status'] = 4; + } else { + request['status'] = status; + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"70e7d427-7436-4fb8-8cdd-97e1f5eadbe9", + // "data":{ + // "current_page":1, + // "orders":[ + // { + // "order_id":2147601241, + // "symbol":"BTC_USDT", + // "create_time":1591099963000, + // "side":"sell", + // "type":"limit", + // "price":"9000.00", + // "price_avg":"0.00", + // "size":"1.00000", + // "notional":"9000.00000000", + // "filled_notional":"0.00000000", + // "filled_size":"0.00000", + // "status":"4" + // } + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "orders": [ + // { + // "order_id": 10284160, + // "contract_id": 1, + // "price": "8", + // "vol": "4", + // "done_avg_price": "0", + // "done_vol": "0", + // "way": 1, + // "category": 1, + // "open_type": 2, + // "make_fee": "0", + // "take_fee": "0", + // "origin": "", + // "created_at": "2018-07-17T07:24:13.410507Z", + // "finished_at": null, + // "status": 2, + // "errno": 0 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orders = this.safeValue (data, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('open', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('closed', symbol, since, limit, params); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (!(market['swap'] || market['future'])) { + throw new NotSupported (this.id + ' fetchOrders does not support ' + market['type'] + ' markets, only contracts are supported'); + } + return await this.fetchOrdersByStatus (0, symbol, since, limit, params); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const request = {}; + const market = this.market (symbol); + let method = undefined; + if (typeof id !== 'string') { + id = id.toString (); + } + if (market['spot']) { + request['symbol'] = market['id']; + request['order_id'] = id; + method = 'privateSpotGetOrderDetail'; + } else if (market['swap'] || market['future']) { + request['contractID'] = market['id']; + request['orderID'] = id; + method = 'privateContractGetUserOrderInfo'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a27c2cb5-ead4-471d-8455-1cfeda054ea6", + // "data": { + // "order_id":1736871726781, + // "symbol":"BTC_USDT", + // "create_time":1591096004000, + // "side":"sell", + // "type":"market", + // "price":"0.00", + // "price_avg":"0.00", + // "size":"0.02000", + // "notional":"0.00000000", + // "filled_notional":"0.00000000", + // "filled_size":"0.00000", + // "status":"8" + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "orders": [ + // { + // "order_id": 10539098, + // "contract_id": 1, + // "position_id": 10539088, + // "account_id": 10, + // "price": "16", + // "vol": "1", + // "done_avg_price": "16", + // "done_vol": "1", + // "way": 3, + // "category": 1, + // "make_fee": "0.00025", + // "take_fee": "0.012", + // "origin": "", + // "created_at": "2018-07-23T11:55:56.715305Z", + // "finished_at": "2018-07-23T11:55:56.763941Z", + // "status": 4, + // "errno": 0 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data'); + if ('orders' in data) { + const orders = this.safeValue (data, 'orders', []); + const firstOrder = this.safeValue (orders, 0); + if (firstOrder === undefined) { + throw new OrderNotFound (this.id + ' fetchOrder could not find ' + symbol + ' order id ' + id); + } + return this.parseOrder (firstOrder, market); + } else { + return this.parseOrder (data, market); + } + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateAccountGetDepositAddress (this.extend (request, params)); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"0e6edd79-f77f-4251-abe5-83ba75d06c1a", + // "data":{ + // "currency":"USDT-TRC20", + // "chain":"USDT-TRC20", + // "address":"TGR3ghy2b5VLbyAYrmiE15jasR6aPHTvC5", + // "address_memo":"" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const address = this.safeString (data, 'address'); + const tag = this.safeString (data, 'address_memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'destination': 'To Digital Address', // To Digital Address, To Binance, To OKEX + 'address': address, + }; + if (tag !== undefined) { + request['address_memo'] = tag; + } + const response = await this.privateAccountPostWithdrawApply (this.extend (request, params)); + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "withdraw_id": "121212" + // } + // } + // + const data = this.safeValue (response, 'data'); + const transaction = this.parseTransaction (data, currency); + return this.extend (transaction, { + 'code': code, + 'address': address, + 'tag': tag, + }); + } + + async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 50; // max 50 + } + const request = { + 'operation_type': type, // deposit or withdraw + 'offset': 1, + 'limit': limit, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currenc (code); + request['currency'] = currency['id']; + } + const response = await this.privateAccountGetDepositWithdrawHistory (this.extend (request, params)); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"142bf92a-fc50-4689-92b6-590886f90b97", + // "data":{ + // "records":[ + // { + // "withdraw_id":"1679952", + // "deposit_id":"", + // "operation_type":"withdraw", + // "currency":"BMX", + // "apply_time":1588867374000, + // "arrival_amount":"59.000000000000", + // "fee":"1.000000000000", + // "status":0, + // "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5", + // "address_memo":"", + // "tx_id":"" + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const records = this.safeValue (data, 'records', []); + return this.parseTransactions (records, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposit', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdraw', code, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + '0': 'pending', // Create + '1': 'pending', // Submitted, waiting for withdrawal + '2': 'pending', // Processing + '3': 'ok', // Success + '4': 'canceled', // Cancel + '5': 'failed', // Fail + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "withdraw_id": "121212" + // } + // + // fetchDeposits, fetchWithdrawals + // + // { + // "withdraw_id":"1679952", + // "deposit_id":"", + // "operation_type":"withdraw", + // "currency":"BMX", + // "apply_time":1588867374000, + // "arrival_amount":"59.000000000000", + // "fee":"1.000000000000", + // "status":0, + // "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5", + // "address_memo":"", + // "tx_id":"" + // } + // + let id = undefined; + const withdrawId = this.safeString (transaction, 'withdraw_id'); + const depositId = this.safeString (transaction, 'deposit_id'); + let type = undefined; + if ((withdrawId !== undefined) && (withdrawId !== '')) { + type = 'withdraw'; + id = withdrawId; + } else if ((depositId !== undefined) && (depositId !== '')) { + type = 'deposit'; + id = depositId; + } + const amount = this.safeFloat (transaction, 'arrival_amount'); + const timestamp = this.safeInteger (transaction, 'tapply_timeime'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + let txid = this.safeString (transaction, 'tx_id'); + if (txid === '') { + txid = undefined; + } + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'address_memo'); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressFrom': undefined, + 'addressTo': undefined, + 'tag': tag, + 'tagFrom': undefined, + 'tagTo': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const baseUrl = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }); + const access = this.safeString (api, 0); + const type = this.safeString (api, 1); + let url = baseUrl + '/' + type; + if (type !== 'system') { + url += '/' + this.version; + } + if (type === 'contract') { + url += '/' + 'ifcontract'; + } + url += '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (type === 'system') { + if (Object.keys (query).length) { + // console.log (query); + url += '?' + this.urlencode (query); + } + } else if (access === 'public') { + if (Object.keys (query).length) { + // console.log (query); + url += '?' + this.urlencode (query); + } + } else if (access === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + let queryString = ''; + headers = { + 'X-BM-KEY': this.apiKey, + 'X-BM-TIMESTAMP': timestamp, + }; + if ((method === 'POST') || (method === 'PUT')) { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + queryString = body; + } else { + if (Object.keys (query).length) { + queryString = this.urlencode (query); + url += '?' + queryString; + } + } + const auth = timestamp + '#' + this.uid + '#' + queryString; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers['X-BM-SIGN'] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // spot + // + // {"message":"Bad Request [to is empty]","code":50000,"trace":"f9d46e1b-4edb-4d07-a06e-4895fb2fc8fc","data":{}} + // {"message":"Bad Request [from is empty]","code":50000,"trace":"579986f7-c93a-4559-926b-06ba9fa79d76","data":{}} + // {"message":"Kline size over 500","code":50004,"trace":"d625caa8-e8ca-4bd2-b77c-958776965819","data":{}} + // {"message":"Balance not enough","code":50020,"trace":"7c709d6a-3292-462c-98c5-32362540aeef","data":{}} + // + // contract + // + // {"errno":"OK","message":"INVALID_PARAMETER","code":49998,"trace":"eb5ebb54-23cd-4de2-9064-e090b6c3b2e3","data":null} + // + const message = this.safeString (response, 'message'); + const errorCode = this.safeString (response, 'code'); + if (((errorCode !== undefined) && (errorCode !== '1000')) || ((message !== undefined) && (message !== 'OK'))) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],37:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, AuthenticationError, ExchangeError, InsufficientFunds, InvalidOrder, BadSymbol, PermissionDenied, BadRequest } = require ('./base/errors'); +const { ROUND, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitmax extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitmax', + 'name': 'BitMax', + 'countries': [ 'CN' ], // China + 'rateLimit': 500, + // new metainfo interface + 'has': { + 'CORS': false, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOHLCV': true, + 'fetchTrades': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchDepositAddress': true, + 'fetchTransactions': true, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '6h': '360', + '12h': '720', + '1d': '1d', + '1w': '1w', + '1M': '1m', + }, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/66820319-19710880-ef49-11e9-8fbe-16be62a11992.jpg', + 'api': 'https://bitmax.io', + 'test': 'https://bitmax-test.io', + 'www': 'https://bitmax.io', + 'doc': [ + 'https://bitmax-exchange.github.io/bitmax-pro-api/#bitmax-pro-api-documentation', + ], + 'fees': 'https://bitmax.io/#/feeRate/tradeRate', + 'referral': 'https://bitmax.io/#/register?inviteCode=EL6BXBQM', + }, + 'api': { + 'public': { + 'get': [ + 'assets', + 'products', + 'ticker', + 'barhist/info', + 'barhist', + 'depth', + 'trades', + 'cash/assets', // not documented + 'cash/products', // not documented + 'margin/assets', // not documented + 'margin/products', // not documented + 'futures/collateral', + 'futures/contracts', + 'futures/ref-px', + 'futures/market-data', + 'futures/funding-rates', + ], + }, + 'accountCategory': { + 'get': [ + 'balance', + 'order/open', + 'order/status', + 'order/hist/current', + 'risk', + ], + 'post': [ + 'order', + 'order/batch', + ], + 'delete': [ + 'order', + 'order/all', + 'order/batch', + ], + }, + 'accountGroup': { + 'get': [ + 'cash/balance', + 'margin/balance', + 'margin/risk', + 'transfer', + 'futures/collateral-balance', + 'futures/position', + 'futures/risk', + 'futures/funding-payments', + 'order/hist', + ], + 'post': [ + 'futures/transfer/deposit', + 'futures/transfer/withdraw', + ], + }, + 'private': { + 'get': [ + 'info', + 'wallet/transactions', + 'wallet/deposit/address', // not documented + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'account-category': 'cash', // 'cash'/'margin'/'futures' + 'account-group': undefined, + 'fetchClosedOrders': { + 'method': 'accountGroupGetOrderHist', // 'accountGroupGetAccountCategoryOrderHistCurrent' + }, + }, + 'exceptions': { + 'exact': { + // not documented + '1900': BadRequest, // {"code":1900,"message":"Invalid Http Request Input"} + '2100': AuthenticationError, // {"code":2100,"message":"ApiKeyFailure"} + '5002': BadSymbol, // {"code":5002,"message":"Invalid Symbol"} + '6001': BadSymbol, // {"code":6001,"message":"Trading is disabled on symbol."} + '6010': InsufficientFunds, // {'code': 6010, 'message': 'Not enough balance.'} + '60060': InvalidOrder, // { 'code': 60060, 'message': 'The order is already filled or canceled.' } + '600503': InvalidOrder, // {"code":600503,"message":"Notional is too small."} + // documented + '100001': BadRequest, // INVALID_HTTP_INPUT Http request is invalid + '100002': BadRequest, // DATA_NOT_AVAILABLE Some required data is missing + '100003': BadRequest, // KEY_CONFLICT The same key exists already + '100004': BadRequest, // INVALID_REQUEST_DATA The HTTP request contains invalid field or argument + '100005': BadRequest, // INVALID_WS_REQUEST_DATA Websocket request contains invalid field or argument + '100006': BadRequest, // INVALID_ARGUMENT The arugment is invalid + '100007': BadRequest, // ENCRYPTION_ERROR Something wrong with data encryption + '100008': BadSymbol, // SYMBOL_ERROR Symbol does not exist or not valid for the request + '100009': AuthenticationError, // AUTHORIZATION_NEEDED Authorization is require for the API access or request + '100010': BadRequest, // INVALID_OPERATION The action is invalid or not allowed for the account + '100011': BadRequest, // INVALID_TIMESTAMP Not a valid timestamp + '100012': BadRequest, // INVALID_STR_FORMAT String format does not + '100013': BadRequest, // INVALID_NUM_FORMAT Invalid number input + '100101': ExchangeError, // UNKNOWN_ERROR Some unknown error + '150001': BadRequest, // INVALID_JSON_FORMAT Require a valid json object + '200001': AuthenticationError, // AUTHENTICATION_FAILED Authorization failed + '200002': ExchangeError, // TOO_MANY_ATTEMPTS Tried and failed too many times + '200003': ExchangeError, // ACCOUNT_NOT_FOUND Account not exist + '200004': ExchangeError, // ACCOUNT_NOT_SETUP Account not setup properly + '200005': ExchangeError, // ACCOUNT_ALREADY_EXIST Account already exist + '200006': ExchangeError, // ACCOUNT_ERROR Some error related with error + '200007': ExchangeError, // CODE_NOT_FOUND + '200008': ExchangeError, // CODE_EXPIRED Code expired + '200009': ExchangeError, // CODE_MISMATCH Code does not match + '200010': AuthenticationError, // PASSWORD_ERROR Wrong assword + '200011': ExchangeError, // CODE_GEN_FAILED Do not generate required code promptly + '200012': ExchangeError, // FAKE_COKE_VERIFY + '200013': ExchangeError, // SECURITY_ALERT Provide security alert message + '200014': PermissionDenied, // RESTRICTED_ACCOUNT Account is restricted for certain activity, such as trading, or withdraw. + '200015': PermissionDenied, // PERMISSION_DENIED No enough permission for the operation + '300001': InvalidOrder, // INVALID_PRICE Order price is invalid + '300002': InvalidOrder, // INVALID_QTY Order size is invalid + '300003': InvalidOrder, // INVALID_SIDE Order side is invalid + '300004': InvalidOrder, // INVALID_NOTIONAL Notional is too small or too large + '300005': InvalidOrder, // INVALID_TYPE Order typs is invalid + '300006': InvalidOrder, // INVALID_ORDER_ID Order id is invalid + '300007': InvalidOrder, // INVALID_TIME_IN_FORCE Time In Force in order request is invalid + '300008': InvalidOrder, // INVALID_ORDER_PARAMETER Some order parameter is invalid + '300009': InvalidOrder, // TRADING_VIOLATION Trading violation on account or asset + '300011': InsufficientFunds, // INVALID_BALANCE No enough account or asset balance for the trading + '300012': BadSymbol, // INVALID_PRODUCT Not a valid product supported by exchange + '300013': InvalidOrder, // INVALID_BATCH_ORDER Some or all orders are invalid in batch order request + '300020': InvalidOrder, // TRADING_RESTRICTED There is some trading restriction on account or asset + '300021': InvalidOrder, // TRADING_DISABLED Trading is disabled on account or asset + '300031': InvalidOrder, // NO_MARKET_PRICE No market price for market type order trading + '310001': InsufficientFunds, // INVALID_MARGIN_BALANCE No enough margin balance + '310002': InvalidOrder, // INVALID_MARGIN_ACCOUNT Not a valid account for margin trading + '310003': InvalidOrder, // MARGIN_TOO_RISKY Leverage is too high + '310004': BadSymbol, // INVALID_MARGIN_ASSET This asset does not support margin trading + '310005': InvalidOrder, // INVALID_REFERENCE_PRICE There is no valid reference price + '510001': ExchangeError, // SERVER_ERROR Something wrong with server. + '900001': ExchangeError, // HUMAN_CHALLENGE Human change do not pass + }, + 'broad': {}, + }, + 'commonCurrencies': { + 'BTCBEAR': 'BEAR', + 'BTCBULL': 'BULL', + }, + }); + } + + getAccount (params = {}) { + // get current or provided bitmax sub-account + const account = this.safeValue (params, 'account', this.options['account']); + return account.toLowerCase ().capitalize (); + } + + async fetchCurrencies (params = {}) { + const assets = await this.publicGetAssets (params); + // + // { + // "code":0, + // "data":[ + // { + // "assetCode" : "LTCBULL", + // "assetName" : "3X Long LTC Token", + // "precisionScale" : 9, + // "nativeScale" : 4, + // "withdrawalFee" : "0.2", + // "minWithdrawalAmt" : "1.0", + // "status" : "Normal" + // }, + // ] + // } + // + const margin = await this.publicGetMarginAssets (params); + // + // { + // "code":0, + // "data":[ + // { + // "assetCode":"BTT", + // "borrowAssetCode":"BTT-B", + // "interestAssetCode":"BTT-I", + // "nativeScale":0, + // "numConfirmations":1, + // "withdrawFee":"100.0", + // "minWithdrawalAmt":"1000.0", + // "statusCode":"Normal", + // "statusMessage":"", + // "interestRate":"0.001" + // } + // ] + // } + // + const cash = await this.publicGetCashAssets (params); + // + // { + // "code":0, + // "data":[ + // { + // "assetCode":"LTCBULL", + // "nativeScale":4, + // "numConfirmations":20, + // "withdrawFee":"0.2", + // "minWithdrawalAmt":"1.0", + // "statusCode":"Normal", + // "statusMessage":"" + // } + // ] + // } + // + const assetsData = this.safeValue (assets, 'data', []); + const marginData = this.safeValue (margin, 'data', []); + const cashData = this.safeValue (cash, 'data', []); + const assetsById = this.indexBy (assetsData, 'assetCode'); + const marginById = this.indexBy (marginData, 'assetCode'); + const cashById = this.indexBy (cashData, 'assetCode'); + const dataById = this.deepExtend (assetsById, marginById, cashById); + const ids = Object.keys (dataById); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = dataById[id]; + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger2 (currency, 'precisionScale', 'nativeScale'); + // why would the exchange API have different names for the same field + const fee = this.safeFloat2 (currency, 'withdrawFee', 'withdrawalFee'); + const status = this.safeString2 (currency, 'status', 'statusCode'); + const active = (status === 'Normal'); + const margin = ('borrowAssetCode' in currency); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'margin': margin, + 'name': this.safeString (currency, 'assetName'), + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawalAmt'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const products = await this.publicGetProducts (params); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"LBA/BTC", + // "baseAsset":"LBA", + // "quoteAsset":"BTC", + // "status":"Normal", + // "minNotional":"0.000625", + // "maxNotional":"6.25", + // "marginTradable":false, + // "commissionType":"Quote", + // "commissionReserveRate":"0.001", + // "tickSize":"0.000000001", + // "lotSize":"1" + // }, + // ] + // } + // + const cash = await this.publicGetCashProducts (params); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"QTUM/BTC", + // "domain":"BTC", + // "tradingStartTime":1569506400000, + // "collapseDecimals":"0.0001,0.000001,0.00000001", + // "minQty":"0.000000001", + // "maxQty":"1000000000", + // "minNotional":"0.000625", + // "maxNotional":"12.5", + // "statusCode":"Normal", + // "statusMessage":"", + // "tickSize":"0.00000001", + // "useTick":false, + // "lotSize":"0.1", + // "useLot":false, + // "commissionType":"Quote", + // "commissionReserveRate":"0.001", + // "qtyScale":1, + // "priceScale":8, + // "notionalScale":4 + // } + // ] + // } + // + const futures = await this.publicGetFuturesContracts (params); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"BTC-PERP", + // "tradingStartTime":1579701600000, + // "collapseDecimals":"1,0.1,0.01", + // "minQty":"0.000000001", + // "maxQty":"1000000000", + // "minNotional":"5", + // "maxNotional":"1000000", + // "statusCode":"Normal", + // "statusMessage":"", + // "tickSize":"0.25", + // "lotSize":"0.0001", + // "priceScale":2, + // "qtyScale":4, + // "notionalScale":2 + // } + // ] + // } + // + const productsData = this.safeValue (products, 'data', []); + const productsById = this.indexBy (productsData, 'symbol'); + const cashData = this.safeValue (cash, 'data', []); + const futuresData = this.safeValue (futures, 'data', []); + const cashAndFuturesData = this.arrayConcat (cashData, futuresData); + const cashAndFuturesById = this.indexBy (cashAndFuturesData, 'symbol'); + const dataById = this.deepExtend (productsById, cashAndFuturesById); + const ids = Object.keys (dataById); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = dataById[id]; + const baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const precision = { + 'amount': this.safeFloat (market, 'lotSize'), + 'price': this.safeFloat (market, 'tickSize'), + }; + const status = this.safeString (market, 'status'); + const active = (status === 'Normal'); + const type = ('useLot' in market) ? 'spot' : 'future'; + const spot = (type === 'spot'); + const future = (type === 'future'); + let symbol = id; + if (!future) { + symbol = base + '/' + quote; + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'future': future, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minQty'), + 'max': this.safeFloat (market, 'maxQty'), + }, + 'price': { + 'min': this.safeFloat (market, 'tickSize'), + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minNotional'), + 'max': this.safeFloat (market, 'maxNotional'), + }, + }, + }); + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + // TODO: fee calculation here is incorrect, we need to support tiered fee calculation. + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchAccounts (params = {}) { + let accountGroup = this.safeString (this.options, 'account-group'); + let response = undefined; + if (accountGroup === undefined) { + response = await this.privateGetInfo (params); + // + // { + // "code":0, + // "data":{ + // "email":"igor.kroitor@gmail.com", + // "accountGroup":8, + // "viewPermission":true, + // "tradePermission":true, + // "transferPermission":true, + // "cashAccount":["cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda"], + // "marginAccount":["martXoh1v1N3EMQC5FDtSj5VHso8aI2Z"], + // "futuresAccount":["futc9r7UmFJAyBY2rE3beA2JFxav2XFF"], + // "userUID":"U6491137460" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + accountGroup = this.safeString (data, 'accountGroup'); + this.options['account-group'] = accountGroup; + } + return [ + { + 'id': accountGroup, + 'type': undefined, + 'currency': undefined, + 'info': response, + }, + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'fetchBalance', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeString (account, 'id'); + const request = { + 'account-group': accountGroup, + }; + let method = 'accountCategoryGetBalance'; + if (accountCategory === 'futures') { + method = 'accountGroupGetFuturesCollateralBalance'; + } else { + request['account-category'] = accountCategory; + } + const response = await this[method] (this.extend (request, params)); + // + // cash + // + // { + // 'code': 0, + // 'data': [ + // { + // 'asset': 'BCHSV', + // 'totalBalance': '64.298000048', + // 'availableBalance': '64.298000048', + // }, + // ] + // } + // + // margin + // + // { + // 'code': 0, + // 'data': [ + // { + // 'asset': 'BCHSV', + // 'totalBalance': '64.298000048', + // 'availableBalance': '64.298000048', + // 'borrowed': '0', + // 'interest': '0', + // }, + // ] + // } + // + // futures + // + // { + // "code":0, + // "data":[ + // {"asset":"BTC","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"9456.59"}, + // {"asset":"ETH","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"235.95"}, + // {"asset":"USDT","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1"}, + // {"asset":"USDC","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1.00035"}, + // {"asset":"PAX","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1.00045"}, + // {"asset":"USDTR","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1"} + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const code = this.safeCurrencyCode (this.safeString (balance, 'asset')); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableBalance'); + account['total'] = this.safeFloat (balance, 'totalBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetDepth (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "m":"depth-snapshot", + // "symbol":"BTC-PERP", + // "data":{ + // "ts":1590223998202, + // "seqnum":115444921, + // "asks":[ + // ["9207.5","18.2383"], + // ["9207.75","18.8235"], + // ["9208","10.7873"], + // ], + // "bids":[ + // ["9207.25","0.4009"], + // ["9207","0.003"], + // ["9206.5","0.003"], + // ] + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orderbook = this.safeValue (data, 'data', {}); + const timestamp = this.safeInteger (orderbook, 'ts'); + const result = this.parseOrderBook (orderbook, timestamp); + result['nonce'] = this.safeInteger (orderbook, 'seqnum'); + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { + // "symbol":"QTUM/BTC", + // "open":"0.00016537", + // "close":"0.00019077", + // "high":"0.000192", + // "low":"0.00016537", + // "volume":"846.6", + // "ask":["0.00018698","26.2"], + // "bid":["0.00018408","503.7"], + // "type":"spot" + // } + // + const timestamp = undefined; + const marketId = this.safeString (ticker, 'symbol'); + let symbol = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else if (marketId !== undefined) { + const type = this.safeString (ticker, 'type'); + if (type === 'spot') { + const [ baseId, quoteId ] = marketId.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const close = this.safeFloat (ticker, 'close'); + const bid = this.safeValue (ticker, 'bid', []); + const ask = this.safeValue (ticker, 'ask', []); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((open !== undefined) && (close !== undefined)) { + change = close - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, close) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (bid, 0), + 'bidVolume': this.safeFloat (bid, 1), + 'ask': this.safeFloat (ask, 0), + 'askVolume': this.safeFloat (ask, 1), + 'vwap': undefined, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "symbol":"BTC-PERP", // or "BTC/USDT" + // "open":"9073", + // "close":"9185.75", + // "high":"9185.75", + // "low":"9185.75", + // "volume":"576.8334", + // "ask":["9185.75","15.5863"], + // "bid":["9185.5","0.003"], + // "type":"derivatives", // or "spot" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const marketIds = this.marketIds (symbols); + request['symbol'] = marketIds.join (','); + } + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"QTUM/BTC", + // "open":"0.00016537", + // "close":"0.00019077", + // "high":"0.000192", + // "low":"0.00016537", + // "volume":"846.6", + // "ask":["0.00018698","26.2"], + // "bid":["0.00018408","503.7"], + // "type":"spot" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTickers (data, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "m":"bar", + // "s":"BTC/USDT", + // "data":{ + // "i":"1", + // "ts":1590228000000, + // "o":"9139.59", + // "c":"9131.94", + // "h":"9139.99", + // "l":"9121.71", + // "v":"25.20648" + // } + // } + // + const data = this.safeValue (ohlcv, 'data', {}); + return [ + this.safeInteger (data, 'ts'), + this.safeFloat (data, 'o'), + this.safeFloat (data, 'h'), + this.safeFloat (data, 'l'), + this.safeFloat (data, 'c'), + this.safeFloat (data, 'v'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + // if since and limit are not specified + // the exchange will return just 1 last candle by default + const duration = this.parseTimeframe (timeframe); + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const defaultLimit = this.safeInteger (options, 'limit', 500); + if (since !== undefined) { + request['from'] = since; + if (limit === undefined) { + limit = defaultLimit; + } else { + limit = Math.min (limit, defaultLimit); + } + request['to'] = this.sum (since, limit * duration * 1000, 1); + } else if (limit !== undefined) { + request['n'] = limit; // max 500 + } + const response = await this.publicGetBarhist (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // { + // "m":"bar", + // "s":"BTC/USDT", + // "data":{ + // "i":"1", + // "ts":1590228000000, + // "o":"9139.59", + // "c":"9131.94", + // "h":"9139.99", + // "l":"9121.71", + // "v":"25.20648" + // } + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "p":"9128.5", // price + // "q":"0.0030", // quantity + // "ts":1590229002385, // timestamp + // "bm":false, // if true, the buyer is the market maker, we only use this field to "define the side" of a public trade + // "seqnum":180143985289898554 + // } + // + const timestamp = this.safeInteger (trade, 'ts'); + const price = this.safeFloat2 (trade, 'price', 'p'); + const amount = this.safeFloat (trade, 'q'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const buyerIsMaker = this.safeValue (trade, 'bm', false); + const makerOrTaker = buyerIsMaker ? 'maker' : 'taker'; + const side = buyerIsMaker ? 'buy' : 'sell'; + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': undefined, + 'order': undefined, + 'type': undefined, + 'takerOrMaker': makerOrTaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['n'] = limit; // max 100 + } + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "m":"trades", + // "symbol":"BTC-PERP", + // "data":[ + // {"p":"9128.5","q":"0.0030","ts":1590229002385,"bm":false,"seqnum":180143985289898554}, + // {"p":"9129","q":"0.0030","ts":1590229002642,"bm":false,"seqnum":180143985289898587}, + // {"p":"9129.5","q":"0.0030","ts":1590229021306,"bm":false,"seqnum":180143985289899043} + // ] + // } + // } + // + const records = this.safeValue (response, 'data', []); + const trades = this.safeValue (records, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'PendingNew': 'open', + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'Canceled': 'canceled', + 'Rejected': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": "16e607e2b83a8bXHbAwwoqDo55c166fa", + // "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830", + // "orderType": "Market", + // "symbol": "BTC/USDT", + // "timestamp": 1573576916201 + // } + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "symbol": "BTC/USDT", + // "price": "8131.22", + // "orderQty": "0.00082", + // "orderType": "Market", + // "avgPx": "7392.02", + // "cumFee": "0.005152238", + // "cumFilledQty": "0.00082", + // "errorCode": "", + // "feeAsset": "USDT", + // "lastExecTime": 1575953151764, + // "orderId": "a16eee20b6750866943712zWEDdAjt3", + // "seqNum": 2623469, + // "side": "Buy", + // "status": "Filled", + // "stopPrice": "", + // "execInst": "NULL_VAL" + // } + // + // { + // "ac": "FUTURES", + // "accountId": "testabcdefg", + // "avgPx": "0", + // "cumFee": "0", + // "cumQty": "0", + // "errorCode": "NULL_VAL", + // "execInst": "NULL_VAL", + // "feeAsset": "USDT", + // "lastExecTime": 1584072844085, + // "orderId": "r170d21956dd5450276356bbtcpKa74", + // "orderQty": "1.1499", + // "orderType": "Limit", + // "price": "4000", + // "sendingTime": 1584072841033, + // "seqNum": 24105338, + // "side": "Buy", + // "status": "Canceled", + // "stopPrice": "", + // "symbol": "BTC-PERP" + // }, + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '/'); + let timestamp = this.safeInteger2 (order, 'timestamp', 'sendingTime'); + let lastTradeTimestamp = this.safeInteger (order, 'lastExecTime'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'orderQty'); + const average = this.safeFloat (order, 'avgPx'); + const filled = this.safeFloat2 (order, 'cumFilledQty', 'cumQty'); + let remaining = undefined; + if (filled !== undefined) { + if (filled === 0) { + timestamp = lastTradeTimestamp; + lastTradeTimestamp = undefined; + } + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + let cost = undefined; + if ((average !== undefined) && (filled !== undefined)) { + cost = average * filled; + } + const id = this.safeString (order, 'orderId'); + let clientOrderId = this.safeString (order, 'id'); + if (clientOrderId !== undefined) { + if (clientOrderId.length < 1) { + clientOrderId = undefined; + } + } + const type = this.safeStringLower (order, 'orderType'); + const side = this.safeStringLower (order, 'side'); + const feeCost = this.safeFloat (order, 'cumFee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (order, 'feeAsset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'createOrder', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'symbol': market['id'], + 'time': this.milliseconds (), + 'orderQty': this.amountToPrecision (symbol, amount), + 'orderType': type, // "limit", "market", "stop_market", "stop_limit" + 'side': side, // "buy" or "sell" + // 'orderPrice': this.priceToPrecision (symbol, price), + // 'stopPrice': this.priceToPrecision (symbol, stopPrice), // required for stop orders + // 'postOnly': 'false', // 'false', 'true' + // 'timeInForce': 'GTC', // GTC, IOC, FOK + // 'respInst': 'ACK', // ACK, 'ACCEPT, DONE + }; + if (clientOrderId !== undefined) { + request['id'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'id' ]); + } + if ((type === 'limit') || (type === 'stop_limit')) { + request['orderPrice'] = this.priceToPrecision (symbol, price); + } + if ((type === 'stop_limit') || (type === 'stop_market')) { + const stopPrice = this.safeFloat (params, 'stopPrice'); + if (stopPrice === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a stopPrice parameter for ' + type + ' orders'); + } else { + request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); + params = this.omit (params, 'stopPrice'); + } + } + const response = await this.accountCategoryPostOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "ac": "MARGIN", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "action": "place-order", + // "info": { + // "id": "16e607e2b83a8bXHbAwwoqDo55c166fa", + // "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830", + // "orderType": "Market", + // "symbol": "BTC/USDT", + // "timestamp": 1573576916201 + // }, + // "status": "Ack" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const info = this.safeValue (data, 'info', {}); + return this.parseOrder (info, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'fetchOrder', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'orderId': id, + }; + const response = await this.accountCategoryGetOrderStatus (this.extend (request, params)); + // + // { + // "code": 0, + // "accountCategory": "CASH", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "data": [ + // { + // "symbol": "BTC/USDT", + // "price": "8131.22", + // "orderQty": "0.00082", + // "orderType": "Market", + // "avgPx": "7392.02", + // "cumFee": "0.005152238", + // "cumFilledQty": "0.00082", + // "errorCode": "", + // "feeAsset": "USDT", + // "lastExecTime": 1575953151764, + // "orderId": "a16eee20b6750866943712zWEDdAjt3", + // "seqNum": 2623469, + // "side": "Buy", + // "status": "Filled", + // "stopPrice": "", + // "execInst": "NULL_VAL" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + }; + const response = await this.accountCategoryGetOrderOpen (this.extend (request, params)); + // + // { + // "ac": "CASH", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "code": 0, + // "data": [ + // { + // "avgPx": "0", // Average filled price of the order + // "cumFee": "0", // cumulative fee paid for this order + // "cumFilledQty": "0", // cumulative filled quantity + // "errorCode": "", // error code; could be empty + // "feeAsset": "USDT", // fee asset + // "lastExecTime": 1576019723550, // The last execution time of the order + // "orderId": "s16ef21882ea0866943712034f36d83", // server provided orderId + // "orderQty": "0.0083", // order quantity + // "orderType": "Limit", // order type + // "price": "7105", // order price + // "seqNum": 8193258, // sequence number + // "side": "Buy", // order side + // "status": "New", // order status on matching engine + // "stopPrice": "", // only available for stop market and stop limit orders; otherwise empty + // "symbol": "BTC/USDT", + // "execInst": "NULL_VAL" // execution instruction + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + if (accountCategory === 'futures') { + return this.parseOrders (data, market, since, limit); + } + // a workaround for https://github.com/ccxt/ccxt/issues/7187 + const orders = []; + for (let i = 0; i < data.length; i++) { + const order = this.parseOrder (data[i], market); + orders.push (order); + } + return this.filterBySymbolSinceLimit (orders, symbol, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const defaultAccountCategory = this.safeString (this.options, 'account-category'); + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + // 'category': accountCategory, + // 'symbol': market['id'], + // 'orderType': 'market', // optional, string + // 'side': 'buy', // or 'sell', optional, case insensitive. + // 'status': 'Filled', // "Filled", "Canceled", or "Rejected" + // 'startTime': exchange.milliseconds (), + // 'endTime': exchange.milliseconds (), + // 'page': 1, + // 'pageSize': 100, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const method = this.safeValue (options, 'method', 'accountGroupGetOrderHist'); + if (method === 'accountGroupGetOrderHist') { + if (accountCategory !== undefined) { + request['category'] = accountCategory; + } + } else { + request['account-category'] = accountCategory; + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // accountCategoryGetOrderHistCurrent + // + // { + // "code":0, + // "accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda", + // "ac":"CASH", + // "data":[ + // { + // "seqNum":15561826728, + // "orderId":"a17294d305c0U6491137460bethu7kw9", + // "symbol":"ETH/USDT", + // "orderType":"Limit", + // "lastExecTime":1591635618200, + // "price":"200", + // "orderQty":"0.1", + // "side":"Buy", + // "status":"Canceled", + // "avgPx":"0", + // "cumFilledQty":"0", + // "stopPrice":"", + // "errorCode":"", + // "cumFee":"0", + // "feeAsset":"USDT", + // "execInst":"NULL_VAL" + // } + // ] + // } + // + // accountGroupGetOrderHist + // + // { + // "code": 0, + // "data": { + // "data": [ + // { + // "ac": "FUTURES", + // "accountId": "testabcdefg", + // "avgPx": "0", + // "cumFee": "0", + // "cumQty": "0", + // "errorCode": "NULL_VAL", + // "execInst": "NULL_VAL", + // "feeAsset": "USDT", + // "lastExecTime": 1584072844085, + // "orderId": "r170d21956dd5450276356bbtcpKa74", + // "orderQty": "1.1499", + // "orderType": "Limit", + // "price": "4000", + // "sendingTime": 1584072841033, + // "seqNum": 24105338, + // "side": "Buy", + // "status": "Canceled", + // "stopPrice": "", + // "symbol": "BTC-PERP" + // }, + // ], + // "hasNext": False, + // "limit": 500, + // "page": 1, + // "pageSize": 20 + // } + // } + // + let data = this.safeValue (response, 'data'); + const isArray = Array.isArray (data); + if (!isArray) { + data = this.safeValue (data, 'data', []); + } + return this.parseOrders (data, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'cancelOrder', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'symbol': market['id'], + 'time': this.milliseconds (), + 'id': 'foobar', + }; + if (clientOrderId === undefined) { + request['orderId'] = id; + } else { + request['id'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'id' ]); + } + const response = await this.accountCategoryDeleteOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "ac": "CASH", + // "action": "cancel-order", + // "status": "Ack", + // "info": { + // "id": "wv8QGquoeamhssvQBeHOHGQCGlcBjj23", + // "orderId": "16e6198afb4s8bXHbAwwoqDo2ebc19dc", + // "orderType": "", // could be empty + // "symbol": "ETH/USDT", + // "timestamp": 1573594877822 + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const info = this.safeValue (data, 'info', {}); + return this.parseOrder (info, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'cancelAllOrders', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'time': this.milliseconds (), + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.accountCategoryDeleteOrderAll (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "ac": "CASH", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "action": "cancel-all", + // "info": { + // "id": "2bmYvi7lyTrneMzpcJcf2D7Pe9V1P9wy", + // "orderId": "", + // "orderType": "NULL_VAL", + // "symbol": "", + // "timestamp": 1574118495462 + // }, + // "status": "Ack" + // } + // } + // + return response; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722", + // destTag: "", + // tagType: "", + // tagId: "", + // chainName: "ERC20", + // numConfirmations: 20, + // withdrawalFee: 1, + // nativeScale: 4, + // tips: [] + // } + // + const address = this.safeString (depositAddress, 'address'); + const tagId = this.safeString (depositAddress, 'tagId'); + const tag = this.safeString (depositAddress, tagId); + this.checkAddress (address); + const code = (currency === undefined) ? undefined : currency['code']; + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const chainName = this.safeString (params, 'chainName'); + params = this.omit (params, 'chainName'); + const request = { + 'asset': currency['id'], + }; + const response = await this.privateGetWalletDepositAddress (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "asset":"USDT", + // "assetName":"Tether", + // "address":[ + // { + // "address":"1N22odLHXnLPCjC8kwBJPTayarr9RtPod6", + // "destTag":"", + // "tagType":"", + // "tagId":"", + // "chainName":"Omni", + // "numConfirmations":3, + // "withdrawalFee":4.7, + // "nativeScale":4, + // "tips":[] + // }, + // { + // "address":"0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722", + // "destTag":"", + // "tagType":"", + // "tagId":"", + // "chainName":"ERC20", + // "numConfirmations":20, + // "withdrawalFee":1.0, + // "nativeScale":4, + // "tips":[] + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const addresses = this.safeValue (data, 'address', []); + const numAddresses = addresses.length; + let address = undefined; + if (numAddresses > 1) { + const addressesByChainName = this.indexBy (addresses, 'chainName'); + if (chainName === undefined) { + const chainNames = Object.keys (addressesByChainName); + const chains = chainNames.join (', '); + throw new ArgumentsRequired (this.id + ' fetchDepositAddress returned more than one address, a chainName parameter is required, one of ' + chains); + } + address = this.safeValue (addressesByChainName, chainName, {}); + } else { + // first address + address = this.safeValue (addresses, 0, {}); + } + const result = this.parseDepositAddress (address, currency); + return this.extend (result, { + 'info': response, + }); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'txType': 'deposit', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'txType': 'withdrawal', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'asset': currency['id'], + // 'page': 1, + // 'pageSize': 20, + // 'startTs': this.milliseconds (), + // 'endTs': this.milliseconds (), + // 'txType': undefned, // deposit, withdrawal + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTs'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this.privateGetWalletTransactions (this.extend (request, params)); + // + // { + // code: 0, + // data: { + // data: [ + // { + // requestId: "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB", + // time: 1591606166000, + // asset: "USDT", + // transactionType: "deposit", + // amount: "25", + // commission: "0", + // networkTransactionId: "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce", + // status: "pending", + // numConfirmed: 8, + // numConfirmations: 20, + // destAddress: { address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722" } + // } + // ], + // page: 1, + // pageSize: 20, + // hasNext: false + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const transactions = this.safeValue (data, 'data', []); + return this.parseTransactions (transactions, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'reviewing': 'pending', + 'pending': 'pending', + 'confirmed': 'ok', + 'rejected': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // requestId: "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB", + // time: 1591606166000, + // asset: "USDT", + // transactionType: "deposit", + // amount: "25", + // commission: "0", + // networkTransactionId: "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce", + // status: "pending", + // numConfirmed: 8, + // numConfirmations: 20, + // destAddress: { + // address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722", + // destTag: "..." // for currencies that have it + // } + // } + // + const id = this.safeString (transaction, 'requestId'); + const amount = this.safeFloat (transaction, 'amount'); + const destAddress = this.safeValue (transaction, 'destAddress', {}); + const address = this.safeString (destAddress, 'address'); + const tag = this.safeString (destAddress, 'destTag'); + const txid = this.safeString (transaction, 'networkTransactionId'); + const type = this.safeString (transaction, 'transactionType'); + const timestamp = this.safeInteger (transaction, 'time'); + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const feeCost = this.safeFloat (transaction, 'commission'); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = ''; + let query = params; + const accountCategory = (api === 'accountCategory'); + if (accountCategory || (api === 'accountGroup')) { + url += this.implodeParams ('/{account-group}', params); + query = this.omit (params, 'account-group'); + } + const request = this.implodeParams (path, query); + url += '/api/pro/' + this.version; + if (accountCategory) { + url += this.implodeParams ('/{account-category}', query); + query = this.omit (query, 'account-category'); + } + url += '/' + request; + query = this.omit (query, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + const payload = timestamp + '+' + request; + const hmac = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64'); + headers = { + 'x-auth-key': this.apiKey, + 'x-auth-timestamp': timestamp, + 'x-auth-signature': hmac, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + } + } + url = this.urls['api'] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {'code': 6010, 'message': 'Not enough balance.'} + // {'code': 60060, 'message': 'The order is already filled or canceled.'} + // {"code":2100,"message":"ApiKeyFailure"} + // {"code":300001,"message":"Price is too low from market price.","reason":"INVALID_PRICE","accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda","ac":"CASH","action":"place-order","status":"Err","info":{"symbol":"BTC/USDT"}} + // + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + const error = (code !== undefined) && (code !== '0'); + if (error || (message !== undefined)) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],38:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { AuthenticationError, BadRequest, DDoSProtection, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitmex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitmex', + 'name': 'BitMEX', + 'countries': [ 'SC' ], // Seychelles + 'version': 'v1', + 'userAgent': undefined, + 'rateLimit': 2000, + 'pro': true, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': 'emulated', + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '1h': '1h', + '1d': '1d', + }, + 'urls': { + 'test': { + 'public': 'https://testnet.bitmex.com', + 'private': 'https://testnet.bitmex.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/27766319-f653c6e6-5ed4-11e7-933d-f0bc3699ae8f.jpg', + 'api': { + 'public': 'https://www.bitmex.com', + 'private': 'https://www.bitmex.com', + }, + 'www': 'https://www.bitmex.com', + 'doc': [ + 'https://www.bitmex.com/app/apiOverview', + 'https://github.com/BitMEX/api-connectors/tree/master/official-http', + ], + 'fees': 'https://www.bitmex.com/app/fees', + 'referral': 'https://www.bitmex.com/register/upZpOX', + }, + 'api': { + 'public': { + 'get': [ + 'announcement', + 'announcement/urgent', + 'funding', + 'instrument', + 'instrument/active', + 'instrument/activeAndIndices', + 'instrument/activeIntervals', + 'instrument/compositeIndex', + 'instrument/indices', + 'insurance', + 'leaderboard', + 'liquidation', + 'orderBook', + 'orderBook/L2', + 'quote', + 'quote/bucketed', + 'schema', + 'schema/websocketHelp', + 'settlement', + 'stats', + 'stats/history', + 'trade', + 'trade/bucketed', + ], + }, + 'private': { + 'get': [ + 'apiKey', + 'chat', + 'chat/channels', + 'chat/connected', + 'execution', + 'execution/tradeHistory', + 'notification', + 'order', + 'position', + 'user', + 'user/affiliateStatus', + 'user/checkReferralCode', + 'user/commission', + 'user/depositAddress', + 'user/executionHistory', + 'user/margin', + 'user/minWithdrawalFee', + 'user/wallet', + 'user/walletHistory', + 'user/walletSummary', + ], + 'post': [ + 'apiKey', + 'apiKey/disable', + 'apiKey/enable', + 'chat', + 'order', + 'order/bulk', + 'order/cancelAllAfter', + 'order/closePosition', + 'position/isolate', + 'position/leverage', + 'position/riskLimit', + 'position/transferMargin', + 'user/cancelWithdrawal', + 'user/confirmEmail', + 'user/confirmEnableTFA', + 'user/confirmWithdrawal', + 'user/disableTFA', + 'user/logout', + 'user/logoutAll', + 'user/preferences', + 'user/requestEnableTFA', + 'user/requestWithdrawal', + ], + 'put': [ + 'order', + 'order/bulk', + 'user', + ], + 'delete': [ + 'apiKey', + 'order', + 'order/all', + ], + }, + }, + 'exceptions': { + 'exact': { + 'Invalid API Key.': AuthenticationError, + 'This key is disabled.': PermissionDenied, + 'Access Denied': PermissionDenied, + 'Duplicate clOrdID': InvalidOrder, + 'orderQty is invalid': InvalidOrder, + 'Invalid price': InvalidOrder, + 'Invalid stopPx for ordType': InvalidOrder, + }, + 'broad': { + 'Signature not valid': AuthenticationError, + 'overloaded': ExchangeNotAvailable, + 'Account has insufficient Available Balance': InsufficientFunds, + 'Service unavailable': ExchangeNotAvailable, // {"error":{"message":"Service unavailable","name":"HTTPError"}} + 'Server Error': ExchangeError, // {"error":{"message":"Server Error","name":"HTTPError"}} + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + // https://blog.bitmex.com/api_announcement/deprecation-of-api-nonce-header/ + // https://github.com/ccxt/ccxt/issues/4789 + 'api-expires': 5, // in seconds + 'fetchOHLCVOpenTimestamp': true, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInstrumentActiveAndIndices (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const active = (market['state'] !== 'Unlisted'); + const id = market['symbol']; + const baseId = market['underlying']; + const quoteId = market['quoteCurrency']; + const basequote = baseId + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const swap = (id === basequote); + // 'positionCurrency' may be empty ("", as Bitmex currently returns for ETHUSD) + // so let's take the quote currency first and then adjust if needed + const positionId = this.safeString2 (market, 'positionCurrency', 'quoteCurrency'); + let type = undefined; + let future = false; + let prediction = false; + const position = this.safeCurrencyCode (positionId); + let symbol = id; + if (swap) { + type = 'swap'; + symbol = base + '/' + quote; + } else if (id.indexOf ('B_') >= 0) { + prediction = true; + type = 'prediction'; + } else { + future = true; + type = 'future'; + } + const precision = { + 'amount': undefined, + 'price': undefined, + }; + const lotSize = this.safeFloat (market, 'lotSize'); + const tickSize = this.safeFloat (market, 'tickSize'); + if (lotSize !== undefined) { + precision['amount'] = lotSize; + } + if (tickSize !== undefined) { + precision['price'] = tickSize; + } + const limits = { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': tickSize, + 'max': this.safeFloat (market, 'maxPrice'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + const limitField = (position === quote) ? 'cost' : 'amount'; + limits[limitField] = { + 'min': lotSize, + 'max': this.safeFloat (market, 'maxOrderQty'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'taker': this.safeFloat (market, 'takerFee'), + 'maker': this.safeFloat (market, 'makerFee'), + 'type': type, + 'spot': false, + 'swap': swap, + 'future': future, + 'prediction': prediction, + 'info': market, + }); + } + return result; + } + + parseBalanceResponse (response) { + // + // [ + // { + // "account":1455728, + // "currency":"XBt", + // "riskLimit":1000000000000, + // "prevState":"", + // "state":"", + // "action":"", + // "amount":263542, + // "pendingCredit":0, + // "pendingDebit":0, + // "confirmedDebit":0, + // "prevRealisedPnl":0, + // "prevUnrealisedPnl":0, + // "grossComm":0, + // "grossOpenCost":0, + // "grossOpenPremium":0, + // "grossExecCost":0, + // "grossMarkValue":0, + // "riskValue":0, + // "taxableMargin":0, + // "initMargin":0, + // "maintMargin":0, + // "sessionMargin":0, + // "targetExcessMargin":0, + // "varMargin":0, + // "realisedPnl":0, + // "unrealisedPnl":0, + // "indicativeTax":0, + // "unrealisedProfit":0, + // "syntheticMargin":null, + // "walletBalance":263542, + // "marginBalance":263542, + // "marginBalancePcnt":1, + // "marginLeverage":0, + // "marginUsedPcnt":0, + // "excessMargin":263542, + // "excessMarginPcnt":1, + // "availableMargin":263542, + // "withdrawableMargin":263542, + // "timestamp":"2020-08-03T12:01:01.246Z", + // "grossLastValue":0, + // "commission":null + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + let free = this.safeFloat (balance, 'availableMargin'); + let total = this.safeFloat (balance, 'marginBalance'); + if (code === 'BTC') { + if (free !== undefined) { + free /= 100000000; + } + if (total !== undefined) { + total /= 100000000; + } + } + account['free'] = free; + account['total'] = total; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'currency': 'all', + }; + const response = await this.privateGetUserMargin (this.extend (request, params)); + // + // [ + // { + // "account":1455728, + // "currency":"XBt", + // "riskLimit":1000000000000, + // "prevState":"", + // "state":"", + // "action":"", + // "amount":263542, + // "pendingCredit":0, + // "pendingDebit":0, + // "confirmedDebit":0, + // "prevRealisedPnl":0, + // "prevUnrealisedPnl":0, + // "grossComm":0, + // "grossOpenCost":0, + // "grossOpenPremium":0, + // "grossExecCost":0, + // "grossMarkValue":0, + // "riskValue":0, + // "taxableMargin":0, + // "initMargin":0, + // "maintMargin":0, + // "sessionMargin":0, + // "targetExcessMargin":0, + // "varMargin":0, + // "realisedPnl":0, + // "unrealisedPnl":0, + // "indicativeTax":0, + // "unrealisedProfit":0, + // "syntheticMargin":null, + // "walletBalance":263542, + // "marginBalance":263542, + // "marginBalancePcnt":1, + // "marginLeverage":0, + // "marginUsedPcnt":0, + // "excessMargin":263542, + // "excessMarginPcnt":1, + // "availableMargin":263542, + // "withdrawableMargin":263542, + // "timestamp":"2020-08-03T12:01:01.246Z", + // "grossLastValue":0, + // "commission":null + // } + // ] + // + return this.parseBalanceResponse (response); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetOrderBookL2 (this.extend (request, params)); + const result = { + 'bids': [], + 'asks': [], + 'timestamp': undefined, + 'datetime': undefined, + 'nonce': undefined, + }; + for (let i = 0; i < response.length; i++) { + const order = response[i]; + const side = (order['side'] === 'Sell') ? 'asks' : 'bids'; + const amount = this.safeFloat (order, 'size'); + const price = this.safeFloat (order, 'price'); + // https://github.com/ccxt/ccxt/issues/4926 + // https://github.com/ccxt/ccxt/issues/4927 + // the exchange sometimes returns null price in the orderbook + if (price !== undefined) { + result[side].push ([ price, amount ]); + } + } + result['bids'] = this.sortBy (result['bids'], 0, true); + result['asks'] = this.sortBy (result['asks'], 0); + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + const filter = { + 'filter': { + 'orderID': id, + }, + }; + const response = await this.fetchOrders (symbol, undefined, undefined, this.deepExtend (filter, params)); + const numResults = response.length; + if (numResults === 1) { + return response[0]; + } + throw new OrderNotFound (this.id + ': The order ' + id + ' not found.'); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['startTime'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['count'] = limit; + } + request = this.deepExtend (request, params); + // why the hassle? urlencode in python is kinda broken for nested dicts. + // E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}" + // Bitmex doesn't like that. Hence resorting to this hack. + if ('filter' in request) { + request['filter'] = this.json (request['filter']); + } + const response = await this.privateGetOrder (request); + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'filter': { + 'open': true, + }, + }; + return await this.fetchOrders (symbol, since, limit, this.deepExtend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // Bitmex barfs if you set 'open': false in the filter... + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['startTime'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['count'] = limit; + } + request = this.deepExtend (request, params); + // why the hassle? urlencode in python is kinda broken for nested dicts. + // E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}" + // Bitmex doesn't like that. Hence resorting to this hack. + if ('filter' in request) { + request['filter'] = this.json (request['filter']); + } + const response = await this.privateGetExecutionTradeHistory (request); + // + // [ + // { + // "execID": "string", + // "orderID": "string", + // "clOrdID": "string", + // "clOrdLinkID": "string", + // "account": 0, + // "symbol": "string", + // "side": "string", + // "lastQty": 0, + // "lastPx": 0, + // "underlyingLastPx": 0, + // "lastMkt": "string", + // "lastLiquidityInd": "string", + // "simpleOrderQty": 0, + // "orderQty": 0, + // "price": 0, + // "displayQty": 0, + // "stopPx": 0, + // "pegOffsetValue": 0, + // "pegPriceType": "string", + // "currency": "string", + // "settlCurrency": "string", + // "execType": "string", + // "ordType": "string", + // "timeInForce": "string", + // "execInst": "string", + // "contingencyType": "string", + // "exDestination": "string", + // "ordStatus": "string", + // "triggered": "string", + // "workingIndicator": true, + // "ordRejReason": "string", + // "simpleLeavesQty": 0, + // "leavesQty": 0, + // "simpleCumQty": 0, + // "cumQty": 0, + // "avgPx": 0, + // "commission": 0, + // "tradePublishIndicator": "string", + // "multiLegReportingType": "string", + // "text": "string", + // "trdMatchID": "string", + // "execCost": 0, + // "execComm": 0, + // "homeNotional": 0, + // "foreignNotional": 0, + // "transactTime": "2019-03-05T12:47:02.762Z", + // "timestamp": "2019-03-05T12:47:02.762Z" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseLedgerEntryType (type) { + const types = { + 'Withdrawal': 'transaction', + 'RealisedPNL': 'margin', + 'UnrealisedPNL': 'margin', + 'Deposit': 'transaction', + 'Transfer': 'transfer', + 'AffiliatePayout': 'referral', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // transactID: "69573da3-7744-5467-3207-89fd6efe7a47", + // account: 24321, + // currency: "XBt", + // transactType: "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ... + // amount: -1000000, + // fee: 300000, + // transactStatus: "Completed", // "Canceled", ... + // address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9", + // tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA", + // text: "", + // transactTime: "2017-03-21T20:05:14.388Z", + // walletBalance: 0, // balance after + // marginBalance: null, + // timestamp: "2017-03-22T13:09:23.514Z" + // } + // + // ButMEX returns the unrealized pnl from the wallet history endpoint. + // The unrealized pnl transaction has an empty timestamp. + // It is not related to historical pnl it has status set to "Pending". + // Therefore it's not a part of the history at all. + // https://github.com/ccxt/ccxt/issues/6047 + // + // { + // "transactID":"00000000-0000-0000-0000-000000000000", + // "account":121210, + // "currency":"XBt", + // "transactType":"UnrealisedPNL", + // "amount":-5508, + // "fee":0, + // "transactStatus":"Pending", + // "address":"XBTUSD", + // "tx":"", + // "text":"", + // "transactTime":null, # ←---------------------------- null + // "walletBalance":139198767, + // "marginBalance":139193259, + // "timestamp":null # ←---------------------------- null + // } + // + const id = this.safeString (item, 'transactID'); + const account = this.safeString (item, 'account'); + const referenceId = this.safeString (item, 'tx'); + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'transactType')); + const currencyId = this.safeString (item, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + let amount = this.safeFloat (item, 'amount'); + if (amount !== undefined) { + amount = amount / 100000000; + } + let timestamp = this.parse8601 (this.safeString (item, 'transactTime')); + if (timestamp === undefined) { + // https://github.com/ccxt/ccxt/issues/6047 + // set the timestamp to zero, 1970 Jan 1 00:00:00 + // for unrealized pnl and other transactions without a timestamp + timestamp = 0; // see comments above + } + let feeCost = this.safeFloat (item, 'fee', 0); + if (feeCost !== undefined) { + feeCost = feeCost / 100000000; + } + const fee = { + 'cost': feeCost, + 'currency': code, + }; + let after = this.safeFloat (item, 'walletBalance'); + if (after !== undefined) { + after = after / 100000000; + } + const before = this.sum (after, -amount); + let direction = undefined; + if (amount < 0) { + direction = 'out'; + amount = Math.abs (amount); + } else { + direction = 'in'; + } + const status = this.parseTransactionStatus (this.safeString (item, 'transactStatus')); + return { + 'id': id, + 'info': item, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = { + // 'start': 123, + }; + // + // if (since !== undefined) { + // // date-based pagination not supported + // } + // + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetUserWalletHistory (this.extend (request, params)); + // + // [ + // { + // transactID: "69573da3-7744-5467-3207-89fd6efe7a47", + // account: 24321, + // currency: "XBt", + // transactType: "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ... + // amount: -1000000, + // fee: 300000, + // transactStatus: "Completed", // "Canceled", ... + // address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9", + // tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA", + // text: "", + // transactTime: "2017-03-21T20:05:14.388Z", + // walletBalance: 0, // balance after + // marginBalance: null, + // timestamp: "2017-03-22T13:09:23.514Z" + // } + // ] + // + return this.parseLedger (response, currency, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'start': 123, + }; + // + // if (since !== undefined) { + // // date-based pagination not supported + // } + // + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetUserWalletHistory (this.extend (request, params)); + const transactions = this.filterByArray (response, 'transactType', [ 'Withdrawal', 'Deposit' ], false); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (transactions, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'Canceled': 'canceled', + 'Completed': 'ok', + 'Pending': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // 'transactID': 'ffe699c2-95ee-4c13-91f9-0faf41daec25', + // 'account': 123456, + // 'currency': 'XBt', + // 'transactType': 'Withdrawal', + // 'amount': -100100000, + // 'fee': 100000, + // 'transactStatus': 'Completed', + // 'address': '385cR5DM96n1HvBDMzLHPYcw89fZAXULJP', + // 'tx': '3BMEXabcdefghijklmnopqrstuvwxyz123', + // 'text': '', + // 'transactTime': '2019-01-02T01:00:00.000Z', + // 'walletBalance': 99900000, + // 'marginBalance': None, + // 'timestamp': '2019-01-02T13:00:00.000Z' + // } + // + const id = this.safeString (transaction, 'transactID'); + // For deposits, transactTime == timestamp + // For withdrawals, transactTime is submission, timestamp is processed + const transactTime = this.parse8601 (this.safeString (transaction, 'transactTime')); + const timestamp = this.parse8601 (this.safeString (transaction, 'timestamp')); + const type = this.safeStringLower (transaction, 'transactType'); + // Deposits have no from address or to address, withdrawals have both + let address = undefined; + let addressFrom = undefined; + let addressTo = undefined; + if (type === 'withdrawal') { + address = this.safeString (transaction, 'address'); + addressFrom = this.safeString (transaction, 'tx'); + addressTo = address; + } + let amount = this.safeInteger (transaction, 'amount'); + if (amount !== undefined) { + amount = Math.abs (amount) / 10000000; + } + let feeCost = this.safeInteger (transaction, 'fee'); + if (feeCost !== undefined) { + feeCost = feeCost / 10000000; + } + const fee = { + 'cost': feeCost, + 'currency': 'BTC', + }; + let status = this.safeString (transaction, 'transactStatus'); + if (status !== undefined) { + status = this.parseTransactionStatus (status); + } + return { + 'info': transaction, + 'id': id, + 'txid': undefined, + 'timestamp': transactTime, + 'datetime': this.iso8601 (transactTime), + 'addressFrom': addressFrom, + 'address': address, + 'addressTo': addressTo, + 'tagFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'type': type, + 'amount': amount, + // BTC is the only currency on Bitmex + 'currency': 'BTC', + 'status': status, + 'updated': timestamp, + 'comment': undefined, + 'fee': fee, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (!market['active']) { + throw new ExchangeError (this.id + ': symbol ' + symbol + ' is delisted'); + } + const tickers = await this.fetchTickers ([ symbol ], params); + const ticker = this.safeValue (tickers, symbol); + if (ticker === undefined) { + throw new ExchangeError (this.id + ' ticker symbol ' + symbol + ' not found'); + } + return ticker; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetInstrumentActiveAndIndices (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = this.safeString (ticker, 'symbol'); + if (symbol !== undefined) { + result[symbol] = ticker; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // + // { symbol: "ETHH19", + // rootSymbol: "ETH", + // state: "Open", + // typ: "FFCCSX", + // listing: "2018-12-17T04:00:00.000Z", + // front: "2019-02-22T12:00:00.000Z", + // expiry: "2019-03-29T12:00:00.000Z", + // settle: "2019-03-29T12:00:00.000Z", + // relistInterval: null, + // inverseLeg: "", + // sellLeg: "", + // buyLeg: "", + // optionStrikePcnt: null, + // optionStrikeRound: null, + // optionStrikePrice: null, + // optionMultiplier: null, + // positionCurrency: "ETH", + // underlying: "ETH", + // quoteCurrency: "XBT", + // underlyingSymbol: "ETHXBT=", + // reference: "BMEX", + // referenceSymbol: ".BETHXBT30M", + // calcInterval: null, + // publishInterval: null, + // publishTime: null, + // maxOrderQty: 100000000, + // maxPrice: 10, + // lotSize: 1, + // tickSize: 0.00001, + // multiplier: 100000000, + // settlCurrency: "XBt", + // underlyingToPositionMultiplier: 1, + // underlyingToSettleMultiplier: null, + // quoteToSettleMultiplier: 100000000, + // isQuanto: false, + // isInverse: false, + // initMargin: 0.02, + // maintMargin: 0.01, + // riskLimit: 5000000000, + // riskStep: 5000000000, + // limit: null, + // capped: false, + // taxed: true, + // deleverage: true, + // makerFee: -0.0005, + // takerFee: 0.0025, + // settlementFee: 0, + // insuranceFee: 0, + // fundingBaseSymbol: "", + // fundingQuoteSymbol: "", + // fundingPremiumSymbol: "", + // fundingTimestamp: null, + // fundingInterval: null, + // fundingRate: null, + // indicativeFundingRate: null, + // rebalanceTimestamp: null, + // rebalanceInterval: null, + // openingTimestamp: "2019-02-13T08:00:00.000Z", + // closingTimestamp: "2019-02-13T09:00:00.000Z", + // sessionInterval: "2000-01-01T01:00:00.000Z", + // prevClosePrice: 0.03347, + // limitDownPrice: null, + // limitUpPrice: null, + // bankruptLimitDownPrice: null, + // bankruptLimitUpPrice: null, + // prevTotalVolume: 1386531, + // totalVolume: 1387062, + // volume: 531, + // volume24h: 17118, + // prevTotalTurnover: 4741294246000, + // totalTurnover: 4743103466000, + // turnover: 1809220000, + // turnover24h: 57919845000, + // homeNotional24h: 17118, + // foreignNotional24h: 579.19845, + // prevPrice24h: 0.03349, + // vwap: 0.03383564, + // highPrice: 0.03458, + // lowPrice: 0.03329, + // lastPrice: 0.03406, + // lastPriceProtected: 0.03406, + // lastTickDirection: "ZeroMinusTick", + // lastChangePcnt: 0.017, + // bidPrice: 0.03406, + // midPrice: 0.034065, + // askPrice: 0.03407, + // impactBidPrice: 0.03406, + // impactMidPrice: 0.034065, + // impactAskPrice: 0.03407, + // hasLiquidity: true, + // openInterest: 83679, + // openValue: 285010674000, + // fairMethod: "ImpactMidPrice", + // fairBasisRate: 0, + // fairBasis: 0, + // fairPrice: 0.03406, + // markMethod: "FairPrice", + // markPrice: 0.03406, + // indicativeTaxRate: 0, + // indicativeSettlePrice: 0.03406, + // optionUnderlyingPrice: null, + // settledPrice: null, + // timestamp: "2019-02-13T08:40:30.000Z", + // } + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'symbol'); + market = this.safeValue (this.markets_by_id, marketId, market); + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const open = this.safeFloat (ticker, 'prevPrice24h'); + const last = this.safeFloat (ticker, 'lastPrice'); + let change = undefined; + let percentage = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + if (open > 0) { + percentage = change / open * 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': undefined, + 'vwap': this.safeFloat (ticker, 'vwap'), + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': this.sum (open, last) / 2, + 'baseVolume': this.safeFloat (ticker, 'homeNotional24h'), + 'quoteVolume': this.safeFloat (ticker, 'foreignNotional24h'), + 'info': ticker, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "timestamp":"2015-09-25T13:38:00.000Z", + // "symbol":"XBTUSD", + // "open":237.45, + // "high":237.45, + // "low":237.45, + // "close":237.45, + // "trades":0, + // "volume":0, + // "vwap":null, + // "lastSize":null, + // "turnover":0, + // "homeNotional":0, + // "foreignNotional":0 + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // send JSON key/value pairs, such as {"key": "value"} + // filter by individual fields and do advanced queries on timestamps + // let filter = { 'key': 'value' }; + // send a bare series (e.g. XBU) to nearest expiring contract in that series + // you can also send a timeframe, e.g. XBU:monthly + // timeframes: daily, weekly, monthly, quarterly, and biquarterly + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'binSize': this.timeframes[timeframe], + 'partial': true, // true == include yet-incomplete current bins + // 'filter': filter, // filter by individual fields and do advanced queries + // 'columns': [], // will return all columns if omitted + // 'start': 0, // starting point for results (wtf?) + // 'reverse': false, // true == newest first + // 'endTime': '', // ending date filter for results + }; + if (limit !== undefined) { + request['count'] = limit; // default 100, max 500 + } + const duration = this.parseTimeframe (timeframe) * 1000; + const fetchOHLCVOpenTimestamp = this.safeValue (this.options, 'fetchOHLCVOpenTimestamp', true); + // if since is not set, they will return candles starting from 2017-01-01 + if (since !== undefined) { + let timestamp = since; + if (fetchOHLCVOpenTimestamp) { + timestamp = this.sum (timestamp, duration); + } + const ymdhms = this.ymdhms (timestamp); + request['startTime'] = ymdhms; // starting date filter for results + } else { + request['reverse'] = true; + } + const response = await this.publicGetTradeBucketed (this.extend (request, params)); + // + // [ + // {"timestamp":"2015-09-25T13:38:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}, + // {"timestamp":"2015-09-25T13:39:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}, + // {"timestamp":"2015-09-25T13:40:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0} + // ] + // + const result = this.parseOHLCVs (response, market, timeframe, since, limit); + if (fetchOHLCVOpenTimestamp) { + // bitmex returns the candle's close timestamp - https://github.com/ccxt/ccxt/issues/4446 + // we can emulate the open timestamp by shifting all the timestamps one place + // so the previous close becomes the current open, and we drop the first candle + for (let i = 0; i < result.length; i++) { + result[i][0] = result[i][0] - duration; + } + } + return result; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // timestamp: '2018-08-28T00:00:02.735Z', + // symbol: 'XBTUSD', + // side: 'Buy', + // size: 2000, + // price: 6906.5, + // tickDirection: 'PlusTick', + // trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8', + // grossValue: 28958000, + // homeNotional: 0.28958, + // foreignNotional: 2000 + // } + // + // fetchMyTrades (private) + // + // { + // "execID": "string", + // "orderID": "string", + // "clOrdID": "string", + // "clOrdLinkID": "string", + // "account": 0, + // "symbol": "string", + // "side": "string", + // "lastQty": 0, + // "lastPx": 0, + // "underlyingLastPx": 0, + // "lastMkt": "string", + // "lastLiquidityInd": "string", + // "simpleOrderQty": 0, + // "orderQty": 0, + // "price": 0, + // "displayQty": 0, + // "stopPx": 0, + // "pegOffsetValue": 0, + // "pegPriceType": "string", + // "currency": "string", + // "settlCurrency": "string", + // "execType": "string", + // "ordType": "string", + // "timeInForce": "string", + // "execInst": "string", + // "contingencyType": "string", + // "exDestination": "string", + // "ordStatus": "string", + // "triggered": "string", + // "workingIndicator": true, + // "ordRejReason": "string", + // "simpleLeavesQty": 0, + // "leavesQty": 0, + // "simpleCumQty": 0, + // "cumQty": 0, + // "avgPx": 0, + // "commission": 0, + // "tradePublishIndicator": "string", + // "multiLegReportingType": "string", + // "text": "string", + // "trdMatchID": "string", + // "execCost": 0, + // "execComm": 0, + // "homeNotional": 0, + // "foreignNotional": 0, + // "transactTime": "2019-03-05T12:47:02.762Z", + // "timestamp": "2019-03-05T12:47:02.762Z" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const price = this.safeFloat2 (trade, 'avgPx', 'price'); + const amount = this.safeFloat2 (trade, 'size', 'lastQty'); + const id = this.safeString (trade, 'trdMatchID'); + const order = this.safeString (trade, 'orderID'); + const side = this.safeStringLower (trade, 'side'); + // price * amount doesn't work for all symbols (e.g. XBT, ETH) + let cost = this.safeFloat (trade, 'execCost'); + if (cost !== undefined) { + cost = Math.abs (cost) / 100000000; + } + let fee = undefined; + if ('execComm' in trade) { + let feeCost = this.safeFloat (trade, 'execComm'); + feeCost = feeCost / 100000000; + const currencyId = this.safeString (trade, 'settlCurrency'); + const feeCurrency = this.safeCurrencyCode (currencyId); + const feeRate = this.safeFloat (trade, 'commission'); + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }; + } + let takerOrMaker = undefined; + if (fee !== undefined) { + takerOrMaker = (fee['cost'] < 0) ? 'maker' : 'taker'; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const type = this.safeStringLower (trade, 'ordType'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': order, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'cost': cost, + 'amount': amount, + 'fee': fee, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'DoneForDay': 'open', + 'Canceled': 'canceled', + 'PendingCancel': 'open', + 'PendingNew': 'open', + 'Rejected': 'rejected', + 'Expired': 'expired', + 'Stopped': 'open', + 'Untriggered': 'open', + 'Triggered': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'Day': 'Day', + 'GoodTillCancel': 'GTC', + 'ImmediateOrCancel': 'IOC', + 'FillOrKill': 'FOK', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'transactTime')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'orderQty'); + const filled = this.safeFloat (order, 'cumQty', 0.0); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = Math.max (amount - filled, 0.0); + } + } + const average = this.safeFloat (order, 'avgPx'); + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = average * filled; + } else if (price !== undefined) { + cost = price * filled; + } + } + const id = this.safeString (order, 'orderID'); + const type = this.safeStringLower (order, 'ordType'); + const side = this.safeStringLower (order, 'side'); + const clientOrderId = this.safeString (order, 'clOrdID'); + const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); + const stopPrice = this.safeFloat (order, 'stopPx'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startTime'] = this.iso8601 (since); + } else { + // by default reverse=false, i.e. trades are fetched since the time of market inception (year 2015 for XBTUSD) + request['reverse'] = true; + } + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.publicGetTrade (this.extend (request, params)); + // + // [ + // { + // timestamp: '2018-08-28T00:00:02.735Z', + // symbol: 'XBTUSD', + // side: 'Buy', + // size: 2000, + // price: 6906.5, + // tickDirection: 'PlusTick', + // trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8', + // grossValue: 28958000, + // homeNotional: 0.28958, + // foreignNotional: 2000 + // }, + // { + // timestamp: '2018-08-28T00:00:03.778Z', + // symbol: 'XBTUSD', + // side: 'Sell', + // size: 1000, + // price: 6906, + // tickDirection: 'MinusTick', + // trdMatchID: '0d4f1682-5270-a800-569b-4a0eb92db97c', + // grossValue: 14480000, + // homeNotional: 0.1448, + // foreignNotional: 1000 + // }, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderType = this.capitalize (type); + const request = { + 'symbol': market['id'], + 'side': this.capitalize (side), + 'orderQty': amount, + 'ordType': orderType, + }; + if (price !== undefined) { + if (orderType === 'Stop') { + const stopPrice = this.safeFloat2 (params, 'stopPx', 'stopPrice'); + request['stopPx'] = stopPrice; + params = this.omit (params, [ 'stopPx', 'stopPrice' ]); + } else { + request['price'] = price; + } + } + const clientOrderId = this.safeString2 (params, 'clOrdID', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + params = this.omit (params, [ 'clOrdID', 'clientOrderId' ]); + } + const response = await this.privatePostOrder (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const origClOrdID = this.safeString2 (params, 'origClOrdID', 'clientOrderId'); + if (origClOrdID !== undefined) { + request['origClOrdID'] = origClOrdID; + const clientOrderId = this.safeString (params, 'clOrdID', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + } + params = this.omit (params, [ 'origClOrdID', 'clOrdID', 'clientOrderId' ]); + } else { + request['orderID'] = id; + } + if (amount !== undefined) { + request['orderQty'] = amount; + } + if (price !== undefined) { + request['price'] = price; + } + const response = await this.privatePutOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // https://github.com/ccxt/ccxt/issues/6507 + const clientOrderId = this.safeString2 (params, 'clOrdID', 'clientOrderId'); + const request = {}; + if (clientOrderId === undefined) { + request['orderID'] = id; + } else { + request['clOrdID'] = clientOrderId; + params = this.omit (params, [ 'clOrdID', 'clientOrderId' ]); + } + const response = await this.privateDeleteOrder (this.extend (request, params)); + const order = this.safeValue (response, 0, {}); + const error = this.safeString (order, 'error'); + if (error !== undefined) { + if (error.indexOf ('Unable to cancel order due to existing state') >= 0) { + throw new OrderNotFound (this.id + ' cancelOrder() failed: ' + error); + } + } + return this.parseOrder (order); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateDeleteOrderAll (this.extend (request, params)); + // + // [ + // { + // "orderID": "string", + // "clOrdID": "string", + // "clOrdLinkID": "string", + // "account": 0, + // "symbol": "string", + // "side": "string", + // "simpleOrderQty": 0, + // "orderQty": 0, + // "price": 0, + // "displayQty": 0, + // "stopPx": 0, + // "pegOffsetValue": 0, + // "pegPriceType": "string", + // "currency": "string", + // "settlCurrency": "string", + // "ordType": "string", + // "timeInForce": "string", + // "execInst": "string", + // "contingencyType": "string", + // "exDestination": "string", + // "ordStatus": "string", + // "triggered": "string", + // "workingIndicator": true, + // "ordRejReason": "string", + // "simpleLeavesQty": 0, + // "leavesQty": 0, + // "simpleCumQty": 0, + // "cumQty": 0, + // "avgPx": 0, + // "multiLegReportingType": "string", + // "text": "string", + // "transactTime": "2020-06-01T09:36:35.290Z", + // "timestamp": "2020-06-01T09:36:35.290Z" + // } + // ] + // + return this.parseOrders (response, market); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetPosition (params); + // [ + // { + // "account": 0, + // "symbol": "string", + // "currency": "string", + // "underlying": "string", + // "quoteCurrency": "string", + // "commission": 0, + // "initMarginReq": 0, + // "maintMarginReq": 0, + // "riskLimit": 0, + // "leverage": 0, + // "crossMargin": true, + // "deleveragePercentile": 0, + // "rebalancedPnl": 0, + // "prevRealisedPnl": 0, + // "prevUnrealisedPnl": 0, + // "prevClosePrice": 0, + // "openingTimestamp": "2020-11-09T06:53:59.892Z", + // "openingQty": 0, + // "openingCost": 0, + // "openingComm": 0, + // "openOrderBuyQty": 0, + // "openOrderBuyCost": 0, + // "openOrderBuyPremium": 0, + // "openOrderSellQty": 0, + // "openOrderSellCost": 0, + // "openOrderSellPremium": 0, + // "execBuyQty": 0, + // "execBuyCost": 0, + // "execSellQty": 0, + // "execSellCost": 0, + // "execQty": 0, + // "execCost": 0, + // "execComm": 0, + // "currentTimestamp": "2020-11-09T06:53:59.893Z", + // "currentQty": 0, + // "currentCost": 0, + // "currentComm": 0, + // "realisedCost": 0, + // "unrealisedCost": 0, + // "grossOpenCost": 0, + // "grossOpenPremium": 0, + // "grossExecCost": 0, + // "isOpen": true, + // "markPrice": 0, + // "markValue": 0, + // "riskValue": 0, + // "homeNotional": 0, + // "foreignNotional": 0, + // "posState": "string", + // "posCost": 0, + // "posCost2": 0, + // "posCross": 0, + // "posInit": 0, + // "posComm": 0, + // "posLoss": 0, + // "posMargin": 0, + // "posMaint": 0, + // "posAllowance": 0, + // "taxableMargin": 0, + // "initMargin": 0, + // "maintMargin": 0, + // "sessionMargin": 0, + // "targetExcessMargin": 0, + // "varMargin": 0, + // "realisedGrossPnl": 0, + // "realisedTax": 0, + // "realisedPnl": 0, + // "unrealisedGrossPnl": 0, + // "longBankrupt": 0, + // "shortBankrupt": 0, + // "taxBase": 0, + // "indicativeTaxRate": 0, + // "indicativeTax": 0, + // "unrealisedTax": 0, + // "unrealisedPnl": 0, + // "unrealisedPnlPcnt": 0, + // "unrealisedRoePcnt": 0, + // "simpleQty": 0, + // "simpleCost": 0, + // "simpleValue": 0, + // "simplePnl": 0, + // "simplePnlPcnt": 0, + // "avgCostPrice": 0, + // "avgEntryPrice": 0, + // "breakEvenPrice": 0, + // "marginCallPrice": 0, + // "liquidationPrice": 0, + // "bankruptPrice": 0, + // "timestamp": "2020-11-09T06:53:59.894Z", + // "lastPrice": 0, + // "lastValue": 0 + // } + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + isFiat (currency) { + if (currency === 'EUR') { + return true; + } + if (currency === 'PLN') { + return true; + } + return false; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + // let currency = this.currency (code); + if (code !== 'BTC') { + throw new ExchangeError (this.id + ' supoprts BTC withdrawals only, other currencies coming soon...'); + } + const request = { + 'currency': 'XBt', // temporarily + 'amount': amount, + 'address': address, + // 'otpToken': '123456', // requires if two-factor auth (OTP) is enabled + // 'fee': 0.001, // bitcoin network fee + }; + const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'transactID'), + }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code === 429) { + throw new DDoSProtection (this.id + ' ' + body); + } + if (code >= 400) { + const error = this.safeValue (response, 'error', {}); + const message = this.safeString (error, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + if (code === 400) { + throw new BadRequest (feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let query = '/api/' + this.version + '/' + path; + if (method === 'GET') { + if (Object.keys (params).length) { + query += '?' + this.urlencode (params); + } + } else { + const format = this.safeString (params, '_format'); + if (format !== undefined) { + query += '?' + this.urlencode ({ '_format': format }); + params = this.omit (params, '_format'); + } + } + const url = this.urls['api'][api] + query; + if (this.apiKey && this.secret) { + let auth = method + query; + let expires = this.safeInteger (this.options, 'api-expires'); + headers = { + 'Content-Type': 'application/json', + 'api-key': this.apiKey, + }; + expires = this.sum (this.seconds (), expires); + expires = expires.toString (); + auth += expires; + headers['api-expires'] = expires; + if (method === 'POST' || method === 'PUT' || method === 'DELETE') { + if (Object.keys (params).length) { + body = this.json (params); + auth += body; + } + } + headers['api-signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],39:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, PermissionDenied, BadRequest, ArgumentsRequired, OrderNotFound, InsufficientFunds, ExchangeNotAvailable, DDoSProtection, InvalidAddress, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitpanda extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitpanda', + 'name': 'Bitpanda Pro', + 'countries': [ 'AT' ], // Austria + 'rateLimit': 300, + 'version': 'v1', + // new metainfo interface + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'cancelOrders': true, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDeposits': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingFees': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1/MINUTES', + '5m': '5/MINUTES', + '15m': '15/MINUTES', + '30m': '30/MINUTES', + '1h': '1/HOURS', + '4h': '4/HOURS', + '1d': '1/DAYS', + '1w': '1/WEEKS', + '1M': '1/MONTHS', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87591171-9a377d80-c6f0-11ea-94ac-97a126eac3bc.jpg', + 'api': { + 'public': 'https://api.exchange.bitpanda.com/public', + 'private': 'https://api.exchange.bitpanda.com/public', + }, + 'www': 'https://www.bitpanda.com/en/pro', + 'doc': [ + 'https://developers.bitpanda.com/exchange/', + ], + 'fees': 'https://www.bitpanda.com/en/pro/fees', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'candlesticks/{instrument_code}', + 'fees', + 'instruments', + 'order-book/{instrument_code}', + 'market-ticker', + 'market-ticker/{instrument_code}', + 'price-ticks/{instrument_code}', + 'time', + ], + }, + 'private': { + 'get': [ + 'account/balances', + 'account/deposit/crypto/{currency_code}', + 'account/deposit/fiat/EUR', + 'account/deposits', + 'account/deposits/bitpanda', + 'account/withdrawals', + 'account/withdrawals/bitpanda', + 'account/fees', + 'account/orders', + 'account/orders/{order_id}', + 'account/orders/{order_id}/trades', + 'account/trades', + 'account/trades/{trade_id}', + 'account/trading-volume', + ], + 'post': [ + 'account/deposit/crypto', + 'account/withdraw/crypto', + 'account/withdraw/fiat', + 'account/fees', + 'account/orders', + ], + 'delete': [ + 'account/orders', + 'account/orders/{order_id}', + 'account/orders/client/{client_id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.15 / 100, + 'maker': 0.10 / 100, + 'tiers': [ + // volume in BTC + { + 'taker': [ + [0, 0.15 / 100], + [100, 0.13 / 100], + [250, 0.13 / 100], + [1000, 0.1 / 100], + [5000, 0.09 / 100], + [10000, 0.075 / 100], + [20000, 0.065 / 100], + ], + 'maker': [ + [0, 0.1 / 100], + [100, 0.1 / 100], + [250, 0.09 / 100], + [1000, 0.075 / 100], + [5000, 0.06 / 100], + [10000, 0.05 / 100], + [20000, 0.05 / 100], + ], + }, + ], + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + }, + 'exceptions': { + 'exact': { + 'INVALID_CLIENT_UUID': InvalidOrder, + 'ORDER_NOT_FOUND': OrderNotFound, + 'ONLY_ONE_ERC20_ADDRESS_ALLOWED': InvalidAddress, + 'DEPOSIT_ADDRESS_NOT_USED': InvalidAddress, + 'INVALID_CREDENTIALS': AuthenticationError, + 'MISSING_CREDENTIALS': AuthenticationError, + 'INVALID_APIKEY': AuthenticationError, + 'INVALID_SCOPES': AuthenticationError, + 'INVALID_SUBJECT': AuthenticationError, + 'INVALID_ISSUER': AuthenticationError, + 'INVALID_AUDIENCE': AuthenticationError, + 'INVALID_DEVICE_ID': AuthenticationError, + 'INVALID_IP_RESTRICTION': AuthenticationError, + 'APIKEY_REVOKED': AuthenticationError, + 'APIKEY_EXPIRED': AuthenticationError, + 'SYNCHRONIZER_TOKEN_MISMATCH': AuthenticationError, + 'SESSION_EXPIRED': AuthenticationError, + 'INTERNAL_ERROR': AuthenticationError, + 'CLIENT_IP_BLOCKED': PermissionDenied, + 'MISSING_PERMISSION': PermissionDenied, + 'ILLEGAL_CHARS': BadRequest, + 'UNSUPPORTED_MEDIA_TYPE': BadRequest, + 'ACCOUNT_HISTORY_TIME_RANGE_TOO_BIG': BadRequest, + 'CANDLESTICKS_TIME_RANGE_TOO_BIG': BadRequest, + 'INVALID_INSTRUMENT_CODE': BadRequest, + 'INVALID_ORDER_TYPE': BadRequest, + 'INVALID_UNIT': BadRequest, + 'INVALID_PERIOD': BadRequest, + 'INVALID_TIME': BadRequest, + 'INVALID_DATE': BadRequest, + 'INVALID_CURRENCY': BadRequest, + 'INVALID_AMOUNT': BadRequest, + 'INVALID_PRICE': BadRequest, + 'INVALID_LIMIT': BadRequest, + 'INVALID_QUERY': BadRequest, + 'INVALID_CURSOR': BadRequest, + 'INVALID_ACCOUNT_ID': BadRequest, + 'INVALID_SIDE': InvalidOrder, + 'INVALID_ACCOUNT_HISTORY_FROM_TIME': BadRequest, + 'INVALID_ACCOUNT_HISTORY_MAX_PAGE_SIZE': BadRequest, + 'INVALID_ACCOUNT_HISTORY_TIME_PERIOD': BadRequest, + 'INVALID_ACCOUNT_HISTORY_TO_TIME': BadRequest, + 'INVALID_CANDLESTICKS_GRANULARITY': BadRequest, + 'INVALID_CANDLESTICKS_UNIT': BadRequest, + 'INVALID_ORDER_BOOK_DEPTH': BadRequest, + 'INVALID_ORDER_BOOK_LEVEL': BadRequest, + 'INVALID_PAGE_CURSOR': BadRequest, + 'INVALID_TIME_RANGE': BadRequest, + 'INVALID_TRADE_ID': BadRequest, + 'INVALID_UI_ACCOUNT_SETTINGS': BadRequest, + 'NEGATIVE_AMOUNT': InvalidOrder, + 'NEGATIVE_PRICE': InvalidOrder, + 'MIN_SIZE_NOT_SATISFIED': InvalidOrder, + 'BAD_AMOUNT_PRECISION': InvalidOrder, + 'BAD_PRICE_PRECISION': InvalidOrder, + 'BAD_TRIGGER_PRICE_PRECISION': InvalidOrder, + 'MAX_OPEN_ORDERS_EXCEEDED': BadRequest, + 'MISSING_PRICE': InvalidOrder, + 'MISSING_ORDER_TYPE': InvalidOrder, + 'MISSING_SIDE': InvalidOrder, + 'MISSING_CANDLESTICKS_PERIOD_PARAM': ArgumentsRequired, + 'MISSING_CANDLESTICKS_UNIT_PARAM': ArgumentsRequired, + 'MISSING_FROM_PARAM': ArgumentsRequired, + 'MISSING_INSTRUMENT_CODE': ArgumentsRequired, + 'MISSING_ORDER_ID': InvalidOrder, + 'MISSING_TO_PARAM': ArgumentsRequired, + 'MISSING_TRADE_ID': ArgumentsRequired, + 'INVALID_ORDER_ID': OrderNotFound, + 'NOT_FOUND': OrderNotFound, + 'INSUFFICIENT_LIQUIDITY': InsufficientFunds, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'NO_TRADING': ExchangeNotAvailable, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'GATEWAY_TIMEOUT': ExchangeNotAvailable, + 'RATELIMIT': DDoSProtection, + 'CF_RATELIMIT': DDoSProtection, + 'INTERNAL_SERVER_ERROR': ExchangeError, + }, + 'broad': { + }, + }, + 'commonCurrencies': { + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + }, + // exchange-specific options + 'options': { + 'fetchTradingFees': { + 'method': 'fetchPrivateTradingFees', // or 'fetchPublicTradingFees' + }, + 'fiat': [ 'EUR', 'CHF' ], + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // iso: '2020-07-10T05:17:26.716Z', + // epoch_millis: 1594358246716, + // } + // + return this.safeInteger (response, 'epoch_millis'); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // "code":"BEST", + // "precision":8 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'name': undefined, + 'info': currency, // the original payload + 'active': undefined, + 'fee': undefined, + 'precision': this.safeInteger (currency, 'precision'), + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInstruments (params); + // + // [ + // { + // state: 'ACTIVE', + // base: { code: 'ETH', precision: 8 }, + // quote: { code: 'CHF', precision: 2 }, + // amount_precision: 4, + // market_precision: 2, + // min_size: '10.0' + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const baseAsset = this.safeValue (market, 'base', {}); + const quoteAsset = this.safeValue (market, 'quote', {}); + const baseId = this.safeString (baseAsset, 'code'); + const quoteId = this.safeString (quoteAsset, 'code'); + const id = baseId + '_' + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amount_precision'), + 'price': this.safeInteger (market, 'market_precision'), + }; + const limits = { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'min_size'), + 'max': undefined, + }, + }; + const state = this.safeString (market, 'state'); + const active = (state === 'ACTIVE'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + async fetchTradingFees (params = {}) { + let method = this.safeString (params, 'method'); + params = this.omit (params, 'method'); + if (method === undefined) { + const options = this.safeValue (this.options, 'fetchTradingFees', {}); + method = this.safeString (options, 'method', 'fetchPrivateTradingFees'); + } + return await this[method] (params); + } + + async fetchPublicTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.publicGetFees (params); + // + // [ + // { + // "fee_group_id":"default", + // "display_text":"The standard fee plan.", + // "fee_tiers":[ + // {"volume":"0.0","fee_group_id":"default","maker_fee":"0.1","taker_fee":"0.15"}, + // {"volume":"100.0","fee_group_id":"default","maker_fee":"0.1","taker_fee":"0.13"}, + // {"volume":"250.0","fee_group_id":"default","maker_fee":"0.09","taker_fee":"0.13"}, + // {"volume":"1000.0","fee_group_id":"default","maker_fee":"0.075","taker_fee":"0.1"}, + // {"volume":"5000.0","fee_group_id":"default","maker_fee":"0.06","taker_fee":"0.09"}, + // {"volume":"10000.0","fee_group_id":"default","maker_fee":"0.05","taker_fee":"0.075"}, + // {"volume":"20000.0","fee_group_id":"default","maker_fee":"0.05","taker_fee":"0.065"} + // ], + // "fee_discount_rate":"25.0", + // "minimum_price_value":"0.12" + // } + // ] + // + const feeGroupsById = this.indexBy (response, 'fee_group_id'); + const feeGroupId = this.safeValue (this.options, 'fee_group_id', 'default'); + const feeGroup = this.safeValue (feeGroupsById, feeGroupId, {}); + const feeTiers = this.safeValue (feeGroup, 'fee_tiers'); + const result = {}; + for (let i = 0; i < this.symbols.length; i++) { + const symbol = this.symbols[i]; + const fee = { + 'info': feeGroup, + 'symbol': symbol, + 'maker': undefined, + 'taker': undefined, + 'percentage': true, + 'tierBased': true, + }; + const takerFees = []; + const makerFees = []; + for (let i = 0; i < feeTiers.length; i++) { + const tier = feeTiers[i]; + const volume = this.safeFloat (tier, 'volume'); + let taker = this.safeFloat (tier, 'taker_fee'); + let maker = this.safeFloat (tier, 'maker_fee'); + taker /= 100; + maker /= 100; + takerFees.push ([ volume, taker ]); + makerFees.push ([ volume, maker ]); + if (i === 0) { + fee['taker'] = taker; + fee['maker'] = maker; + } + } + const tiers = { + 'taker': takerFees, + 'maker': makerFees, + }; + fee['tiers'] = tiers; + result[symbol] = fee; + } + return result; + } + + async fetchPrivateTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountFees (params); + // + // { + // "account_id": "ed524d00-820a-11e9-8f1e-69602df16d85", + // "running_trading_volume": "0.0", + // "fee_group_id": "default", + // "collect_fees_in_best": false, + // "fee_discount_rate": "25.0", + // "minimum_price_value": "0.12", + // "fee_tiers": [ + // { "volume": "0.0", "fee_group_id": "default", "maker_fee": "0.1", "taker_fee": "0.1" }, + // { "volume": "100.0", "fee_group_id": "default", "maker_fee": "0.09", "taker_fee": "0.1" }, + // { "volume": "250.0", "fee_group_id": "default", "maker_fee": "0.08", "taker_fee": "0.1" }, + // { "volume": "1000.0", "fee_group_id": "default", "maker_fee": "0.07", "taker_fee": "0.09" }, + // { "volume": "5000.0", "fee_group_id": "default", "maker_fee": "0.06", "taker_fee": "0.08" }, + // { "volume": "10000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.07" }, + // { "volume": "20000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.06" }, + // { "volume": "50000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.05" } + // ], + // "active_fee_tier": { "volume": "0.0", "fee_group_id": "default", "maker_fee": "0.1", "taker_fee": "0.1" } + // } + // + const activeFeeTier = this.safeValue (response, 'active_fee_tier', {}); + const result = { + 'info': response, + 'maker': this.safeFloat (activeFeeTier, 'maker_fee'), + 'taker': this.safeFloat (activeFeeTier, 'taker_fee'), + 'percentage': true, + 'tierBased': true, + }; + const feeTiers = this.safeValue (response, 'fee_tiers'); + const takerFees = []; + const makerFees = []; + for (let i = 0; i < feeTiers.length; i++) { + const tier = feeTiers[i]; + const volume = this.safeFloat (tier, 'volume'); + let taker = this.safeFloat (tier, 'taker_fee'); + let maker = this.safeFloat (tier, 'maker_fee'); + taker /= 100; + maker /= 100; + takerFees.push ([ volume, taker ]); + makerFees.push ([ volume, maker ]); + } + const tiers = { + 'taker': takerFees, + 'maker': makerFees, + }; + result['tiers'] = tiers; + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "instrument_code":"BTC_EUR", + // "sequence":602562, + // "time":"2020-07-10T06:27:34.951Z", + // "state":"ACTIVE", + // "is_frozen":0, + // "quote_volume":"1695555.1783768", + // "base_volume":"205.67436", + // "last_price":"8143.91", + // "best_bid":"8143.71", + // "best_ask":"8156.9", + // "price_change":"-147.47", + // "price_change_percentage":"-1.78", + // "high":"8337.45", + // "low":"8110.0" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'time')); + const marketId = this.safeString (ticker, 'instrument_code'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat (ticker, 'last_price'); + const percentage = this.safeFloat (ticker, 'price_change_percentage'); + const change = this.safeFloat (ticker, 'price_change'); + let open = undefined; + let average = undefined; + if ((last !== undefined) && (change !== undefined)) { + open = last - change; + average = this.sum (last, open) / 2; + } + const baseVolume = this.safeFloat (ticker, 'base_volume'); + const quoteVolume = this.safeFloat (ticker, 'quote_volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_code': market['id'], + }; + const response = await this.publicGetMarketTickerInstrumentCode (this.extend (request, params)); + // + // { + // "instrument_code":"BTC_EUR", + // "sequence":602562, + // "time":"2020-07-10T06:27:34.951Z", + // "state":"ACTIVE", + // "is_frozen":0, + // "quote_volume":"1695555.1783768", + // "base_volume":"205.67436", + // "last_price":"8143.91", + // "best_bid":"8143.71", + // "best_ask":"8156.9", + // "price_change":"-147.47", + // "price_change_percentage":"-1.78", + // "high":"8337.45", + // "low":"8110.0" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketTicker (params); + // + // [ + // { + // "instrument_code":"BTC_EUR", + // "sequence":602562, + // "time":"2020-07-10T06:27:34.951Z", + // "state":"ACTIVE", + // "is_frozen":0, + // "quote_volume":"1695555.1783768", + // "base_volume":"205.67436", + // "last_price":"8143.91", + // "best_bid":"8143.71", + // "best_ask":"8156.9", + // "price_change":"-147.47", + // "price_change_percentage":"-1.78", + // "high":"8337.45", + // "low":"8110.0" + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'instrument_code': this.marketId (symbol), + // level 1 means only the best bid and ask + // level 2 is a compiled order book up to market precision + // level 3 is a full orderbook + // if you wish to get regular updates about orderbooks please use the Websocket channel + // heavy usage of this endpoint may result in limited access according to rate limits rules + // 'level': 3, // default + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetOrderBookInstrumentCode (this.extend (request, params)); + // + // level 1 + // + // { + // "instrument_code":"BTC_EUR", + // "time":"2020-07-10T07:39:06.343Z", + // "asks":{ + // "value":{ + // "price":"8145.29", + // "amount":"0.96538", + // "number_of_orders":1 + // } + // }, + // "bids":{ + // "value":{ + // "price":"8134.0", + // "amount":"1.5978", + // "number_of_orders":5 + // } + // } + // } + // + // level 2 + // + // { + // "instrument_code":"BTC_EUR","time":"2020-07-10T07:36:43.538Z", + // "asks":[ + // {"price":"8146.59","amount":"0.89691","number_of_orders":1}, + // {"price":"8146.89","amount":"1.92062","number_of_orders":1}, + // {"price":"8169.5","amount":"0.0663","number_of_orders":1}, + // ], + // "bids":[ + // {"price":"8143.49","amount":"0.01329","number_of_orders":1}, + // {"price":"8137.01","amount":"5.34748","number_of_orders":1}, + // {"price":"8137.0","amount":"2.0","number_of_orders":1}, + // ] + // } + // + // level 3 + // + // { + // "instrument_code":"BTC_EUR", + // "time":"2020-07-10T07:32:31.525Z", + // "bids":[ + // {"price":"8146.79","amount":"0.01537","order_id":"5d717da1-a8f4-422d-afcc-03cb6ab66825"}, + // {"price":"8139.32","amount":"3.66009","order_id":"d0715c68-f28d-4cf1-a450-d56cf650e11c"}, + // {"price":"8137.51","amount":"2.61049","order_id":"085fd6f4-e835-4ca5-9449-a8f165772e60"}, + // ], + // "asks":[ + // {"price":"8153.49","amount":"0.93384","order_id":"755d3aa3-42b5-46fa-903d-98f42e9ae6c4"}, + // {"price":"8153.79","amount":"1.80456","order_id":"62034cf3-b70d-45ff-b285-ba6307941e7c"}, + // {"price":"8167.9","amount":"0.0018","order_id":"036354e0-71cd-492f-94f2-01f7d4b66422"}, + // ] + // } + // + const timestamp = this.parse8601 (this.safeString (response, 'time')); + return this.parseOrderBook (response, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "instrument_code":"BTC_EUR", + // "granularity":{"unit":"HOURS","period":1}, + // "high":"9252.65", + // "low":"9115.27", + // "open":"9250.0", + // "close":"9132.35", + // "total_amount":"33.85924", + // "volume":"311958.9635744", + // "time":"2020-05-08T22:59:59.999Z", + // "last_sequence":461123 + // } + // + const granularity = this.safeValue (ohlcv, 'granularity'); + const unit = this.safeString (granularity, 'unit'); + const period = this.safeString (granularity, 'period'); + const units = { + 'MINUTES': 'm', + 'HOURS': 'h', + 'DAYS': 'd', + 'WEEKS': 'w', + 'MONTHS': 'M', + }; + const lowercaseUnit = this.safeString (units, unit); + const timeframe = period + lowercaseUnit; + const durationInSeconds = this.parseTimeframe (timeframe); + const duration = durationInSeconds * 1000; + const timestamp = this.parse8601 (this.safeString (ohlcv, 'time')); + const alignedTimestamp = duration * parseInt (timestamp / duration); + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const volumeField = this.safeString (options, 'volume', 'total_amount'); + return [ + alignedTimestamp, + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, volumeField), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const periodUnit = this.safeString (this.timeframes, timeframe); + const [ period, unit ] = periodUnit.split ('/'); + const durationInSeconds = this.parseTimeframe (timeframe); + const duration = durationInSeconds * 1000; + if (limit === undefined) { + limit = 1500; + } + const request = { + 'instrument_code': market['id'], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + 'period': period, + 'unit': unit, + }; + if (since === undefined) { + const now = this.milliseconds (); + request['to'] = this.iso8601 (now); + request['from'] = this.iso8601 (now - limit * duration); + } else { + request['from'] = this.iso8601 (since); + request['to'] = this.iso8601 (this.sum (since, limit * duration)); + } + const response = await this.publicGetCandlesticksInstrumentCode (this.extend (request, params)); + // + // [ + // {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9252.65","low":"9115.27","open":"9250.0","close":"9132.35","total_amount":"33.85924","volume":"311958.9635744","time":"2020-05-08T22:59:59.999Z","last_sequence":461123}, + // {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9162.49","low":"9040.0","open":"9132.53","close":"9083.69","total_amount":"26.19685","volume":"238553.7812365","time":"2020-05-08T23:59:59.999Z","last_sequence":461376}, + // {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9135.7","low":"9002.59","open":"9055.45","close":"9133.98","total_amount":"26.21919","volume":"238278.8724959","time":"2020-05-09T00:59:59.999Z","last_sequence":461521}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "instrument_code":"BTC_EUR", + // "price":"8137.28", + // "amount":"0.22269", + // "taker_side":"BUY", + // "volume":"1812.0908832", + // "time":"2020-07-10T14:44:32.299Z", + // "trade_timestamp":1594392272299, + // "sequence":603047 + // } + // + // fetchMyTrades, fetchOrder, fetchOpenOrders, fetchClosedOrders trades (private) + // + // { + // "fee": { + // "fee_amount": "0.0014", + // "fee_currency": "BTC", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664", + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "amount": "1.4", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "7341.4", + // "time": "2019-09-27T15:05:32.564Z", + // "sequence": 48670 + // } + // } + // + const feeInfo = this.safeValue (trade, 'fee', {}); + trade = this.safeValue (trade, 'trade', trade); + const timestamp = this.parse8601 (this.safeString (trade, 'time')); + const side = this.safeStringLower2 (trade, 'side', 'taker_side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = this.safeFloat (trade, 'volume'); + if ((cost === undefined) && (amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const marketId = this.safeString (trade, 'instrument_code'); + const symbol = this.safeSymbol (marketId, market, '_'); + const feeCost = this.safeFloat (feeInfo, 'fee_amount'); + let takerOrMaker = undefined; + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (feeInfo, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + const feeRate = this.safeFloat (feeInfo, 'fee_percentage'); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + takerOrMaker = this.safeStringLower (feeInfo, 'fee_type'); + } + return { + 'id': this.safeString2 (trade, 'trade_id', 'sequence'), + 'order': this.safeString (trade, 'order_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_code': market['id'], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + }; + if (since !== undefined) { + // returns price ticks for a specific market with an interval of maximum of 4 hours + // sorted by latest first + request['from'] = this.iso8601 (since); + request['to'] = this.iso8601 (this.sum (since, 14400000)); + } + const response = await this.publicGetPriceTicksInstrumentCode (this.extend (request, params)); + // + // [ + // { + // "instrument_code":"BTC_EUR", + // "price":"8137.28", + // "amount":"0.22269", + // "taker_side":"BUY", + // "volume":"1812.0908832", + // "time":"2020-07-10T14:44:32.299Z", + // "trade_timestamp":1594392272299, + // "sequence":603047 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountBalances (params); + // + // { + // "account_id":"4b95934f-55f1-460c-a525-bd5afc0cf071", + // "balances":[ + // { + // "account_id":"4b95934f-55f1-460c-a525-bd5afc0cf071", + // "currency_code":"BTC", + // "change":"10.0", + // "available":"10.0", + // "locked":"0.0", + // "sequence":142135994, + // "time":"2020-07-01T10:57:32.959Z" + // } + // ] + // } + // + const balances = this.safeValue (response, 'balances', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseDepositAddress (depositAddress, currency = undefined) { + let code = undefined; + if (currency !== undefined) { + code = currency['code']; + } + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'destination_tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostAccountDepositCrypto (this.extend (request, params)); + // + // { + // "address":"rBnNhk95FrdNisZtXcStzriFS8vEzz53DM", + // "destination_tag":"865690307", + // "enabled":true, + // "is_smart_contract":false + // } + // + return this.parseDepositAddress (response, currency); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_code': currency['id'], + }; + const response = await this.privateGetAccountDepositCryptoCurrencyCode (this.extend (request, params)); + // + // { + // "address":"rBnNhk95FrdNisZtXcStzriFS8vEzz53DM", + // "destination_tag":"865690307", + // "enabled":true, + // "is_smart_contract":false, + // "can_create_more":false + // } + // + return this.parseDepositAddress (response, currency); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency_code'] = currency['id']; + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a "to" iso8601 string param with the since argument is specified'); + } + request['from'] = this.iso8601 (since); + } + const response = await this.privateGetAccountDeposits (this.extend (request, params)); + // + // { + // "deposit_history": [ + // { + // "transaction_id": "e5342efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "100", + // "type": "CRYPTO", + // "funds_source": "INTERNAL", + // "time": "2020-04-22T09:57:47Z", + // "currency": "BTC", + // "fee_amount": "0.0", + // "fee_currency": "BTC" + // }, + // { + // "transaction_id": "79793d00-2899-4a4d-95b7-73ae6b31384f", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "time": "2020-05-05T11:22:07.925Z", + // "currency": "EUR", + // "funds_source": "EXTERNAL", + // "type": "FIAT", + // "amount": "50.0", + // "fee_amount": "0.01", + // "fee_currency": "EUR" + // } + // ], + // "max_page_size": 2, + // "cursor": "eyJhY2NvdW50X2lkIjp7InMiOiJlMzY5YWM4MC00NTc3LTExZTktYWUwOC05YmVkYzQ3OTBiODQiLCJzcyI6W10sIm5zIjpbXSwiYnMiOltdLCJtIjp7fSwibCI6W119LCJpdGVtX2tleSI6eyJzIjoiV0lUSERSQVdBTDo6MmFlMjYwY2ItOTk3MC00YmNiLTgxNmEtZGY4MDVmY2VhZTY1Iiwic3MiOltdLCJucyI6W10sImJzIjpbXSwibSI6e30sImwiOltdfSwiZ2xvYmFsX3dpdGhkcmF3YWxfaW5kZXhfaGFzaF9rZXkiOnsicyI6ImUzNjlhYzgwLTQ1NzctMTFlOS1hZTA4LTliZWRjNDc5MGI4NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX0sInRpbWVzdGFtcCI6eyJuIjoiMTU4ODA1ODc2Nzk0OCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX19" + // } + // + const depositHistory = this.safeValue (response, 'deposit_history', []); + return this.parseTransactions (depositHistory, currency, since, limit, { 'type': 'deposit' }); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency_code'] = currency['id']; + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a "to" iso8601 string param with the since argument is specified'); + } + request['from'] = this.iso8601 (since); + } + const response = await this.privateGetAccountWithdrawals (this.extend (request, params)); + // + // { + // "withdrawal_history": [ + // { + // "account_id": "e369ac80-4577-11e9-ae08-9bedc4790b84", + // "amount": "0.1", + // "currency": "BTC", + // "fee_amount": "0.00002", + // "fee_currency": "BTC", + // "funds_source": "EXTERNAL", + // "related_transaction_id": "e298341a-3855-405e-bce3-92db368a3157", + // "time": "2020-05-05T11:11:32.110Z", + // "transaction_id": "6693ff40-bb10-4dcf-ada7-3b287727c882", + // "type": "CRYPTO" + // }, + // { + // "account_id": "e369ac80-4577-11e9-ae08-9bedc4790b84", + // "amount": "0.1", + // "currency": "BTC", + // "fee_amount": "0.0", + // "fee_currency": "BTC", + // "funds_source": "INTERNAL", + // "time": "2020-05-05T10:29:53.464Z", + // "transaction_id": "ec9703b1-954b-4f76-adea-faac66eabc0b", + // "type": "CRYPTO" + // } + // ], + // "cursor": "eyJhY2NvdW50X2lkIjp7InMiOiJlMzY5YWM4MC00NTc3LTExZTktYWUwOC05YmVkYzQ3OTBiODQiLCJzcyI6W10sIm5zIjpbXSwiYnMiOltdLCJtIjp7fSwibCI6W119LCJpdGVtX2tleSI6eyJzIjoiV0lUSERSQVdBTDo6ZWM5NzAzYjEtOTU0Yi00Zjc2LWFkZWEtZmFhYzY2ZWFiYzBiIiwic3MiOltdLCJucyI6W10sImJzIjpbXSwibSI6e30sImwiOltdfSwiZ2xvYmFsX3dpdGhkcmF3YWxfaW5kZXhfaGFzaF9rZXkiOnsicyI6ImUzNjlhYzgwLTQ1NzctMTFlOS1hZTA4LTliZWRjNDc5MGI4NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX0sInRpbWVzdGFtcCI6eyJuIjoiMTU4ODY3NDU5MzQ2NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX19", + // "max_page_size": 2 + // } + // + const withdrawalHistory = this.safeValue (response, 'withdrawal_history', []); + return this.parseTransactions (withdrawalHistory, currency, since, limit, { 'type': 'withdrawal' }); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': code, + 'amount': this.currencyToPrecision (code, amount), + // 'payout_account_id': '66756a10-3e86-48f4-9678-b634c4b135b2', // fiat only + // 'recipient': { // crypto only + // 'address': address, + // // 'destination_tag': '', + // }, + }; + const options = this.safeValue (this.options, 'fiat', []); + const isFiat = this.inArray (code, options); + const method = isFiat ? 'privatePostAccountWithdrawFiat' : 'privatePostAccountWithdrawCrypto'; + if (isFiat) { + const payoutAccountId = this.safeString (params, 'payout_account_id'); + if (payoutAccountId === undefined) { + throw ArgumentsRequired (this.id + ' withdraw() requires a payout_account_id param for fiat ' + code + ' withdrawals'); + } + } else { + const recipient = { 'address': address }; + if (tag !== undefined) { + recipient['destination_tag'] = tag; + } + request['recipient'] = recipient; + } + const response = await this[method] (this.extend (request, params)); + // + // crypto + // + // { + // "amount": "1234.5678", + // "fee": "1234.5678", + // "recipient": "3NacQ7rzZdhfyAtfJ5a11k8jFPdcMP2Bq7", + // "destination_tag": "", + // "transaction_id": "d0f8529f-f832-4e6a-9dc5-b8d5797badb2" + // } + // + // fiat + // + // { + // "transaction_id": "54236cd0-4413-11e9-93fb-5fea7e5b5df6" + // } + // + return this.parseTransaction (response, currency); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchWithdrawals + // + // { + // "transaction_id": "C2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "type": "FIAT", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "1234.5678", + // "time": "2019-08-24T14:15:22Z", + // "funds_source": "INTERNAL", + // "currency": "BTC", + // "fee_amount": "1234.5678", + // "fee_currency": "BTC", + // "blockchain_transaction_id": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + // "related_transaction_id": "e298341a-3855-405e-bce3-92db368a3157" + // } + // + // withdraw + // + // + // crypto + // + // { + // "amount": "1234.5678", + // "fee": "1234.5678", + // "recipient": "3NacQ7rzZdhfyAtfJ5a11k8jFPdcMP2Bq7", + // "destination_tag": "", + // "transaction_id": "d0f8529f-f832-4e6a-9dc5-b8d5797badb2" + // } + // + // fiat + // + // { + // "transaction_id": "54236cd0-4413-11e9-93fb-5fea7e5b5df6" + // } + // + const id = this.safeString (transaction, 'transaction_id'); + const amount = this.safeFloat (transaction, 'amount'); + const timestamp = this.parse8601 (this.safeString (transaction, 'time')); + const currencyId = this.safeString (transaction, 'currency'); + currency = this.safeCurrency (currencyId, currency); + const status = 'ok'; // the exchange returns cleared transactions only + const feeCost = this.safeFloat2 (transaction, 'fee_amount', 'fee'); + let fee = undefined; + const addressTo = this.safeString (transaction, 'recipient'); + const tagTo = this.safeString (transaction, 'destination_tag'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (transaction, 'fee_currency', currencyId); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': transaction, + 'id': id, + 'currency': currency['code'], + 'amount': amount, + 'address': addressTo, + 'addressFrom': undefined, + 'addressTo': addressTo, + 'tag': tagTo, + 'tagFrom': undefined, + 'tagTo': tagTo, + 'status': status, + 'type': undefined, + 'updated': undefined, + 'txid': this.safeString (transaction, 'blockchain_transaction_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'FILLED': 'open', + 'FILLED_FULLY': 'closed', + 'FILLED_CLOSED': 'canceled', + 'FILLED_REJECTED': 'rejected', + 'OPEN': 'open', + 'REJECTED': 'rejected', + 'CLOSED': 'canceled', + 'FAILED': 'failed', + 'STOP_TRIGGERED': 'triggered', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "order_id": "d5492c24-2995-4c18-993a-5b8bf8fffc0d", + // "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "instrument_code": "BTC_EUR", + // "time": "2019-08-01T08:00:44.026Z", + // "side": "BUY", + // "price": "5000", + // "amount": "1", + // "filled_amount": "0.5", + // "type": "LIMIT", + // "time_in_force": "GOOD_TILL_CANCELLED" + // } + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "order": { + // "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2", + // "account_id": "1eb2ad5d-55f1-40b5-bc92-7dc05869e905", + // "instrument_code": "BTC_EUR", + // "amount": "1234.5678", + // "filled_amount": "1234.5678", + // "side": "BUY", + // "type": "LIMIT", + // "status": "OPEN", + // "sequence": 123456789, + // "price": "1234.5678", + // "average_price": "1234.5678", + // "reason": "INSUFFICIENT_FUNDS", + // "time": "2019-08-24T14:15:22Z", + // "time_in_force": "GOOD_TILL_CANCELLED", + // "time_last_updated": "2019-08-24T14:15:22Z", + // "expire_after": "2019-08-24T14:15:22Z", + // "is_post_only": false, + // "time_triggered": "2019-08-24T14:15:22Z", + // "trigger_price": "1234.5678" + // }, + // "trades": [ + // { + // "fee": { + // "fee_amount": "0.0014", + // "fee_currency": "BTC", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664", + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "amount": "1.4", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "7341.4", + // "time": "2019-09-27T15:05:32.564Z", + // "sequence": 48670 + // } + // } + // ] + // } + // + const rawTrades = this.safeValue (order, 'trades', []); + order = this.safeValue (order, 'order', order); + const id = this.safeString (order, 'order_id'); + const clientOrderId = this.safeString (order, 'client_id'); + const timestamp = this.parse8601 (this.safeString (order, 'time')); + let status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'instrument_code'); + const symbol = this.safeSymbol (marketId, market, '_'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + const filled = this.safeFloat (order, 'filled_amount'); + let remaining = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + if (status === undefined) { + if (remaining > 0) { + status = 'open'; + } else { + status = 'closed'; + } + } + } + } + const side = this.safeStringLower (order, 'side'); + const type = this.safeStringLower (order, 'type'); + const trades = this.parseTrades (rawTrades, market, undefined, undefined); + const fees = []; + const numTrades = trades.length; + let lastTradeTimestamp = undefined; + let tradeCost = undefined; + let tradeAmount = undefined; + if (numTrades > 0) { + lastTradeTimestamp = trades[0]['timestamp']; + tradeCost = 0; + tradeAmount = 0; + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + fees.push (trade['fee']); + lastTradeTimestamp = Math.max (lastTradeTimestamp, trade['timestamp']); + tradeCost = this.sum (tradeCost, trade['cost']); + tradeAmount = this.sum (tradeAmount, trade['amount']); + } + } + let average = this.safeFloat (order, 'average_price'); + if (average === undefined) { + if ((tradeCost !== undefined) && (tradeAmount !== undefined) && (tradeAmount !== 0)) { + average = tradeCost / tradeAmount; + } + } + if (cost === undefined) { + if ((average !== undefined) && (filled !== undefined)) { + cost = average * filled; + } + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force')); + const stopPrice = this.safeFloat (order, 'trigger_price'); + const result = { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + // 'fee': undefined, + 'trades': trades, + }; + const numFees = fees.length; + if (numFees > 0) { + if (numFees === 1) { + result['fee'] = fees[0]; + } else { + const feesByCurrency = this.groupBy (fees, 'currency'); + const feeCurrencies = Object.keys (feesByCurrency); + const numFeesByCurrency = feeCurrencies.length; + if (numFeesByCurrency === 1) { + const feeCurrency = feeCurrencies[0]; + const feeArray = this.safeValue (feesByCurrency, feeCurrency); + let feeCost = 0; + for (let i = 0; i < feeArray.length; i++) { + feeCost = this.sum (feeCost, feeArray[i]['cost']); + } + result['fee'] = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } else { + result['fees'] = fees; + } + } + } else { + result['fee'] = undefined; + } + return result; + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GOOD_TILL_CANCELLED': 'GTC', + 'GOOD_TILL_TIME': 'GTT', + 'IMMEDIATE_OR_CANCELLED': 'IOC', + 'FILL_OR_KILL': 'FOK', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const request = { + 'instrument_code': market['id'], + 'type': uppercaseType, // LIMIT, MARKET, STOP + 'side': side.toUpperCase (), // or SELL + 'amount': this.amountToPrecision (symbol, amount), + // "price": "1234.5678", // required for LIMIT and STOP orders + // "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206", // optional + // "time_in_force": "GOOD_TILL_CANCELLED", // limit orders only, GOOD_TILL_CANCELLED, GOOD_TILL_TIME, IMMEDIATE_OR_CANCELLED and FILL_OR_KILL + // "expire_after": "2020-07-02T19:40:13Z", // required for GOOD_TILL_TIME + // "is_post_only": false, // limit orders only, optional + // "trigger_price": "1234.5678" // required for stop orders + }; + let priceIsRequired = false; + if (uppercaseType === 'LIMIT' || uppercaseType === 'STOP') { + priceIsRequired = true; + } + if (uppercaseType === 'STOP') { + const triggerPrice = this.safeFloat (params, 'trigger_price'); + if (triggerPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a trigger_price param for ' + type + ' orders'); + } + request['trigger_price'] = this.priceToPrecision (symbol, triggerPrice); + params = this.omit (params, 'trigger_price'); + } + if (priceIsRequired) { + request['price'] = this.priceToPrecision (symbol, price); + } + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_id'); + if (clientOrderId !== undefined) { + request['client_id'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'client_id' ]); + } + const response = await this.privatePostAccountOrders (this.extend (request, params)); + // + // { + // "order_id": "d5492c24-2995-4c18-993a-5b8bf8fffc0d", + // "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "instrument_code": "BTC_EUR", + // "time": "2019-08-01T08:00:44.026Z", + // "side": "BUY", + // "price": "5000", + // "amount": "1", + // "filled_amount": "0.5", + // "type": "LIMIT", + // "time_in_force": "GOOD_TILL_CANCELLED" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_id'); + params = this.omit (params, [ 'clientOrderId', 'client_id' ]); + let method = 'privateDeleteAccountOrdersOrderId'; + const request = {}; + if (clientOrderId !== undefined) { + method = 'privateDeleteAccountOrdersClientClientId'; + request['client_id'] = clientOrderId; + } else { + request['order_id'] = id; + } + const response = await this[method] (this.extend (request, params)); + // + // responds with an empty body + // + return response; + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbol !== undefined) { + const market = this.market (symbol); + request['instrument_code'] = market['id']; + } + const response = await this.privateDeleteAccountOrders (this.extend (request, params)); + // + // [ + // "a10e9bd1-8f72-4cfe-9f1b-7f1c8a9bd8ee" + // ] + // + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'ids': ids.join (','), + }; + const response = await this.privateDeleteAccountOrders (this.extend (request, params)); + // + // [ + // "a10e9bd1-8f72-4cfe-9f1b-7f1c8a9bd8ee" + // ] + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetAccountOrdersOrderId (this.extend (request, params)); + // + // { + // "order": { + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "time_last_updated": "2019-09-27T15:05:35.096Z", + // "sequence": 48782, + // "price": "7349.2", + // "filled_amount": "100.0", + // "status": "FILLED_FULLY", + // "amount": "100.0", + // "instrument_code": "BTC_EUR", + // "side": "BUY", + // "time": "2019-09-27T15:05:32.063Z", + // "type": "MARKET" + // }, + // "trades": [ + // { + // "fee": { + // "fee_amount": "0.0014", + // "fee_currency": "BTC", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664", + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "amount": "1.4", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "7341.4", + // "time": "2019-09-27T15:05:32.564Z", + // "sequence": 48670 + // } + // } + // ] + // } + // + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), // max range is 100 days + // 'instrument_code': market['id'], + // 'with_cancelled_and_rejected': false, // default is false, orders which have been cancelled by the user before being filled or rejected by the system as invalid, additionally, all inactive filled orders which would return with "with_just_filled_inactive" + // 'with_just_filled_inactive': false, // orders which have been filled and are no longer open, use of "with_cancelled_and_rejected" extends "with_just_filled_inactive" and in case both are specified the latter is ignored + // 'with_just_orders': false, // do not return any trades corresponsing to the orders, it may be significanly faster and should be used if user is not interesting in trade information + // 'max_page_size': 100, + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument_code'] = market['id']; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a "to" iso8601 string param with the since argument is specified, max range is 100 days'); + } + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + const response = await this.privateGetAccountOrders (this.extend (request, params)); + // + // { + // "order_history": [ + // { + // "order": { + // "trigger_price": "12089.88", + // "order_id": "d453ca12-c650-46dd-9dee-66910d96bfc0", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "instrument_code": "BTC_USDT", + // "time": "2019-08-23T10:02:31.663Z", + // "side": "SELL", + // "price": "10159.76", + // "average_price": "10159.76", + // "amount": "0.2", + // "filled_amount": "0.2", + // "type": "STOP", + // "sequence": 8, + // "status": "FILLED_FULLY" + // }, + // "trades": [ + // { + // "fee": { + // "fee_amount": "0.4188869", + // "fee_currency": "USDT", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "ec82896f-fd1b-4cbb-89df-a9da85ccbb4b", + // "order_id": "d453ca12-c650-46dd-9dee-66910d96bfc0", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "amount": "0.2", + // "side": "SELL", + // "instrument_code": "BTC_USDT", + // "price": "10159.76", + // "time": "2019-08-23T10:02:32.663Z", + // "sequence": 9 + // } + // } + // ] + // }, + // { + // "order": { + // "order_id": "5151a99e-f414-418f-8cf1-2568d0a63ea5", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "instrument_code": "BTC_USDT", + // "time": "2019-08-23T10:01:36.773Z", + // "side": "SELL", + // "price": "12289.88", + // "amount": "0.5", + // "filled_amount": "0.0", + // "type": "LIMIT", + // "sequence": 7, + // "status": "OPEN" + // }, + // "trades": [] + // }, + // { + // "order": { + // "order_id": "ac80d857-75e1-4733-9070-fd4288395fdc", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "instrument_code": "BTC_USDT", + // "time": "2019-08-23T10:01:25.031Z", + // "side": "SELL", + // "price": "11089.88", + // "amount": "0.1", + // "filled_amount": "0.0", + // "type": "LIMIT", + // "sequence": 6, + // "status": "OPEN" + // }, + // "trades": [] + // } + // ], + // "max_page_size": 100 + // } + // + const orderHistory = this.safeValue (response, 'order_history', []); + return this.parseOrders (orderHistory, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'with_cancelled_and_rejected': true, // default is false, orders which have been cancelled by the user before being filled or rejected by the system as invalid, additionally, all inactive filled orders which would return with "with_just_filled_inactive" + }; + return await this.fetchOpenOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + // 'max_page_size': 100, + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + if (limit !== undefined) { + request['max_page_size'] = limit; + } + const response = await this.privateGetAccountOrdersOrderIdTrades (this.extend (request, params)); + // + // { + // "trade_history": [ + // { + // "trade": { + // "trade_id": "2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "1234.5678", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "1234.5678", + // "time": "2019-08-24T14:15:22Z", + // "price_tick_sequence": 0, + // "sequence": 123456789 + // }, + // "fee": { + // "fee_amount": "1234.5678", + // "fee_percentage": "1234.5678", + // "fee_group_id": "default", + // "running_trading_volume": "1234.5678", + // "fee_currency": "BTC", + // "fee_type": "TAKER" + // } + // } + // ], + // "max_page_size": 0, + // "cursor": "string" + // } + // + const tradeHistory = this.safeValue (response, 'trade_history', []); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (tradeHistory, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), // max range is 100 days + // 'instrument_code': market['id'], + // 'max_page_size': 100, + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument_code'] = market['id']; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a "to" iso8601 string param with the since argument is specified, max range is 100 days'); + } + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + const response = await this.privateGetAccountTrades (this.extend (request, params)); + // + // { + // "trade_history": [ + // { + // "trade": { + // "trade_id": "2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "1234.5678", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "1234.5678", + // "time": "2019-08-24T14:15:22Z", + // "price_tick_sequence": 0, + // "sequence": 123456789 + // }, + // "fee": { + // "fee_amount": "1234.5678", + // "fee_percentage": "1234.5678", + // "fee_group_id": "default", + // "running_trading_volume": "1234.5678", + // "fee_currency": "BTC", + // "fee_type": "TAKER" + // } + // } + // ], + // "max_page_size": 0, + // "cursor": "string" + // } + // + const tradeHistory = this.safeValue (response, 'trade_history', []); + return this.parseTrades (tradeHistory, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer ' + this.apiKey, + }; + if (method === 'POST') { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error":"MISSING_FROM_PARAM"} + // {"error":"MISSING_TO_PARAM"} + // {"error":"CANDLESTICKS_TIME_RANGE_TOO_BIG"} + // + const message = this.safeString (response, 'error'); + if (message !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],40:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidNonce, AuthenticationError, OrderNotFound } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitso extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitso', + 'name': 'Bitso', + 'countries': [ 'MX' ], // Mexico + 'rateLimit': 2000, // 30 requests per minute + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295554-11f98280-c50e-11ea-80d6-15b3bafa8cbf.jpg', + 'api': 'https://api.bitso.com', + 'www': 'https://bitso.com', + 'doc': 'https://bitso.com/api_info', + 'fees': 'https://bitso.com/fees', + 'referral': 'https://bitso.com/?ref=itej', + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'precision': { + 'XRP': 0.000001, + 'MXN': 0.01, + 'TUSD': 0.01, + }, + 'defaultPrecision': 0.00000001, + }, + 'api': { + 'public': { + 'get': [ + 'available_books', + 'ticker', + 'order_book', + 'trades', + ], + }, + 'private': { + 'get': [ + 'account_status', + 'balance', + 'fees', + 'fundings', + 'fundings/{fid}', + 'funding_destination', + 'kyc_documents', + 'ledger', + 'ledger/trades', + 'ledger/fees', + 'ledger/fundings', + 'ledger/withdrawals', + 'mx_bank_codes', + 'open_orders', + 'order_trades/{oid}', + 'orders/{oid}', + 'user_trades', + 'user_trades/{tid}', + 'withdrawals/', + 'withdrawals/{wid}', + ], + 'post': [ + 'bitcoin_withdrawal', + 'debit_card_withdrawal', + 'ether_withdrawal', + 'ripple_withdrawal', + 'bcash_withdrawal', + 'litecoin_withdrawal', + 'orders', + 'phone_number', + 'phone_verification', + 'phone_withdrawal', + 'spei_withdrawal', + 'ripple_withdrawal', + 'bcash_withdrawal', + 'litecoin_withdrawal', + ], + 'delete': [ + 'orders/{oid}', + 'orders/all', + ], + }, + }, + 'exceptions': { + '0201': AuthenticationError, // Invalid Nonce or Invalid Credentials + '104': InvalidNonce, // Cannot perform request - nonce must be higher than 1520307203724237 + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAvailableBooks (params); + // + // { + // "success":true, + // "payload":[ + // { + // "book":"btc_mxn", + // "minimum_price":"500", + // "maximum_price":"10000000", + // "minimum_amount":"0.00005", + // "maximum_amount":"500", + // "minimum_value":"5", + // "maximum_value":"10000000", + // "tick_size":"0.01", + // "fees":{ + // "flat_rate":{"maker":"0.500","taker":"0.650"}, + // "structure":[ + // {"volume":"1500000","maker":"0.00500","taker":"0.00650"}, + // {"volume":"2000000","maker":"0.00490","taker":"0.00637"}, + // {"volume":"5000000","maker":"0.00480","taker":"0.00624"}, + // {"volume":"7000000","maker":"0.00440","taker":"0.00572"}, + // {"volume":"10000000","maker":"0.00420","taker":"0.00546"}, + // {"volume":"15000000","maker":"0.00400","taker":"0.00520"}, + // {"volume":"35000000","maker":"0.00370","taker":"0.00481"}, + // {"volume":"50000000","maker":"0.00300","taker":"0.00390"}, + // {"volume":"150000000","maker":"0.00200","taker":"0.00260"}, + // {"volume":"250000000","maker":"0.00100","taker":"0.00130"}, + // {"volume":"9999999999","maker":"0.00000","taker":"0.00130"}, + // ] + // } + // }, + // ] + // } + const markets = this.safeValue (response, 'payload'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'book'); + const [ baseId, quoteId ] = id.split ('_'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_amount'), + 'max': this.safeFloat (market, 'maximum_amount'), + }, + 'price': { + 'min': this.safeFloat (market, 'minimum_price'), + 'max': this.safeFloat (market, 'maximum_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'minimum_value'), + 'max': this.safeFloat (market, 'maximum_value'), + }, + }; + const defaultPricePrecision = this.safeFloat (this.options['precision'], quote, this.options['defaultPrecision']); + const pricePrecision = this.safeFloat (market, 'tick_size', defaultPricePrecision); + const precision = { + 'amount': this.safeFloat (this.options['precision'], base, this.options['defaultPrecision']), + 'price': pricePrecision, + }; + const fees = this.safeValue (market, 'fees', {}); + const flatRate = this.safeValue (fees, 'flat_rate', {}); + const maker = this.safeFloat (flatRate, 'maker'); + const taker = this.safeFloat (flatRate, 'taker'); + const feeTiers = this.safeValue (fees, 'structure', []); + const fee = { + 'maker': maker, + 'taker': taker, + 'percentage': true, + 'tierBased': true, + }; + const takerFees = []; + const makerFees = []; + for (let i = 0; i < feeTiers.length; i++) { + const tier = feeTiers[i]; + const volume = this.safeFloat (tier, 'volume'); + const takerFee = this.safeFloat (tier, 'taker'); + const makerFee = this.safeFloat (tier, 'maker'); + takerFees.push ([ volume, takerFee ]); + makerFees.push ([ volume, makerFee ]); + if (i === 0) { + fee['taker'] = taker; + fee['maker'] = maker; + } + } + const tiers = { + 'taker': takerFees, + 'maker': makerFees, + }; + fee['tiers'] = tiers; + result.push (this.extend ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'limits': limits, + 'precision': precision, + 'active': undefined, + }, fee)); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const balances = this.safeValue (response['payload'], 'balances'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'locked'), + 'total': this.safeFloat (balance, 'total'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'book': this.marketId (symbol), + }; + const response = await this.publicGetOrderBook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'payload'); + const timestamp = this.parse8601 (this.safeString (orderbook, 'updated_at')); + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'book': this.marketId (symbol), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'payload'); + const timestamp = this.parse8601 (this.safeString (ticker, 'created_at')); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const marketId = this.safeString (trade, 'book'); + const symbol = this.safeSymbol (marketId, market, '_'); + const side = this.safeString2 (trade, 'side', 'maker_side'); + let amount = this.safeFloat2 (trade, 'amount', 'major'); + if (amount !== undefined) { + amount = Math.abs (amount); + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fees_amount'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fees_currency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + let cost = this.safeFloat (trade, 'minor'); + if (cost !== undefined) { + cost = Math.abs (cost); + } + const price = this.safeFloat (trade, 'price'); + const orderId = this.safeString (trade, 'oid'); + const id = this.safeString (trade, 'tid'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'book': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response['payload'], market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = 25, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // the don't support fetching trades starting from a date yet + // use the `marker` extra param for that + // this is not a typo, the variable name is 'marker' (don't confuse with 'market') + const markerInParams = ('marker' in params); + // warn the user with an exception if the user wants to filter + // starting from since timestamp, but does not set the trade id with an extra 'marker' param + if ((since !== undefined) && !markerInParams) { + throw new ExchangeError (this.id + ' fetchMyTrades does not support fetching trades starting from a timestamp with the `since` argument, use the `marker` extra param to filter starting from an integer trade id'); + } + // convert it to an integer unconditionally + if (markerInParams) { + params = this.extend (params, { + 'marker': parseInt (params['marker']), + }); + } + const request = { + 'book': market['id'], + 'limit': limit, // default = 25, max = 100 + // 'sort': 'desc', // default = desc + // 'marker': id, // integer id to start from + }; + const response = await this.privateGetUserTrades (this.extend (request, params)); + return this.parseTrades (response['payload'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'book': this.marketId (symbol), + 'side': side, + 'type': type, + 'major': this.amountToPrecision (symbol, amount), + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + const id = this.safeString (response['payload'], 'oid'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'oid': id, + }; + return await this.privateDeleteOrdersOid (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'partial-fill': 'open', // this is a common substitution in ccxt + 'completed': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'oid'); + const side = this.safeString (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'book'); + const symbol = this.safeSymbol (marketId, market, '_'); + const orderType = this.safeString (order, 'type'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'original_amount'); + const remaining = this.safeFloat (order, 'unfilled_amount'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const clientOrderId = this.safeString (order, 'client_id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': undefined, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = 25, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // the don't support fetching trades starting from a date yet + // use the `marker` extra param for that + // this is not a typo, the variable name is 'marker' (don't confuse with 'market') + const markerInParams = ('marker' in params); + // warn the user with an exception if the user wants to filter + // starting from since timestamp, but does not set the trade id with an extra 'marker' param + if ((since !== undefined) && !markerInParams) { + throw new ExchangeError (this.id + ' fetchOpenOrders does not support fetching orders starting from a timestamp with the `since` argument, use the `marker` extra param to filter starting from an integer trade id'); + } + // convert it to an integer unconditionally + if (markerInParams) { + params = this.extend (params, { + 'marker': parseInt (params['marker']), + }); + } + const request = { + 'book': market['id'], + 'limit': limit, // default = 25, max = 100 + // 'sort': 'desc', // default = desc + // 'marker': id, // integer id to start from + }; + const response = await this.privateGetOpenOrders (this.extend (request, params)); + const orders = this.parseOrders (response['payload'], market, since, limit); + return orders; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetOrdersOid ({ + 'oid': id, + }); + const payload = this.safeValue (response, 'payload'); + if (Array.isArray (payload)) { + const numOrders = response['payload'].length; + if (numOrders === 1) { + return this.parseOrder (payload[0]); + } + } + throw new OrderNotFound (this.id + ': The order ' + id + ' not found.'); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'oid': id, + }; + const response = await this.privateGetOrderTradesOid (this.extend (request, params)); + return this.parseTrades (response['payload'], market); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'fund_currency': currency['id'], + }; + const response = await this.privateGetFundingDestination (this.extend (request, params)); + let address = this.safeString (response['payload'], 'account_identifier'); + let tag = undefined; + if (address.indexOf ('?dt=') >= 0) { + const parts = address.split ('?dt='); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const methods = { + 'BTC': 'Bitcoin', + 'ETH': 'Ether', + 'XRP': 'Ripple', + 'BCH': 'Bcash', + 'LTC': 'Litecoin', + }; + const method = (code in methods) ? methods[code] : undefined; + if (method === undefined) { + throw new ExchangeError (this.id + ' not valid withdraw coin: ' + code); + } + const request = { + 'amount': amount, + 'address': address, + 'destination_tag': tag, + }; + const classMethod = 'privatePost' + method + 'Withdrawal'; + const response = await this[classMethod] (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response['payload'], 'wid'), + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let endpoint = '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + endpoint += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + endpoint; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let request = [ nonce, method, endpoint ].join (''); + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + request += body; + } + } + const signature = this.hmac (this.encode (request), this.encode (this.secret)); + const auth = this.apiKey + ':' + nonce + ':' + signature; + headers = { + 'Authorization': 'Bitso ' + auth, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // {"success":false,"error":{"code":104,"message":"Cannot perform request - nonce must be higher than 1520307203724237"}} + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const feedback = this.id + ' ' + this.json (response); + const error = this.safeValue (response, 'error'); + if (error === undefined) { + throw new ExchangeError (feedback); + } + const code = this.safeString (error, 'code'); + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('success' in response) { + if (response['success']) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],41:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, NotSupported, PermissionDenied, InvalidNonce, OrderNotFound, InsufficientFunds, InvalidAddress, InvalidOrder, ArgumentsRequired, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitstamp extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitstamp', + 'name': 'Bitstamp', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'version': 'v2', + 'userAgent': this.userAgents['chrome'], + 'pro': true, + 'has': { + 'CORS': true, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchFundingFees': true, + 'fetchFees': true, + 'fetchLedger': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg', + 'api': { + 'public': 'https://www.bitstamp.net/api', + 'private': 'https://www.bitstamp.net/api', + 'v1': 'https://www.bitstamp.net/api', + }, + 'www': 'https://www.bitstamp.net', + 'doc': 'https://www.bitstamp.net/api', + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '259200', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'ohlc/{pair}/', + 'order_book/{pair}/', + 'ticker_hour/{pair}/', + 'ticker/{pair}/', + 'transactions/{pair}/', + 'trading-pairs-info/', + ], + }, + 'private': { + 'post': [ + 'balance/', + 'balance/{pair}/', + 'bch_withdrawal/', + 'bch_address/', + 'user_transactions/', + 'user_transactions/{pair}/', + 'open_orders/all/', + 'open_orders/{pair}/', + 'order_status/', + 'cancel_order/', + 'buy/{pair}/', + 'buy/market/{pair}/', + 'buy/instant/{pair}/', + 'sell/{pair}/', + 'sell/market/{pair}/', + 'sell/instant/{pair}/', + 'ltc_withdrawal/', + 'ltc_address/', + 'eth_withdrawal/', + 'eth_address/', + 'xrp_withdrawal/', + 'xrp_address/', + 'xlm_withdrawal/', + 'xlm_address/', + 'pax_withdrawal/', + 'pax_address/', + 'link_withdrawal/', + 'link_address/', + 'usdc_withdrawal/', + 'usdc_address/', + 'omg_withdrawal/', + 'omg_address/', + 'transfer-to-main/', + 'transfer-from-main/', + 'withdrawal-requests/', + 'withdrawal/open/', + 'withdrawal/status/', + 'withdrawal/cancel/', + 'liquidation_address/new/', + 'liquidation_address/info/', + ], + }, + 'v1': { + 'post': [ + 'bitcoin_deposit_address/', + 'unconfirmed_btc/', + 'bitcoin_withdrawal/', + 'ripple_withdrawal/', + 'ripple_address/', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.5 / 100, + 'maker': 0.5 / 100, + 'tiers': { + 'taker': [ + [0, 0.5 / 100], + [20000, 0.25 / 100], + [100000, 0.24 / 100], + [200000, 0.22 / 100], + [400000, 0.20 / 100], + [600000, 0.15 / 100], + [1000000, 0.14 / 100], + [2000000, 0.13 / 100], + [4000000, 0.12 / 100], + [20000000, 0.11 / 100], + [50000000, 0.10 / 100], + [100000000, 0.07 / 100], + [500000000, 0.05 / 100], + [2000000000, 0.03 / 100], + [6000000000, 0.01 / 100], + [10000000000, 0.005 / 100], + [10000000001, 0.0], + ], + 'maker': [ + [0, 0.5 / 100], + [20000, 0.25 / 100], + [100000, 0.24 / 100], + [200000, 0.22 / 100], + [400000, 0.20 / 100], + [600000, 0.15 / 100], + [1000000, 0.14 / 100], + [2000000, 0.13 / 100], + [4000000, 0.12 / 100], + [20000000, 0.11 / 100], + [50000000, 0.10 / 100], + [100000000, 0.07 / 100], + [500000000, 0.05 / 100], + [2000000000, 0.03 / 100], + [6000000000, 0.01 / 100], + [10000000000, 0.005 / 100], + [10000000001, 0.0], + ], + }, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.0005, + 'BCH': 0.0001, + 'LTC': 0.001, + 'ETH': 0.001, + 'XRP': 0.02, + 'XLM': 0.005, + 'PAX': 0.5, + 'USD': 25, + 'EUR': 3.0, + }, + 'deposit': { + 'BTC': 0, + 'BCH': 0, + 'LTC': 0, + 'ETH': 0, + 'XRP': 0, + 'XLM': 0, + 'PAX': 0, + 'USD': 7.5, + 'EUR': 0, + }, + }, + }, + 'exceptions': { + 'exact': { + 'No permission found': PermissionDenied, + 'API key not found': AuthenticationError, + 'IP address not allowed': PermissionDenied, + 'Invalid nonce': InvalidNonce, + 'Invalid signature': AuthenticationError, + 'Authentication failed': AuthenticationError, + 'Missing key, signature and nonce parameters': AuthenticationError, + 'Your account is frozen': PermissionDenied, + 'Please update your profile with your FATCA information, before using API.': PermissionDenied, + 'Order not found': OrderNotFound, + 'Price is more than 20% below market price.': InvalidOrder, + 'Bitstamp.net is under scheduled maintenance.': OnMaintenance, // { "error": "Bitstamp.net is under scheduled maintenance. We'll be back soon." } + }, + 'broad': { + 'Minimum order size is': InvalidOrder, // Minimum order size is 5.0 EUR. + 'Check your account balance for details.': InsufficientFunds, // You have only 0.00100000 BTC available. Check your account balance for details. + 'Ensure this value has at least': InvalidAddress, // Ensure this value has at least 25 characters (it has 4). + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.fetchMarketsFromCache (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const name = this.safeString (market, 'name'); + let [ base, quote ] = name.split ('/'); + const baseId = base.toLowerCase (); + const quoteId = quote.toLowerCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const symbolId = baseId + '_' + quoteId; + const id = this.safeString (market, 'url_symbol'); + const precision = { + 'amount': market['base_decimals'], + 'price': market['counter_decimals'], + }; + const parts = market['minimum_order'].split (' '); + const cost = parts[0]; + // let [ cost, currency ] = market['minimum_order'].split (' '); + const active = (market['trading'] === 'Enabled'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'symbolId': symbolId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': parseFloat (cost), + 'max': undefined, + }, + }, + }); + } + return result; + } + + constructCurrencyObject (id, code, name, precision, minCost, originalPayload) { + let currencyType = 'crypto'; + const description = this.describe (); + if (this.isFiat (code)) { + currencyType = 'fiat'; + } + return { + 'id': id, + 'code': code, + 'info': originalPayload, // the original payload + 'type': currencyType, + 'name': name, + 'active': true, + 'fee': this.safeFloat (description['fees']['funding']['withdraw'], code), + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + + async fetchMarketsFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchMarkets', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetTradingPairsInfo (params); + this.options['fetchMarkets'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchMarkets'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchMarketsFromCache (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const name = this.safeString (market, 'name'); + let [ base, quote ] = name.split ('/'); + const baseId = base.toLowerCase (); + const quoteId = quote.toLowerCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const description = this.safeString (market, 'description'); + const [ baseDescription, quoteDescription ] = description.split (' / '); + const parts = market['minimum_order'].split (' '); + const cost = parts[0]; + if (!(base in result)) { + result[base] = this.constructCurrencyObject (baseId, base, baseDescription, market['base_decimals'], undefined, market); + } + if (!(quote in result)) { + result[quote] = this.constructCurrencyObject (quoteId, quote, quoteDescription, market['counter_decimals'], parseFloat (cost), market); + } + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetOrderBookPair (this.extend (request, params)); + // + // { + // "timestamp": "1583652948", + // "microtimestamp": "1583652948955826", + // "bids": [ + // [ "8750.00", "1.33685271" ], + // [ "8749.39", "0.07700000" ], + // [ "8746.98", "0.07400000" ], + // ] + // "asks": [ + // [ "8754.10", "1.51995636" ], + // [ "8754.71", "1.40000000" ], + // [ "8754.72", "2.50000000" ], + // ] + // } + // + const microtimestamp = this.safeInteger (response, 'microtimestamp'); + const timestamp = parseInt (microtimestamp / 1000); + const orderbook = this.parseOrderBook (response, timestamp); + orderbook['nonce'] = microtimestamp; + return orderbook; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const ticker = await this.publicGetTickerPair (this.extend (request, params)); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + getCurrencyIdFromTransaction (transaction) { + // + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "datetime": XXX, + // "usd": 0.0, + // "btc": 0.0, + // "eth": "0.05000000", + // "type": "0", + // "id": XXX, + // "eur": 0.0 + // } + // + const currencyId = this.safeStringLower (transaction, 'currency'); + if (currencyId !== undefined) { + return currencyId; + } + transaction = this.omit (transaction, [ + 'fee', + 'price', + 'datetime', + 'type', + 'status', + 'id', + ]); + const ids = Object.keys (transaction); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (id.indexOf ('_') < 0) { + const value = this.safeFloat (transaction, id); + if ((value !== undefined) && (value !== 0)) { + return id; + } + } + } + return undefined; + } + + getMarketFromTrade (trade) { + trade = this.omit (trade, [ + 'fee', + 'price', + 'datetime', + 'tid', + 'type', + 'order_id', + 'side', + ]); + const currencyIds = Object.keys (trade); + const numCurrencyIds = currencyIds.length; + if (numCurrencyIds > 2) { + throw new ExchangeError (this.id + ' getMarketFromTrade too many keys: ' + this.json (currencyIds) + ' in the trade: ' + this.json (trade)); + } + if (numCurrencyIds === 2) { + let marketId = currencyIds[0] + currencyIds[1]; + if (marketId in this.markets_by_id) { + return this.markets_by_id[marketId]; + } + marketId = currencyIds[1] + currencyIds[0]; + if (marketId in this.markets_by_id) { + return this.markets_by_id[marketId]; + } + } + return undefined; + } + + getMarketFromTrades (trades) { + const tradesBySymbol = this.indexBy (trades, 'symbol'); + const symbols = Object.keys (tradesBySymbol); + const numSymbols = symbols.length; + if (numSymbols === 1) { + return this.markets[symbols[0]]; + } + return undefined; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // date: '1551814435', + // tid: '83581898', + // price: '0.03532850', + // type: '1', + // amount: '0.85945907' + // }, + // + // fetchMyTrades, trades returned within fetchOrder (private) + // + // { + // "usd": "6.0134400000000000", + // "price": "4008.96000000", + // "datetime": "2019-03-28 23:07:37.233599", + // "fee": "0.02", + // "btc": "0.00150000", + // "tid": 84452058, + // "type": 2 + // } + // + // from fetchOrder: + // { fee: '0.000019', + // price: '0.00015803', + // datetime: '2018-01-07 10:45:34.132551', + // btc: '0.0079015000000000', + // tid: 42777395, + // type: 2, //(0 - deposit; 1 - withdrawal; 2 - market trade) NOT buy/sell + // xrp: '50.00000000' } + const id = this.safeString2 (trade, 'id', 'tid'); + let symbol = undefined; + let side = undefined; + let price = this.safeFloat (trade, 'price'); + let amount = this.safeFloat (trade, 'amount'); + const orderId = this.safeString (trade, 'order_id'); + const type = undefined; + let cost = this.safeFloat (trade, 'cost'); + if (market === undefined) { + const keys = Object.keys (trade); + for (let i = 0; i < keys.length; i++) { + if (keys[i].indexOf ('_') >= 0) { + const marketId = keys[i].replace ('_', ''); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + } + // if the market is still not defined + // try to deduce it from used keys + if (market === undefined) { + market = this.getMarketFromTrade (trade); + } + } + const feeCost = this.safeFloat (trade, 'fee'); + let feeCurrency = undefined; + if (market !== undefined) { + price = this.safeFloat (trade, market['symbolId'], price); + amount = this.safeFloat (trade, market['baseId'], amount); + cost = this.safeFloat (trade, market['quoteId'], cost); + feeCurrency = market['quote']; + symbol = market['symbol']; + } + let timestamp = this.safeString2 (trade, 'date', 'datetime'); + if (timestamp !== undefined) { + if (timestamp.indexOf (' ') >= 0) { + // iso8601 + timestamp = this.parse8601 (timestamp); + } else { + // string unix epoch in seconds + timestamp = parseInt (timestamp); + timestamp = timestamp * 1000; + } + } + // if it is a private trade + if ('id' in trade) { + if (amount !== undefined) { + if (amount < 0) { + side = 'sell'; + amount = -amount; + } else { + side = 'buy'; + } + } + } else { + side = this.safeString (trade, 'type'); + if (side === '1') { + side = 'sell'; + } else if (side === '0') { + side = 'buy'; + } + } + if (cost === undefined) { + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + } + if (cost !== undefined) { + cost = Math.abs (cost); + } + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseTradingFee (balances, symbol) { + const market = this.market (symbol); + const tradeFee = this.safeFloat (balances, market['id'] + '_fee'); + return { + 'symbol': symbol, + 'maker': tradeFee, + 'taker': tradeFee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'time': 'hour', + }; + const response = await this.publicGetTransactionsPair (this.extend (request, params)); + // + // [ + // { + // date: '1551814435', + // tid: '83581898', + // price: '0.03532850', + // type: '1', + // amount: '0.85945907' + // }, + // { + // date: '1551814434', + // tid: '83581896', + // price: '0.03532851', + // type: '1', + // amount: '11.34130961' + // }, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "high": "9064.77", + // "timestamp": "1593961440", + // "volume": "18.49436608", + // "low": "9040.87", + // "close": "9064.77", + // "open": "9040.87" + // } + // + return [ + this.safeTimestamp (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'step': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + if (limit === undefined) { + if (since === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + limit = 1000; + const start = parseInt (since / 1000); + request['start'] = start; + request['end'] = this.sum (start, limit * duration); + request['limit'] = limit; + } + } else { + if (since !== undefined) { + const start = parseInt (since / 1000); + request['start'] = start; + request['end'] = this.sum (start, limit * duration); + } + request['limit'] = Math.min (limit, 1000); // min 1, max 1000 + } + const response = await this.publicGetOhlcPair (this.extend (request, params)); + // + // { + // "data": { + // "pair": "BTC/USD", + // "ohlc": [ + // {"high": "9064.77", "timestamp": "1593961440", "volume": "18.49436608", "low": "9040.87", "close": "9064.77", "open": "9040.87"}, + // {"high": "9071.59", "timestamp": "1593961500", "volume": "3.48631711", "low": "9058.76", "close": "9061.07", "open": "9064.66"}, + // {"high": "9067.33", "timestamp": "1593961560", "volume": "0.04142833", "low": "9061.94", "close": "9061.94", "open": "9067.33"}, + // ], + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const ohlc = this.safeValue (data, 'ohlc', []); + return this.parseOHLCVs (ohlc, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + const result = { 'info': balance }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const account = this.account (); + account['free'] = this.safeFloat (balance, currencyId + '_available'); + account['used'] = this.safeFloat (balance, currencyId + '_reserved'); + account['total'] = this.safeFloat (balance, currencyId + '_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'privatePostBalance'; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + method += 'Pair'; + } + const balance = await this[method] (this.extend (request, params)); + return { + 'info': balance, + 'symbol': symbol, + 'maker': balance.fee, + 'taker': balance.fee, + }; + } + + praseTradingFees (balance) { + const result = { 'info': balance }; + const markets = Object.keys (this.markets); + for (let i = 0; i < markets.length; i++) { + const symbol = markets[i]; + const fee = this.parseTradingFee (balance, symbol); + result[symbol] = fee; + } + return result; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + return this.praseTradingFees (balance); + } + + parseFundingFees (balance) { + const withdraw = {}; + const ids = Object.keys (balance); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (id.indexOf ('_withdrawal_fee') >= 0) { + const currencyId = id.split ('_')[0]; + const code = this.safeCurrencyCode (currencyId); + withdraw[code] = this.safeFloat (balance, id); + } + } + return { + 'info': balance, + 'withdraw': withdraw, + 'deposit': {}, + }; + } + + async fetchFundingFees (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + return this.parseFundingFees (balance); + } + + async fetchFees (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + const tradingFees = this.praseTradingFees (balance); + delete tradingFees['info']; + const fundingFees = this.parseFundingFees (balance); + delete fundingFees['info']; + return { + 'info': balance, + 'trading': tradingFees, + 'funding': fundingFees, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = 'privatePost' + this.capitalize (side); + const request = { + 'pair': market['id'], + 'amount': this.amountToPrecision (symbol, amount), + }; + if (type === 'market') { + method += 'Market'; + } else if (type === 'instant') { + method += 'Instant'; + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + method += 'Pair'; + const response = await this[method] (this.extend (request, params)); + const order = this.parseOrder (response, market); + return this.extend (order, { + 'type': type, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'In Queue': 'open', + 'Open': 'open', + 'Finished': 'closed', + 'Canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrderStatus (this.safeString (response, 'status')); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { 'id': id }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + // + // { + // "status": "Finished", + // "id": 3047704374, + // "transactions": [ + // { + // "usd": "6.0134400000000000", + // "price": "4008.96000000", + // "datetime": "2019-03-28 23:07:37.233599", + // "fee": "0.02", + // "btc": "0.00150000", + // "tid": 84452058, + // "type": 2 + // } + // ] + // } + return this.parseOrder (response, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'privatePostUserTransactions'; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + method += 'Pair'; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + const result = this.filterBy (response, 'type', '2'); + return this.parseTrades (result, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostUserTransactions (this.extend (request, params)); + // + // [ + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1234567894, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-08 09:00:31", + // "type": "1", + // "xrp": "-20.00000000", + // "eur": 0, + // }, + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1134567891, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-07 18:47:52", + // "type": "0", + // "xrp": "20.00000000", + // "eur": 0, + // }, + // ] + // + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const transactions = this.filterByArray (response, 'type', [ '0', '1' ], false); + return this.parseTransactions (transactions, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['timedelta'] = this.milliseconds () - since; + } else { + request['timedelta'] = 50000000; // use max bitstamp approved value + } + const response = await this.privatePostWithdrawalRequests (this.extend (request, params)); + // + // [ + // { + // status: 2, + // datetime: '2018-10-17 10:58:13', + // currency: 'BTC', + // amount: '0.29669259', + // address: 'aaaaa', + // type: 1, + // id: 111111, + // transaction_id: 'xxxx', + // }, + // { + // status: 2, + // datetime: '2018-10-17 10:55:17', + // currency: 'ETH', + // amount: '1.11010664', + // address: 'aaaa', + // type: 16, + // id: 222222, + // transaction_id: 'xxxxx', + // }, + // ] + // + return this.parseTransactions (response, undefined, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchTransactions + // + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1234567894, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-08 09:00:31", + // "type": "1", + // "xrp": "-20.00000000", + // "eur": 0, + // } + // + // fetchWithdrawals + // + // { + // status: 2, + // datetime: '2018-10-17 10:58:13', + // currency: 'BTC', + // amount: '0.29669259', + // address: 'aaaaa', + // type: 1, + // id: 111111, + // transaction_id: 'xxxx', + // } + // + // { + // "id": 3386432, + // "type": 14, + // "amount": "863.21332500", + // "status": 2, + // "address": "rE1sdh25BJQ3qFwngiTBwaq3zPGGYcrjp1?dt=1455", + // "currency": "XRP", + // "datetime": "2018-01-05 15:27:55", + // "transaction_id": "001743B03B0C79BA166A064AC0142917B050347B4CB23BA2AB4B91B3C5608F4C" + // } + // + const timestamp = this.parse8601 (this.safeString (transaction, 'datetime')); + const id = this.safeString (transaction, 'id'); + const currencyId = this.getCurrencyIdFromTransaction (transaction); + const code = this.safeCurrencyCode (currencyId, currency); + const feeCost = this.safeFloat (transaction, 'fee'); + let feeCurrency = undefined; + let amount = undefined; + if ('amount' in transaction) { + amount = this.safeFloat (transaction, 'amount'); + } else if (currency !== undefined) { + amount = this.safeFloat (transaction, currency['id'], amount); + feeCurrency = currency['code']; + } else if ((code !== undefined) && (currencyId !== undefined)) { + amount = this.safeFloat (transaction, currencyId, amount); + feeCurrency = code; + } + if (amount !== undefined) { + // withdrawals have a negative amount + amount = Math.abs (amount); + } + let status = 'ok'; + if ('status' in transaction) { + status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + } + let type = undefined; + if ('type' in transaction) { + // from fetchTransactions + const rawType = this.safeString (transaction, 'type'); + if (rawType === '0') { + type = 'deposit'; + } else if (rawType === '1') { + type = 'withdrawal'; + } + } else { + // from fetchWithdrawals + type = 'withdrawal'; + } + const txid = this.safeString (transaction, 'transaction_id'); + let tag = undefined; + let address = this.safeString (transaction, 'address'); + if (address !== undefined) { + // dt (destination tag) is embedded into the address field + const addressParts = address.split ('?dt='); + const numParts = addressParts.length; + if (numParts > 1) { + address = addressParts[0]; + tag = addressParts[1]; + } + } + const addressFrom = undefined; + const addressTo = address; + const tagFrom = undefined; + const tagTo = tag; + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'currency': feeCurrency, + 'cost': feeCost, + 'rate': undefined, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': tagFrom, + 'tagTo': tagTo, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + // withdrawals: + // 0 (open), 1 (in process), 2 (finished), 3 (canceled) or 4 (failed). + const statuses = { + '0': 'pending', // Open + '1': 'pending', // In process + '2': 'ok', // Finished + '3': 'canceled', // Canceled + '4': 'failed', // Failed + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // from fetch order: + // { status: 'Finished', + // id: 731693945, + // transactions: + // [ { fee: '0.000019', + // price: '0.00015803', + // datetime: '2018-01-07 10:45:34.132551', + // btc: '0.0079015000000000', + // tid: 42777395, + // type: 2, + // xrp: '50.00000000' } ] } + // + // partially filled order: + // { "id": 468646390, + // "status": "Canceled", + // "transactions": [{ + // "eth": "0.23000000", + // "fee": "0.09", + // "tid": 25810126, + // "usd": "69.8947000000000000", + // "type": 2, + // "price": "303.89000000", + // "datetime": "2017-11-11 07:22:20.710567" + // }]} + // + // from create order response: + // { + // price: '0.00008012', + // currency_pair: 'XRP/BTC', + // datetime: '2019-01-31 21:23:36', + // amount: '15.00000000', + // type: '0', + // id: '2814205012' + // } + // + const id = this.safeString (order, 'id'); + let side = this.safeString (order, 'type'); + if (side !== undefined) { + side = (side === '1') ? 'sell' : 'buy'; + } + // there is no timestamp from fetchOrder + const timestamp = this.parse8601 (this.safeString (order, 'datetime')); + let lastTradeTimestamp = undefined; + let symbol = undefined; + let marketId = this.safeStringLower (order, 'currency_pair'); + if (marketId !== undefined) { + marketId = marketId.replace ('/', ''); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } + } + let amount = this.safeFloat (order, 'amount'); + let filled = 0.0; + const trades = []; + const transactions = this.safeValue (order, 'transactions', []); + let feeCost = undefined; + let cost = undefined; + const numTransactions = transactions.length; + if (numTransactions > 0) { + feeCost = 0.0; + for (let i = 0; i < numTransactions; i++) { + const trade = this.parseTrade (this.extend ({ + 'order_id': id, + 'side': side, + }, transactions[i]), market); + filled = this.sum (filled, trade['amount']); + feeCost = this.sum (feeCost, trade['fee']['cost']); + if (cost === undefined) { + cost = 0.0; + } + cost = this.sum (cost, trade['cost']); + trades.push (trade); + } + lastTradeTimestamp = trades[numTransactions - 1]['timestamp']; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + if ((status === 'closed') && (amount === undefined)) { + amount = filled; + } + let remaining = undefined; + if (amount !== undefined) { + remaining = amount - filled; + } + let price = this.safeFloat (order, 'price'); + if (market === undefined) { + market = this.getMarketFromTrades (trades); + } + let feeCurrency = undefined; + if (market !== undefined) { + if (symbol === undefined) { + symbol = market['symbol']; + } + feeCurrency = market['quote']; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } else if (price === undefined) { + if (filled > 0) { + price = cost / filled; + } + } + let fee = undefined; + if (feeCost !== undefined) { + if (feeCurrency !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + parseLedgerEntryType (type) { + const types = { + '0': 'transaction', + '1': 'transaction', + '2': 'trade', + '14': 'transfer', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // [ + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1234567894, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-08 09:00:31", + // "type": "1", + // "xrp": "-20.00000000", + // "eur": 0, + // }, + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1134567891, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-07 18:47:52", + // "type": "0", + // "xrp": "20.00000000", + // "eur": 0, + // }, + // ] + // + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + if (type === 'trade') { + const parsedTrade = this.parseTrade (item); + let market = undefined; + const keys = Object.keys (item); + for (let i = 0; i < keys.length; i++) { + if (keys[i].indexOf ('_') >= 0) { + const marketId = keys[i].replace ('_', ''); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + } + // if the market is still not defined + // try to deduce it from used keys + if (market === undefined) { + market = this.getMarketFromTrade (item); + } + const direction = parsedTrade['side'] === 'buy' ? 'in' : 'out'; + return { + 'id': parsedTrade['id'], + 'info': item, + 'timestamp': parsedTrade['timestamp'], + 'datetime': parsedTrade['datetime'], + 'direction': direction, + 'account': undefined, + 'referenceId': parsedTrade['order'], + 'referenceAccount': undefined, + 'type': type, + 'currency': market['base'], + 'amount': parsedTrade['amount'], + 'before': undefined, + 'after': undefined, + 'status': 'ok', + 'fee': parsedTrade['fee'], + }; + } else { + const parsedTransaction = this.parseTransaction (item); + let direction = undefined; + if ('amount' in item) { + const amount = this.safeFloat (item, 'amount'); + direction = amount > 0 ? 'in' : 'out'; + } else if (('currency' in parsedTransaction) && parsedTransaction['currency'] !== undefined) { + const currencyId = this.currencyId (parsedTransaction['currency']); + const amount = this.safeFloat (item, currencyId); + direction = amount > 0 ? 'in' : 'out'; + } + return { + 'id': parsedTransaction['id'], + 'info': item, + 'timestamp': parsedTransaction['timestamp'], + 'datetime': parsedTransaction['datetime'], + 'direction': direction, + 'account': undefined, + 'referenceId': parsedTransaction['txid'], + 'referenceAccount': undefined, + 'type': type, + 'currency': parsedTransaction['currency'], + 'amount': parsedTransaction['amount'], + 'before': undefined, + 'after': undefined, + 'status': parsedTransaction['status'], + 'fee': parsedTransaction['fee'], + }; + } + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostUserTransactions (this.extend (request, params)); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseLedger (response, currency, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + let market = undefined; + await this.loadMarkets (); + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privatePostOpenOrdersAll (params); + // [ + // { + // price: '0.00008012', + // currency_pair: 'XRP/BTC', + // datetime: '2019-01-31 21:23:36', + // amount: '15.00000000', + // type: '0', + // id: '2814205012', + // } + // ] + // + return this.parseOrders (response, market, since, limit, { + 'status': 'open', + 'type': 'limit', + }); + } + + getCurrencyName (code) { + if (code === 'BTC') { + return 'bitcoin'; + } + return code.toLowerCase (); + } + + isFiat (code) { + return code === 'USD' || code === 'EUR' || code === 'GBP'; + } + + async fetchDepositAddress (code, params = {}) { + if (this.isFiat (code)) { + throw new NotSupported (this.id + ' fiat fetchDepositAddress() for ' + code + ' is not supported!'); + } + const name = this.getCurrencyName (code); + const v1 = (code === 'BTC'); + let method = v1 ? 'v1' : 'private'; // v1 or v2 + method += 'Post' + this.capitalize (name); + method += v1 ? 'Deposit' : ''; + method += 'Address'; + let response = await this[method] (params); + if (v1) { + response = JSON.parse (response); + } + const address = v1 ? response : this.safeString (response, 'address'); + const tag = v1 ? undefined : this.safeString2 (response, 'memo_id', 'destination_tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // For fiat withdrawals please provide all required additional parameters in the 'params' + // Check https://www.bitstamp.net/api/ under 'Open bank withdrawal' for list and description. + await this.loadMarkets (); + this.checkAddress (address); + const request = { + 'amount': amount, + }; + let method = undefined; + if (!this.isFiat (code)) { + const name = this.getCurrencyName (code); + const v1 = (code === 'BTC'); + method = v1 ? 'v1' : 'private'; // v1 or v2 + method += 'Post' + this.capitalize (name) + 'Withdrawal'; + if (code === 'XRP') { + if (tag !== undefined) { + request['destination_tag'] = tag; + } + } + request['address'] = address; + } else { + method = 'privatePostWithdrawalOpen'; + const currency = this.currency (code); + request['iban'] = address; + request['account_currency'] = currency['id']; + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + if (api !== 'v1') { + url += this.version + '/'; + } + url += this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const authVersion = this.safeValue (this.options, 'auth', 'v2'); + if ((authVersion === 'v1') || (api === 'v1')) { + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret))); + query = this.extend ({ + 'key': this.apiKey, + 'signature': signature.toUpperCase (), + 'nonce': nonce, + }, query); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } else { + const xAuth = 'BITSTAMP ' + this.apiKey; + const xAuthNonce = this.uuid (); + const xAuthTimestamp = this.milliseconds ().toString (); + const xAuthVersion = 'v2'; + let contentType = ''; + headers = { + 'X-Auth': xAuth, + 'X-Auth-Nonce': xAuthNonce, + 'X-Auth-Timestamp': xAuthTimestamp, + 'X-Auth-Version': xAuthVersion, + }; + if (method === 'POST') { + if (Object.keys (query).length) { + body = this.urlencode (query); + contentType = 'application/x-www-form-urlencoded'; + headers['Content-Type'] = contentType; + } else { + // sending an empty POST request will trigger + // an API0020 error returned by the exchange + // therefore for empty requests we send a dummy object + // https://github.com/ccxt/ccxt/issues/6846 + body = this.urlencode ({ 'foo': 'bar' }); + contentType = 'application/x-www-form-urlencoded'; + headers['Content-Type'] = contentType; + } + } + const authBody = body ? body : ''; + const auth = xAuth + method + url.replace ('https://', '') + contentType + xAuthNonce + xAuthTimestamp + xAuthVersion + authBody; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers['X-Auth-Signature'] = signature; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error": "No permission found"} // fetchDepositAddress returns this on apiKeys that don't have the permission required + // {"status": "error", "reason": {"__all__": ["Minimum order size is 5.0 EUR."]}} + // reuse of a nonce gives: { status: 'error', reason: 'Invalid nonce', code: 'API0004' } + const status = this.safeString (response, 'status'); + const error = this.safeValue (response, 'error'); + if ((status === 'error') || (error !== undefined)) { + let errors = []; + if (typeof error === 'string') { + errors.push (error); + } else if (error !== undefined) { + const keys = Object.keys (error); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = this.safeValue (error, key); + if (Array.isArray (value)) { + errors = this.arrayConcat (errors, value); + } else { + errors.push (value); + } + } + } + const reason = this.safeValue (response, 'reason', {}); + if (typeof reason === 'string') { + errors.push (reason); + } else { + const all = this.safeValue (reason, '__all__', []); + for (let i = 0; i < all.length; i++) { + errors.push (all[i]); + } + } + const code = this.safeString (response, 'code'); + if (code === 'API0005') { + throw new AuthenticationError (this.id + ' invalid signature, use the uid for the main account if you have subaccounts'); + } + const feedback = this.id + ' ' + body; + for (let i = 0; i < errors.length; i++) { + const value = errors[i]; + this.throwExactlyMatchedException (this.exceptions['exact'], value, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], value, feedback); + } + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],42:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitstamp1 extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitstamp1', + 'name': 'Bitstamp', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMyTrades': true, + 'fetchOrder': false, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg', + 'api': 'https://www.bitstamp.net/api', + 'www': 'https://www.bitstamp.net', + 'doc': 'https://www.bitstamp.net/api', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'ticker', + 'ticker_hour', + 'order_book', + 'transactions', + 'eur_usd', + ], + }, + 'private': { + 'post': [ + 'balance', + 'user_transactions', + 'open_orders', + 'order_status', + 'cancel_order', + 'cancel_all_orders', + 'buy', + 'sell', + 'bitcoin_deposit_address', + 'unconfirmed_btc', + 'ripple_withdrawal', + 'ripple_address', + 'withdrawal_requests', + 'bitcoin_withdrawal', + ], + }, + }, + 'markets': { + 'BTC/USD': { 'id': 'btcusd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 }, + 'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 }, + 'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 }, + }, + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + if (symbol !== 'BTC/USD') { + throw new ExchangeError (this.id + ' ' + this.version + " fetchOrderBook doesn't support " + symbol + ', use it for BTC/USD only'); + } + await this.loadMarkets (); + const orderbook = await this.publicGetOrderBook (params); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + async fetchTicker (symbol, params = {}) { + if (symbol !== 'BTC/USD') { + throw new ExchangeError (this.id + ' ' + this.version + " fetchTicker doesn't support " + symbol + ', use it for BTC/USD only'); + } + await this.loadMarkets (); + const ticker = await this.publicGetTicker (params); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp2 (trade, 'date', 'datetime'); + const side = (trade['type'] === 0) ? 'buy' : 'sell'; + const orderId = this.safeString (trade, 'order_id'); + if ('currency_pair' in trade) { + if (trade['currency_pair'] in this.markets_by_id) { + market = this.markets_by_id[trade['currency_pair']]; + } + } + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + if (symbol !== 'BTC/USD') { + throw new BadSymbol (this.id + ' ' + this.version + " fetchTrades doesn't support " + symbol + ', use it for BTC/USD only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'time': 'minute', + }; + const response = await this.publicGetTransactions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + const balance = await this.privatePostBalance (params); + const result = { 'info': balance }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const account = this.account (); + account['free'] = this.safeFloat (balance, currencyId + '_available'); + account['used'] = this.safeFloat (balance, currencyId + '_reserved'); + account['total'] = this.safeFloat (balance, currencyId + '_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new ExchangeError (this.id + ' ' + this.version + ' accepts limit orders only'); + } + if (symbol !== 'BTC/USD') { + throw new ExchangeError (this.id + ' v1 supports BTC/USD orders only'); + } + await this.loadMarkets (); + const method = 'privatePost' + this.capitalize (side); + const request = { + 'amount': amount, + 'price': price, + }; + const response = await this[method] (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + return await this.privatePostCancelOrder ({ 'id': id }); + } + + parseOrderStatus (status) { + const statuses = { + 'In Queue': 'open', + 'Open': 'open', + 'Finished': 'closed', + 'Canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrderStatus (response); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const pair = market ? market['id'] : 'all'; + const request = { + 'id': pair, + }; + const response = await this.privatePostOpenOrdersId (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOrder is not implemented yet'); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret))); + query = this.extend ({ + 'key': this.apiKey, + 'signature': signature.toUpperCase (), + 'nonce': nonce, + }, query); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('status' in response) { + if (response['status'] === 'error') { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],43:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError, ExchangeNotAvailable, AuthenticationError, InvalidOrder, InsufficientFunds, OrderNotFound, DDoSProtection, PermissionDenied, AddressPending, OnMaintenance, BadRequest } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bittrex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bittrex', + 'name': 'Bittrex', + 'countries': [ 'US' ], + 'version': 'v3', + 'rateLimit': 1500, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDeposits': true, + 'fetchDepositAddress': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': 'emulated', + 'fetchOHLCV': true, + 'fetchOrder': true, + 'fetchOrderTrades': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'MINUTE_1', + '5m': 'MINUTE_5', + '1h': 'HOUR_1', + '1d': 'DAY_1', + }, + 'hostname': 'bittrex.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg', + 'api': { + 'public': 'https://api.bittrex.com', + 'private': 'https://api.bittrex.com', + }, + 'www': 'https://bittrex.com', + 'doc': [ + 'https://bittrex.github.io/api/v3', + ], + 'fees': [ + 'https://bittrex.zendesk.com/hc/en-us/articles/115003684371-BITTREX-SERVICE-FEES-AND-WITHDRAWAL-LIMITATIONS', + 'https://bittrex.zendesk.com/hc/en-us/articles/115000199651-What-fees-does-Bittrex-charge-', + ], + 'referral': 'https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B', + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'currencies', + 'currencies/{symbol}', + 'markets', + 'markets/tickers', + 'markets/summaries', + 'markets/{marketSymbol}', + 'markets/{marketSymbol}/summary', + 'markets/{marketSymbol}/orderbook', + 'markets/{marketSymbol}/trades', + 'markets/{marketSymbol}/ticker', + 'markets/{marketSymbol}/candles/{candleInterval}/recent', + 'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}/{month}/{day}', + 'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}/{month}', + 'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}', + ], + }, + 'private': { + 'get': [ + 'account', + 'account/volume', + 'addresses', + 'addresses/{currencySymbol}', + 'balances', + 'balances/{currencySymbol}', + 'deposits/open', + 'deposits/closed', + 'deposits/ByTxId/{txId}', + 'deposits/{depositId}', + 'orders/closed', + 'orders/open', + 'orders/{orderId}', + 'orders/{orderId}/executions', + 'ping', + 'subaccounts/{subaccountId}', + 'subaccounts', + 'withdrawals/open', + 'withdrawals/closed', + 'withdrawals/ByTxId/{txId}', + 'withdrawals/{withdrawalId}', + 'withdrawals/whitelistAddresses', + 'conditional-orders/{conditionalOrderId}', + 'conditional-orders/closed', + 'conditional-orders/open', + 'transfers/sent', + 'transfers/received', + 'transfers/{transferId}', + ], + 'post': [ + 'addresses', + 'orders', + 'subaccounts', + 'withdrawals', + 'conditional-orders', + 'transfers', + ], + 'delete': [ + 'orders/open', + 'orders/{orderId}', + 'withdrawals/{withdrawalId}', + 'conditional-orders/{conditionalOrderId}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0025, + 'taker': 0.0025, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + }, + }, + 'exceptions': { + 'exact': { + 'BAD_REQUEST': BadRequest, // {"code":"BAD_REQUEST","detail":"Refer to the data field for specific field validation failures.","data":{"invalidRequestParameter":"day"}} + 'STARTDATE_OUT_OF_RANGE': BadRequest, // {"code":"STARTDATE_OUT_OF_RANGE"} + // 'Call to Cancel was throttled. Try again in 60 seconds.': DDoSProtection, + // 'Call to GetBalances was throttled. Try again in 60 seconds.': DDoSProtection, + 'APISIGN_NOT_PROVIDED': AuthenticationError, + 'INVALID_SIGNATURE': AuthenticationError, + 'INVALID_CURRENCY': ExchangeError, + 'INVALID_PERMISSION': AuthenticationError, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'INVALID_CEILING_MARKET_BUY': InvalidOrder, + 'INVALID_FIAT_ACCOUNT': InvalidOrder, + 'INVALID_ORDER_TYPE': InvalidOrder, + 'QUANTITY_NOT_PROVIDED': InvalidOrder, + 'MIN_TRADE_REQUIREMENT_NOT_MET': InvalidOrder, + 'ORDER_NOT_OPEN': OrderNotFound, + 'INVALID_ORDER': InvalidOrder, + 'UUID_INVALID': OrderNotFound, + 'RATE_NOT_PROVIDED': InvalidOrder, // createLimitBuyOrder ('ETH/BTC', 1, 0) + 'INVALID_MARKET': BadSymbol, // {"success":false,"message":"INVALID_MARKET","result":null,"explanation":null} + 'WHITELIST_VIOLATION_IP': PermissionDenied, + 'DUST_TRADE_DISALLOWED_MIN_VALUE': InvalidOrder, + 'RESTRICTED_MARKET': BadSymbol, + 'We are down for scheduled maintenance, but we\u2019ll be back up shortly.': OnMaintenance, // {"success":false,"message":"We are down for scheduled maintenance, but we\u2019ll be back up shortly.","result":null,"explanation":null} + }, + 'broad': { + 'throttled': DDoSProtection, + 'problem': ExchangeNotAvailable, + }, + }, + 'options': { + 'fetchTicker': { + 'method': 'publicGetMarketsMarketSymbolTicker', // publicGetMarketsMarketSymbolSummary + }, + 'fetchTickers': { + 'method': 'publicGetMarketsTickers', // publicGetMarketsSummaries + }, + 'parseOrderStatus': false, + 'hasAlreadyAuthenticatedSuccessfully': false, // a workaround for APIKEY_INVALID + // With certain currencies, like + // AEON, BTS, GXS, NXT, SBD, STEEM, STR, XEM, XLM, XMR, XRP + // an additional tag / memo / payment id is usually required by exchanges. + // With Bittrex some currencies imply the "base address + tag" logic. + // The base address for depositing is stored on this.currencies[code] + // The base address identifies the exchange as the recipient + // while the tag identifies the user account within the exchange + // and the tag is retrieved with fetchDepositAddress. + 'tag': { + 'NXT': true, // NXT, BURST + 'CRYPTO_NOTE_PAYMENTID': true, // AEON, XMR + 'BITSHAREX': true, // BTS + 'RIPPLE': true, // XRP + 'NEM': true, // XEM + 'STELLAR': true, // XLM + 'STEEM': true, // SBD, GOLOS + // https://github.com/ccxt/ccxt/issues/4794 + // 'LISK': true, // LSK + }, + 'subaccountId': undefined, + // see the implementation of fetchClosedOrdersV3 below + // 'fetchClosedOrdersMethod': 'fetch_closed_orders_v3', + 'fetchClosedOrdersFilterBySince': true, + // 'createOrderMethod': 'create_order_v1', + }, + 'commonCurrencies': { + 'REPV2': 'REP', + }, + }); + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); + } + + feeToPrecision (symbol, fee) { + return this.decimalToPrecision (fee, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "symbol":"LTC-BTC", + // "baseCurrencySymbol":"LTC", + // "quoteCurrencySymbol":"BTC", + // "minTradeSize":"0.01686767", + // "precision":8, + // "status":"ONLINE", // "OFFLINE" + // "createdAt":"2014-02-13T00:00:00Z" + // }, + // { + // "symbol":"VDX-USDT", + // "baseCurrencySymbol":"VDX", + // "quoteCurrencySymbol":"USDT", + // "minTradeSize":"300.00000000", + // "precision":8, + // "status":"ONLINE", // "OFFLINE" + // "createdAt":"2019-05-23T00:41:21.843Z", + // "notice":"USDT has swapped to an ERC20-based token as of August 5, 2019." + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const baseId = this.safeString (market, 'baseCurrencySymbol'); + const quoteId = this.safeString (market, 'quoteCurrencySymbol'); + const id = this.safeString (market, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const pricePrecision = this.safeInteger (market, 'precision', 8); + const precision = { + 'amount': 8, + 'price': pricePrecision, + }; + const status = this.safeString (market, 'status'); + const active = (status === 'ONLINE'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minTradeSize'), + 'max': undefined, + }, + 'price': { + 'min': 1 / Math.pow (10, precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privateGetBalances (params); + const result = { 'info': balances }; + const indexed = this.indexBy (balances, 'currencySymbol'); + const currencyIds = Object.keys (indexed); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + const balance = indexed[currencyId]; + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'marketSymbol': this.marketId (symbol), + }; + if (limit !== undefined) { + if ((limit !== 1) && (limit !== 25) && (limit !== 500)) { + throw new BadRequest (this.id + ' fetchOrderBook() limit argument must be undefined, 1, 25 or 500, default is 25'); + } + request['depth'] = limit; + } + const response = await this.publicGetMarketsMarketSymbolOrderbook (this.extend (request, params)); + // + // { + // "bid":[ + // {"quantity":"0.01250000","rate":"10718.56200003"}, + // {"quantity":"0.10000000","rate":"10718.56200002"}, + // {"quantity":"0.39648292","rate":"10718.56200001"}, + // ], + // "ask":[ + // {"quantity":"0.05100000","rate":"10724.30099631"}, + // {"quantity":"0.10000000","rate":"10724.30099632"}, + // {"quantity":"0.26000000","rate":"10724.30099634"}, + // ] + // } + // + const sequence = this.safeInteger (this.last_response_headers, 'Sequence'); + const orderbook = this.parseOrderBook (response, undefined, 'bid', 'ask', 'rate', 'quantity'); + orderbook['nonce'] = sequence; + return orderbook; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // "symbol":"1ST", + // "name":"Firstblood", + // "coinType":"ETH_CONTRACT", + // "status":"ONLINE", + // "minConfirmations":36, + // "notice":"", + // "txFee":"4.50000000", + // "logoUrl":"https://bittrexblobstorage.blob.core.windows.net/public/5685a7be-1edf-4ba0-a313-b5309bb204f8.png", + // "prohibitedIn":[], + // "baseAddress":"0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98", + // "associatedTermsOfService":[] + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const precision = 8; // default precision, todo: fix "magic constants" + const fee = this.safeFloat (currency, 'txFee'); // todo: redesign + const isActive = this.safeString (currency, 'status'); + result[code] = { + 'id': id, + 'code': code, + 'address': this.safeString (currency, 'baseAddress'), + 'info': currency, + 'type': this.safeString (currency, 'coinType'), + 'name': this.safeString (currency, 'name'), + 'active': (isActive === 'ONLINE'), + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'price': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': fee, + 'max': undefined, + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // ticker + // + // { + // "symbol":"ETH-BTC", + // "lastTradeRate":"0.03284496", + // "bidRate":"0.03284523", + // "askRate":"0.03286857" + // } + // + // summary + // + // { + // "symbol":"ETH-BTC", + // "high":"0.03369528", + // "low":"0.03282442", + // "volume":"4307.83794556", + // "quoteVolume":"143.08608869", + // "percentChange":"0.79", + // "updatedAt":"2020-09-29T07:36:57.823Z" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'updatedAt')); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const percentage = this.safeFloat (ticker, 'percentChange'); + const last = this.safeFloat (ticker, 'lastTradeRate'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bidRate'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'askRate'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchTickers', {}); + const defaultMethod = this.safeString (options, 'method', 'publicGetMarketsTickers'); + const method = this.safeString (params, 'method', defaultMethod); + params = this.omit (params, 'method'); + const response = await this[method] (params); + // + // publicGetMarketsTickers + // + // [ + // { + // "symbol":"4ART-BTC", + // "lastTradeRate":"0.00000210", + // "bidRate":"0.00000210", + // "askRate":"0.00000215" + // } + // ] + // + // publicGetMarketsSummaries + // + // [ + // { + // "symbol":"4ART-BTC", + // "high":"0.00000206", + // "low":"0.00000196", + // "volume":"14871.32000233", + // "quoteVolume":"0.02932756", + // "percentChange":"1.48", + // "updatedAt":"2020-09-29T07:34:32.757Z" + // } + // ] + // + const tickers = []; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + tickers.push (ticker); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketSymbol': market['id'], + }; + const options = this.safeValue (this.options, 'fetchTicker', {}); + const defaultMethod = this.safeString (options, 'method', 'publicGetMarketsMarketSymbolTicker'); + const method = this.safeString (params, 'method', defaultMethod); + params = this.omit (params, 'method'); + const response = await this[method] (this.extend (request, params)); + // + // publicGetMarketsMarketSymbolTicker + // + // { + // "symbol":"ETH-BTC", + // "lastTradeRate":"0.03284496", + // "bidRate":"0.03284523", + // "askRate":"0.03286857" + // } + // + // + // publicGetMarketsMarketSymbolSummary + // + // { + // "symbol":"ETH-BTC", + // "high":"0.03369528", + // "low":"0.03282442", + // "volume":"4307.83794556", + // "quoteVolume":"143.08608869", + // "percentChange":"0.79", + // "updatedAt":"2020-09-29T07:36:57.823Z" + // } + // + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "id":"9c5589db-42fb-436c-b105-5e2edcb95673", + // "executedAt":"2020-10-03T11:48:43.38Z", + // "quantity":"0.17939626", + // "rate":"0.03297952", + // "takerSide":"BUY" + // } + // + // private fetchOrderTrades + // + // { + // "id": "aaa3e9bd-5b86-4a21-8b3d-1275c1d30b8e", + // "marketSymbol": "OMG-BTC", + // "executedAt": "2020-10-02T16:00:30.3Z", + // "quantity": "7.52710000", + // "rate": "0.00034907", + // "orderId": "3a3dbd33-3a30-4ae5-a41d-68d3c1ac537e", + // "commission": "0.00000525", + // "isTaker": false + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'executedAt')); + const id = this.safeString (trade, 'id'); + const order = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'marketSymbol'); + market = this.safeMarket (marketId, market, '-'); + let cost = undefined; + const price = this.safeFloat (trade, 'rate'); + const amount = this.safeFloat (trade, 'quantity'); + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + let takerOrMaker = undefined; + const isTaker = this.safeValue (trade, 'isTaker'); + if (isTaker !== undefined) { + takerOrMaker = isTaker ? 'taker' : 'maker'; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + const side = this.safeStringLower (trade, 'takerSide'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'id': id, + 'order': order, + 'takerOrMaker': takerOrMaker, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { + // "serverTime": 1594596023162 + // } + // + return this.safeInteger (response, 'serverTime'); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketSymbol': this.marketId (symbol), + }; + const response = await this.publicGetMarketsMarketSymbolTrades (this.extend (request, params)); + // + // [ + // { + // "id":"9c5589db-42fb-436c-b105-5e2edcb95673", + // "executedAt":"2020-10-03T11:48:43.38Z", + // "quantity":"0.17939626", + // "rate":"0.03297952", + // "takerSide":"BUY" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "startsAt":"2020-06-12T02:35:00Z", + // "open":"0.02493753", + // "high":"0.02493753", + // "low":"0.02493753", + // "close":"0.02493753", + // "volume":"0.09590123", + // "quoteVolume":"0.00239153" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'startsAt')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const reverseId = market['baseId'] + '-' + market['quoteId']; + const request = { + 'candleInterval': this.timeframes[timeframe], + 'marketSymbol': reverseId, + }; + let method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalRecent'; + if (since !== undefined) { + const now = this.milliseconds (); + const difference = Math.abs (now - since); + const sinceDate = this.ymd (since); + const parts = sinceDate.split ('-'); + const sinceYear = this.safeInteger (parts, 0); + const sinceMonth = this.safeInteger (parts, 1); + const sinceDay = this.safeInteger (parts, 2); + if (timeframe === '1d') { + // if the since argument is beyond one year into the past + if (difference > 31622400000) { + method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYear'; + request['year'] = sinceYear; + } + // request['year'] = year; + } else if (timeframe === '1h') { + // if the since argument is beyond 31 days into the past + if (difference > 2678400000) { + method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYearMonth'; + request['year'] = sinceYear; + request['month'] = sinceMonth; + } + } else { + // if the since argument is beyond 1 day into the past + if (difference > 86400000) { + method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYearMonthDay'; + request['year'] = sinceYear; + request['month'] = sinceMonth; + request['day'] = sinceDay; + } + } + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // {"startsAt":"2020-06-12T02:35:00Z","open":"0.02493753","high":"0.02493753","low":"0.02493753","close":"0.02493753","volume":"0.09590123","quoteVolume":"0.00239153"}, + // {"startsAt":"2020-06-12T02:40:00Z","open":"0.02491874","high":"0.02491874","low":"0.02490970","close":"0.02490970","volume":"0.04515695","quoteVolume":"0.00112505"}, + // {"startsAt":"2020-06-12T02:45:00Z","open":"0.02490753","high":"0.02493143","low":"0.02490753","close":"0.02493143","volume":"0.17769640","quoteVolume":"0.00442663"} + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketSymbol'] = market['id']; + } + const response = await this.privateGetOrdersOpen (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateGetOrdersOrderIdExecutions (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + // A ceiling order is a market or limit order that allows you to specify + // the amount of quote currency you want to spend (or receive, if selling) + // instead of the quantity of the market currency (e.g. buy $100 USD of BTC + // at the current market BTC price) + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const reverseId = market['baseId'] + '-' + market['quoteId']; + const request = { + 'marketSymbol': reverseId, + 'direction': side.toUpperCase (), + 'type': uppercaseType, // LIMIT, MARKET, CEILING_LIMIT, CEILING_MARKET + // 'quantity': this.amountToPrecision (symbol, amount), // required for limit orders, excluded for ceiling orders + // 'ceiling': this.priceToPrecision (symbol, price), // required for ceiling orders, excluded for non-ceiling orders + // 'limit': this.priceToPrecision (symbol, price), // required for limit orders, excluded for market orders + // 'timeInForce': 'GOOD_TIL_CANCELLED', // IMMEDIATE_OR_CANCEL, FILL_OR_KILL, POST_ONLY_GOOD_TIL_CANCELLED + // 'useAwards': false, // optional + }; + const isCeilingLimit = (uppercaseType === 'CEILING_LIMIT'); + const isCeilingMarket = (uppercaseType === 'CEILING_MARKET'); + const isCeilingOrder = isCeilingLimit || isCeilingMarket; + if (isCeilingOrder) { + request['ceiling'] = this.priceToPrecision (symbol, price); + // bittrex only accepts IMMEDIATE_OR_CANCEL or FILL_OR_KILL for ceiling orders + request['timeInForce'] = 'IMMEDIATE_OR_CANCEL'; + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + if (uppercaseType === 'LIMIT') { + request['limit'] = this.priceToPrecision (symbol, price); + request['timeInForce'] = 'GOOD_TIL_CANCELLED'; + } else { + // bittrex does not allow GOOD_TIL_CANCELLED for market orders + request['timeInForce'] = 'IMMEDIATE_OR_CANCEL'; + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // id: 'f03d5e98-b5ac-48fb-8647-dd4db828a297', + // marketSymbol: 'BTC-USDT', + // direction: 'SELL', + // type: 'LIMIT', + // quantity: '0.01', + // limit: '6000', + // timeInForce: 'GOOD_TIL_CANCELLED', + // fillQuantity: '0.00000000', + // commission: '0.00000000', + // proceeds: '0.00000000', + // status: 'OPEN', + // createdAt: '2020-03-18T02:37:33.42Z', + // updatedAt: '2020-03-18T02:37:33.42Z' + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateDeleteOrdersOrderId (this.extend (request, params)); + return this.extend (this.parseOrder (response), { + 'id': id, + 'info': response, + 'status': 'canceled', + }); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketSymbol'] = market['id']; + } + const response = await this.privateDeleteOrdersOpen (this.extend (request, params)); + // + // [ + // { + // "id":"66582be0-5337-4d8c-b212-c356dd525801", + // "statusCode":"SUCCESS", + // "result":{ + // "id":"66582be0-5337-4d8c-b212-c356dd525801", + // "marketSymbol":"BTC-USDT", + // "direction":"BUY", + // "type":"LIMIT", + // "quantity":"0.01000000", + // "limit":"3000.00000000", + // "timeInForce":"GOOD_TIL_CANCELLED", + // "fillQuantity":"0.00000000", + // "commission":"0.00000000", + // "proceeds":"0.00000000", + // "status":"CLOSED", + // "createdAt":"2020-10-06T12:31:53.39Z", + // "updatedAt":"2020-10-06T12:54:28.8Z", + // "closedAt":"2020-10-06T12:54:28.8Z" + // } + // } + // ] + // + const orders = []; + for (let i = 0; i < response.length; i++) { + const result = this.safeValue (response[i], 'result', {}); + orders.push (result); + } + return this.parseOrders (orders, market); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // https://support.bittrex.com/hc/en-us/articles/115003723911 + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currencySymbol'] = currency['id']; + } + const response = await this.privateGetDepositsClosed (this.extend (request, params)); + // we cannot filter by `since` timestamp, as it isn't set by Bittrex + // see https://github.com/ccxt/ccxt/issues/4067 + // return this.parseTransactions (response, currency, since, limit); + return this.parseTransactions (response, currency, undefined, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // https://support.bittrex.com/hc/en-us/articles/115003723911 + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currencySymbol'] = currency['id']; + } + const response = await this.privateGetWithdrawalsClosed (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // { + // "id": "d00fdf2e-df9e-48f1-....", + // "currencySymbol": "BTC", + // "quantity": "0.00550000", + // "cryptoAddress": "1PhmYjnJPZH5NUwV8AU...", + // "txId": "d1f1afffe1b9b6614eaee7e8133c85d98...", + // "confirmations": 2, + // "updatedAt": "2020-01-12T16:49:30.41Z", + // "completedAt": "2020-01-12T16:49:30.41Z", + // "status": "COMPLETED", + // "source": "BLOCKCHAIN" + // } + // + // fetchWithdrawals + // { + // "PaymentUuid" : "e293da98-788c-4188-a8f9-8ec2c33fdfcf", + // "Currency" : "XC", + // "Amount" : 7513.75121715, + // "Address" : "EVnSMgAd7EonF2Dgc4c9K14L12RBaW5S5J", + // "Opened" : "2014-07-08T23:13:31.83", + // "Authorized" : true, + // "PendingPayment" : false, + // "TxCost" : 0.00002000, + // "TxId" : "b4a575c2a71c7e56d02ab8e26bb1ef0a2f6cf2094f6ca2116476a569c1e84f6e", + // "Canceled" : false, + // "InvalidAddress" : false + // } + // + const id = this.safeString (transaction, 'id'); + const amount = this.safeFloat (transaction, 'quantity'); + const address = this.safeString (transaction, 'cryptoAddress'); + const txid = this.safeString (transaction, 'txId'); + const updated = this.parse8601 (this.safeString (transaction, 'updatedAt')); + const opened = this.parse8601 (this.safeString (transaction, 'createdAt')); + const timestamp = opened ? opened : updated; + const type = (opened === undefined) ? 'deposit' : 'withdrawal'; + const currencyId = this.safeString (transaction, 'currencySymbol'); + const code = this.safeCurrencyCode (currencyId, currency); + let status = 'pending'; + if (type === 'deposit') { + // + // deposits numConfirmations never reach the minConfirmations number + // we set all of them to 'ok', otherwise they'd all be 'pending' + // + // const numConfirmations = this.safeInteger (transaction, 'Confirmations', 0); + // const minConfirmations = this.safeInteger (currency['info'], 'MinConfirmation'); + // if (numConfirmations >= minConfirmations) { + // status = 'ok'; + // } + // + status = 'ok'; + } else { + const responseStatus = this.safeString (transaction, 'status'); + if (responseStatus === 'ERROR_INVALID_ADDRESS') { + status = 'failed'; + } else if (responseStatus === 'CANCELLED') { + status = 'canceled'; + } else if (responseStatus === 'PENDING') { + status = 'pending'; + } else if (responseStatus === 'COMPLETED') { + status = 'ok'; + } else if (responseStatus === 'AUTHORIZED' && (txid !== undefined)) { + status = 'ok'; + } + } + let feeCost = this.safeFloat (transaction, 'txCost'); + if (feeCost === undefined) { + if (type === 'deposit') { + // according to https://support.bittrex.com/hc/en-us/articles/115000199651-What-fees-does-Bittrex-charge- + feeCost = 0; + } + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GOOD_TIL_CANCELLED': 'GTC', + 'IMMEDIATE_OR_CANCEL': 'IOC', + 'FILL_OR_KILL': 'FOK', + 'POST_ONLY_GOOD_TIL_CANCELLED': 'POST_ONLY_GOOD_TIL_CANCELLED', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + // + // { + // id: '1be35109-b763-44ce-b6ea-05b6b0735c0c', + // marketSymbol: 'LTC-ETH', + // direction: 'BUY', + // type: 'LIMIT', + // quantity: '0.50000000', + // limit: '0.17846699', + // timeInForce: 'GOOD_TIL_CANCELLED', + // fillQuantity: '0.50000000', + // commission: '0.00022286', + // proceeds: '0.08914915', + // status: 'CLOSED', + // createdAt: '2018-06-23T13:14:28.613Z', + // updatedAt: '2018-06-23T13:14:30.19Z', + // closedAt: '2018-06-23T13:14:30.19Z' + // } + // + const marketSymbol = this.safeString (order, 'marketSymbol'); + let symbol = undefined; + let feeCurrency = undefined; + if (marketSymbol !== undefined) { + const [ baseId, quoteId ] = marketSymbol.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + feeCurrency = quote; + } + const direction = this.safeStringLower (order, 'direction'); + const createdAt = this.safeString (order, 'createdAt'); + const updatedAt = this.safeString (order, 'updatedAt'); + const closedAt = this.safeString (order, 'closedAt'); + let lastTradeTimestamp = undefined; + if (closedAt !== undefined) { + lastTradeTimestamp = this.parse8601 (closedAt); + } else if (updatedAt) { + lastTradeTimestamp = this.parse8601 (updatedAt); + } + const timestamp = this.parse8601 (createdAt); + const type = this.safeStringLower (order, 'type'); + const quantity = this.safeFloat (order, 'quantity'); + const limit = this.safeFloat (order, 'limit'); + const fillQuantity = this.safeFloat (order, 'fillQuantity'); + const commission = this.safeFloat (order, 'commission'); + const proceeds = this.safeFloat (order, 'proceeds'); + let average = undefined; + let remaining = undefined; + if (fillQuantity !== undefined) { + if (proceeds !== undefined) { + if (fillQuantity > 0) { + average = proceeds / fillQuantity; + } else if (proceeds === 0) { + average = 0; + } + } + if (quantity !== undefined) { + remaining = quantity - fillQuantity; + } + } + let status = this.safeStringLower (order, 'status'); + if ((status === 'closed') && (remaining !== undefined) && (remaining > 0)) { + status = 'canceled'; + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': direction, + 'price': limit, + 'stopPrice': undefined, + 'cost': proceeds, + 'average': average, + 'amount': quantity, + 'filled': fillQuantity, + 'remaining': remaining, + 'status': status, + 'fee': { + 'cost': commission, + 'currency': feeCurrency, + }, + 'info': order, + 'trades': undefined, + }; + } + + parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { + if (this.options['fetchClosedOrdersFilterBySince']) { + return super.parseOrders (orders, market, since, limit, params); + } else { + return super.parseOrders (orders, market, undefined, limit, params); + } + } + + parseOrderStatus (status) { + const statuses = { + 'CLOSED': 'closed', + 'OPEN': 'open', + 'CANCELLED': 'canceled', + 'CANCELED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let response = undefined; + try { + const request = { + 'orderId': id, + }; + response = await this.privateGetOrdersOrderId (this.extend (request, params)); + } catch (e) { + if (this.last_json_response) { + const message = this.safeString (this.last_json_response, 'message'); + if (message === 'UUID_INVALID') { + throw new OrderNotFound (this.id + ' fetchOrder() error: ' + this.last_http_response); + } + } + throw e; + } + return this.parseOrder (response); + } + + orderToTrade (order) { + // this entire method should be moved to the base class + const timestamp = this.safeInteger2 (order, 'lastTradeTimestamp', 'timestamp'); + return { + 'id': this.safeString (order, 'id'), + 'side': this.safeString (order, 'side'), + 'order': this.safeString (order, 'id'), + 'type': this.safeString (order, 'type'), + 'price': this.safeFloat (order, 'average'), + 'amount': this.safeFloat (order, 'filled'), + 'cost': this.safeFloat (order, 'cost'), + 'symbol': this.safeString (order, 'symbol'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': this.safeValue (order, 'fee'), + 'info': order, + 'takerOrMaker': undefined, + }; + } + + ordersToTrades (orders) { + // this entire method should be moved to the base class + const result = []; + for (let i = 0; i < orders.length; i++) { + result.push (this.orderToTrade (orders[i])); + } + return result; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['pageSize'] = limit; + } + if (since !== undefined) { + request['startDate'] = this.ymdhms (since, 'T') + 'Z'; + } + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + // because of this line we will have to rethink the entire v3 + // in other words, markets define all the rest of the API + // and v3 market ids are reversed in comparison to v1 + // v3 has to be a completely separate implementation + // otherwise we will have to shuffle symbols and currencies everywhere + // which is prone to errors, as was shown here + // https://github.com/ccxt/ccxt/pull/5219#issuecomment-499646209 + request['marketSymbol'] = market['base'] + '-' + market['quote']; + } + const response = await this.privateGetOrdersClosed (this.extend (request, params)); + const orders = this.parseOrders (response, market); + const trades = this.ordersToTrades (orders); + return this.filterBySymbolSinceLimit (trades, symbol, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['pageSize'] = limit; + } + if (since !== undefined) { + request['startDate'] = this.ymdhms (since, 'T') + 'Z'; + } + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + // because of this line we will have to rethink the entire v3 + // in other words, markets define all the rest of the API + // and v3 market ids are reversed in comparison to v1 + // v3 has to be a completely separate implementation + // otherwise we will have to shuffle symbols and currencies everywhere + // which is prone to errors, as was shown here + // https://github.com/ccxt/ccxt/pull/5219#issuecomment-499646209 + request['marketSymbol'] = market['base'] + '-' + market['quote']; + } + const response = await this.privateGetOrdersClosed (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencySymbol': currency['id'], + }; + const response = await this.privateGetAddressesCurrencySymbol (this.extend (request, params)); + // { + // "status": "PROVISIONED", + // "currencySymbol": "BTC", + // "cryptoAddress": "1PhmYjnJPZH5NUwV8AUjqkeDkCBpbE2xqX" + // } + let address = this.safeString (response, 'cryptoAddress'); + const message = this.safeString (response, 'status'); + if (!address || message === 'REQUESTED') { + throw new AddressPending (this.id + ' the address for ' + code + ' is being generated (pending, not ready yet, retry again later)'); + } + let tag = undefined; + if (currency['type'] in this.options['tag']) { + tag = address; + address = currency['address']; + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencySymbol': currency['id'], + 'quantity': amount, + 'cryptoAddress': address, + }; + if (tag !== undefined) { + request['cryptoAddressTag'] = tag; + } + const response = await this.privatePostWithdrawals (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'v3', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }) + '/'; + if (api === 'private') { + url += this.version + '/'; + this.checkRequiredCredentials (); + url += this.implodeParams (path, params); + params = this.omit (params, this.extractParams (path)); + let hashString = ''; + if (method === 'POST') { + body = this.json (params); + hashString = body; + } else { + if (Object.keys (params).length) { + url += '?' + this.rawencode (params); + } + } + const contentHash = this.hash (this.encode (hashString), 'sha512', 'hex'); + const timestamp = this.milliseconds ().toString (); + let auth = timestamp + url + method + contentHash; + const subaccountId = this.safeValue (this.options, 'subaccountId'); + if (subaccountId !== undefined) { + auth += subaccountId; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'); + headers = { + 'Api-Key': this.apiKey, + 'Api-Timestamp': timestamp, + 'Api-Content-Hash': contentHash, + 'Api-Signature': signature, + }; + if (subaccountId !== undefined) { + headers['Api-Subaccount-Id'] = subaccountId; + } + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + } + } else { + if (api === 'public') { + url += this.version + '/'; + } + url += this.implodeParams (path, params); + params = this.omit (params, this.extractParams (path)); + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { success: false, message: "message" } + // + if (body[0] === '{') { + const feedback = this.id + ' ' + body; + let success = this.safeValue (response, 'success'); + if (success === undefined) { + const code = this.safeString (response, 'code'); + if (code !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], code, feedback); + } + // throw new ExchangeError (this.id + ' malformed response ' + this.json (response)); + return; + } + if (typeof success === 'string') { + // bleutrade uses string instead of boolean + success = (success === 'true'); + } + if (!success) { + const message = this.safeString (response, 'message'); + if (message === 'APIKEY_INVALID') { + if (this.options['hasAlreadyAuthenticatedSuccessfully']) { + throw new DDoSProtection (feedback); + } else { + throw new AuthenticationError (feedback); + } + } + // https://github.com/ccxt/ccxt/issues/4932 + // the following two lines are now redundant, see line 171 in describe() + // + // if (message === 'DUST_TRADE_DISALLOWED_MIN_VALUE_50K_SAT') + // throw new InvalidOrder (this.id + ' order cost should be over 50k satoshi ' + this.json (response)); + // + if (message === 'INVALID_ORDER') { + // Bittrex will return an ambiguous INVALID_ORDER message + // upon canceling already-canceled and closed orders + // therefore this special case for cancelOrder + // let url = 'https://bittrex.com/api/v1.1/market/cancel?apikey=API_KEY&uuid=ORDER_UUID' + const cancel = 'cancel'; + const indexOfCancel = url.indexOf (cancel); + if (indexOfCancel >= 0) { + const urlParts = url.split ('?'); + const numParts = urlParts.length; + if (numParts > 1) { + const query = urlParts[1]; + const params = query.split ('&'); + const numParams = params.length; + let orderId = undefined; + for (let i = 0; i < numParams; i++) { + const param = params[i]; + const keyValue = param.split ('='); + if (keyValue[0] === 'uuid') { + orderId = keyValue[1]; + break; + } + } + if (orderId !== undefined) { + throw new OrderNotFound (this.id + ' cancelOrder ' + orderId + ' ' + this.json (response)); + } else { + throw new OrderNotFound (this.id + ' cancelOrder ' + this.json (response)); + } + } + } + } + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + if (message !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],44:[function(require,module,exports){ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, InvalidAddress, BadRequest, RateLimitExceeded, PermissionDenied, ExchangeNotAvailable, AccountSuspended, OnMaintenance } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class bitvavo extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitvavo', + 'name': 'Bitvavo', + 'countries': [ 'NL' ], // Netherlands + 'rateLimit': 500, + 'version': 'v2', + 'certified': true, + 'pro': true, + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchMarkets': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/83165440-2f1cf200-a116-11ea-9046-a255d09fb2ed.jpg', + 'api': { + 'public': 'https://api.bitvavo.com', + 'private': 'https://api.bitvavo.com', + }, + 'www': 'https://bitvavo.com/', + 'doc': 'https://docs.bitvavo.com/', + 'fees': 'https://bitvavo.com/en/fees', + 'referral': 'https://bitvavo.com/?a=24F34952F7', + }, + 'api': { + 'public': { + 'get': [ + 'time', + 'markets', + 'assets', + '{market}/book', + '{market}/trades', + '{market}/candles', + 'ticker/price', + 'ticker/book', + 'ticker/24h', + ], + }, + 'private': { + 'get': [ + 'order', + 'orders', + 'ordersOpen', + 'trades', + 'balance', + 'deposit', + 'depositHistory', + 'withdrawalHistory', + ], + 'post': [ + 'order', + 'withdrawal', + ], + 'put': [ + 'order', + ], + 'delete': [ + 'order', + 'orders', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.25 / 100, + 'maker': 0.20 / 100, + 'tiers': { + 'taker': [ + [ 0, 0.0025 ], + [ 50000, 0.0024 ], + [ 100000, 0.0022 ], + [ 250000, 0.0020 ], + [ 500000, 0.0018 ], + [ 1000000, 0.0016 ], + [ 2500000, 0.0014 ], + [ 5000000, 0.0012 ], + [ 10000000, 0.0010 ], + ], + 'maker': [ + [ 0, 0.0020 ], + [ 50000, 0.0015 ], + [ 100000, 0.0010 ], + [ 250000, 0.0006 ], + [ 500000, 0.0003 ], + [ 1000000, 0.0001 ], + [ 2500000, -0.0001 ], + [ 5000000, -0.0003 ], + [ 10000000, -0.0005 ], + ], + }, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'exceptions': { + 'exact': { + '101': ExchangeError, // Unknown error. Operation may or may not have succeeded. + '102': BadRequest, // Invalid JSON. + '103': RateLimitExceeded, // You have been rate limited. Please observe the Bitvavo-Ratelimit-AllowAt header to see when you can send requests again. Failure to respect this limit will result in an IP ban. The default value is 1000 weighted requests per minute. Please contact support if you wish to increase this limit. + '104': RateLimitExceeded, // You have been rate limited by the number of new orders. The default value is 100 new orders per second or 100.000 new orders per day. Please update existing orders instead of cancelling and creating orders. Please contact support if you wish to increase this limit. + '105': PermissionDenied, // Your IP or API key has been banned for not respecting the rate limit. The ban expires at ${expiryInMs}. + '107': ExchangeNotAvailable, // The matching engine is overloaded. Please wait 500ms and resubmit your order. + '108': ExchangeNotAvailable, // The matching engine could not process your order in time. Please consider increasing the access window or resubmit your order. + '109': ExchangeNotAvailable, // The matching engine did not respond in time. Operation may or may not have succeeded. + '110': BadRequest, // Invalid endpoint. Please check url and HTTP method. + '200': BadRequest, // ${param} url parameter is not supported. Please note that parameters are case-sensitive and use body parameters for PUT and POST requests. + '201': BadRequest, // ${param} body parameter is not supported. Please note that parameters are case-sensitive and use url parameters for GET and DELETE requests. + '202': BadRequest, // ${param} order parameter is not supported. Please note that certain parameters are only allowed for market or limit orders. + '203': BadSymbol, // {"errorCode":203,"error":"symbol parameter is required."} + '204': BadRequest, // ${param} parameter is not supported. + '205': BadRequest, // ${param} parameter is invalid. + '206': BadRequest, // Use either ${paramA} or ${paramB}. The usage of both parameters at the same time is not supported. + '210': InvalidOrder, // Amount exceeds the maximum allowed amount (1000000000). + '211': InvalidOrder, // Price exceeds the maximum allowed amount (100000000000). + '212': InvalidOrder, // Amount is below the minimum allowed amount for this asset. + '213': InvalidOrder, // Price is below the minimum allowed amount (0.000000000000001). + '214': InvalidOrder, // Price is too detailed + '215': InvalidOrder, // Price is too detailed. A maximum of 15 digits behind the decimal point are allowed. + '216': InsufficientFunds, // {"errorCode":216,"error":"You do not have sufficient balance to complete this operation."} + '217': InvalidOrder, // {"errorCode":217,"error":"Minimum order size in quote currency is 5 EUR or 0.001 BTC."} + '230': ExchangeError, // The order is rejected by the matching engine. + '231': ExchangeError, // The order is rejected by the matching engine. TimeInForce must be GTC when markets are paused. + '232': BadRequest, // You must change at least one of amount, amountRemaining, price, timeInForce, selfTradePrevention or postOnly. + '233': InvalidOrder, // {"errorCode":233,"error":"Order must be active (status new or partiallyFilled) to allow updating/cancelling."} + '234': InvalidOrder, // Market orders cannot be updated. + '235': ExchangeError, // You can only have 100 open orders on each book. + '236': BadRequest, // You can only update amount or amountRemaining, not both. + '240': OrderNotFound, // {"errorCode":240,"error":"No order found. Please be aware that simultaneously updating the same order may return this error."} + '300': AuthenticationError, // Authentication is required for this endpoint. + '301': AuthenticationError, // {"errorCode":301,"error":"API Key must be of length 64."} + '302': AuthenticationError, // Timestamp is invalid. This must be a timestamp in ms. See Bitvavo-Access-Timestamp header or timestamp parameter for websocket. + '303': AuthenticationError, // Window must be between 100 and 60000 ms. + '304': AuthenticationError, // Request was not received within acceptable window (default 30s, or custom with Bitvavo-Access-Window header) of Bitvavo-Access-Timestamp header (or timestamp parameter for websocket). + // '304': AuthenticationError, // Authentication is required for this endpoint. + '305': AuthenticationError, // {"errorCode":305,"error":"No active API key found."} + '306': AuthenticationError, // No active API key found. Please ensure that you have confirmed the API key by e-mail. + '307': PermissionDenied, // This key does not allow access from this IP. + '308': AuthenticationError, // {"errorCode":308,"error":"The signature length is invalid (HMAC-SHA256 should return a 64 length hexadecimal string)."} + '309': AuthenticationError, // {"errorCode":309,"error":"The signature is invalid."} + '310': PermissionDenied, // This key does not allow trading actions. + '311': PermissionDenied, // This key does not allow showing account information. + '312': PermissionDenied, // This key does not allow withdrawal of funds. + '315': BadRequest, // Websocket connections may not be used in a browser. Please use REST requests for this. + '317': AccountSuspended, // This account is locked. Please contact support. + '400': ExchangeError, // Unknown error. Please contact support with a copy of your request. + '401': ExchangeError, // Deposits for this asset are not available at this time. + '402': PermissionDenied, // You need to verify your identitiy before you can deposit and withdraw digital assets. + '403': PermissionDenied, // You need to verify your phone number before you can deposit and withdraw digital assets. + '404': OnMaintenance, // Could not complete this operation, because our node cannot be reached. Possibly under maintenance. + '405': ExchangeError, // You cannot withdraw digital assets during a cooldown period. This is the result of newly added bank accounts. + '406': BadRequest, // {"errorCode":406,"error":"Your withdrawal is too small."} + '407': ExchangeError, // Internal transfer is not possible. + '408': InsufficientFunds, // {"errorCode":408,"error":"You do not have sufficient balance to complete this operation."} + '409': InvalidAddress, // {"errorCode":409,"error":"This is not a verified bank account."} + '410': ExchangeError, // Withdrawals for this asset are not available at this time. + '411': BadRequest, // You can not transfer assets to yourself. + '412': InvalidAddress, // {"errorCode":412,"error":"eth_address_invalid."} + '413': InvalidAddress, // This address violates the whitelist. + '414': ExchangeError, // You cannot withdraw assets within 2 minutes of logging in. + }, + 'broad': { + 'start parameter is invalid': BadRequest, // {"errorCode":205,"error":"start parameter is invalid."} + 'symbol parameter is invalid': BadSymbol, // {"errorCode":205,"error":"symbol parameter is invalid."} + 'amount parameter is invalid': InvalidOrder, // {"errorCode":205,"error":"amount parameter is invalid."} + 'orderId parameter is invalid': InvalidOrder, // {"errorCode":205,"error":"orderId parameter is invalid."} + }, + }, + 'options': { + 'BITVAVO-ACCESS-WINDOW': 10000, // default 10 sec + 'fetchCurrencies': { + 'expires': 1000, // 1 second + }, + }, + 'commonCurrencies': { + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { "time": 1590379519148 } + // + return this.safeInteger (response, 'time'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const currencies = await this.fetchCurrenciesFromCache (params); + const currenciesById = this.indexBy (currencies, 'symbol'); + // + // [ + // { + // "market":"ADA-BTC", + // "status":"trading", // "trading" "halted" "auction" + // "base":"ADA", + // "quote":"BTC", + // "pricePrecision":5, + // "minOrderInBaseAsset":"100", + // "minOrderInQuoteAsset":"0.001", + // "orderTypes": [ "market", "limit" ] + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'market'); + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const status = this.safeString (market, 'status'); + const active = (status === 'trading'); + const baseCurrency = this.safeValue (currenciesById, baseId); + let amountPrecision = undefined; + if (baseCurrency !== undefined) { + amountPrecision = this.safeInteger (baseCurrency, 'decimals', 8); + } + const precision = { + 'price': this.safeInteger (market, 'pricePrecision'), + 'amount': amountPrecision, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minOrderInBaseAsset'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minOrderInQuoteAsset'), + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchCurrenciesFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetAssets (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchCurrencies'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + // + // [ + // { + // "symbol":"ADA", + // "name":"Cardano", + // "decimals":6, + // "depositFee":"0", + // "depositConfirmations":15, + // "depositStatus":"OK", // "OK", "MAINTENANCE", "DELISTED" + // "withdrawalFee":"0.2", + // "withdrawalMinAmount":"0.2", + // "withdrawalStatus":"OK", // "OK", "MAINTENANCE", "DELISTED" + // "networks": [ "Mainnet" ], // "ETH", "NEO", "ONT", "SEPA", "VET" + // "message":"", + // }, + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const depositStatus = this.safeValue (currency, 'depositStatus'); + const deposit = (depositStatus === 'OK'); + const withdrawalStatus = this.safeValue (currency, 'withdrawalStatus'); + const withdrawal = (withdrawalStatus === 'OK'); + const active = deposit && withdrawal; + const name = this.safeString (currency, 'name'); + const precision = this.safeInteger (currency, 'decimals', 8); + result[code] = { + 'id': id, + 'info': currency, + 'code': code, + 'name': name, + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawalFee'), + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'withdrawalMinAmount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTicker24h (this.extend (request, params)); + // + // { + // "market":"ETH-BTC", + // "open":"0.022578", + // "high":"0.023019", + // "low":"0.022573", + // "last":"0.023019", + // "volume":"25.16366324", + // "volumeQuote":"0.57333305", + // "bid":"0.023039", + // "bidSize":"0.53500578", + // "ask":"0.023041", + // "askSize":"0.47859202", + // "timestamp":1590381666900 + // } + // + return this.parseTicker (response, market); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "market":"ETH-BTC", + // "open":"0.022578", + // "high":"0.023019", + // "low":"0.022573", + // "last":"0.023019", + // "volume":"25.16366324", + // "volumeQuote":"0.57333305", + // "bid":"0.023039", + // "bidSize":"0.53500578", + // "ask":"0.023041", + // "askSize":"0.47859202", + // "timestamp":1590381666900 + // } + // + const marketId = this.safeString (ticker, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.safeInteger (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + const baseVolume = this.safeFloat (ticker, 'volume'); + const quoteVolume = this.safeFloat (ticker, 'volumeQuote'); + const vwap = this.vwap (baseVolume, quoteVolume); + let change = undefined; + let percentage = undefined; + let average = undefined; + const open = this.safeFloat (ticker, 'open'); + if ((open !== undefined) && (last !== undefined)) { + change = last - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, last) / 2; + } + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': this.safeFloat (ticker, 'bidSize'), + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': this.safeFloat (ticker, 'askSize'), + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + return result; + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker24h (params); + // + // [ + // { + // "market":"ADA-BTC", + // "open":"0.0000059595", + // "high":"0.0000059765", + // "low":"0.0000059595", + // "last":"0.0000059765", + // "volume":"2923.172", + // "volumeQuote":"0.01743483", + // "bid":"0.0000059515", + // "bidSize":"1117.630919", + // "ask":"0.0000059585", + // "askSize":"809.999739", + // "timestamp":1590382266324 + // } + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + // 'limit': 500, // default 500, max 1000 + // 'start': since, + // 'end': this.milliseconds (), + // 'tradeIdFrom': '57b1159b-6bf5-4cde-9e2c-6bd6a5678baf', + // 'tradeIdTo': '57b1159b-6bf5-4cde-9e2c-6bd6a5678baf', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['start'] = since; + } + const response = await this.publicGetMarketTrades (this.extend (request, params)); + // + // [ + // { + // "id":"94154c98-6e8b-4e33-92a8-74e33fc05650", + // "timestamp":1590382761859, + // "amount":"0.06026079", + // "price":"8095.3", + // "side":"buy" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":"94154c98-6e8b-4e33-92a8-74e33fc05650", + // "timestamp":1590382761859, + // "amount":"0.06026079", + // "price":"8095.3", + // "side":"buy" + // } + // + // createOrder, fetchOpenOrders, fetchOrders, editOrder (private) + // + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // + // fetchMyTrades (private) + // + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "timestamp":1590505649245, + // "market":"ETH-EUR", + // "side":"sell", + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // + // watchMyTrades (private) + // + // { + // event: 'fill', + // timestamp: 1590964470132, + // market: 'ETH-EUR', + // orderId: '85d082e1-eda4-4209-9580-248281a29a9a', + // fillId: '861d2da5-aa93-475c-8d9a-dce431bd4211', + // side: 'sell', + // amount: '0.1', + // price: '211.46', + // taker: true, + // fee: '0.056', + // feeCurrency: 'EUR' + // } + // + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const timestamp = this.safeInteger (trade, 'timestamp'); + const side = this.safeString (trade, 'side'); + const id = this.safeString2 (trade, 'id', 'fillId'); + const marketId = this.safeInteger (trade, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const taker = this.safeValue (trade, 'taker'); + let takerOrMaker = undefined; + if (taker !== undefined) { + takerOrMaker = taker ? 'taker' : 'maker'; + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const orderId = this.safeString (trade, 'orderId'); + return { + 'info': trade, + 'id': id, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetMarketBook (this.extend (request, params)); + // + // { + // "market":"BTC-EUR", + // "nonce":35883831, + // "bids":[ + // ["8097.4","0.6229099"], + // ["8097.2","0.64151283"], + // ["8097.1","0.24966294"], + // ], + // "asks":[ + // ["8097.5","1.36916911"], + // ["8098.8","0.33462248"], + // ["8099.3","1.12908646"], + // ] + // } + // + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'nonce'); + return orderbook; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1590383700000, + // "8088.5", + // "8088.5", + // "8088.5", + // "8088.5", + // "0.04788623" + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'interval': this.timeframes[timeframe], + // 'limit': 1440, // default 1440, max 1440 + // 'start': since, + // 'end': this.milliseconds (), + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 1440, max 1440 + } + const response = await this.publicGetMarketCandles (this.extend (request, params)); + // + // [ + // [1590383700000,"8088.5","8088.5","8088.5","8088.5","0.04788623"], + // [1590383580000,"8091.3","8091.5","8091.3","8091.5","0.04931221"], + // [1590383520000,"8090.3","8092.7","8090.3","8092.5","0.04001286"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + // + // [ + // { + // "symbol": "BTC", + // "available": "1.57593193", + // "inOrder": "0.74832374" + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'symbol'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'inOrder'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'symbol': currency['id'], + }; + const response = await this.privateGetDeposit (this.extend (request, params)); + // + // { + // "address": "0x449889e3234514c45d57f7c5a571feba0c7ad567", + // "paymentId": "10002653" + // } + // + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'paymentId'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': side, + 'orderType': type, + // 'amount': this.amountToPrecision (symbol, amount), + // 'price': this.priceToPrecision (symbol, price), + // 'amountQuote': this.costToPrecision (symbol, cost), + // 'timeInForce': 'GTC', // "GTC" "IOC" "FOK" + // 'selfTradePrevention': "decrementAndCancel", // "decrementAndCancel" "cancelOldest" "cancelNewest" "cancelBoth" + // 'postOnly': false, + // 'disableMarketProtection': false, // don't cancel if the next fill price is 10% worse than the best fill price + // 'responseRequired': true, // false is faster + }; + if (type === 'market') { + let cost = undefined; + if (price !== undefined) { + cost = amount * price; + } else { + cost = this.safeFloat2 (params, 'cost', 'amountQuote'); + } + if (cost !== undefined) { + const precision = market['precision']['price']; + request['amountQuote'] = this.decimalToPrecision (cost, TRUNCATE, precision, this.precisionMode); + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + params = this.omit (params, [ 'cost', 'amountQuote' ]); + } else if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + request['amount'] = this.amountToPrecision (symbol, amount); + } + const response = await this.privatePostOrder (this.extend (request, params)); + // + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // + return this.parseOrder (response, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let request = {}; + const amountRemaining = this.safeFloat (params, 'amountRemaining'); + params = this.omit (params, 'amountRemaining'); + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + if (amount !== undefined) { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if (amountRemaining !== undefined) { + request['amountRemaining'] = this.amountToPrecision (symbol, amountRemaining); + } + request = this.extend (request, params); + if (Object.keys (request).length) { + request['orderId'] = id; + request['market'] = market['id']; + const response = await this.privatePutOrder (this.extend (request, params)); + return this.parseOrder (response, market); + } else { + throw new ArgumentsRequired (this.id + ' editOrder requires an amount argument, or a price argument, or non-empty params'); + } + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'orderId': id, + 'market': market['id'], + }; + const response = await this.privateDeleteOrder (this.extend (request, params)); + // + // { + // "orderId": "2e7ce7fc-44e2-4d80-a4a7-d079c4750b61" + // } + // + return this.parseOrder (response, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.privateDeleteOrders (this.extend (request, params)); + // + // [ + // { + // "orderId": "1be6d0df-d5dc-4b53-a250-3376f3b393e6" + // } + // ] + // + return this.parseOrders (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'orderId': id, + 'market': market['id'], + }; + const response = await this.privateGetOrder (this.extend (request, params)); + // + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // + return this.parseOrder (response, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + // 'limit': 500, + // 'start': since, + // 'end': this.milliseconds (), + // 'orderIdFrom': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + // 'orderIdTo': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // [ + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'market': market['id'], + // 'limit': 500, + // 'start': since, + // 'end': this.milliseconds (), + // 'orderIdFrom': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + // 'orderIdTo': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetOrdersOpen (this.extend (request, params)); + // + // [ + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'new': 'open', + 'canceled': 'canceled', + 'canceledAuction': 'canceled', + 'canceledSelfTradePrevention': 'canceled', + 'canceledIOC': 'canceled', + 'canceledFOK': 'canceled', + 'canceledMarketProtection': 'canceled', + 'canceledPostOnly': 'canceled', + 'filled': 'closed', + 'partiallyFilled': 'open', + 'expired': 'canceled', + 'rejected': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // cancelOrder, cancelAllOrders + // + // { + // "orderId": "2e7ce7fc-44e2-4d80-a4a7-d079c4750b61" + // } + // + // createOrder, fetchOrder, fetchOpenOrders, fetchOrders, editOrder + // + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "price": "183.49", // limit orders only + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // "timeInForce": "GTC", + // "postOnly": true, + // } + // + const id = this.safeString (order, 'orderId'); + const timestamp = this.safeInteger (order, 'created'); + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'orderType'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let remaining = this.safeFloat (order, 'amountRemaining'); + let filled = this.safeFloat (order, 'filledAmount'); + const remainingCost = this.safeFloat (order, 'remainingCost'); + if ((remainingCost !== undefined) && (remainingCost === 0.0)) { + remaining = 0; + } + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + const cost = this.safeFloat (order, 'filledAmountQuote'); + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'feePaid'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (order, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + let lastTradeTimestamp = undefined; + const rawTrades = this.safeValue (order, 'fills'); + let trades = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'symbol': symbol, + 'order': id, + 'side': side, + }); + const numTrades = trades.length; + if (numTrades > 0) { + const lastTrade = this.safeValue (trades, numTrades - 1); + lastTradeTimestamp = lastTrade['timestamp']; + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + // 'limit': 500, + // 'start': since, + // 'end': this.milliseconds (), + // 'tradeIdFrom': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + // 'tradeIdTo': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // [ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "timestamp":1590505649245, + // "market":"ETH-EUR", + // "side":"sell", + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'symbol': currency['id'], + 'amount': this.currencyToPrecision (code, amount), + 'address': address, // address or IBAN + // 'internal': false, // transfer to another Bitvavo user address, no fees + // 'addWithdrawalFee': false, // true = add the fee on top, otherwise the fee is subtracted from the amount + }; + if (tag !== undefined) { + request['paymentId'] = tag; + } + const response = await this.privatePostWithdrawal (this.extend (request, params)); + // + // { + // "success": true, + // "symbol": "BTC", + // "amount": "1.5" + // } + // + return this.parseTransaction (response, currency); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': currency['id'], + // 'limit': 500, // default 500, max 1000 + // 'start': since, + // 'end': this.milliseconds (), + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetWithdrawalHistory (this.extend (request, params)); + // + // [ + // { + // "timestamp":1590531212000, + // "symbol":"ETH", + // "amount":"0.091", + // "fee":"0.009", + // "status":"awaiting_bitvavo_inspection", + // "address":"0xe42b309f1eE9F0cbf7f54CcF3bc2159eBfA6735b", + // "paymentId": "10002653", + // "txId": "927b3ea50c5bb52c6854152d305dfa1e27fc01d10464cf10825d96d69d235eb3", + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': currency['id'], + // 'limit': 500, // default 500, max 1000 + // 'start': since, + // 'end': this.milliseconds (), + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetDepositHistory (this.extend (request, params)); + // + // [ + // { + // "timestamp":1590492401000, + // "symbol":"ETH", + // "amount":"0.249825", + // "fee":"0", + // "status":"completed", + // "txId":"0x5167b473fd37811f9ef22364c3d54726a859ef9d98934b3a1e11d7baa8d2c2e2" + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'awaiting_processing': 'pending', + 'awaiting_email_confirmation': 'pending', + 'awaiting_bitvavo_inspection': 'pending', + 'approved': 'pending', + 'sending': 'pending', + 'in_mempool': 'pending', + 'processed': 'pending', + 'completed': 'ok', + 'canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "success": true, + // "symbol": "BTC", + // "amount": "1.5" + // } + // + // fetchWithdrawals + // + // { + // "timestamp": 1542967486256, + // "symbol": "BTC", + // "amount": "0.99994", + // "address": "BitcoinAddress", + // "paymentId": "10002653", + // "txId": "927b3ea50c5bb52c6854152d305dfa1e27fc01d10464cf10825d96d69d235eb3", + // "fee": "0.00006", + // "status": "awaiting_processing" + // } + // + // fetchDeposits + // + // { + // "timestamp":1590492401000, + // "symbol":"ETH", + // "amount":"0.249825", + // "fee":"0", + // "status":"completed", + // "txId":"0x5167b473fd37811f9ef22364c3d54726a859ef9d98934b3a1e11d7baa8d2c2e2" + // } + // + const id = undefined; + const timestamp = this.safeInteger (transaction, 'timestamp'); + const currencyId = this.safeString (transaction, 'symbol'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const txid = this.safeString (transaction, 'txId'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + let type = undefined; + if ('success' in transaction) { + type = 'withdrawal'; + } else { + type = (status === undefined) ? 'deposit' : 'withdrawal'; + } + const tag = this.safeString (transaction, 'paymentId'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = '/' + this.version + '/' + this.implodeParams (path, params); + const getOrDelete = (method === 'GET') || (method === 'DELETE'); + if (getOrDelete) { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + let payload = ''; + if (!getOrDelete) { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + const timestamp = this.milliseconds ().toString (); + const auth = timestamp + method + url + payload; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + const accessWindow = this.safeString (this.options, 'BITVAVO-ACCESS-WINDOW', '10000'); + headers = { + 'BITVAVO-ACCESS-KEY': this.apiKey, + 'BITVAVO-ACCESS-SIGNATURE': signature, + 'BITVAVO-ACCESS-TIMESTAMP': timestamp, + 'BITVAVO-ACCESS-WINDOW': accessWindow, + }; + if (!getOrDelete) { + headers['Content-Type'] = 'application/json'; + } + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"errorCode":308,"error":"The signature length is invalid (HMAC-SHA256 should return a 64 length hexadecimal string)."} + // {"errorCode":203,"error":"symbol parameter is required."} + // {"errorCode":205,"error":"symbol parameter is invalid."} + // + const errorCode = this.safeString (response, 'errorCode'); + const error = this.safeString (response, 'error'); + if (errorCode !== undefined) { + const feedback = this.id + ' ' + body; + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],45:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, PermissionDenied, InvalidOrder, AuthenticationError, InsufficientFunds, OrderNotFound, DDoSProtection, OnMaintenance, RateLimitExceeded } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitz extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitz', + 'name': 'Bit-Z', + 'countries': [ 'HK' ], + 'rateLimit': 2000, + 'version': 'v2', + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'createOrder': true, + 'createMarketOrder': false, + 'fetchBalance': true, + 'fetchDeposits': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '60min', + '4h': '4hour', + '1d': '1day', + '5d': '5day', + '1w': '1week', + '1M': '1mon', + }, + 'hostname': 'apiv2.bitz.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443304-fec5e000-c5fd-11ea-98f8-ba8e67f7eaff.jpg', + 'api': { + 'market': 'https://{hostname}', + 'trade': 'https://{hostname}', + 'assets': 'https://{hostname}', + }, + 'www': 'https://www.bitz.com', + 'doc': 'https://apidoc.bitz.com/en/', + 'fees': 'https://www.bitz.com/fee?type=1', + 'referral': 'https://u.bitz.com/register?invite_code=1429193', + }, + 'api': { + 'market': { + 'get': [ + 'ticker', + 'depth', + 'order', // trades + 'tickerall', + 'kline', + 'symbolList', + 'getServerTime', + 'currencyRate', + 'currencyCoinRate', + 'coinRate', + ], + }, + 'trade': { + 'post': [ + 'addEntrustSheet', + 'cancelEntrustSheet', + 'cancelAllEntrustSheet', + 'coinOut', // withdraw + 'getUserHistoryEntrustSheet', // closed orders + 'getUserNowEntrustSheet', // open orders + 'getEntrustSheetInfo', // order + 'depositOrWithdraw', // transactions + ], + }, + 'assets': { + 'post': [ + 'getUserAssets', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.002, + 'taker': 0.002, + }, + 'funding': { + 'withdraw': { + 'BTC': '0.5%', + 'DKKT': '0.5%', + 'ETH': 0.01, + 'USDT': '0.5%', + 'LTC': '0.5%', + 'FCT': '0.5%', + 'LSK': '0.5%', + 'HXI': '0.8%', + 'ZEC': '0.5%', + 'DOGE': '0.5%', + 'MZC': '0.5%', + 'ETC': '0.5%', + 'GXS': '0.5%', + 'XPM': '0.5%', + 'PPC': '0.5%', + 'BLK': '0.5%', + 'XAS': '0.5%', + 'HSR': '0.5%', + 'NULS': 5.0, + 'VOISE': 350.0, + 'PAY': 1.5, + 'EOS': 0.6, + 'YBCT': 35.0, + 'OMG': 0.3, + 'OTN': 0.4, + 'BTX': '0.5%', + 'QTUM': '0.5%', + 'DASH': '0.5%', + 'GAME': '0.5%', + 'BCH': '0.5%', + 'GNT': 9.0, + 'SSS': 1500.0, + 'ARK': '0.5%', + 'PART': '0.5%', + 'LEO': '0.5%', + 'DGB': '0.5%', + 'ZSC': 130.0, + 'VIU': 350.0, + 'BTG': '0.5%', + 'ARN': 10.0, + 'VTC': '0.5%', + 'BCD': '0.5%', + 'TRX': 200.0, + 'HWC': '0.5%', + 'UNIT': '0.5%', + 'OXY': '0.5%', + 'MCO': 0.3500, + 'SBTC': '0.5%', + 'BCX': '0.5%', + 'ETF': '0.5%', + 'PYLNT': 0.4000, + 'XRB': '0.5%', + 'ETP': '0.5%', + }, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'options': { + 'fetchOHLCVVolume': true, + 'fetchOHLCVWarning': true, + 'lastNonceTimestamp': 0, + }, + 'commonCurrencies': { + // https://github.com/ccxt/ccxt/issues/3881 + // https://support.bit-z.pro/hc/en-us/articles/360007500654-BOX-BOX-Token- + 'BOX': 'BOX Token', + 'LEO': 'LeoCoin', + 'XRB': 'NANO', + 'PXC': 'Pixiecoin', + 'VTC': 'VoteCoin', + 'TTC': 'TimesChain', + }, + 'exceptions': { + // '200': Success + '-102': ExchangeError, // Invalid parameter + '-103': AuthenticationError, // Verification failed + '-104': ExchangeNotAvailable, // Network Error-1 + '-105': AuthenticationError, // Invalid api signature + '-106': ExchangeNotAvailable, // Network Error-2 + '-109': AuthenticationError, // Invalid scretKey + '-110': DDoSProtection, // The number of access requests exceeded + '-111': PermissionDenied, // Current IP is not in the range of trusted IP + '-112': OnMaintenance, // Service is under maintenance + '-114': RateLimitExceeded, // The number of daily requests has reached the limit + '-117': AuthenticationError, // The apikey expires + '-100015': AuthenticationError, // Trade password error + '-100044': ExchangeError, // Fail to request data + '-100101': ExchangeError, // Invalid symbol + '-100201': ExchangeError, // Invalid symbol + '-100301': ExchangeError, // Invalid symbol + '-100401': ExchangeError, // Invalid symbol + '-100302': ExchangeError, // Type of K-line error + '-100303': ExchangeError, // Size of K-line error + '-200003': AuthenticationError, // Please set trade password + '-200005': PermissionDenied, // This account can not trade + '-200025': ExchangeNotAvailable, // Temporary trading halt + '-200027': InvalidOrder, // Price Error + '-200028': InvalidOrder, // Amount must be greater than 0 + '-200029': InvalidOrder, // Number must be between %s and %d + '-200030': InvalidOrder, // Over price range + '-200031': InsufficientFunds, // Insufficient assets + '-200032': ExchangeError, // System error. Please contact customer service + '-200033': ExchangeError, // Fail to trade + '-200034': OrderNotFound, // The order does not exist + '-200035': OrderNotFound, // Cancellation error, order filled + '-200037': InvalidOrder, // Trade direction error + '-200038': ExchangeError, // Trading Market Error + '-200055': OrderNotFound, // Order record does not exist + '-300069': AuthenticationError, // api_key is illegal + '-300101': ExchangeError, // Transaction type error + '-300102': InvalidOrder, // Price or number cannot be less than 0 + '-300103': AuthenticationError, // Trade password error + '-301001': ExchangeNotAvailable, // Network Error-3 + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.marketGetSymbolList (params); + // + // { status: 200, + // msg: "", + // data: { ltc_btc: { id: "1", + // name: "ltc_btc", + // coinFrom: "ltc", + // coinTo: "btc", + // numberFloat: "4", + // priceFloat: "8", + // status: "1", + // minTrade: "0.010", + // maxTrade: "500000000.000" }, + // qtum_usdt: { id: "196", + // name: "qtum_usdt", + // coinFrom: "qtum", + // coinTo: "usdt", + // numberFloat: "4", + // priceFloat: "2", + // status: "1", + // minTrade: "0.100", + // maxTrade: "500000000.000" }, }, + // time: 1535969146, + // microtime: "0.66955600 1535969146", + // source: "api" } + // + const markets = this.safeValue (response, 'data'); + const ids = Object.keys (markets); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = markets[id]; + const numericId = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'coinFrom'); + const quoteId = this.safeString (market, 'coinTo'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'numberFloat'), + 'price': this.safeInteger (market, 'priceFloat'), + }; + result.push ({ + 'info': market, + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minTrade'), + 'max': this.safeFloat (market, 'maxTrade'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.assetsPostGetUserAssets (params); + // + // { + // status: 200, + // msg: "", + // data: { + // cny: 0, + // usd: 0, + // btc_total: 0, + // info: [{ + // "name": "zpr", + // "num": "37.49067275", + // "over": "37.49067275", + // "lock": "0.00000000", + // "btc": "0.00000000", + // "usd": "0.00000000", + // "cny": "0.00000000", + // }], + // }, + // time: 1535983966, + // microtime: "0.70400500 1535983966", + // source: "api", + // } + // + const balances = this.safeValue (response['data'], 'info'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'name'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'lock'); + account['total'] = this.safeFloat (balance, 'num'); + account['free'] = this.safeFloat (balance, 'over'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTicker (ticker, market = undefined) { + // + // { symbol: "eth_btc", + // quoteVolume: "3905.72", + // volume: "97058.21", + // priceChange: "-1.72", + // priceChange24h: "-1.65", + // askPrice: "0.03971272", + // askQty: "0.0663", + // bidPrice: "0.03961469", + // bidQty: "19.5451", + // open: "0.04036769", + // high: "0.04062988", + // low: "0.03956123", + // now: "0.03970100", + // firstId: 115567767, + // lastId: 115795316, + // dealCount: 14078, + // numberPrecision: 4, + // pricePrecision: 8, + // cny: "1959.05", + // usd: "287.10", + // krw: "318655.82" } + // + const timestamp = undefined; + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat (ticker, 'now'); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': this.safeFloat (ticker, 'priceChange24h'), + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + parseMicrotime (microtime) { + if (microtime === undefined) { + return microtime; + } + const parts = microtime.split (' '); + const milliseconds = parseFloat (parts[0]); + const seconds = parseInt (parts[1]); + const total = this.sum (seconds, milliseconds); + return parseInt (total * 1000); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetTicker (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: { symbol: "eth_btc", + // quoteVolume: "3905.72", + // volume: "97058.21", + // priceChange: "-1.72", + // priceChange24h: "-1.65", + // askPrice: "0.03971272", + // askQty: "0.0663", + // bidPrice: "0.03961469", + // bidQty: "19.5451", + // open: "0.04036769", + // high: "0.04062988", + // low: "0.03956123", + // now: "0.03970100", + // firstId: 115567767, + // lastId: 115795316, + // dealCount: 14078, + // numberPrecision: 4, + // pricePrecision: 8, + // cny: "1959.05", + // usd: "287.10", + // krw: "318655.82" }, + // time: 1535970397, + // microtime: "0.76341900 1535970397", + // source: "api" } + // + const ticker = this.parseTicker (response['data'], market); + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + return this.extend (ticker, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + }); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['symbols'] = ids.join (','); + } + const response = await this.marketGetTickerall (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: { ela_btc: { symbol: "ela_btc", + // quoteVolume: "0.00", + // volume: "3.28", + // priceChange: "0.00", + // priceChange24h: "0.00", + // askPrice: "0.00147984", + // askQty: "5.4580", + // bidPrice: "0.00120230", + // bidQty: "12.5384", + // open: "0.00149078", + // high: "0.00149078", + // low: "0.00149078", + // now: "0.00149078", + // firstId: 115581219, + // lastId: 115581219, + // dealCount: 1, + // numberPrecision: 4, + // pricePrecision: 8, + // cny: "73.66", + // usd: "10.79", + // krw: "11995.03" } }, + // time: 1535971578, + // microtime: "0.39854200 1535971578", + // source: "api" } + // + const tickers = this.safeValue (response, 'data'); + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + const result = {}; + const ids = Object.keys (tickers); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let ticker = tickers[id]; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + } + ticker = this.parseTicker (tickers[id], market); + let symbol = ticker['symbol']; + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } else { + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if (symbol !== undefined) { + result[symbol] = this.extend (ticker, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + }); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTime (params = {}) { + const response = await this.marketGetGetServerTime (params); + // + // { + // "status":200, + // "msg":"", + // "data":[], + // "time":1555490875, + // "microtime":"0.35994200 1555490875", + // "source":"api" + // } + // + return this.safeTimestamp (response, 'time'); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.marketGetDepth (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: { asks: [ ["10.00000000", "0.4426", "4.4260"], + // ["1.00000000", "0.8339", "0.8339"], + // ["0.91700000", "0.0500", "0.0458"], + // ["0.20000000", "0.1000", "0.0200"], + // ["0.03987120", "16.1262", "0.6429"], + // ["0.03986120", "9.7523", "0.3887"] ], + // bids: [ ["0.03976145", "0.0359", "0.0014"], + // ["0.03973401", "20.9493", "0.8323"], + // ["0.03967970", "0.0328", "0.0013"], + // ["0.00000002", "10000.0000", "0.0002"], + // ["0.00000001", "231840.7500", "0.0023"] ], + // coinPair: "eth_btc" }, + // time: 1535974778, + // microtime: "0.04017400 1535974778", + // source: "api" } + // + const orderbook = this.safeValue (response, 'data'); + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { id: 115807453, + // t: "19:36:24", + // T: 1535974584, + // p: "0.03983296", + // n: "0.1000", + // s: "buy" }, + // + const id = this.safeString (trade, 'id'); + const timestamp = this.safeTimestamp (trade, 'T'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const price = this.safeFloat (trade, 'p'); + const amount = this.safeFloat (trade, 'n'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = this.priceToPrecision (symbol, amount * price); + } + } + const side = this.safeString (trade, 's'); + return { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': undefined, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetOrder (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: [ { id: 115807453, + // t: "19:36:24", + // T: 1535974584, + // p: "0.03983296", + // n: "0.1000", + // s: "buy" }, + // { id: 115806811, + // t: "19:33:19", + // T: 1535974399, + // p: "0.03981135", + // n: "9.4612", + // s: "sell" } ], + // time: 1535974583, + // microtime: "0.57118100 1535974583", + // source: "api" } + // + return this.parseTrades (response['data'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // time: "1535973420000", + // open: "0.03975084", + // high: "0.03975084", + // low: "0.03967700", + // close: "0.03967700", + // volume: "12.4733", + // datetime: "2018-09-03 19:17:00" + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const duration = this.parseTimeframe (timeframe) * 1000; + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['size'] = Math.min (limit, 300); // 1-300 + if (since !== undefined) { + request['to'] = this.sum (since, limit * duration * 1000); + } + } else { + if (since !== undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a limit argument if the since argument is specified'); + } + } + const response = await this.marketGetKline (this.extend (request, params)); + // + // { + // status: 200, + // msg: "", + // data: { + // bars: [ + // { time: "1535973420000", open: "0.03975084", high: "0.03975084", low: "0.03967700", close: "0.03967700", volume: "12.4733", datetime: "2018-09-03 19:17:00" }, + // { time: "1535955480000", open: "0.04009900", high: "0.04016745", low: "0.04009900", close: "0.04012074", volume: "74.4803", datetime: "2018-09-03 14:18:00" }, + // ], + // resolution: "1min", + // symbol: "eth_btc", + // from: "1535973420000", + // to: "1535955480000", + // size: 300 + // }, + // time: 1535973435, + // microtime: "0.56462100 1535973435", + // source: "api" + // } + // + const data = this.safeValue (response, 'data', {}); + const bars = this.safeValue (data, 'bars', []); + return this.parseOHLCVs (bars, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', // partially filled + '2': 'closed', // filled + '3': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": "693248739", // order id + // "uId": "2074056", // uid + // "price": "100", // price + // "number": "10", // number + // "numberOver": "10", // undealed + // "flag": "sale", // flag + // "status": "0", // unfilled + // "coinFrom": "vtc", + // "coinTo": "dkkt", + // "numberDeal": "0" // dealed + // } + // + const id = this.safeString (order, 'id'); + let symbol = undefined; + if (market === undefined) { + const baseId = this.safeString (order, 'coinFrom'); + const quoteId = this.safeString (order, 'coinTo'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const marketId = baseId + '_' + quoteId; + if (marketId in this.markets_by_id) { + market = this.safeValue (this.markets_by_id, marketId); + } else { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let side = this.safeString (order, 'flag'); + if (side !== undefined) { + side = (side === 'sale') ? 'sell' : 'buy'; + } + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'number'); + const remaining = this.safeFloat (order, 'numberOver'); + const filled = this.safeFloat (order, 'numberDeal'); + let timestamp = this.safeInteger (order, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.safeTimestamp (order, 'created'); + } + let cost = this.safeFloat (order, 'orderTotalPrice'); + if (price !== undefined) { + if (filled !== undefined) { + cost = filled * price; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type !== 'limit') { + throw new ExchangeError (this.id + ' createOrder allows limit orders only'); + } + const market = this.market (symbol); + const orderType = (side === 'buy') ? '1' : '2'; + if (!this.password) { + throw new ExchangeError (this.id + ' createOrder() requires you to set exchange.password = "YOUR_TRADING_PASSWORD" (a trade password is NOT THE SAME as your login password)'); + } + const request = { + 'symbol': market['id'], + 'type': orderType, + 'price': this.priceToPrecision (symbol, price), + 'number': this.amountToPrecision (symbol, amount), + 'tradePwd': this.password, + }; + const response = await this.tradePostAddEntrustSheet (this.extend (request, params)); + // + // { + // "status": 200, + // "msg": "", + // "data": { + // "id": "693248739", // order id + // "uId": "2074056", // uid + // "price": "100", // price + // "number": "10", // number + // "numberOver": "10", // undealed + // "flag": "sale", // flag + // "status": "0", // unfilled + // "coinFrom": "vtc", + // "coinTo": "dkkt", + // "numberDeal": "0" // dealed + // }, + // "time": "1533035297", + // "microtime": "0.41892000 1533035297", + // "source": "api", + // } + // + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + const order = this.extend ({ + 'timestamp': timestamp, + }, response['data']); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'entrustSheetId': id, + }; + const response = await this.tradePostCancelEntrustSheet (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "updateAssetsData":{ + // "coin":"bz", + // "over":"1000.00000000", + // "lock":"-1000.00000000" + // }, + // "assetsInfo":{ + // "coin":"bz", + // "over":"9999.99999999", + // "lock":"9999.99999999" + // } + // }, + // "time":"1535464383", + // "microtime":"0.91558000 1535464383", + // "source":"api" + // } + // + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'ids': ids.join (','), + }; + const response = await this.tradePostCancelEntrustSheet (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "744173808":{ + // "updateAssetsData":{ + // "coin":"bz", + // "over":"100.00000000", + // "lock":"-100.00000000" + // }, + // "assetsInfo":{ + // "coin":"bz", + // "over":"899.99999999", + // "lock":"19099.99999999" + // } + // }, + // "744173809":{ + // "updateAssetsData":{ + // "coin":"bz", + // "over":"100.00000000", + // "lock":"-100.00000000" + // }, + // "assetsInfo":{ + // "coin":"bz", + // "over":"999.99999999", + // "lock":"18999.99999999" + // } + // } + // }, + // "time":"1535525649", + // "microtime":"0.05009400 1535525649", + // "source":"api" + // } + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'entrustSheetId': id, + }; + const response = await this.tradePostGetEntrustSheetInfo (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "id":"708279852", + // "uId":"2074056", + // "price":"100.00000000", + // "number":"10.0000", + // "total":"0.00000000", + // "numberOver":"10.0000", + // "numberDeal":"0.0000", + // "flag":"sale", + // "status":"0", //0:unfilled, 1:partial deal, 2:all transactions, 3:already cancelled + // "coinFrom":"bz", + // "coinTo":"usdt", + // "orderTotalPrice":"0", + // "created":"1533279876" + // }, + // "time":"1533280294", + // "microtime":"0.36859200 1533280294", + // "source":"api" + // } + // + return this.parseOrder (response['data']); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coinFrom': market['baseId'], + 'coinTo': market['quoteId'], + // 'type': 1, // optional integer, 1 = buy, 2 = sell + // 'page': 1, // optional integer + // 'pageSize': 100, // optional integer, max 100 + // 'startTime': 1510235730, // optional integer timestamp in seconds + // 'endTime': 1510235730, // optional integer timestamp in seconds + }; + if (limit !== undefined) { + request['page'] = 1; + request['pageSize'] = limit; + } + if (since !== undefined) { + request['startTime'] = parseInt (since / 1000); + // request['endTime'] = parseInt (since / 1000); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "status": 200, + // "msg": "", + // "data": { + // "data": [ + // { + // "id": "693248739", + // "uid": "2074056", + // "price": "100.00000000", + // "number": "10.0000", + // "total": "0.00000000", + // "numberOver": "0.0000", + // "numberDeal": "0.0000", + // "flag": "sale", + // "status": "3", // 0:unfilled, 1:partial deal, 2:all transactions, 3:already cancelled + // "isNew": "N", + // "coinFrom": "vtc", + // "coinTo": "dkkt", + // "created": "1533035300", + // }, + // { + // "id": "723086996", + // "uid": "2074056", + // "price": "100.00000000", + // "number": "10.0000", + // "total": "0.00000000", + // "numberOver": "0.0000", + // "numberDeal": "0.0000", + // "flag": "sale", + // "status": "3", + // "isNew": "N", + // "coinFrom": "bz", + // "coinTo": "usdt", + // "created": "1533523568", + // }, + // ], + // "pageInfo": { + // "limit": "10", + // "offest": "0", + // "current_page": "1", + // "page_size": "10", + // "total_count": "17", + // "page_count": "2", + // } + // }, + // "time": "1533279329", + // "microtime": "0.15305300 1533279329", + // "source": "api" + // } + // + const orders = this.safeValue (response['data'], 'data', []); + return this.parseOrders (orders, undefined, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('tradePostGetUserHistoryEntrustSheet', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('tradePostGetUserNowEntrustSheet', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('tradePostGetUserHistoryEntrustSheet', symbol, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + '1': 'pending', + '2': 'pending', + '3': 'pending', + '4': 'ok', + '5': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": '96275', + // "uid": '2109073', + // "wallet": '0xf4c4141c0127bc37b1d0c409a091920eba13ada7', + // "txid": '0xb7adfa52aa566f9ac112e3c01f77bd91179b19eab12092a9a5a8b33d5086e31d', + // "confirm": '12', + // "number": '0.50000000', + // "status": 4, + // "updated": '1534944168605', + // "addressUrl": 'https://etherscan.io/address/', + // "txidUrl": 'https://etherscan.io/tx/', + // "description": 'Ethereum', + // "coin": 'eth', + // "memo": '' + // } + // + // { + // "id":"397574", + // "uid":"2033056", + // "wallet":"1AG1gZvQAYu3WBvgg7p4BMMghQD2gE693k", + // "txid":"", + // "confirm":"0", + // "number":"1000.00000000", + // "status":1, + // "updated":"0", + // "addressUrl":"http://omniexplorer.info/lookupadd.aspx?address=", + // "txidUrl":"http://omniexplorer.info/lookuptx.aspx?txid=", + // "description":"Tether", + // "coin":"usdt", + // "memo":"" + // } + // + // { + // "id":"153606", + // "uid":"2033056", + // "wallet":"1AG1gZvQAYu3WBvgg7p4BMMghQD2gE693k", + // "txid":"aa2b179f84cd6dedafd41845e0fbf7f01e14c0d71ea3140d03d6f5a9ccd93199", + // "confirm":"0", + // "number":"761.11110000", + // "status":4, + // "updated":"1536726133579", + // "addressUrl":"http://omniexplorer.info/lookupadd.aspx?address=", + // "txidUrl":"http://omniexplorer.info/lookuptx.aspx?txid=", + // "description":"Tether", + // "coin":"usdt", + // "memo":"" + // } + // + // withdraw + // + // { + // "id":397574, + // "email":"***@email.com", + // "coin":"usdt", + // "network_fee":"", + // "eid":23112 + // } + // + let timestamp = this.safeInteger (transaction, 'updated'); + if (timestamp === 0) { + timestamp = undefined; + } + const currencyId = this.safeString (transaction, 'coin'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeStringLower (transaction, 'type'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'network_fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'code': code, + }; + } + return { + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'txid'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'wallet'), + 'tag': this.safeString (transaction, 'memo'), + 'type': type, + 'amount': this.safeFloat (transaction, 'number'), + 'currency': code, + 'status': status, + 'updated': timestamp, + 'fee': fee, + 'info': transaction, + }; + } + + parseTransactionsByType (type, transactions, code = undefined, since = undefined, limit = undefined) { + const result = []; + for (let i = 0; i < transactions.length; i++) { + const transaction = this.parseTransaction (this.extend ({ + 'type': type, + }, transactions[i])); + result.push (transaction); + } + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + parseTransactionType (type) { + const types = { + 'deposit': 1, + 'withdrawal': 2, + }; + return this.safeInteger (types, type, type); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsForType ('deposit', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsForType ('withdrawal', code, since, limit, params); + } + + async fetchTransactionsForType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'type': this.parseTransactionType (type), + }; + if (since !== undefined) { + request['startTime'] = parseInt (since / 1000).toString (); + } + if (limit !== undefined) { + request['page'] = 1; + request['pageSize'] = limit; + } + const response = await this.tradePostDepositOrWithdraw (this.extend (request, params)); + const transactions = this.safeValue (response['data'], 'data', []); + return this.parseTransactionsByType (type, transactions, code, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'number': this.currencyToPrecision (code, amount), + 'address': address, + // 'type': 'erc20', // omni, trc20, optional + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const response = await this.tradePostCoinOut (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "id":397574, + // "email":"***@email.com", + // "coin":"usdt", + // "network_fee":"", + // "eid":23112 + // }, + // "time":1552641646, + // "microtime":"0.70304500 1552641646", + // "source":"api" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransaction (data, currency); + } + + nonce () { + const currentTimestamp = this.seconds (); + if (currentTimestamp > this.options['lastNonceTimestamp']) { + this.options['lastNonceTimestamp'] = currentTimestamp; + this.options['lastNonce'] = 100000; + } + this.options['lastNonce'] = this.sum (this.options['lastNonce'], 1); + return this.options['lastNonce']; + } + + sign (path, api = 'market', method = 'GET', params = {}, headers = undefined, body = undefined) { + const baseUrl = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }); + let url = baseUrl + '/' + this.capitalize (api) + '/' + path; + let query = undefined; + if (api === 'market') { + query = this.urlencode (params); + if (query.length) { + url += '?' + query; + } + } else { + this.checkRequiredCredentials (); + body = this.rawencode (this.keysort (this.extend ({ + 'apiKey': this.apiKey, + 'timeStamp': this.seconds (), + 'nonce': this.nonce (), + }, params))); + body += '&sign=' + this.hash (this.encode (body + this.secret)); + headers = { 'Content-type': 'application/x-www-form-urlencoded' }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const status = this.safeString (response, 'status'); + if (status !== undefined) { + const feedback = this.id + ' ' + body; + // + // {"status":-107,"msg":"","data":"","time":1535968848,"microtime":"0.89092200 1535968848","source":"api"} + // + if (status === '200') { + // + // {"status":200,"msg":"","data":-200031,"time":1535999806,"microtime":"0.85476800 1535999806","source":"api"} + // + const code = this.safeInteger (response, 'data'); + if (code !== undefined) { + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } else { + return; // no error + } + } + this.throwExactlyMatchedException (this.exceptions, status, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],46:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class bl3p extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bl3p', + 'name': 'BL3P', + 'countries': [ 'NL', 'EU' ], // Netherlands, EU + 'rateLimit': 1000, + 'version': '1', + 'comment': 'An exchange market by BitonicNL', + 'has': { + 'CORS': false, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28501752-60c21b82-6feb-11e7-818b-055ee6d0e754.jpg', + 'api': 'https://api.bl3p.eu', + 'www': 'https://bl3p.eu', // 'https://bitonic.nl' + 'doc': [ + 'https://github.com/BitonicNL/bl3p-api/tree/master/docs', + 'https://bl3p.eu/api', + 'https://bitonic.nl/en/api', + ], + }, + 'api': { + 'public': { + 'get': [ + '{market}/ticker', + '{market}/orderbook', + '{market}/trades', + ], + }, + 'private': { + 'post': [ + '{market}/money/depth/full', + '{market}/money/order/add', + '{market}/money/order/cancel', + '{market}/money/order/result', + '{market}/money/orders', + '{market}/money/orders/history', + '{market}/money/trades/fetch', + 'GENMKT/money/info', + 'GENMKT/money/deposit_address', + 'GENMKT/money/new_deposit_address', + 'GENMKT/money/wallet/history', + 'GENMKT/money/withdraw', + ], + }, + }, + 'markets': { + 'BTC/EUR': { 'id': 'BTCEUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'BTC', 'quoteId': 'EUR', 'maker': 0.0025, 'taker': 0.0025 }, + 'LTC/EUR': { 'id': 'LTCEUR', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'LTC', 'quoteId': 'EUR', 'maker': 0.0025, 'taker': 0.0025 }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGENMKTMoneyInfo (params); + const data = this.safeValue (response, 'data', {}); + const wallets = this.safeValue (data, 'wallets'); + const result = { 'info': data }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const wallet = this.safeValue (wallets, currencyId, {}); + const available = this.safeValue (wallet, 'available', {}); + const balance = this.safeValue (wallet, 'balance', {}); + const account = this.account (); + account['free'] = this.safeFloat (available, 'value'); + account['total'] = this.safeFloat (balance, 'value'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + return [ + bidask[priceKey] / 100000.0, + bidask[amountKey] / 100000000.0, + ]; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketOrderbook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'data'); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price_int', 'amount_int'); + } + + async fetchTicker (symbol, params = {}) { + const request = { + 'market': this.marketId (symbol), + }; + const ticker = await this.publicGetMarketTicker (this.extend (request, params)); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker['volume'], '24h'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const id = this.safeString (trade, 'trade_id'); + const timestamp = this.safeInteger (trade, 'date'); + let price = this.safeFloat (trade, 'price_int'); + if (price !== undefined) { + price /= 100000.0; + } + let amount = this.safeFloat (trade, 'amount_int'); + if (amount !== undefined) { + amount /= 100000000.0; + } + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + const market = this.market (symbol); + const response = await this.publicGetMarketTrades (this.extend ({ + 'market': market['id'], + }, params)); + const result = this.parseTrades (response['data']['trades'], market, since, limit); + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + const market = this.market (symbol); + const order = { + 'market': market['id'], + 'amount_int': parseInt (amount * 100000000), + 'fee_currency': market['quote'], + 'type': (side === 'buy') ? 'bid' : 'ask', + }; + if (type === 'limit') { + order['price_int'] = parseInt (price * 100000.0); + } + const response = await this.privatePostMarketMoneyOrderAdd (this.extend (order, params)); + const orderId = this.safeString (response['data'], 'order_id'); + return { + 'info': response, + 'id': orderId, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.privatePostMarketMoneyOrderCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = this.implodeParams (path, params); + let url = this.urls['api'] + '/' + this.version + '/' + request; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, query)); + const secret = this.base64ToBinary (this.secret); + // eslint-disable-next-line quotes + const auth = request + "\0" + body; + const signature = this.hmac (this.encode (auth), secret, 'sha512', 'base64'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Rest-Key': this.apiKey, + 'Rest-Sign': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5}],47:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidOrder, AuthenticationError, InsufficientFunds, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bleutrade extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bleutrade', + 'name': 'Bleutrade', + 'countries': ['BR'], // Brazil + 'rateLimit': 1000, + 'certified': false, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createLimitOrder': false, + 'createMarketOrder': false, + 'createOrder': true, + 'editOrder': false, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': false, + 'fetchWithdrawals': true, + 'withdraw': false, + }, + 'timeframes': { + '1h': '1h', + '4h': '4h', + '8h': '8h', + '1d': '1d', + '1w': '1w', + }, + 'hostname': 'bleutrade.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30303000-b602dbe6-976d-11e7-956d-36c5049c01e7.jpg', + 'api': { + 'v3Private': 'https://{hostname}/api/v3/private', + 'v3Public': 'https://{hostname}/api/v3/public', + }, + 'www': 'https://bleutrade.com', + 'doc': [ + 'https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0', + ], + 'fees': 'https://bleutrade.com/fees/', + }, + 'api': { + 'v3Public': { + 'get': [ + 'getassets', + 'getmarkets', + 'getticker', + 'getmarketsummary', + 'getmarketsummaries', + 'getorderbook', + 'getmarkethistory', + 'getcandles', + ], + }, + 'v3Private': { + 'get': [ + 'statement', + ], + 'post': [ + 'getbalance', + 'getbalances', + 'buylimit', + 'selllimit', + 'buylimitami', + 'selllimitami', + 'buystoplimit', + 'sellstoplimit', + 'ordercancel', + 'getopenorders', + 'getcloseorders', + 'getdeposithistory', + 'getdepositaddress', + 'getmytransactions', + 'withdraw', + 'directtransfer', + 'getwithdrawhistory', + 'getlimits', + ], + }, + }, + 'commonCurrencies': { + 'EPC': 'Epacoin', + }, + 'exceptions': { + 'exact': { + 'ERR_INSUFICIENT_BALANCE': InsufficientFunds, + 'ERR_LOW_VOLUME': BadRequest, + 'Invalid form': BadRequest, + }, + 'broad': { + 'Order is not open': InvalidOrder, + 'Invalid Account / Api KEY / Api Secret': AuthenticationError, // also happens when an invalid nonce is used + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.25 / 100, + 'maker': 0.25 / 100, + }, + }, + 'options': { + 'parseOrderStatus': true, + }, + }); + // undocumented api calls + // https://bleutrade.com/api/v3/public/tradingview/symbols?symbol=ETH_BTC + // https://bleutrade.com/api/v3/public/tradingview/config + // https://bleutrade.com/api/v3/public/tradingview/time + // https://bleutrade.com/api/v3/private/getcloseorders?market=ETH_BTC + // https://bleutrade.com/config contains the fees + } + + async fetchCurrencies (params = {}) { + const response = await this.v3PublicGetGetassets (params); + const items = response['result']; + const result = {}; + for (let i = 0; i < items.length; i++) { + // { Asset: 'USDT', + // AssetLong: 'Tether', + // MinConfirmation: 4, + // WithdrawTxFee: 1, + // WithdrawTxFeePercent: 0, + // SystemProtocol: 'ETHERC20', + // IsActive: true, + // InfoMessage: '', + // MaintenanceMode: false, + // MaintenanceMessage: '', + // FormatPrefix: '', + // FormatSufix: '', + // DecimalSeparator: '.', + // ThousandSeparator: ',', + // DecimalPlaces: 8, + // Currency: 'USDT', + // CurrencyLong: 'Tether', + // CoinType: 'ETHERC20' } + const item = items[i]; + const id = this.safeString (item, 'Asset'); + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'name': this.safeString (item, 'AssetLong'), + 'active': this.safeValue (item, 'IsActive') && !this.safeValue (item, 'MaintenanceMode'), + 'fee': this.safeFloat (item, 'WithdrawTxFee'), + 'precision': this.safeFloat (item, 'DecimalPlaces'), + 'info': item, + 'limits': this.limits, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + // https://github.com/ccxt/ccxt/issues/5668 + const response = await this.v3PublicGetGetmarkets (params); + const result = []; + const markets = this.safeValue (response, 'result'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + // { MarketName: 'LTC_USDT', + // MarketAsset: 'LTC', + // BaseAsset: 'USDT', + // MarketAssetLong: 'Litecoin', + // BaseAssetLong: 'Tether', + // IsActive: true, + // MinTradeSize: 0.0001, + // InfoMessage: '', + // MarketCurrency: 'LTC', + // BaseCurrency: 'USDT', + // MarketCurrencyLong: 'Litecoin', + // BaseCurrencyLong: 'Tether' } + const id = this.safeString (market, 'MarketName'); + const baseId = this.safeString (market, 'MarketAsset'); + const quoteId = this.safeString (market, 'BaseAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const active = this.safeValue (market, 'IsActive', false); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'maker': this.fees['trading']['maker'], + 'taker': this.fees['trading']['taker'], + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'MinTradeSize'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'type': 'ALL', + }; + if (limit !== undefined) { + request['depth'] = limit; // 50 + } + const response = await this.v3PublicGetGetorderbook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'result'); + if (!orderbook) { + throw new ExchangeError (this.id + ' no orderbook data in ' + this.json (response)); + } + return this.parseOrderBook (orderbook, undefined, 'buy', 'sell', 'Rate', 'Quantity'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.v3PublicGetGetmarketsummary (this.extend (request, params)); + const ticker = this.safeValue (response, 'result', {}); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.v3PublicGetGetmarketsummaries (params); + const result = this.safeValue (response, 'result'); + const tickers = []; + for (let i = 0; i < result.length; i++) { + const ticker = this.parseTicker (result[i]); + tickers.push (ticker); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // { TimeStamp: '2020-01-14 14:32:28', + // MarketName: 'LTC_USDT', + // MarketAsset: 'LTC', + // BaseAsset: 'USDT', + // MarketAssetName: 'Litecoin', + // BaseAssetName: 'Tether', + // PrevDay: 49.2867503, + // High: 56.78622664, + // Low: 49.27384025, + // Last: 53.94, + // Average: 51.37509368, + // Volume: 1.51282404, + // BaseVolume: 77.72147677, + // Bid: 53.62070218, + // Ask: 53.94, + // IsActive: 'true', + // InfoMessage: '', + // MarketCurrency: 'Litecoin', + // BaseCurrency: 'Tether' } + const timestamp = this.parse8601 (this.safeString (ticker, 'TimeStamp')); + const marketId = this.safeString (ticker, 'MarketName'); + const symbol = this.safeSymbol (marketId, market, '_'); + const previous = this.safeFloat (ticker, 'PrevDay'); + const last = this.safeFloat (ticker, 'Last'); + let change = undefined; + let percentage = undefined; + if (last !== undefined) { + if (previous !== undefined) { + change = last - previous; + if (previous > 0) { + percentage = (change / previous) * 100; + } + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'High'), + 'low': this.safeFloat (ticker, 'Low'), + 'bid': this.safeFloat (ticker, 'Bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'Ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': previous, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'Volume'), + 'quoteVolume': this.safeFloat (ticker, 'BaseVolume'), + 'info': ticker, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.parse8601 (ohlcv['TimeStamp'] + '+00:00'), + this.safeFloat (ohlcv, 'Open'), + this.safeFloat (ohlcv, 'High'), + this.safeFloat (ohlcv, 'Low'), + this.safeFloat (ohlcv, 'Close'), + this.safeFloat (ohlcv, 'Volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '15m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'period': this.timeframes[timeframe], + 'market': market['id'], + 'count': limit, + }; + const response = await this.v3PublicGetGetcandles (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + // todo: STOP-LIMIT and AMI order types are supported + throw new InvalidOrder (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const request = { + 'rate': this.priceToPrecision (symbol, price), + 'quantity': this.amountToPrecision (symbol, amount), + 'tradeType': (side === 'buy') ? '1' : '0', + 'market': this.marketId (symbol), + }; + let response = undefined; + if (side === 'buy') { + response = await this.v3PrivatePostBuylimit (this.extend (request, params)); + } else { + response = await this.v3PrivatePostSelllimit (this.extend (request, params)); + } + // { success: true, + // message: "", + // result: "161105236" }, + return { + 'info': response, + 'id': this.safeString (response, 'result'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'orderid': id, + }; + const response = await this.v3PrivatePostOrdercancel (this.extend (request, params)); + // { success: true, message: '', result: '' } + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.v3PrivatePostGetopenorders (this.extend (request, params)); + const items = this.safeValue (response, 'result', []); + return this.parseOrders (items, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.v3PrivatePostGetbalances (params); + const result = { 'info': response }; + const items = response['result']; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const currencyId = this.safeString (item, 'Asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (item, 'Available'); + account['total'] = this.safeFloat (item, 'Balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.v3PrivatePostGetcloseorders (this.extend (request, params)); + const orders = this.safeValue (response, 'result', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this[method] (params); + const transactions = this.safeValue (response, 'result', []); + return this.parseTransactions (transactions, code, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('v3PrivatePostGetdeposithistory', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('v3PrivatePostGetwithdrawhistory', code, since, limit, params); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.v3PrivatePostGetdepositaddress (this.extend (request, params)); + // { success: true, + // message: '', + // result: + // { Asset: 'ETH', + // AssetName: 'Ethereum', + // DepositAddress: '0x748c5c8jhksjdfhd507d3aa9', + // Currency: 'ETH', + // CurrencyName: 'Ethereum' } } + const item = response['result']; + const address = this.safeString (item, 'DepositAddress'); + return { + 'currency': code, + 'address': this.checkAddress (address), + // 'tag': tag, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + // deposits don't seem to appear in here + const types = { + 'TRADE': 'trade', + 'WITHDRAW': 'transaction', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // trade (both sides) + // + // { + // ID: 109660527, + // TimeStamp: '2018-11-14 15:12:57.140776', + // Asset: 'ETH', + // AssetName: 'Ethereum', + // Amount: 0.01, + // Type: 'TRADE', + // Description: 'Trade +, order id 133111123', + // Comments: '', + // CoinSymbol: 'ETH', + // CoinName: 'Ethereum' + // } + // + // { + // ID: 109660526, + // TimeStamp: '2018-11-14 15:12:57.140776', + // Asset: 'BTC', + // AssetName: 'Bitcoin', + // Amount: -0.00031776, + // Type: 'TRADE', + // Description: 'Trade -, order id 133111123, fee -0.00000079', + // Comments: '', + // CoinSymbol: 'BTC', + // CoinName: 'Bitcoin' + // } + // + // withdrawal + // + // { + // ID: 104672316, + // TimeStamp: '2018-05-03 08:18:19.031831', + // Asset: 'DOGE', + // AssetName: 'Dogecoin', + // Amount: -61893.87864686, + // Type: 'WITHDRAW', + // Description: 'Withdraw: 61883.87864686 to address DD8tgehNNyYB2iqVazi2W1paaztgcWXtF6; fee 10.00000000', + // Comments: '', + // CoinSymbol: 'DOGE', + // CoinName: 'Dogecoin' + // } + // + const code = this.safeCurrencyCode (this.safeString (item, 'CoinSymbol'), currency); + const description = this.safeString (item, 'Description'); + const type = this.parseLedgerEntryType (this.safeString (item, 'Type')); + let referenceId = undefined; + let fee = undefined; + const delimiter = (type === 'trade') ? ', ' : '; '; + const parts = description.split (delimiter); + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; + if (part.indexOf ('fee') === 0) { + part = part.replace ('fee ', ''); + let feeCost = parseFloat (part); + if (feeCost < 0) { + feeCost = -feeCost; + } + fee = { + 'cost': feeCost, + 'currency': code, + }; + } else if (part.indexOf ('order id') === 0) { + referenceId = part.replace ('order id ', ''); + } + // + // does not belong to Ledger, related to parseTransaction + // + // if (part.indexOf ('Withdraw') === 0) { + // const details = part.split (' to address '); + // if (details.length > 1) { + // address = details[1]; + // } + // + } + const timestamp = this.parse8601 (this.safeString (item, 'TimeStamp')); + let amount = this.safeFloat (item, 'Amount'); + let direction = undefined; + if (amount !== undefined) { + direction = 'in'; + if (amount < 0) { + direction = 'out'; + amount = -amount; + } + } + const id = this.safeString (item, 'ID'); + return { + 'id': id, + 'info': item, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'direction': direction, + 'account': undefined, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': undefined, + 'after': undefined, + 'status': 'ok', + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // only seems to return 100 items and there is no documented way to change page size or offset + const request = { + }; + const response = await this.v3PrivatePostGetmytransactions (this.extend (request, params)); + const items = response['result']; + return this.parseLedger (items, code, since, limit); + } + + parseOrder (order, market = undefined) { + // + // fetchClosedOrders + // + // { OrderID: 89742658, + // Exchange: 'DOGE_BTC', + // Type: 'BUY', + // Quantity: 10000, + // QuantityRemaining: 0, + // QuantityBaseTraded: 0, + // Price: 6.6e-7, + // Status: 'OK', + // Created: '2018-02-16 08:55:36', + // Comments: '' } + // + // fetchOpenOrders + // + // { OrderID: 161105302, + // Exchange: 'ETH_BTC', + // Type: 'SELL', + // Quantity: 0.4, + // QuantityRemaining: 0.4, + // QuantityBaseTraded: 0, + // Price: 0.04, + // Status: 'OPEN', + // Created: '2020-01-22 09:21:27', + // Comments: { String: '', Valid: true } + const side = this.safeString (order, 'Type').toLowerCase (); + const status = this.parseOrderStatus (this.safeString (order, 'Status')); + const marketId = this.safeString (order, 'Exchange'); + const symbol = this.safeSymbol (marketId, market, '_'); + let timestamp = undefined; + if ('Created' in order) { + timestamp = this.parse8601 (order['Created'] + '+00:00'); + } + let price = this.safeFloat (order, 'Price'); + let cost = undefined; + const amount = this.safeFloat (order, 'Quantity'); + const remaining = this.safeFloat (order, 'QuantityRemaining'); + let filled = undefined; + if (amount !== undefined && remaining !== undefined) { + filled = amount - remaining; + } + if (!cost) { + if (price && filled) { + cost = price * filled; + } + } + if (!price) { + if (cost && filled) { + price = cost / filled; + } + } + const average = this.safeFloat (order, 'PricePerUnit'); + const id = this.safeString (order, 'OrderID'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'OK': 'closed', + 'OPEN': 'open', + 'CANCELED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // deposit: + // + // { ID: 118698752, + // Timestamp: '2020-01-21 11:16:09', + // Asset: 'ETH', + // Amount: 1, + // TransactionID: '', + // Status: 'CONFIRMED', + // Label: '0x748c5c8228d0c596f4d07f338blah', + // Symbol: 'ETH' } + // + // withdrawal: + // + // { ID: 689281, + // Timestamp: '2019-07-05 13:14:43', + // Asset: 'BTC', + // Amount: -0.108959, + // TransactionID: 'da48d6901fslfjsdjflsdjfls852b87e362cad1', + // Status: 'CONFIRMED', + // Label: '0.1089590;35wztHPMgrebFvvblah;0.00100000', + // Symbol: 'BTC' } + // + const id = this.safeString (transaction, 'ID'); + let amount = this.safeFloat (transaction, 'Amount'); + let type = 'deposit'; + if (amount < 0) { + amount = Math.abs (amount); + type = 'withdrawal'; + } + const currencyId = this.safeString (transaction, 'Asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const label = this.safeString (transaction, 'Label'); + const timestamp = this.parse8601 (this.safeString (transaction, 'Timestamp')); + let txid = this.safeString (transaction, 'TransactionID'); + let address = undefined; + let feeCost = undefined; + const labelParts = label.split (';'); + if (labelParts.length === 3) { + amount = parseFloat (labelParts[0]); + address = labelParts[1]; + feeCost = parseFloat (labelParts[2]); + } else { + address = label; + } + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'currency': code, + 'cost': feeCost, + }; + } + let status = 'ok'; + if (txid === 'CANCELED') { + txid = undefined; + status = 'canceled'; + } + return { + 'info': transaction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'fee': fee, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }) + '/'; + if (api === 'v3Private') { + this.checkRequiredCredentials (); + const request = { + 'apikey': this.apiKey, + 'nonce': this.nonce (), + }; + url += path + '?' + this.urlencode (this.extend (request, params)); + const signature = this.hmac (this.encode (url), this.encode (this.secret), 'sha512'); + headers = { 'apisign': signature }; + } else { + url += path + '?' + this.urlencode (params); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // examples... + // {"success":false,"message":"Erro: Order is not open.","result":""} <-- 'error' is spelt wrong + // {"success":false,"message":"Error: Very low volume.","result":"ERR_LOW_VOLUME"} + // {"success":false,"message":"Error: Insuficient Balance","result":"ERR_INSUFICIENT_BALANCE"} + // {"success":false,"message":"Invalid form","result":null} + // + const success = this.safeValue (response, 'success'); + if (success === undefined) { + throw new ExchangeError (this.id + ': malformed response: ' + this.json (response)); + } + if (!success) { + const feedback = this.id + ' ' + body; + const errorCode = this.safeString (response, 'result'); + if (errorCode !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + const errorMessage = this.safeString (response, 'message'); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorMessage, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorMessage, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],48:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidOrder, AuthenticationError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class braziliex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'braziliex', + 'name': 'Braziliex', + 'countries': [ 'BR' ], + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/34703593-c4498674-f504-11e7-8d14-ff8e44fb78c1.jpg', + 'api': 'https://braziliex.com/api/v1', + 'www': 'https://braziliex.com/', + 'doc': 'https://braziliex.com/exchange/api.php', + 'fees': 'https://braziliex.com/exchange/fees.php', + 'referral': 'https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'ticker', + 'ticker/{market}', + 'orderbook/{market}', + 'tradehistory/{market}', + ], + }, + 'private': { + 'post': [ + 'balance', + 'complete_balance', + 'open_orders', + 'trade_history', + 'deposit_address', + 'sell', + 'buy', + 'cancel_order', + 'order_status', + ], + }, + }, + 'commonCurrencies': { + 'EPC': 'Epacoin', + 'ABC': 'Anti Bureaucracy Coin', + }, + 'fees': { + 'trading': { + 'maker': 0.005, + 'taker': 0.005, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'options': { + 'fetchCurrencies': { + 'expires': 1000, // 1 second + }, + }, + }); + } + + async fetchCurrenciesFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetCurrencies (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchCurrencies'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + // + // { + // brl: { + // name: "Real", + // withdrawal_txFee: 0.0075, + // txWithdrawalFee: 9, + // MinWithdrawal: 30, + // minConf: 1, + // minDeposit: 0, + // txDepositFee: 0, + // txDepositPercentageFee: 0, + // minAmountTradeFIAT: 5, + // minAmountTradeBTC: 0.0001, + // minAmountTradeUSDT: 0.0001, + // decimal: 8, + // decimal_withdrawal: 8, + // active: 1, + // dev_active: 1, + // under_maintenance: 0, + // order: "010", + // is_withdrawal_active: 1, + // is_deposit_active: 1, + // is_token_erc20: 0, + // is_fiat: 1, + // gateway: 0, + // }, + // btc: { + // name: "Bitcoin", + // txWithdrawalMinFee: 0.000125, + // txWithdrawalFee: 0.00015625, + // MinWithdrawal: 0.0005, + // minConf: 1, + // minDeposit: 0, + // txDepositFee: 0, + // txDepositPercentageFee: 0, + // minAmountTradeFIAT: 5, + // minAmountTradeBTC: 0.0001, + // minAmountTradeUSDT: 0.0001, + // decimal: 8, + // decimal_withdrawal: 8, + // active: 1, + // dev_active: 1, + // under_maintenance: 0, + // order: "011", + // is_withdrawal_active: 1, + // is_deposit_active: 1, + // is_token_erc20: 0, + // is_fiat: 0, + // gateway: 1, + // } + // } + // + this.options['currencies'] = { + 'timestamp': this.milliseconds (), + 'response': response, + }; + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = response[id]; + const precision = this.safeInteger (currency, 'decimal'); + const code = this.safeCurrencyCode (id); + let active = this.safeInteger (currency, 'active') === 1; + const maintenance = this.safeInteger (currency, 'under_maintenance'); + if (maintenance !== 0) { + active = false; + } + const canWithdraw = this.safeInteger (currency, 'is_withdrawal_active') === 1; + const canDeposit = this.safeInteger (currency, 'is_deposit_active') === 1; + if (!canWithdraw || !canDeposit) { + active = false; + } + result[code] = { + 'id': id, + 'code': code, + 'name': currency['name'], + 'active': active, + 'precision': precision, + 'funding': { + 'withdraw': { + 'active': canWithdraw, + 'fee': this.safeFloat (currency, 'txWithdrawalFee'), + }, + 'deposit': { + 'active': canDeposit, + 'fee': this.safeFloat (currency, 'txDepositFee'), + }, + }, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'MinWithdrawal'), + 'max': Math.pow (10, precision), + }, + 'deposit': { + 'min': this.safeFloat (currency, 'minDeposit'), + 'max': undefined, + }, + }, + 'info': currency, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const currencies = await this.fetchCurrenciesFromCache (params); + const response = await this.publicGetTicker (); + // + // { + // btc_brl: { + // active: 1, + // market: 'btc_brl', + // last: 14648, + // percentChange: -0.95, + // baseVolume24: 27.856, + // quoteVolume24: 409328.039, + // baseVolume: 27.856, + // quoteVolume: 409328.039, + // highestBid24: 14790, + // lowestAsk24: 14450.01, + // highestBid: 14450.37, + // lowestAsk: 14699.98 + // }, + // ... + // } + // + const ids = Object.keys (response); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = response[id]; + const [ baseId, quoteId ] = id.split ('_'); + const uppercaseBaseId = baseId.toUpperCase (); + const uppercaseQuoteId = quoteId.toUpperCase (); + const base = this.safeCurrencyCode (uppercaseBaseId); + const quote = this.safeCurrencyCode (uppercaseQuoteId); + const symbol = base + '/' + quote; + const baseCurrency = this.safeValue (currencies, baseId, {}); + const quoteCurrency = this.safeValue (currencies, quoteId, {}); + const quoteIsFiat = this.safeInteger (quoteCurrency, 'is_fiat', 0); + let minCost = undefined; + if (quoteIsFiat) { + minCost = this.safeFloat (baseCurrency, 'minAmountTradeFIAT'); + } else { + minCost = this.safeFloat (baseCurrency, 'minAmountTrade' + uppercaseQuoteId); + } + const isActive = this.safeInteger (market, 'active'); + const active = (isActive === 1); + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highestBid24'), + 'low': this.safeFloat (ticker, 'lowestAsk24'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'percentChange'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume24'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume24'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTickerMarket (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const result = {}; + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const marketId = ids[i]; + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (response[marketId], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + const response = await this.publicGetOrderbookMarket (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString2 (trade, 'date_exec', 'date')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const cost = this.safeFloat (trade, 'total'); + const orderId = this.safeString (trade, 'order_number'); + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, '_id'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTradehistoryMarket (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privatePostCompleteBalance (params); + const result = { 'info': balances }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // { + // "order_number":"58ee441d05f8233fadabfb07", + // "type":"buy", + // "market":"ltc_btc", + // "price":"0.01000000", + // "amount":"0.00200000", + // "total":"0.00002000", + // "progress":"1.0000", + // "date":"2017-03-12 15:13:33" + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '_'); + let timestamp = this.safeInteger (order, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'date')); + } + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'total', 0.0); + const amount = this.safeFloat (order, 'amount'); + const filledPercentage = this.safeFloat (order, 'progress'); + const filled = amount * filledPercentage; + const remaining = parseFloat (this.amountToPrecision (symbol, amount - filled)); + let info = order; + if ('info' in info) { + info = order['info']; + } + const id = this.safeString (order, 'order_number'); + const fee = this.safeValue (order, 'fee'); // propagated from createOrder + const status = (filledPercentage === 1.0) ? 'closed' : 'open'; + const side = this.safeString (order, 'type'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': info, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = 'privatePost' + this.capitalize (side); + const request = { + 'market': market['id'], + // 'price': this.priceToPrecision (symbol, price), + // 'amount': this.amountToPrecision (symbol, amount), + 'price': price, + 'amount': amount, + }; + const response = await this[method] (this.extend (request, params)); + // + // sell + // + // { + // "success":1, + // "message":" ##RESERVED FOR ORDER / SELL / XMR_BTC / AMOUNT: 0.01 XMR / PRICE: 0.017 BTC / TOTAL: 0.00017000 BTC / FEE: 0.00002500 XMR ", + // "order_number":"590b962ba5b98335965fa0a8" + // } + // + // buy + // + // { + // "success":1, + // "message":" ##RESERVED FOR ORDER / BUY / XMR_BTC / AMOUNT: 0.005 XMR / PRICE: 0.017 BTC / TOTAL: 0.00008500 BTC / FEE: 0.00000021 BTC ", + // "order_number":"590b962ba5b98335965fa0c0" + // } + // + const success = this.safeInteger (response, 'success'); + if (success !== 1) { + throw new InvalidOrder (this.id + ' ' + this.json (response)); + } + const message = this.safeString (response, 'message'); + let parts = message.split (' / '); + parts = parts.slice (1); + const feeParts = parts[5].split (' '); + const amountParts = parts[2].split (' '); + const priceParts = parts[3].split (' '); + const totalParts = parts[4].split (' '); + const order = this.parseOrder ({ + 'timestamp': this.milliseconds (), + 'order_number': response['order_number'], + 'type': this.safeStringLower (parts, 0), + 'market': parts[0].toLowerCase (), + 'amount': this.safeString (amountParts, 1), + 'price': this.safeString (priceParts, 1), + 'total': this.safeString (totalParts, 1), + 'fee': { + 'cost': this.safeFloat (feeParts, 1), + 'currency': this.safeString (feeParts, 2), + }, + 'progress': '0.0', + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_number': id, + 'market': market['id'], + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_number': id, + 'market': market['id'], + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privatePostOpenOrders (this.extend (request, params)); + const orders = this.safeValue (response, 'order_open', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const trades = this.safeValue (response, 'trade_history', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostDepositAddress (this.extend (request, params)); + const address = this.safeString (response, 'deposit_address'); + this.checkAddress (address); + const tag = this.safeString (response, 'payment_id'); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + api; + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + query = this.extend ({ + 'command': path, + 'nonce': this.nonce (), + }, query); + body = this.urlencode (query); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Content-type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ((typeof response === 'string') && (response.length < 1)) { + throw new ExchangeError (this.id + ' returned empty response'); + } + if ('success' in response) { + const success = this.safeInteger (response, 'success'); + if (success === 0) { + const message = this.safeString (response, 'message'); + if (message === 'Invalid APIKey') { + throw new AuthenticationError (message); + } + throw new ExchangeError (message); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],49:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, DDoSProtection, InvalidOrder, InsufficientFunds } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcalpha extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcalpha', + 'name': 'BTC-Alpha', + 'countries': [ 'US' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': false, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '4h': '240', + '1d': 'D', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/42625213-dabaa5da-85cf-11e8-8f99-aa8f8f7699f0.jpg', + 'api': 'https://btc-alpha.com/api', + 'www': 'https://btc-alpha.com', + 'doc': 'https://btc-alpha.github.io/api-docs', + 'fees': 'https://btc-alpha.com/fees/', + 'referral': 'https://btc-alpha.com/?r=123788', + }, + 'api': { + 'public': { + 'get': [ + 'currencies/', + 'pairs/', + 'orderbook/{pair_name}/', + 'exchanges/', + 'charts/{pair}/{type}/chart/', + ], + }, + 'private': { + 'get': [ + 'wallets/', + 'orders/own/', + 'order/{id}/', + 'exchanges/own/', + 'deposits/', + 'withdraws/', + ], + 'post': [ + 'order/', + 'order-cancel/', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.00135, + 'LTC': 0.0035, + 'XMR': 0.018, + 'ZEC': 0.002, + 'ETH': 0.01, + 'ETC': 0.01, + 'SIB': 1.5, + 'CCRB': 4, + 'PZM': 0.05, + 'ITI': 0.05, + 'DCY': 5, + 'R': 5, + 'ATB': 0.05, + 'BRIA': 0.05, + 'KZC': 0.05, + 'HWC': 1, + 'SPA': 1, + 'SMS': 0.001, + 'REC': 0.01, + 'SUP': 1, + 'BQ': 100, + 'GDS': 0.1, + 'EVN': 300, + 'TRKC': 0.01, + 'UNI': 1, + 'STN': 1, + 'BCH': undefined, + 'QBIC': 0.5, + }, + }, + }, + 'commonCurrencies': { + 'CBC': 'Cashbery', + }, + 'exceptions': { + 'exact': {}, + 'broad': { + 'Out of balance': InsufficientFunds, // {"date":1570599531.4814300537,"error":"Out of balance -9.99243661 BTC"} + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPairs (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'currency1'); + const quoteId = this.safeString (market, 'currency2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': this.safeInteger (market, 'price_precision'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minimum_order_size'), + 'max': this.safeFloat (market, 'maximum_order_size'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + 'baseId': undefined, + 'quoteId': undefined, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair_name': this.marketId (symbol), + }; + if (limit) { + request['limit_sell'] = limit; + request['limit_buy'] = limit; + } + const response = await this.publicGetOrderbookPairName (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'buy', 'sell', 'price', 'amount'); + } + + parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) { + const result = []; + for (let i = 0; i < bidasks.length; i++) { + const bidask = bidasks[i]; + if (bidask) { + result.push (this.parseBidAsk (bidask, priceKey, amountKey)); + } + } + return result; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market === undefined) { + market = this.safeValue (this.marketsById, trade['pair']); + } + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (trade, 'timestamp'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const id = this.safeString2 (trade, 'id', 'tid'); + const side = this.safeString2 (trade, 'my_side', 'side'); + const orderId = this.safeString (trade, 'o_id'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const trades = await this.publicGetExchanges (this.extend (request, params)); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":1591296000, + // "open":0.024746, + // "close":0.024728, + // "low":0.024728, + // "high":0.024753, + // "volume":16.624 + // } + // + return [ + this.safeTimestamp (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.publicGetChartsPairTypeChart (this.extend (request, params)); + // + // [ + // {"time":1591296000,"open":0.024746,"close":0.024728,"low":0.024728,"high":0.024753,"volume":16.624}, + // {"time":1591295700,"open":0.024718,"close":0.02475,"low":0.024711,"high":0.02475,"volume":31.645}, + // {"time":1591295400,"open":0.024721,"close":0.024717,"low":0.024711,"high":0.02473,"volume":65.071} + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWallets (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'reserve'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + '1': 'open', + '2': 'canceled', + '3': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + let symbol = undefined; + if (market === undefined) { + market = this.safeValue (this.marketsById, order['pair']); + } + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (order, 'date'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString2 (order, 'oid', 'id'); + let trades = this.safeValue (order, 'trades', []); + trades = this.parseTrades (trades, market); + const side = this.safeString2 (order, 'my_side', 'type'); + let filled = undefined; + const numTrades = trades.length; + if (numTrades > 0) { + filled = 0.0; + for (let i = 0; i < numTrades; i++) { + filled = this.sum (filled, trades[i]['amount']); + } + } + let remaining = undefined; + if ((amount !== undefined) && (amount > 0) && (filled !== undefined)) { + remaining = Math.max (0, amount - filled); + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': undefined, + 'info': order, + 'lastTradeTimestamp': undefined, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'amount': amount, + 'price': this.priceToPrecision (symbol, price), + }; + const response = await this.privatePostOrder (this.extend (request, params)); + if (!response['success']) { + throw new InvalidOrder (this.id + ' ' + this.json (response)); + } + const order = this.parseOrder (response, market); + amount = (order['amount'] > 0) ? order['amount'] : amount; + return this.extend (order, { + 'amount': amount, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order': id, + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const order = await this.privateGetOrderId (this.extend (request, params)); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const orders = await this.privateGetOrdersOwn (this.extend (request, params)); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': '1', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': '3', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbol !== undefined) { + const market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const trades = await this.privateGetExchangesOwn (this.extend (request, params)); + return this.parseTrades (trades, undefined, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.urlencode (this.keysort (this.omit (params, this.extractParams (path)))); + let url = this.urls['api'] + '/'; + if (path !== 'charts/{pair}/{type}/chart/') { + url += 'v1/'; + } + url += this.implodeParams (path, params); + headers = { 'Accept': 'application/json' }; + if (api === 'public') { + if (query.length) { + url += '?' + query; + } + } else { + this.checkRequiredCredentials (); + let payload = this.apiKey; + if (method === 'POST') { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + body = query; + payload += body; + } else if (query.length) { + url += '?' + query; + } + headers['X-KEY'] = this.apiKey; + headers['X-SIGN'] = this.hmac (this.encode (payload), this.encode (this.secret)); + headers['X-NONCE'] = this.nonce ().toString (); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"date":1570599531.4814300537,"error":"Out of balance -9.99243661 BTC"} + // + const error = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + if (error !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + } + if (code === 401 || code === 403) { + throw new AuthenticationError (feedback); + } else if (code === 429) { + throw new DDoSProtection (feedback); + } + if (code < 400) { + return; + } + throw new ExchangeError (feedback); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],50:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InsufficientFunds, InvalidOrder, AuthenticationError, PermissionDenied, InvalidNonce, OrderNotFound, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcbox extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcbox', + 'name': 'BtcBox', + 'countries': [ 'JP' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg', + 'api': 'https://www.btcbox.co.jp/api', + 'www': 'https://www.btcbox.co.jp/', + 'doc': 'https://blog.btcbox.jp/en/archives/8762', + 'fees': 'https://support.btcbox.co.jp/hc/en-us/articles/360001235694-Fees-introduction', + }, + 'api': { + 'public': { + 'get': [ + 'depth', + 'orders', + 'ticker', + ], + }, + 'private': { + 'post': [ + 'balance', + 'trade_add', + 'trade_cancel', + 'trade_list', + 'trade_view', + 'wallet', + ], + }, + }, + 'markets': { + 'BTC/JPY': { 'id': 'btc', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy', 'taker': 0.05 / 100, 'maker': 0.05 / 100 }, + 'ETH/JPY': { 'id': 'eth', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 }, + 'LTC/JPY': { 'id': 'ltc', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 }, + 'BCH/JPY': { 'id': 'bch', 'symbol': 'BCH/JPY', 'base': 'BCH', 'quote': 'JPY', 'baseId': 'bch', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 }, + }, + 'exceptions': { + '104': AuthenticationError, + '105': PermissionDenied, + '106': InvalidNonce, + '107': InvalidOrder, // price should be an integer + '200': InsufficientFunds, + '201': InvalidOrder, // amount too small + '202': InvalidOrder, // price should be [0 : 1000000] + '203': OrderNotFound, + '401': OrderNotFound, // cancel canceled, closed or non-existent order + '402': DDoSProtection, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const free = currencyId + '_balance'; + if (free in response) { + const account = this.account (); + const used = currencyId + '_lock'; + account['free'] = this.safeFloat (response, free); + account['used'] = this.safeFloat (response, used); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + const numSymbols = this.symbols.length; + if (numSymbols > 1) { + request['coin'] = market['baseId']; + } + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + const numSymbols = this.symbols.length; + if (numSymbols > 1) { + request['coin'] = market['baseId']; + } + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const type = undefined; + const side = this.safeString (trade, 'type'); + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + const numSymbols = this.symbols.length; + if (numSymbols > 1) { + request['coin'] = market['baseId']; + } + const response = await this.publicGetOrders (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amount': amount, + 'price': price, + 'type': side, + 'coin': market['baseId'], + }; + const response = await this.privatePostTradeAdd (this.extend (request, params)); + // + // { + // "result":true, + // "id":"11" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // a special case for btcbox – default symbol is BTC/JPY + if (symbol === undefined) { + symbol = 'BTC/JPY'; + } + const market = this.market (symbol); + const request = { + 'id': id, + 'coin': market['baseId'], + }; + const response = await this.privatePostTradeCancel (this.extend (request, params)); + // + // {"result":true, "id":"11"} + // + return this.parseOrder (response, market); + } + + parseOrderStatus (status) { + const statuses = { + // TODO: complete list + 'part': 'open', // partially or not at all executed + 'all': 'closed', // fully executed + 'cancelled': 'canceled', + 'closed': 'closed', // never encountered, seems to be bug in the doc + 'no': 'closed', // not clarified in the docs... + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id":11, + // "datetime":"2014-10-21 10:47:20", + // "type":"sell", + // "price":42000, + // "amount_original":1.2, + // "amount_outstanding":1.2, + // "status":"closed", + // "trades":[] + // } + // + const id = this.safeString (order, 'id'); + const datetimeString = this.safeString (order, 'datetime'); + let timestamp = undefined; + if (datetimeString !== undefined) { + timestamp = this.parse8601 (order['datetime'] + '+09:00'); // Tokyo time + } + const amount = this.safeFloat (order, 'amount_original'); + const remaining = this.safeFloat (order, 'amount_outstanding'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const price = this.safeFloat (order, 'price'); + let cost = undefined; + if (price !== undefined) { + if (filled !== undefined) { + cost = filled * price; + } + } + // status is set by fetchOrder method only + let status = this.parseOrderStatus (this.safeString (order, 'status')); + // fetchOrders do not return status, use heuristic + if (status === undefined) { + if (remaining !== undefined && remaining === 0) { + status = 'closed'; + } + } + const trades = undefined; // todo: this.parseTrades (order['trades']); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (order, 'type'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'side': side, + 'type': undefined, + 'timeInForce': undefined, + 'status': status, + 'symbol': symbol, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'trades': trades, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // a special case for btcbox – default symbol is BTC/JPY + if (symbol === undefined) { + symbol = 'BTC/JPY'; + } + const market = this.market (symbol); + const request = this.extend ({ + 'id': id, + 'coin': market['baseId'], + }, params); + const response = await this.privatePostTradeView (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOrdersByType (type, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // a special case for btcbox – default symbol is BTC/JPY + if (symbol === undefined) { + symbol = 'BTC/JPY'; + } + const market = this.market (symbol); + const request = { + 'type': type, // 'open' or 'all' + 'coin': market['baseId'], + }; + const response = await this.privatePostTradeList (this.extend (request, params)); + const orders = this.parseOrders (response, market, since, limit); + // status (open/closed/canceled) is undefined + // btcbox does not return status, but we know it's 'open' as we queried for open orders + if (type === 'open') { + for (let i = 0; i < orders.length; i++) { + orders[i]['status'] = 'open'; + } + } + return orders; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByType ('all', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByType ('open', symbol, since, limit, params); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const query = this.extend ({ + 'key': this.apiKey, + 'nonce': nonce, + }, params); + const request = this.urlencode (query); + const secret = this.hash (this.encode (this.secret)); + query['signature'] = this.hmac (this.encode (request), this.encode (secret)); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // resort to defaultErrorHandler + } + // typical error response: {"result":false,"code":"401"} + if (httpCode >= 400) { + return; // resort to defaultErrorHandler + } + const result = this.safeValue (response, 'result'); + if (result === undefined || result === true) { + return; // either public API (no error codes expected) or success + } + const code = this.safeValue (response, 'code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); // unknown message + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let response = await this.fetch2 (path, api, method, params, headers, body); + if (typeof response === 'string') { + // sometimes the exchange returns whitespace prepended to json + response = this.strip (response); + if (!this.isJsonEncodedObject (response)) { + throw new ExchangeError (this.id + ' ' + response); + } + response = JSON.parse (response); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],51:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, OrderNotFound, InvalidOrder, InsufficientFunds, DDoSProtection, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcmarkets extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcmarkets', + 'name': 'BTC Markets', + 'countries': [ 'AU' ], // Australia + 'rateLimit': 1000, // market data cached for 1 second (trades cached for 2 seconds) + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': 'emulated', + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/89731817-b3fb8480-da52-11ea-817f-783b08aaf32b.jpg', + 'api': { + 'public': 'https://api.btcmarkets.net', + 'private': 'https://api.btcmarkets.net', + }, + 'www': 'https://btcmarkets.net', + 'doc': [ + 'https://api.btcmarkets.net/doc/v3', + 'https://github.com/BTCMarkets/API', + ], + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'markets/{marketId}/ticker', + 'markets/{marketId}/trades', + 'markets/{marketId}/orderbook', + 'markets/{marketId}/candles', + 'markets/tickers', + 'markets/orderbooks', + 'time', + ], + }, + 'private': { + 'get': [ + 'orders', + 'orders/{id}', + 'batchorders/{ids}', + 'trades', + 'trades/{id}', + 'withdrawals', + 'withdrawals/{id}', + 'deposits', + 'deposits/{id}', + 'transfers', + 'transfers/{id}', + 'addresses', + 'withdrawal-fees', + 'assets', + 'accounts/me/trading-fees', + 'accounts/me/withdrawal-limits', + 'accounts/me/balances', + 'accounts/me/transactions', + 'reports/{id}', + ], + 'post': [ + 'orders', + 'batchorders', + 'withdrawals', + 'reports', + ], + 'delete': [ + 'orders', + 'orders/{id}', + 'batchorders/{ids}', + ], + 'put': [ + 'orders/{id}', + ], + }, + }, + 'timeframes': { + '1m': '1m', + '1h': '1h', + '1d': '1d', + }, + 'exceptions': { + '3': InvalidOrder, + '6': DDoSProtection, + 'InsufficientFund': InsufficientFunds, + 'InvalidPrice': InvalidOrder, + 'InvalidAmount': InvalidOrder, + 'MissingArgument': InvalidOrder, + 'OrderAlreadyCancelled': InvalidOrder, + 'OrderNotFound': OrderNotFound, + 'OrderStatusIsFinal': InvalidOrder, + 'InvalidPaginationParameter': BadRequest, + }, + 'fees': { + 'percentage': true, + 'tierBased': true, + 'maker': -0.05 / 100, + 'taker': 0.20 / 100, + }, + 'options': { + 'fees': { + 'AUD': { + 'maker': 0.85 / 100, + 'taker': 0.85 / 100, + }, + }, + }, + }); + } + + async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['after'] = since; + } + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('privateGetTransfers', code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('privateGetDeposits', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('privateGetWithdrawals', code, since, limit, params); + } + + parseTransactionStatus (status) { + // todo: find more statuses + const statuses = { + 'Complete': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const statuses = { + 'Withdraw': 'withdrawal', + 'Deposit': 'deposit', + }; + return this.safeString (statuses, type, type); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": "6500230339", + // "assetName": "XRP", + // "amount": "500", + // "type": "Deposit", + // "creationTime": "2020-07-27T07:52:08.640000Z", + // "status": "Complete", + // "description": "RIPPLE Deposit, XRP 500", + // "fee": "0", + // "lastUpdate": "2020-07-27T07:52:08.665000Z", + // "paymentDetail": { + // "txId": "lsjflsjdfljsd", + // "address": "kjasfkjsdf?dt=873874545" + // } + // } + // + // { + // "id": "500985282", + // "assetName": "BTC", + // "amount": "0.42570126", + // "type": "Withdraw", + // "creationTime": "2017-07-29T12:49:03.931000Z", + // "status": "Complete", + // "description": "BTC withdraw from [nick-btcmarkets@snowmonkey.co.uk] to Address: 1B9DsnSYQ54VMqFHVJYdGoLMCYzFwrQzsj amount: 0.42570126 fee: 0.00000000", + // "fee": "0.0005", + // "lastUpdate": "2017-07-29T12:52:20.676000Z", + // "paymentDetail": { + // "txId": "fkjdsfjsfljsdfl", + // "address": "a;daddjas;djas" + // } + // } + // + // { + // "id": "505102262", + // "assetName": "XRP", + // "amount": "979.836", + // "type": "Deposit", + // "creationTime": "2017-07-31T08:50:01.053000Z", + // "status": "Complete", + // "description": "Ripple Deposit, X 979.8360", + // "fee": "0", + // "lastUpdate": "2017-07-31T08:50:01.290000Z" + // } + // + const timestamp = this.parse8601 (this.safeString (transaction, 'creationTime')); + const lastUpdate = this.parse8601 (this.safeString (transaction, 'lastUpdate')); + let type = this.parseTransactionType (this.safeStringLower (transaction, 'type')); + if (type === 'withdraw') { + type = 'withdrawal'; + } + const cryptoPaymentDetail = this.safeValue (transaction, 'paymentDetail', {}); + const txid = this.safeString (cryptoPaymentDetail, 'txId'); + let address = this.safeString (cryptoPaymentDetail, 'address'); + let tag = undefined; + if (address !== undefined) { + const addressParts = address.split ('?dt='); + const numParts = addressParts.length; + if (numParts > 1) { + address = addressParts[0]; + tag = addressParts[1]; + } + } + const addressTo = address; + const tagTo = tag; + const addressFrom = undefined; + const tagFrom = undefined; + const fee = this.safeFloat (transaction, 'fee'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const currencyId = this.safeString (transaction, 'assetName'); + const code = this.safeCurrencyCode (currencyId); + let amount = this.safeFloat (transaction, 'amount'); + if (fee) { + amount -= fee; + } + return { + 'id': this.safeString (transaction, 'id'), + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': addressTo, + 'addressFrom': addressFrom, + 'tag': tag, + 'tagTo': tagTo, + 'tagFrom': tagFrom, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': lastUpdate, + 'fee': { + 'currency': code, + 'cost': fee, + }, + 'info': transaction, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "marketId":"COMP-AUD", + // "baseAssetName":"COMP", + // "quoteAssetName":"AUD", + // "minOrderAmount":"0.00007", + // "maxOrderAmount":"1000000", + // "amountDecimals":"8", + // "priceDecimals":"2" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const baseId = this.safeString (market, 'baseAssetName'); + const quoteId = this.safeString (market, 'quoteAssetName'); + const id = this.safeString (market, 'marketId'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const fees = this.safeValue (this.safeValue (this.options, 'fees', {}), quote, this.fees); + const pricePrecision = this.safeFloat (market, 'priceDecimals'); + const amountPrecision = this.safeFloat (market, 'amountDecimals'); + const minAmount = this.safeFloat (market, 'minOrderAmount'); + const maxAmount = this.safeFloat (market, 'maxOrderAmount'); + let minPrice = undefined; + if (quote === 'AUD') { + minPrice = Math.pow (10, -pricePrecision); + } + const precision = { + 'amount': amountPrecision, + 'price': pricePrecision, + }; + const limits = { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': minPrice, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'info': market, + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'maker': fees['maker'], + 'taker': fees['taker'], + 'limits': limits, + 'precision': precision, + }); + } + return result; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "timestamp": "2019-09-01T18:34:27.045000Z" + // } + // + return this.parse8601 (this.safeString (response, 'timestamp')); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountsMeBalances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'assetName'); + const code = this.safeCurrencyCode (currencyId); + const total = this.safeFloat (balance, 'balance'); + const used = this.safeFloat (balance, 'locked'); + const account = this.account (); + account['used'] = used; + account['total'] = total; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "2020-09-12T18:30:00.000000Z", + // "14409.45", // open + // "14409.45", // high + // "14403.91", // low + // "14403.91", // close + // "0.01571701" // volume + // ] + // + return [ + this.parse8601 (this.safeString (ohlcv, 0)), + this.safeFloat (ohlcv, 1), // open + this.safeFloat (ohlcv, 2), // high + this.safeFloat (ohlcv, 3), // low + this.safeFloat (ohlcv, 4), // close + this.safeFloat (ohlcv, 5), // volume + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'timeWindow': this.timeframes[timeframe], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + // 'before': 1234567890123, + // 'after': 1234567890123, + // 'limit': limit, // default 10, max 200 + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; // default is 10, max 200 + } + const response = await this.publicGetMarketsMarketIdCandles (this.extend (request, params)); + // + // [ + // ["2020-09-12T18:30:00.000000Z","14409.45","14409.45","14403.91","14403.91","0.01571701"], + // ["2020-09-12T18:21:00.000000Z","14409.45","14409.45","14409.45","14409.45","0.0035"], + // ["2020-09-12T18:03:00.000000Z","14361.37","14361.37","14361.37","14361.37","0.00345221"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + const response = await this.publicGetMarketsMarketIdOrderbook (this.extend (request, params)); + // + // { + // "marketId":"BTC-AUD", + // "snapshotId":1599936148941000, + // "asks":[ + // ["14459.45","0.00456475"], + // ["14463.56","2"], + // ["14470.91","0.98"], + // ], + // "bids":[ + // ["14421.01","0.52"], + // ["14421","0.75"], + // ["14418","0.3521"], + // ] + // } + // + const timestamp = this.safeIntegerProduct (response, 'snapshotId', 0.001); + const orderbook = this.parseOrderBook (response, timestamp); + orderbook['nonce'] = this.safeInteger (response, 'snapshotId'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "marketId":"BAT-AUD", + // "bestBid":"0.3751", + // "bestAsk":"0.377", + // "lastPrice":"0.3769", + // "volume24h":"56192.97613335", + // "volumeQte24h":"21179.13270465", + // "price24h":"0.0119", + // "pricePct24h":"3.26", + // "low24h":"0.3611", + // "high24h":"0.3799", + // "timestamp":"2020-08-09T18:28:23.280000Z" + // } + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'marketId'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const last = this.safeFloat (ticker, 'lastPrice'); + const baseVolume = this.safeFloat (ticker, 'volume24h'); + const quoteVolume = this.safeFloat (ticker, 'volumeQte24h'); + const vwap = this.vwap (baseVolume, quoteVolume); + const change = this.safeFloat (ticker, 'price24h'); + const percentage = this.safeFloat (ticker, 'pricePct24h'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24h'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'bestAsk'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + const response = await this.publicGetMarketsMarketIdTicker (this.extend (request, params)); + // + // { + // "marketId":"BAT-AUD", + // "bestBid":"0.3751", + // "bestAsk":"0.377", + // "lastPrice":"0.3769", + // "volume24h":"56192.97613335", + // "volumeQte24h":"21179.13270465", + // "price24h":"0.0119", + // "pricePct24h":"3.26", + // "low24h":"0.3611", + // "high24h":"0.3799", + // "timestamp":"2020-08-09T18:28:23.280000Z" + // } + // + return this.parseTicker (response, market); + } + + async fetchTicker2 (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetMarketIdTick (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "id":"6191646611", + // "price":"539.98", + // "amount":"0.5", + // "timestamp":"2020-08-09T15:21:05.016000Z", + // "side":"Ask" + // } + // + // private fetchMyTrades + // + // { + // "id": "36014819", + // "marketId": "XRP-AUD", + // "timestamp": "2019-06-25T16:01:02.977000Z", + // "price": "0.67", + // "amount": "1.50533262", + // "side": "Ask", + // "fee": "0.00857285", + // "orderId": "3648306", + // "liquidityType": "Taker", + // "clientOrderId": "48" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const marketId = this.safeString (trade, 'marketId'); + let symbol = undefined; + let base = undefined; + let quote = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let feeCurrencyCode = undefined; + if (quote === 'AUD') { + feeCurrencyCode = quote; + } else { + feeCurrencyCode = base; + } + let side = this.safeString (trade, 'side'); + if (side === 'Bid') { + side = 'buy'; + } else if (side === 'Ask') { + side = 'sell'; + } + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const orderId = this.safeString (trade, 'orderId'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const takerOrMaker = this.safeStringLower (trade, 'liquidityType'); + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + // 'since': 59868345231, + 'marketId': market['id'], + }; + const response = await this.publicGetMarketsMarketIdTrades (this.extend (request, params)); + // + // [ + // {"id":"6191646611","price":"539.98","amount":"0.5","timestamp":"2020-08-09T15:21:05.016000Z","side":"Ask"}, + // {"id":"6191646610","price":"539.99","amount":"0.5","timestamp":"2020-08-09T15:21:05.015000Z","side":"Ask"}, + // {"id":"6191646590","price":"540","amount":"0.00233785","timestamp":"2020-08-09T15:21:04.171000Z","side":"Bid"}, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + // 'price': this.priceToPrecision (symbol, price), + 'amount': this.amountToPrecision (symbol, amount), + // 'type': 'Limit', // "Limit", "Market", "Stop Limit", "Stop", "Take Profit" + 'side': (side === 'buy') ? 'Bid' : 'Ask', + // 'triggerPrice': this.priceToPrecision (symbol, triggerPrice), // required for Stop, Stop Limit, Take Profit orders + // 'targetAmount': this.amountToPrecision (symbol, targetAmount), // target amount when a desired target outcome is required for order execution + // 'timeInForce': 'GTC', // GTC, FOK, IOC + // 'postOnly': false, // boolean if this is a post-only order + // 'selfTrade': 'A', // A = allow, P = prevent + // 'clientOrderId': this.uuid (), + }; + const lowercaseType = type.toLowerCase (); + const orderTypes = this.safeValue (this.options, 'orderTypes', { + 'limit': 'Limit', + 'market': 'Market', + 'stop': 'Stop', + 'stop limit': 'Stop Limit', + 'take profit': 'Take Profit', + }); + request['type'] = this.safeString (orderTypes, lowercaseType, type); + let priceIsRequired = false; + let triggerPriceIsRequired = false; + if (lowercaseType === 'limit') { + priceIsRequired = true; + // } else if (lowercaseType === 'market') { + // ... + // } + } else if (lowercaseType === 'stop limit') { + triggerPriceIsRequired = true; + priceIsRequired = true; + } else if (lowercaseType === 'take profit') { + triggerPriceIsRequired = true; + } else if (lowercaseType === 'stop') { + triggerPriceIsRequired = true; + } + if (priceIsRequired) { + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a price argument for a ' + type + 'order'); + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + } + if (triggerPriceIsRequired) { + const triggerPrice = this.safeFloat (params, 'triggerPrice'); + params = this.omit (params, 'triggerPrice'); + if (triggerPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a triggerPrice parameter for a ' + type + 'order'); + } else { + request['triggerPrice'] = this.priceToPrecision (symbol, triggerPrice); + } + } + const clientOrderId = this.safeString (params, 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clientOrderId'] = clientOrderId; + } + params = this.omit (params, 'clientOrderId'); + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "orderId": "7524", + // "marketId": "BTC-AUD", + // "side": "Bid", + // "type": "Limit", + // "creationTime": "2019-08-30T11:08:21.956000Z", + // "price": "100.12", + // "amount": "1.034", + // "openAmount": "1.034", + // "status": "Accepted", + // "clientOrderId": "1234-5678", + // "timeInForce": "IOC", + // "postOnly": false, + // "selfTrade": "P", + // "triggerAmount": "105", + // "targetAmount": "1000" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + for (let i = 0; i < ids.length; i++) { + ids[i] = parseInt (ids[i]); + } + const request = { + 'ids': ids, + }; + return await this.privateDeleteBatchordersIds (this.extend (request, params)); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privateDeleteOrdersId (this.extend (request, params)); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let currency = undefined; + let cost = undefined; + if (market['quote'] === 'AUD') { + currency = market['quote']; + cost = parseFloat (this.costToPrecision (symbol, amount * price)); + } else { + currency = market['base']; + cost = parseFloat (this.amountToPrecision (symbol, amount)); + } + return { + 'type': takerOrMaker, + 'currency': currency, + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, rate * cost)), + }; + } + + parseOrderStatus (status) { + const statuses = { + 'Accepted': 'open', + 'Placed': 'open', + 'Partially Matched': 'open', + 'Fully Matched': 'closed', + 'Cancelled': 'canceled', + 'Partially Cancelled': 'canceled', + 'Failed': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "orderId": "7524", + // "marketId": "BTC-AUD", + // "side": "Bid", + // "type": "Limit", + // "creationTime": "2019-08-30T11:08:21.956000Z", + // "price": "100.12", + // "amount": "1.034", + // "openAmount": "1.034", + // "status": "Accepted", + // "clientOrderId": "1234-5678", + // "timeInForce": "IOC", + // "postOnly": false, + // "selfTrade": "P", + // "triggerAmount": "105", + // "targetAmount": "1000" + // } + // + const timestamp = this.parse8601 (this.safeString (order, 'creationTime')); + const marketId = this.safeString (order, 'marketId'); + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let side = this.safeString (order, 'side'); + if (side === 'Bid') { + side = 'buy'; + } else if (side === 'Ask') { + side = 'sell'; + } + const type = this.safeStringLower (order, 'type'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const remaining = this.safeFloat (order, 'openAmount'); + let filled = undefined; + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let cost = undefined; + if (price !== undefined) { + if (filled !== undefined) { + cost = price * filled; + } + } + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'triggerPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': undefined, + 'status': status, + 'trades': undefined, + 'fee': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': 'all', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (since !== undefined) { + request['after'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'status': 'open' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (since !== undefined) { + request['after'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // [ + // { + // "id": "36014819", + // "marketId": "XRP-AUD", + // "timestamp": "2019-06-25T16:01:02.977000Z", + // "price": "0.67", + // "amount": "1.50533262", + // "side": "Ask", + // "fee": "0.00857285", + // "orderId": "3648306", + // "liquidityType": "Taker", + // "clientOrderId": "48" + // }, + // { + // "id": "3568960", + // "marketId": "GNT-AUD", + // "timestamp": "2019-06-20T08:44:04.488000Z", + // "price": "0.1362", + // "amount": "0.85", + // "side": "Bid", + // "fee": "0.00098404", + // "orderId": "3543015", + // "liquidityType": "Maker" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + lookupSymbolFromMarketId (marketId) { + let market = undefined; + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return symbol; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.keysort (this.omit (params, this.extractParams (path))); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const secret = this.base64ToBinary (this.encode (this.secret)); + let auth = method + request + nonce; + if ((method === 'GET') || (method === 'DELETE')) { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + auth += body; + } + const signature = this.hmac (this.encode (auth), secret, 'sha512', 'base64'); + headers = { + 'Accept': 'application/json', + 'Accept-Charset': 'UTF-8', + 'Content-Type': 'application/json', + 'BM-AUTH-APIKEY': this.apiKey, + 'BM-AUTH-TIMESTAMP': nonce, + 'BM-AUTH-SIGNATURE': signature, + }; + } else if (api === 'public') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'][api] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + if (!response['success']) { + const error = this.safeString (response, 'errorCode'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, error, feedback); + throw new ExchangeError (feedback); + } + } + // v3 api errors + if (code >= 400) { + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],52:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btctradeua extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btctradeua', + 'name': 'BTC Trade UA', + 'countries': [ 'UA' ], // Ukraine, + 'rateLimit': 3000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'signIn': true, + }, + 'urls': { + 'referral': 'https://btc-trade.com.ua/registration/22689', + 'logo': 'https://user-images.githubusercontent.com/1294454/27941483-79fc7350-62d9-11e7-9f61-ac47f28fcd96.jpg', + 'api': 'https://btc-trade.com.ua/api', + 'www': 'https://btc-trade.com.ua', + 'doc': 'https://docs.google.com/document/d/1ocYA0yMy_RXd561sfG3qEPZ80kyll36HUxvCRe5GbhE/edit', + }, + 'api': { + 'public': { + 'get': [ + 'deals/{symbol}', + 'trades/sell/{symbol}', + 'trades/buy/{symbol}', + 'japan_stat/high/{symbol}', + ], + }, + 'private': { + 'post': [ + 'auth', + 'ask/{symbol}', + 'balance', + 'bid/{symbol}', + 'buy/{symbol}', + 'my_orders/{symbol}', + 'order/status/{id}', + 'remove/order/{id}', + 'sell/{symbol}', + ], + }, + }, + 'markets': { + 'BCH/UAH': { 'id': 'bch_uah', 'symbol': 'BCH/UAH', 'base': 'BCH', 'quote': 'UAH', 'baseId': 'bch', 'quoteId': 'uah' }, + 'BTC/UAH': { 'id': 'btc_uah', 'symbol': 'BTC/UAH', 'base': 'BTC', 'quote': 'UAH', 'baseId': 'btc', 'quoteId': 'uah', 'precision': { 'price': 1 }, 'limits': { 'amount': { 'min': 0.0000000001 }}}, + 'DASH/BTC': { 'id': 'dash_btc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC', 'baseId': 'dash', 'quoteId': 'btc' }, + 'DASH/UAH': { 'id': 'dash_uah', 'symbol': 'DASH/UAH', 'base': 'DASH', 'quote': 'UAH', 'baseId': 'dash', 'quoteId': 'uah' }, + 'DOGE/BTC': { 'id': 'doge_btc', 'symbol': 'DOGE/BTC', 'base': 'DOGE', 'quote': 'BTC', 'baseId': 'doge', 'quoteId': 'btc' }, + 'DOGE/UAH': { 'id': 'doge_uah', 'symbol': 'DOGE/UAH', 'base': 'DOGE', 'quote': 'UAH', 'baseId': 'doge', 'quoteId': 'uah' }, + 'ETH/UAH': { 'id': 'eth_uah', 'symbol': 'ETH/UAH', 'base': 'ETH', 'quote': 'UAH', 'baseId': 'eth', 'quoteId': 'uah' }, + 'ITI/UAH': { 'id': 'iti_uah', 'symbol': 'ITI/UAH', 'base': 'ITI', 'quote': 'UAH', 'baseId': 'iti', 'quoteId': 'uah' }, + 'KRB/UAH': { 'id': 'krb_uah', 'symbol': 'KRB/UAH', 'base': 'KRB', 'quote': 'UAH', 'baseId': 'krb', 'quoteId': 'uah' }, + 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' }, + 'LTC/UAH': { 'id': 'ltc_uah', 'symbol': 'LTC/UAH', 'base': 'LTC', 'quote': 'UAH', 'baseId': 'ltc', 'quoteId': 'uah' }, + 'NVC/BTC': { 'id': 'nvc_btc', 'symbol': 'NVC/BTC', 'base': 'NVC', 'quote': 'BTC', 'baseId': 'nvc', 'quoteId': 'btc' }, + 'NVC/UAH': { 'id': 'nvc_uah', 'symbol': 'NVC/UAH', 'base': 'NVC', 'quote': 'UAH', 'baseId': 'nvc', 'quoteId': 'uah' }, + 'PPC/BTC': { 'id': 'ppc_btc', 'symbol': 'PPC/BTC', 'base': 'PPC', 'quote': 'BTC', 'baseId': 'ppc', 'quoteId': 'btc' }, + 'SIB/UAH': { 'id': 'sib_uah', 'symbol': 'SIB/UAH', 'base': 'SIB', 'quote': 'UAH', 'baseId': 'sib', 'quoteId': 'uah' }, + 'XMR/UAH': { 'id': 'xmr_uah', 'symbol': 'XMR/UAH', 'base': 'XMR', 'quote': 'UAH', 'baseId': 'xmr', 'quoteId': 'uah' }, + 'ZEC/UAH': { 'id': 'zec_uah', 'symbol': 'ZEC/UAH', 'base': 'ZEC', 'quote': 'UAH', 'baseId': 'zec', 'quoteId': 'uah' }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.0006, + 'LTC': 0.01, + 'NVC': 0.01, + 'DOGE': 10, + }, + }, + }, + }); + } + + async signIn (params = {}) { + return await this.privatePostAuth (params); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'accounts'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const bids = await this.publicGetTradesBuySymbol (this.extend (request, params)); + const asks = await this.publicGetTradesSellSymbol (this.extend (request, params)); + const orderbook = { + 'bids': [], + 'asks': [], + }; + if (bids) { + if ('list' in bids) { + orderbook['bids'] = bids['list']; + } + } + if (asks) { + if ('list' in asks) { + orderbook['asks'] = asks['list']; + } + } + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'currency_trade'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetJapanStatHighSymbol (this.extend (request, params)); + const ticker = this.safeValue (response, 'trades'); + const timestamp = this.milliseconds (); + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': undefined, + 'last': undefined, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': ticker, + }; + const tickerLength = ticker.length; + if (tickerLength > 0) { + const start = Math.max (tickerLength - 48, 0); + for (let i = start; i < ticker.length; i++) { + const candle = ticker[i]; + if (result['open'] === undefined) { + result['open'] = candle[1]; + } + if ((result['high'] === undefined) || (result['high'] < candle[2])) { + result['high'] = candle[2]; + } + if ((result['low'] === undefined) || (result['low'] > candle[3])) { + result['low'] = candle[3]; + } + if (result['baseVolume'] === undefined) { + result['baseVolume'] = -candle[5]; + } else { + result['baseVolume'] -= candle[5]; + } + } + const last = tickerLength - 1; + result['last'] = ticker[last][4]; + result['close'] = result['last']; + result['baseVolume'] = -1 * result['baseVolume']; + } + return result; + } + + convertCyrillicMonthNameToString (cyrillic) { + const months = { + 'января': '01', + 'февраля': '02', + 'марта': '03', + 'апреля': '04', + 'мая': '05', + 'июня': '06', + 'июля': '07', + 'августа': '08', + 'сентября': '09', + 'октября': '10', + 'ноября': '11', + 'декабря': '12', + }; + return this.safeString (months, cyrillic); + } + + parseCyrillicDatetime (cyrillic) { + const parts = cyrillic.split (' '); + let day = parts[0]; + const month = this.convertCyrillicMonthNameToString (parts[1]); + if (!month) { + throw new ExchangeError (this.id + ' parseTrade() undefined month name: ' + cyrillic); + } + const year = parts[2]; + let hms = parts[4]; + const hmsLength = hms.length; + if (hmsLength === 7) { + hms = '0' + hms; + } + if (day.length === 1) { + day = '0' + day; + } + const ymd = [ year, month, day ].join ('-'); + const ymdhms = ymd + 'T' + hms; + const timestamp = this.parse8601 (ymdhms); + // server reports local time, adjust to UTC + let md = [ month, day ].join (''); + md = parseInt (md); + // a special case for DST + // subtract 2 hours during winter + if (md < 325 || md > 1028) { + return timestamp - 7200000; + } + // subtract 3 hours during summer + return timestamp - 10800000; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parseCyrillicDatetime (this.safeString (trade, 'pub_date')); + const id = this.safeString (trade, 'id'); + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amnt_trade'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetDealsSymbol (this.extend (request, params)); + // they report each trade twice (once for both of the two sides of the fill) + // deduplicate trades for that reason + const trades = []; + for (let i = 0; i < response.length; i++) { + if (response[i]['id'] % 2) { + trades.push (response[i]); + } + } + return this.parseTrades (trades, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = 'privatePost' + this.capitalize (side) + 'Id'; + const request = { + 'count': amount, + 'currency1': market['quoteId'], + 'currency': market['baseId'], + 'price': price, + }; + return this[method] (this.extend (request, params)); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privatePostRemoveOrderId (this.extend (request, params)); + } + + parseOrder (order, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'timestamp': timestamp, // until they fix their timestamp + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': 'open', + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': this.safeString (order, 'type'), + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'amount': this.safeFloat (order, 'amnt_trade'), + 'filled': 0, + 'remaining': this.safeFloat (order, 'amnt_trade'), + 'trades': undefined, + 'info': order, + 'cost': undefined, + 'average': undefined, + 'fee': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privatePostMyOrdersSymbol (this.extend (request, params)); + const orders = this.safeValue (response, 'your_open_orders'); + return this.parseOrders (orders, market, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += this.implodeParams (path, query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'out_order_id': nonce, + 'nonce': nonce, + }, query)); + const auth = body + this.secret; + headers = { + 'public-key': this.apiKey, + 'api-sign': this.hash (this.encode (auth), 'sha256'), + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],53:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcturk extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcturk', + 'name': 'BTCTurk', + 'countries': [ 'TR' ], // Turkey + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1d': '1d', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153926-efbef500-c2c0-11ea-9842-05b63612c4b9.jpg', + 'api': 'https://www.btcturk.com/api', + 'www': 'https://www.btcturk.com', + 'doc': 'https://github.com/BTCTrader/broker-api-docs', + }, + 'api': { + 'public': { + 'get': [ + 'ohlcdata', // ?last=COUNT + 'orderbook', + 'ticker', + 'trades', // ?last=COUNT (max 50) + ], + }, + 'private': { + 'get': [ + 'balance', + 'openOrders', + 'userTransactions', // ?offset=0&limit=25&sort=asc + ], + 'post': [ + 'exchange', + 'cancelOrder', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.002 * 1.18, + 'taker': 0.003 * 1.18, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTicker (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'pair'); + let baseId = id.slice (0, 3); + let quoteId = id.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + baseId = baseId.toLowerCase (); + quoteId = quoteId.toLowerCase (); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const active = true; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const result = { 'info': response }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currencies[code]; + const free = currency['id'] + '_available'; + const total = currency['id'] + '_balance'; + const used = currency['id'] + '_reserved'; + if (free in response) { + const account = this.account (); + account['free'] = this.safeFloat (response, free); + account['total'] = this.safeFloat (response, total); + account['used'] = this.safeFloat (response, used); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pairSymbol': market['id'], + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'average'), + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const tickers = await this.publicGetTicker (params); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const marketId = this.safeString (ticker, 'pair'); + let symbol = marketId; + let market = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[symbol]; + symbol = market['symbol']; + } + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tickers = await this.fetchTickers (params); + return this.safeValue2 (tickers, market['id'], symbol); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // let maxCount = 50; + const request = { + 'pairSymbol': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.parse8601 (this.safeString (ohlcv, 'Time')), + this.safeFloat (ohlcv, 'Open'), + this.safeFloat (ohlcv, 'High'), + this.safeFloat (ohlcv, 'Low'), + this.safeFloat (ohlcv, 'Close'), + this.safeFloat (ohlcv, 'Volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1d', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + if (limit !== undefined) { + request['last'] = limit; + } + const response = await this.publicGetOhlcdata (this.extend (request, params)); + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'PairSymbol': this.marketId (symbol), + 'OrderType': (side === 'buy') ? 0 : 1, + 'OrderMethod': (type === 'market') ? 1 : 0, + }; + if (type === 'market') { + if (!('Total' in params)) { + throw new ExchangeError (this.id + ' createOrder requires the "Total" extra parameter for market orders (amount and price are both ignored)'); + } + } else { + request['Price'] = price; + request['Amount'] = amount; + } + const response = await this.privatePostExchange (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (this.id === 'btctrader') { + throw new ExchangeError (this.id + ' is an abstract base API for BTCExchange, BTCTurk'); + } + let url = this.urls['api'] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + body = this.urlencode (params); + const secret = this.base64ToBinary (this.secret); + const auth = this.apiKey + nonce; + headers = { + 'X-PCK': this.apiKey, + 'X-Stamp': nonce, + 'X-Signature': this.hmac (this.encode (auth), secret, 'sha256', 'base64'), + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],54:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AddressPending, AuthenticationError, ExchangeError, NotSupported, PermissionDenied, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class buda extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'buda', + 'name': 'Buda', + 'countries': [ 'AR', 'CL', 'CO', 'PE' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTrades': true, + 'fetchTicker': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/47380619-8a029200-d706-11e8-91e0-8a391fe48de3.jpg', + 'api': 'https://www.buda.com/api', + 'www': 'https://www.buda.com', + 'doc': 'https://api.buda.com', + 'fees': 'https://www.buda.com/comisiones', + }, + 'status': { + 'status': 'error', + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + }, + 'api': { + 'public': { + 'get': [ + 'pairs', + 'markets', + 'currencies', + 'markets/{market}', + 'markets/{market}/ticker', + 'markets/{market}/volume', + 'markets/{market}/order_book', + 'markets/{market}/trades', + 'currencies/{currency}/fees/deposit', + 'currencies/{currency}/fees/withdrawal', + 'tv/history', + ], + 'post': [ + 'markets/{market}/quotations', + ], + }, + 'private': { + 'get': [ + 'balances', + 'balances/{currency}', + 'currencies/{currency}/balances', + 'orders', + 'orders/{id}', + 'markets/{market}/orders', + 'deposits', + 'currencies/{currency}/deposits', + 'withdrawals', + 'currencies/{currency}/withdrawals', + 'currencies/{currency}/receive_addresses', + 'currencies/{currency}/receive_addresses/{id}', + ], + 'post': [ + 'markets/{market}/orders', + 'currencies/{currency}/deposits', + 'currencies/{currency}/withdrawals', + 'currencies/{currency}/simulated_withdrawals', + 'currencies/{currency}/receive_addresses', + ], + 'put': [ + 'orders/{id}', + ], + }, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '30m': '30', + '1h': '60', + '2h': '120', + '1d': 'D', + '1w': 'W', + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.008, // 0.8% + 'maker': 0.004, // 0.4% + 'tiers': { + 'taker': [ + [0, 0.008], // 0.8% + [2000, 0.007], // 0.7% + [20000, 0.006], // 0.6% + [100000, 0.005], // 0.5% + [500000, 0.004], // 0.4% + [2500000, 0.003], // 0.3% + [12500000, 0.002], // 0.2% + ], + 'maker': [ + [0, 0.004], // 0.4% + [2000, 0.0035], // 0.35% + [20000, 0.003], // 0.3% + [100000, 0.0025], // 0.25% + [500000, 0.002], // 0.2% + [2500000, 0.0015], // 0.15% + [12500000, 0.001], // 0.1% + ], + }, + }, + }, + 'exceptions': { + 'not_authorized': AuthenticationError, // { message: 'Invalid credentials', code: 'not_authorized' } + 'forbidden': PermissionDenied, // { message: 'You dont have access to this resource', code: 'forbidden' } + 'invalid_record': ExchangeError, // { message: 'Validation Failed', code: 'invalid_record', errors: [] } + 'not_found': ExchangeError, // { message: 'Not found', code: 'not_found' } + 'parameter_missing': ExchangeError, // { message: 'Parameter missing', code: 'parameter_missing' } + 'bad_parameter': ExchangeError, // { message: 'Bad Parameter format', code: 'bad_parameter' } + }, + }); + } + + async fetchCurrencyInfo (currency, currencies = undefined) { + if (!currencies) { + const response = await this.publicGetCurrencies (); + currencies = this.safeValue (response, 'currencies'); + } + for (let i = 0; i < currencies.length; i++) { + const currencyInfo = currencies[i]; + if (currencyInfo['id'] === currency) { + return currencyInfo; + } + } + return undefined; + } + + async fetchMarkets (params = {}) { + const marketsResponse = await this.publicGetMarkets (params); + const markets = this.safeValue (marketsResponse, 'markets'); + const currenciesResponse = await this.publicGetCurrencies (); + const currencies = this.safeValue (currenciesResponse, 'currencies'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const baseInfo = await this.fetchCurrencyInfo (baseId, currencies); + const quoteInfo = await this.fetchCurrencyInfo (quoteId, currencies); + const symbol = base + '/' + quote; + const precision = { + 'amount': baseInfo['input_decimals'], + 'price': quoteInfo['input_decimals'], + }; + const limits = { + 'amount': { + 'min': parseFloat (market['minimum_order_amount'][0]), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }; + limits['cost'] = { + 'min': limits['amount']['min'] * limits['price']['min'], + 'max': undefined, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (); + const currencies = response['currencies']; + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + if (!currency['managed']) { + continue; + } + const id = this.safeString (currency, 'id'); + const code = this.safeCurrencyCode (id); + const precision = this.safeFloat (currency, 'input_decimals'); + const minimum = Math.pow (10, -precision); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': undefined, + 'active': true, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minimum, + 'max': undefined, + }, + 'price': { + 'min': minimum, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'deposit': { + 'min': parseFloat (currency['deposit_minimum'][0]), + 'max': undefined, + }, + 'withdraw': { + 'min': parseFloat (currency['withdrawal_minimum'][0]), + }, + }, + }; + } + return result; + } + + async fetchFundingFees (codes = undefined, params = {}) { + // by default it will try load withdrawal fees of all currencies (with separate requests) + // however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those + await this.loadMarkets (); + const withdrawFees = {}; + const depositFees = {}; + const info = {}; + if (codes === undefined) { + codes = Object.keys (this.currencies); + } + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const request = { 'currency': currency['id'] }; + const withdrawResponse = await this.publicGetCurrenciesCurrencyFeesWithdrawal (request); + const depositResponse = await this.publicGetCurrenciesCurrencyFeesDeposit (request); + withdrawFees[code] = this.parseFundingFee (withdrawResponse['fee']); + depositFees[code] = this.parseFundingFee (depositResponse['fee']); + info[code] = { + 'withdraw': withdrawResponse, + 'deposit': depositResponse, + }; + } + return { + 'withdraw': withdrawFees, + 'deposit': depositFees, + 'info': info, + }; + } + + parseFundingFee (fee, type = undefined) { + if (type === undefined) { + type = fee['name']; + } + if (type === 'withdrawal') { + type = 'withdraw'; + } + return { + 'type': type, + 'currency': fee['base'][1], + 'rate': fee['percent'], + 'cost': parseFloat (fee['base'][0]), + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketsMarketTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'ticker'); + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = parseFloat (ticker['last_price'][0]); + const percentage = parseFloat (ticker['price_variation_24h']); + const open = parseFloat (this.priceToPrecision (symbol, last / (percentage + 1))); + const change = last - open; + const average = this.sum (last, open) / 2; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': parseFloat (ticker['max_bid'][0]), + 'bidVolume': undefined, + 'ask': parseFloat (ticker['min_ask'][0]), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': open, + 'change': change, + 'percentage': percentage * 100, + 'average': average, + 'baseVolume': parseFloat (ticker['volume'][0]), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + // the since argument works backwards – returns trades up to the specified timestamp + // therefore not implemented here + // the method is still available for users to be able to traverse backwards in time + // by using the timestamp from the first received trade upon each iteration + if (limit !== undefined) { + request['limit'] = limit; // 50 max + } + const response = await this.publicGetMarketsMarketTrades (this.extend (request, params)); + // + // { trades: { market_id: "ETH-BTC", + // timestamp: null, + // last_timestamp: "1536901277302", + // entries: [ [ "1540077456791", "0.0063767", "0.03", "sell", 479842 ], + // [ "1539916642772", "0.01888263", "0.03019563", "sell", 479438 ], + // [ "1539834081787", "0.023718648", "0.031001", "sell", 479069 ], + // ... ] + // + return this.parseTrades (response['trades']['entries'], market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // [ "1540077456791", "0.0063767", "0.03", "sell", 479842 ] + // + let timestamp = undefined; + let side = undefined; + const type = undefined; + let price = undefined; + let amount = undefined; + let id = undefined; + const order = undefined; + const fee = undefined; + let symbol = undefined; + let cost = undefined; + if (market) { + symbol = market['symbol']; + } + if (Array.isArray (trade)) { + timestamp = parseInt (trade[0]); + price = parseFloat (trade[1]); + amount = parseFloat (trade[2]); + cost = price * amount; + side = trade[3]; + id = trade[4].toString (); + } + return { + 'id': id, + 'order': order, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketsMarketOrderBook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'order_book'); + return this.parseOrderBook (orderbook); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (since === undefined) { + since = this.milliseconds () - 86400000; + } + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + 'from': since / 1000, + 'to': this.seconds (), + }; + const response = await this.publicGetTvHistory (this.extend (request, params)); + return this.parseTradingViewOHLCV (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalances (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'balances'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'id'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = parseFloat (balance['available_amount'][0]); + account['total'] = parseFloat (balance['amount'][0]); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': parseInt (id), + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + const order = this.safeValue (response, 'order'); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'market': market['id'], + 'per': limit, + }; + const response = await this.privateGetMarketsMarketOrders (this.extend (request, params)); + const orders = this.safeValue (response, 'orders'); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'pending', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'traded', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + side = (side === 'buy') ? 'Bid' : 'Ask'; + const request = { + 'market': this.marketId (symbol), + 'price_type': type, + 'type': side, + 'amount': this.amountToPrecision (symbol, amount), + }; + if (type === 'limit') { + request['limit'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostMarketsMarketOrders (this.extend (request, params)); + const order = this.safeValue (response, 'order'); + return this.parseOrder (order); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': parseInt (id), + 'state': 'canceling', + }; + const response = await this.privatePutOrdersId (this.extend (request, params)); + const order = this.safeValue (response, 'order'); + return this.parseOrder (order); + } + + parseOrderStatus (status) { + const statuses = { + 'traded': 'closed', + 'received': 'open', + 'canceling': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const marketId = this.safeString (order, 'market_id'); + const symbol = this.safeSymbol (marketId, market); + const type = this.safeString (order, 'price_type'); + const side = this.safeStringLower (order, 'type'); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const amount = parseFloat (order['original_amount'][0]); + const remaining = parseFloat (order['amount'][0]); + const filled = parseFloat (order['traded_amount'][0]); + const cost = parseFloat (order['total_exchanged'][0]); + let price = this.safeFloat (order, 'limit'); + if (price !== undefined) { + price = parseFloat (price[0]); + } + if (cost > 0 && filled > 0) { + price = this.priceToPrecision (symbol, cost / filled); + } + const fee = { + 'cost': parseFloat (order['paid_fee'][0]), + 'currency': order['paid_fee'][1], + }; + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + isFiat (code) { + const fiats = { + 'ARS': true, + 'CLP': true, + 'COP': true, + 'PEN': true, + }; + return this.safeValue (fiats, code, false); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + if (this.isFiat (code)) { + throw new NotSupported (this.id + ' fetchDepositAddress() for fiat ' + code + ' is not supported'); + } + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetCurrenciesCurrencyReceiveAddresses (this.extend (request, params)); + const receiveAddresses = this.safeValue (response, 'receive_addresses'); + const addressPool = []; + for (let i = 1; i < receiveAddresses.length; i++) { + const receiveAddress = receiveAddresses[i]; + if (receiveAddress['ready']) { + const address = receiveAddress['address']; + this.checkAddress (address); + addressPool.push (address); + } + } + const addressPoolLength = addressPool.length; + if (addressPoolLength < 1) { + throw new AddressPending (this.id + ': there are no addresses ready for receiving ' + code + ', retry again later)'); + } + const address = addressPool[0]; + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': receiveAddresses, + }; + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + if (this.isFiat (code)) { + throw new NotSupported (this.id + ': fiat fetchDepositAddress() for ' + code + ' is not supported'); + } + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostCurrenciesCurrencyReceiveAddresses (this.extend (request, params)); + const address = this.safeString (response['receive_address'], 'address'); // the creation is async and returns a null address, returns only the id + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'rejected': 'failed', + 'confirmed': 'ok', + 'anulled': 'canceled', + 'retained': 'canceled', + 'pending_confirmation': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + const id = this.safeString (transaction, 'id'); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const amount = parseFloat (transaction['amount'][0]); + const fee = parseFloat (transaction['fee'][0]); + const feeCurrency = transaction['fee'][1]; + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const type = ('deposit_data' in transaction) ? 'deposit' : 'withdrawal'; + const data = this.safeValue (transaction, type + '_data', {}); + const address = this.safeValue (data, 'target_address'); + const txid = this.safeString (data, 'tx_hash'); + const updated = this.parse8601 (this.safeString (data, 'updated_at')); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'cost': fee, + 'rate': feeCurrency, + }, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (code === undefined) { + throw new ArgumentsRequired (this.id + ': fetchDeposits() requires a currency code argument'); + } + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'per': limit, + }; + const response = await this.privateGetCurrenciesCurrencyDeposits (this.extend (request, params)); + const deposits = this.safeValue (response, 'deposits'); + return this.parseTransactions (deposits, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (code === undefined) { + throw new ArgumentsRequired (this.id + ': fetchDeposits() requires a currency code argument'); + } + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'per': limit, + }; + const response = await this.privateGetCurrenciesCurrencyWithdrawals (this.extend (request, params)); + const withdrawals = this.safeValue (response, 'withdrawals'); + return this.parseTransactions (withdrawals, currency, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'withdrawal_data': { + 'target_address': address, + }, + }; + const response = await this.privatePostCurrenciesCurrencyWithdrawals (this.extend (request, params)); + const withdrawal = this.safeValue (response, 'withdrawal'); + return this.parseTransaction (withdrawal); + } + + nonce () { + return this.microseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + if (method === 'GET') { + request += '?' + this.urlencode (query); + } else { + body = this.json (query); + } + } + const url = this.urls['api'] + '/' + this.version + '/' + request; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const components = [ method, '/api/' + this.version + '/' + request ]; + if (body) { + const base64Body = this.stringToBase64 (body); + components.push (this.decode (base64Body)); + } + components.push (nonce); + const message = components.join (' '); + const signature = this.hmac (this.encode (message), this.encode (this.secret), 'sha384'); + headers = { + 'X-SBTC-APIKEY': this.apiKey, + 'X-SBTC-SIGNATURE': signature, + 'X-SBTC-NONCE': nonce, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (code >= 400) { + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message', body); + const feedback = this.id + ' ' + message; + if (errorCode !== undefined) { + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],55:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { RateLimitExceeded, BadSymbol, OrderNotFound, ExchangeError, AuthenticationError, ArgumentsRequired, ExchangeNotAvailable } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bw extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bw', + 'name': 'BW', + 'countries': [ 'CN' ], + 'rateLimit': 1500, + 'version': 'v1', + 'has': { + 'cancelAllOrders': false, + 'cancelOrder': true, + 'cancelOrders': false, + 'CORS': false, + 'createDepositAddress': false, + 'createLimitOrder': true, + 'createMarketOrder': false, + 'createOrder': true, + 'deposit': false, + 'editOrder': false, + 'fetchBalance': true, + 'fetchBidsAsks': false, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': false, + 'fetchL2OrderBook': false, + 'fetchLedger': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': false, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTradingLimits': false, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'privateAPI': false, + 'publicAPI': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': '1M', + '5m': '5M', + '15m': '15M', + '30m': '30M', + '1h': '1H', + '1w': '1W', + }, + 'hostname': 'bw.com', // set to 'bw.io' for China mainland + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/69436317-31128c80-0d52-11ea-91d1-eb7bb5818812.jpg', + 'api': 'https://www.{hostname}', + 'www': 'https://www.bw.com', + 'doc': 'https://github.com/bw-exchange/api_docs_en/wiki', + 'fees': 'https://www.bw.com/feesRate', + 'referral': 'https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.2 / 100, + 'maker': 0.2 / 100, + }, + 'funding': { + }, + }, + 'exceptions': { + 'exact': { + '999': AuthenticationError, + '1000': ExchangeNotAvailable, // {"datas":null,"resMsg":{"message":"getKlines error:data not exitsts\uff0cplease wait ,dataType=4002_KLINE_1M","method":null,"code":"1000"}} + '2012': OrderNotFound, // {"datas":null,"resMsg":{"message":"entrust not exists or on dealing with system","method":null,"code":"2012"}} + '5017': BadSymbol, // {"datas":null,"resMsg":{"message":"market not exist","method":null,"code":"5017"}} + '10001': RateLimitExceeded, // {"resMsg":{"code":"10001","message":"API frequency limit"}} + }, + }, + 'api': { + 'public': { + 'get': [ + 'api/data/v1/klines', + 'api/data/v1/ticker', + 'api/data/v1/tickers', + 'api/data/v1/trades', + 'api/data/v1/entrusts', + 'exchange/config/controller/website/marketcontroller/getByWebId', + 'exchange/config/controller/website/currencycontroller/getCurrencyList', + ], + }, + 'private': { + 'get': [ + 'exchange/entrust/controller/website/EntrustController/getEntrustById', + 'exchange/entrust/controller/website/EntrustController/getUserEntrustRecordFromCacheWithPage', + 'exchange/entrust/controller/website/EntrustController/getUserEntrustList', + 'exchange/fund/controller/website/fundwebsitecontroller/getwithdrawaddress', + 'exchange/fund/controller/website/fundwebsitecontroller/getpayoutcoinrecord', + 'exchange/entrust/controller/website/EntrustController/getUserEntrustList', + // the docs say that the following URLs are HTTP POST + // in the docs header and HTTP GET in the docs body + // the docs contradict themselves, a typo most likely + // the actual HTTP method is POST for this endpoint + // 'exchange/fund/controller/website/fundcontroller/getPayinAddress', + // 'exchange/fund/controller/website/fundcontroller/getPayinCoinRecord', + ], + 'post': [ + 'exchange/fund/controller/website/fundcontroller/getPayinAddress', // see the comment above + 'exchange/fund/controller/website/fundcontroller/getPayinCoinRecord', // see the comment above + 'exchange/fund/controller/website/fundcontroller/findbypage', + 'exchange/entrust/controller/website/EntrustController/addEntrust', + 'exchange/entrust/controller/website/EntrustController/cancelEntrust', + ], + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeConfigControllerWebsiteMarketcontrollerGetByWebId (params); + // + // { + // "datas": [ + // { + // "orderNum":null, + // "leverEnable":true, + // "leverMultiple":10, + // "marketId":"291", + // "webId":"102", + // "serverId":"entrust_bw_23", + // "name":"eos_usdt", + // "leverType":"2", + // "buyerCurrencyId":"11", + // "sellerCurrencyId":"7", + // "amountDecimal":4, + // "priceDecimal":3, + // "minAmount":"0.0100000000", + // "state":1, + // "openTime":1572537600000, + // "defaultFee":"0.00200000", + // "createUid":null, + // "createTime":0, + // "modifyUid":null, + // "modifyTime":1574160113735, + // "combineMarketId":"", + // "isCombine":0, + // "isMining":0 + // } + // ], + // "resMsg": { "message":"success !", "method":null, "code":"1" } + // } + // + const markets = this.safeValue (response, 'datas', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'marketId'); + const numericId = parseInt (id); + const name = this.safeStringUpper (market, 'name'); + let [ base, quote ] = name.split ('_'); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const baseId = this.safeString (market, 'sellerCurrencyId'); + const quoteId = this.safeString (market, 'buyerCurrencyId'); + const baseNumericId = parseInt (baseId); + const quoteNumericId = parseInt (quoteId); + const symbol = base + '/' + quote; + const state = this.safeInteger (market, 'state'); + const active = (state === 1); + const fee = this.safeFloat (market, 'defaultFee'); + result.push ({ + 'id': id, + 'active': active, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'baseNumericId': baseNumericId, + 'quoteNumericId': quoteNumericId, + 'maker': fee, + 'taker': fee, + 'info': market, + 'precision': { + 'amount': this.safeInteger (market, 'amountDecimal'), + 'price': this.safeInteger (market, 'priceDecimal'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minAmount'), + 'max': undefined, + }, + 'price': { + 'min': 0, + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetExchangeConfigControllerWebsiteCurrencycontrollerGetCurrencyList (params); + // + // { + // "datas":[ + // { + // "currencyId":"456", + // "name":"pan", + // "alias":"pan", + // "logo":"pan.svg", + // "description":"pan", + // "descriptionEnglish":"pan", + // "defaultDecimal":2, + // "createUid":null, + // "createTime":1574068133762, + // "modifyUid":null, + // "modifyTime":0, + // "state":1, + // "mark":"pan", + // "totalNumber":"0", + // "publishNumber":"0", + // "marketValue":"0", + // "isLegalCoin":0, + // "needBlockUrl":1, + // "blockChainUrl":"https://etherscan.io/tx/", + // "tradeSearchUrl":null, + // "tokenCoinsId":0, + // "isMining":"0", + // "arithmetic":null, + // "founder":"bw_nxwal", + // "teamAddress":null, + // "remark":null, + // "tokenName":"ethw2", + // "isMemo":0, + // "websiteCurrencyId":"7rhqoHLohkG", + // "drawFlag":0, + // "rechargeFlag":1, + // "drawFee":"0.03000000", + // "onceDrawLimit":100, + // "dailyDrawLimit":500, + // "timesFreetrial":"0", + // "hourFreetrial":"0", + // "dayFreetrial":"0", + // "minFee":"0", + // "inConfigTimes":7, + // "outConfigTimes":7, + // "minCash":"0.06000000", + // "limitAmount":"0", + // "zbExist":false, + // "zone":1 + // }, + // ], + // "resMsg": { "message":"success !", "method":null, "code":"1" } + // } + // + const currencies = this.safeValue (response, 'datas', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currencyId'); + const code = this.safeCurrencyCode (this.safeStringUpper (currency, 'name')); + const state = this.safeInteger (currency, 'state'); + const active = state === 1; + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': this.safeFloat (currency, 'drawFee'), + 'precision': undefined, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'limitAmount', 0), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': this.safeFloat (currency, 'onceDrawLimit'), + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // [ + // "281", // market id + // "9754.4", // last + // "9968.8", // high + // "9631.5", // low + // "47865.6432", // base volume + // "-2.28", // change + // // closing price for last 6 hours + // "[[1, 9750.1], [2, 9737.1], [3, 9727.5], [4, 9722], [5, 9722.1], [6, 9754.4]]", + // "9752.12", // bid + // "9756.69", // ask + // "469849357.2364" // quote volume + // ] + // + const marketId = this.safeString (ticker, 0); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.milliseconds (); + const close = parseFloat (this.safeValue (ticker, 1)); + const bid = this.safeValue (ticker, 'bid', {}); + const ask = this.safeValue (ticker, 'ask', {}); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': parseFloat (this.safeValue (ticker, 2)), + 'low': parseFloat (this.safeValue (ticker, 3)), + 'bid': parseFloat (this.safeValue (ticker, 7)), + 'bidVolume': this.safeFloat (bid, 'quantity'), + 'ask': parseFloat (this.safeValue (ticker, 8)), + 'askVolume': this.safeFloat (ask, 'quantity'), + 'vwap': undefined, + 'open': undefined, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': parseFloat (this.safeValue (ticker, 5)), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': parseFloat (this.safeValue (ticker, 4)), + 'quoteVolume': parseFloat (this.safeValue (ticker, 9)), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + const response = await this.publicGetApiDataV1Ticker (this.extend (request, params)); + // + // { + // "datas": [ + // "281", + // "7601.99", + // "8126.5", + // "7474.68", + // "47004.8708", + // "-6.18", + // "[[1, 7800.34], [2, 7626.41], [3, 7609.97], [4, 7569.04], [5, 7577.93], [6, 7601.99]]", + // "7600.24", + // "7603.69", + // "371968300.0119", + // ], + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + const ticker = this.safeValue (response, 'datas', []); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetApiDataV1Tickers (params); + // + // { + // "datas": [ + // [ + // "4051", + // "0.00194", + // "0.00863", + // "0.0012", + // "1519020", + // "-38.22", + // "[[1, 0.0023], [2, 0.00198], [3, 0.00199], [4, 0.00195], [5, 0.00199], [6, 0.00194]]", + // "0.00123", + // "0.0045", + // "4466.8104", + // ], + // ], + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const datas = this.safeValue (response, 'datas', []); + const result = {}; + for (let i = 0; i < datas.length; i++) { + const ticker = this.parseTicker (datas[i]); + const symbol = ticker['symbol']; + if ((symbols === undefined) || this.inArray (symbol, symbols)) { + result[symbol] = ticker; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + if (limit !== undefined) { + request['dataSize'] = limit; + } + const response = await this.publicGetApiDataV1Entrusts (this.extend (request, params)); + // + // { + // "datas": { + // "asks": [ + // [ "9740.43", "0.0083" ], + // ], + // "bids": [ + // [ "9734.33", "0.0133" ], + // ], + // "timestamp": "1569303520", + // }, + // "resMsg": { + // "message": "success !", + // "method": null, + // "code": "1", + // }, + // } + // + const orderbook = this.safeValue (response, 'datas', []); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // "T", // trade + // "281", // market id + // "1569303302", // timestamp + // "BTC_USDT", // market name + // "ask", // side + // "9745.08", // price + // "0.0026" // amount + // ] + // + // fetchMyTrades (private) + // + // ... + // + const timestamp = this.safeTimestamp (trade, 2); + const price = this.safeFloat (trade, 5); + const amount = this.safeFloat (trade, 6); + const marketId = this.safeString (trade, 1); + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const marketName = this.safeString (trade, 3); + const [ baseId, quoteId ] = marketName.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = this.costToPrecision (symbol, price * amount); + } + } + const sideString = this.safeString (trade, 4); + const side = (sideString === 'ask') ? 'sell' : 'buy'; + return { + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': parseFloat (cost), + 'fee': undefined, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + if (limit !== undefined) { + request['dataSize'] = limit; // max 20 + } + const response = await this.publicGetApiDataV1Trades (this.extend (request, params)); + // + // { + // "datas": [ + // [ + // "T", // trade + // "281", // market id + // "1569303302", // timestamp + // "BTC_USDT", // market name + // "ask", // side + // "9745.08", // price + // "0.0026" // amount + // ], + // ], + // "resMsg": { "code": "1", "method": null, "message": "success !" }, + // } + // + const trades = this.safeValue (response, 'datas', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "K", + // "305", + // "eth_btc", + // "1591511280", + // "0.02504", + // "0.02504", + // "0.02504", + // "0.02504", + // "0.0123", + // "0", + // "285740.17", + // "1M", + // "false", + // "0.000308" + // ] + // + return [ + this.safeTimestamp (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + this.safeFloat (ohlcv, 6), + this.safeFloat (ohlcv, 7), + this.safeFloat (ohlcv, 8), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'type': this.timeframes[timeframe], + 'dataSize': 500, + }; + if (limit !== undefined) { + request['dataSize'] = limit; + } + const response = await this.publicGetApiDataV1Klines (this.extend (request, params)); + // + // { + // "datas":[ + // ["K","305","eth_btc","1591511280","0.02504","0.02504","0.02504","0.02504","0.0123","0","285740.17","1M","false","0.000308"], + // ["K","305","eth_btc","1591511220","0.02504","0.02504","0.02504","0.02504","0.0006","0","285740.17","1M","false","0.00001502"], + // ["K","305","eth_btc","1591511100","0.02505","0.02505","0.02504","0.02504","0.0012","-0.0399","285740.17","1M","false","0.00003005"], + // ], + // "resMsg":{"code":"1","method":null,"message":"success !"} + // } + // + const data = this.safeValue (response, 'datas', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostExchangeFundControllerWebsiteFundcontrollerFindbypage (params); + // + // { + // "datas": { + // "totalRow": 6, + // "pageSize": 99, + // "list": [ + // { + // "amount": "0.000090000000000000", // The current number of tokens available + // "currencyTypeId": 2, // Token ID + // "freeze": "0.009900000000000000", // Current token freezing quantity + // }, + // ], + // "pageNum": 1, + // }, + // "resMsg": { "code": "1", "message": "success !" } + // } + // + const data = this.safeValue (response, 'datas', {}); + const balances = this.safeValue (data, 'list', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currencyTypeId'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'amount'); + account['used'] = this.safeFloat (balance, 'freeze'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (price === undefined) { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amount': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), + 'type': (side === 'buy') ? 1 : 0, + 'rangeType': 0, // limit order + 'marketId': market['id'], + }; + const response = await this.privatePostExchangeEntrustControllerWebsiteEntrustControllerAddEntrust (this.extend (request, params)); + // + // { + // "datas": { + // "entrustId": "E6581105708337483776", + // }, + // "resMsg": { + // "message": "success !", + // "method": null, + // "code": "1" + // } + // } + // + const data = this.safeValue (response, 'datas'); + const id = this.safeString (data, 'entrustId'); + return { + 'id': id, + 'info': response, + 'timestamp': undefined, + 'datetime': undefined, + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': 'open', + 'fee': undefined, + 'trades': undefined, + 'clientOrderId': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + '-3': 'canceled', + '-2': 'canceled', + '-1': 'canceled', + '0': 'open', + '1': 'canceled', + '2': 'closed', + '3': 'open', + '4': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "entrustId": "E6581108027628212224", // Order id + // "price": "1450", // price + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "amount": "14.05", // Order quantity + // "totalMoney": "20372.50", // Total order amount + // "completeAmount": "0", // Quantity sold + // "completeTotalMoney": "0", // Total dealt amount + // "type": 1, // 0 = sell, 1 = buy, -1 = cancel + // "entrustType": 0, // 0 = ordinary current price commission, 1 = lever commission + // "status": 0, // + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "availabelAmount": "14.05" // Outstanding quantity, typo in the docs or in the API, availabel vs available + // } + // + const marketId = this.safeString (order, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (order, 'createTime'); + let side = this.safeString (order, 'type'); + if (side === '0') { + side = 'sell'; + } else if (side === '1') { + side = 'buy'; + } + const amount = this.safeFloat (order, 'amount'); + const price = this.safeFloat (order, 'price'); + const filled = this.safeFloat (order, 'completeAmount'); + let remaining = this.safeFloat2 (order, 'availabelAmount', 'availableAmount'); // typo in the docs or in the API, availabel vs available + let cost = this.safeFloat (order, 'totalMoney'); + if (filled !== undefined) { + if (amount !== undefined) { + if (remaining === undefined) { + remaining = amount - filled; + } + } + if (cost === undefined) { + if (price !== undefined) { + cost = filled * cost; + } + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + return { + 'info': order, + 'id': this.safeString (order, 'entrustId'), + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'entrustId': id, + }; + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetEntrustById (this.extend (request, params)); + // + // { + // "datas": { + // "entrustId": "E6581108027628212224", // Order id + // "price": "1450", // price + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "amount": "14.05", // Order quantity + // "totalMoney": "20372.50", // Total order amount + // "completeAmount": "0", // Quantity sold + // "completeTotalMoney": "0", // Total dealt amount + // "type": 1, // Trade direction, 0: sell, 1: buy, -1: cancel + // "entrustType": 0, // Commission type, 0: ordinary current price commission, 1: lever commission + // "status": 0, // Order status,-3:fund Freeze exception,Order status to be confirmed -2: fund freeze failure, order failure, -1: insufficient funds, order failure, 0: pending order, 1: cancelled, 2: dealt, 3: partially dealt + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "availabelAmount": "14.05" // Outstanding quantity + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + const order = this.safeValue (response, 'datas', {}); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'entrustId': id, + }; + const response = await this.privatePostExchangeEntrustControllerWebsiteEntrustControllerCancelEntrust (this.extend (request, params)); + // + // { + // "datas": null, + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + return { + 'info': response, + 'id': id, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageIndex': 0, // also works without it, most likely a typo in the docs + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default limit is 20 + } + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustRecordFromCacheWithPage (this.extend (request, params)); + // + // { + // "datas": { + // "pageNum": 1, + // "pageSize": 2, + // "totalPage": 20, + // "totalRow": 40, + // "entrustList": [ + // { + // "amount": "14.050000000000000000", // Order quantity + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "totalMoney": "20372.500000000000000000", // Total order amount + // "entrustId": "E6581108027628212224", // Order id + // "type": 1, // Trade direction, 0: sell, 1: buy, -1: cancel + // "completeAmount": "0", // Quantity sold + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "price": "1450.000000000", // price + // "completeTotalMoney": "0", // Quantity sold + // "entrustType": 0, // Commission type, 0: ordinary current price commission, 1: lever commission + // "status": 0 // Order status,-3:fund Freeze exception,Order status to be confirmed -2: fund freeze failure, order failure, -1: insufficient funds, order failure, 0: pending order, 1: cancelled, 2: dealt, 3: partially dealt + // }, + // ], + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const orders = this.safeValue (data, 'entrustList', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default limit is 20 + } + if (since !== undefined) { + request['startDateTime'] = since; + } + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustList (this.extend (request, params)); + const data = this.safeValue (response, 'datas', {}); + const orders = this.safeValue (data, 'entrustList', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageIndex': 0, // also works without it, most likely a typo in the docs + // 'type': 0, // 0 = sell, 1 = buy, -1 = cancel + // 'status': -1, // -1 = insufficient funds, failed orders, 0 = pending orders, 1 = canceled, 2 = closed, 3 = partial + // 'startDateTime': since, + // 'endDateTime': this.milliseconds (), + }; + if (since !== undefined) { + request['startDateTime'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; // default limit is 20 + } + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustList (this.extend (request, params)); + // + // { + // "datas": { + // "pageNum": 1, + // "pageSize": 2, + // "totalPage": 20, + // "totalRow": 40, + // "entrustList": [ + // { + // "amount": "14.050000000000000000", // Order quantity + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "totalMoney": "20372.500000000000000000", // Total order amount + // "entrustId": "E6581108027628212224", // Order id + // "type": 1, // Trade direction, 0: sell, 1: buy, -1: cancel + // "completeAmount": "0", // Quantity sold + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "price": "1450.000000000", // price + // "completeTotalMoney": "0", // Quantity sold + // "entrustType": 0, // Commission type, 0: ordinary current price commission, 1: lever commission + // "status": 0 // Order status,-3:fund Freeze exception,Order status to be confirmed -2: fund freeze failure, order failure, -1: insufficient funds, order failure, 0: pending order, 1: cancelled, 2: dealt, 3: partially dealt + // }, + // ], + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const orders = this.safeValue (data, 'entrustList', []); + return this.parseOrders (orders, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }) + '/' + path; + if (method === 'GET') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + body = this.json (params); + } + if (api === 'private') { + const ms = this.milliseconds ().toString (); + let content = ''; + if (method === 'GET') { + const sortedParams = this.keysort (params); + const keys = Object.keys (sortedParams); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + content += key + sortedParams[key].toString (); + } + } else { + content = body; + } + const signature = this.apiKey + ms + content + this.secret; + const hash = this.hash (this.encode (signature), 'md5'); + if (!headers) { + headers = {}; + } + headers['Apiid'] = this.apiKey; + headers['Timestamp'] = ms; + headers['Sign'] = hash; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyTypeName': currency['name'], + }; + const response = await this.privatePostExchangeFundControllerWebsiteFundcontrollerGetPayinAddress (this.extend (request, params)); + // + // { + // "datas": { + // "isMemo": true, // 是否为memo 格式,false:否,true :是 + // "address": "bweosdeposit_787928102918558272", // 充币地址 + // "memo": "787928102918558272", // 币种memo + // "account": "bweosdeposit" // 币种账户 + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + const data = this.safeValue (response, 'datas', {}); + const address = this.safeString (data, 'address'); + const tag = this.safeString (data, 'memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + '-1': 'canceled', // or auditing failed + '0': 'pending', + '1': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "depositId": "D6574268549744189441", // Deposit ID + // "amount": "54.753589700000000000", // Deposit amount + // "txId": "INNER_SYSTEM_TRANSFER_1198941", // Trading ID + // "confirmTimes": 0, // Confirmation number + // "depositAddress": "bweosdeposit_787928102918558272", // Deposit address + // "createTime": "2019-09-02 20:36:08.0", // Deposit time + // "status": 1, // Deposit status, 0: not received, 1: received + // "currencyTypeId": 7, // Token ID + // } + // + // fetchWithdrawals + // + // { + // "withdrawalId": "W6527498439872634880", // Withdrawal ID + // "fees": "0.500000000000000000", // Withdrawal fee + // "withdrawalAddress": "okbtothemoon_941657", // Withdrawal address + // "currencyId": "7", // Token ID + // "amount": "10.000000000000000000", // Withdrawal amount + // "state": 1, // Status, 1: normal, -1: delete + // "verifyStatus": 1, // Audit status, 0: to be audited, 1: auditing passed, -1: auditing failed + // "createTime": 1556276903656, // WIthdrawal time + // "actuallyAmount": "9.500000000000000000", // Actual amount received + // } + // + const id = this.safeString (transaction, 'depositId', 'withdrawalId'); + const address = this.safeString2 (transaction, 'depositAddress', 'withdrawalAddress'); + const currencyId = this.safeString2 (transaction, 'currencyId', 'currencyTypeId'); + let code = undefined; + if (currencyId in this.currencies_by_id) { + currency = this.currencies_by_id[currencyId]; + } + if ((code === undefined) && (currency !== undefined)) { + code = currency['code']; + } + const type = ('depositId' in transaction) ? 'deposit' : 'withdrawal'; + const amount = this.safeFloat2 (transaction, 'actuallyAmount', 'amount'); + const status = this.parseTransactionStatus (this.safeString2 (transaction, 'verifyStatus', 'state')); + const timestamp = this.safeInteger (transaction, 'createTime'); + const txid = this.safeString (transaction, 'txId'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fees'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': undefined, + 'tagFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyTypeName': currency['name'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageNum': 0, // also works without it, most likely a typo in the docs + // 'sort': 1, // 1 = asc, 0 = desc + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default 50 + } + const response = await this.privatePostExchangeFundControllerWebsiteFundcontrollerGetPayinCoinRecord (this.extend (request, params)); + // + // { + // "datas": { + // "totalRow":2, + // "totalPage": 1, + // "pageSize": 2, + // "pageNum": 1, + // "list": [ + // { + // "depositId": "D6574268549744189441", // Deposit ID + // "amount": "54.753589700000000000", // Deposit amount + // "txId": "INNER_SYSTEM_TRANSFER_1198941", // Trading ID + // "confirmTimes": 0, // Confirmation number + // "depositAddress": "bweosdeposit_787928102918558272", // Deposit address + // "createTime": "2019-09-02 20:36:08.0", // Deposit time + // "status": 1, // Deposit status, 0: not received, 1: received + // "currencyTypeId": 7, // Token ID + // }, + // ] + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const deposits = this.safeValue (data, 'list', []); + return this.parseTransactions (deposits, code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyId': currency['id'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageIndex': 0, // also works without it, most likely a typo in the docs + // 'tab': 'all', // all, wait (submitted, not audited), success (auditing passed), fail (auditing failed), cancel (canceled by user) + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default 50 + } + const response = await this.privateGetExchangeFundControllerWebsiteFundwebsitecontrollerGetpayoutcoinrecord (this.extend (request, params)); + // + // { + // "datas": { + // "totalRow": 1, + // "totalPage": 1, + // "pageSize": 2, + // "pageNum": 1, + // "list": [ + // { + // "withdrawalId": "W6527498439872634880", // Withdrawal ID + // "fees": "0.500000000000000000", // Withdrawal fee + // "withdrawalAddress": "okbtothemoon_941657", // Withdrawal address + // "currencyId": "7", // Token ID + // "amount": "10.000000000000000000", // Withdrawal amount + // "state": 1, // Status, 1: normal, -1: delete + // "verifyStatus": 1, // Audit status, 0: to be audited, 1: auditing passed, -1: auditing failed + // "createTime": 1556276903656, // WIthdrawal time + // "actuallyAmount": "9.500000000000000000", // Actual amount received + // }, + // ], + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const withdrawals = this.safeValue (data, 'list', []); + return this.parseTransactions (withdrawals, code, since, limit); + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // default error handler + } + const resMsg = this.safeValue (response, 'resMsg'); + const errorCode = this.safeString (resMsg, 'code'); + if (errorCode !== '1') { + const feedback = this.id + ' ' + this.json (response); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); // unknown error + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],56:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, InsufficientFunds, BadRequest, RateLimitExceeded, InvalidNonce } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bybit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bybit', + 'name': 'Bybit', + 'countries': [ 'VG' ], // British Virgin Islands + 'version': 'v2', + 'userAgent': undefined, + 'rateLimit': 100, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'cancelAllOrders': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '1', + '3m': '3', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '6h': '360', + '12h': '720', + '1d': 'D', + '1w': 'W', + '1M': 'M', + '1y': 'Y', + }, + 'urls': { + 'test': 'https://api-testnet.bybit.com', + 'logo': 'https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg', + 'api': 'https://api.bybit.com', + 'www': 'https://www.bybit.com', + 'doc': [ + 'https://bybit-exchange.github.io/docs/inverse/', + 'https://bybit-exchange.github.io/docs/linear/', + 'https://github.com/bybit-exchange', + ], + 'fees': 'https://help.bybit.com/hc/en-us/articles/360039261154', + 'referral': 'https://www.bybit.com/app/register?ref=X7Prm', + }, + 'api': { + 'public': { + 'get': [ + 'orderBook/L2', + 'kline/list', + 'tickers', + 'trading-records', + 'symbols', + 'liq-records', + 'mark-price-kline', + 'open-interest', + 'big-deal', + 'account-ratio', + 'time', + 'announcement', + ], + }, + 'private': { + 'get': [ + 'order/list', + 'order', + 'stop-order/list', + 'stop-order', + 'position/list', + 'wallet/balance', + 'execution/list', + 'trade/closed-pnl/list', + 'account/lcp', + 'exchange-order/list', + ], + 'post': [ + 'order/create', + 'order/cancel', + 'order/cancelAll', + 'order/replace', + 'stop-order/create', + 'stop-order/cancel', + 'stop-order/cancelAll', + 'stop-order/replace', + ], + }, + 'openapi': { + 'get': [ + 'order/list', // deprecated + 'stop-order/list', // deprecated + 'wallet/risk-limit/list', + 'funding/prev-funding-rate', + 'funding/prev-funding', + 'funding/predicted-funding', + 'api-key', + 'wallet/fund/records', + 'wallet/withdraw/list', + ], + 'post': [ + 'order/replace', // deprecated + 'stop-order/create', // deprecated + 'stop-order/cancel', // deprecated + 'stop-order/replace', // deprecated + 'position/trading-stop', + 'wallet/risk-limit', + ], + }, + 'publicLinear': { + 'get': [ + 'kline', + 'recent-trading-records', + 'funding/prev-funding-rate', + 'mark-price-kline', + 'risk-limit', + ], + }, + 'privateLinear': { + 'get': [ + 'order/list', + 'order/search', + 'stop-order/list', + 'stop-order/search', + 'position/list', + 'trade/execution/list', + 'trade/closed-pnl/list', + 'funding/prev-funding', + 'funding/predicted-funding', + ], + 'post': [ + 'order/create', + 'order/cancel', + 'order/cancel-all', + 'order/replace', + 'stop-order/create', + 'stop-order/cancel', + 'stop-order/cancel-all', + 'stop-order/replace', + 'position/switch-isolated', + 'position/set-auto-add-margin', + 'tpsl/switch-mode', + 'position/set-leverage', + 'position/trading-stop', + 'position/add-margin', + ], + }, + 'position': { + 'post': [ + 'change-position-margin', + ], + }, + 'user': { + 'get': [ + 'leverage', // deprecated + ], + 'post': [ + 'leverage/save', + ], + }, + }, + 'httpExceptions': { + '403': RateLimitExceeded, // Forbidden -- You request too many times + }, + 'exceptions': { + 'exact': { + '10001': BadRequest, // parameter error + '10002': InvalidNonce, // request expired, check your timestamp and recv_window + '10003': AuthenticationError, // Invalid apikey + '10004': AuthenticationError, // invalid sign + '10005': PermissionDenied, // permission denied for current apikey + '10006': RateLimitExceeded, // too many requests + '10007': AuthenticationError, // api_key not found in your request parameters + '10010': PermissionDenied, // request ip mismatch + '10017': BadRequest, // request path not found or request method is invalid + '20001': OrderNotFound, // Order not exists + '20003': InvalidOrder, // missing parameter side + '20004': InvalidOrder, // invalid parameter side + '20005': InvalidOrder, // missing parameter symbol + '20006': InvalidOrder, // invalid parameter symbol + '20007': InvalidOrder, // missing parameter order_type + '20008': InvalidOrder, // invalid parameter order_type + '20009': InvalidOrder, // missing parameter qty + '20010': InvalidOrder, // qty must be greater than 0 + '20011': InvalidOrder, // qty must be an integer + '20012': InvalidOrder, // qty must be greater than zero and less than 1 million + '20013': InvalidOrder, // missing parameter price + '20014': InvalidOrder, // price must be greater than 0 + '20015': InvalidOrder, // missing parameter time_in_force + '20016': InvalidOrder, // invalid value for parameter time_in_force + '20017': InvalidOrder, // missing parameter order_id + '20018': InvalidOrder, // invalid date format + '20019': InvalidOrder, // missing parameter stop_px + '20020': InvalidOrder, // missing parameter base_price + '20021': InvalidOrder, // missing parameter stop_order_id + '20022': BadRequest, // missing parameter leverage + '20023': BadRequest, // leverage must be a number + '20031': BadRequest, // leverage must be greater than zero + '20070': BadRequest, // missing parameter margin + '20071': BadRequest, // margin must be greater than zero + '20084': BadRequest, // order_id or order_link_id is required + '30001': BadRequest, // order_link_id is repeated + '30003': InvalidOrder, // qty must be more than the minimum allowed + '30004': InvalidOrder, // qty must be less than the maximum allowed + '30005': InvalidOrder, // price exceeds maximum allowed + '30007': InvalidOrder, // price exceeds minimum allowed + '30008': InvalidOrder, // invalid order_type + '30009': ExchangeError, // no position found + '30010': InsufficientFunds, // insufficient wallet balance + '30011': PermissionDenied, // operation not allowed as position is undergoing liquidation + '30012': PermissionDenied, // operation not allowed as position is undergoing ADL + '30013': PermissionDenied, // position is in liq or adl status + '30014': InvalidOrder, // invalid closing order, qty should not greater than size + '30015': InvalidOrder, // invalid closing order, side should be opposite + '30016': ExchangeError, // TS and SL must be cancelled first while closing position + '30017': InvalidOrder, // estimated fill price cannot be lower than current Buy liq_price + '30018': InvalidOrder, // estimated fill price cannot be higher than current Sell liq_price + '30019': InvalidOrder, // cannot attach TP/SL params for non-zero position when placing non-opening position order + '30020': InvalidOrder, // position already has TP/SL params + '30021': InvalidOrder, // cannot afford estimated position_margin + '30022': InvalidOrder, // estimated buy liq_price cannot be higher than current mark_price + '30023': InvalidOrder, // estimated sell liq_price cannot be lower than current mark_price + '30024': InvalidOrder, // cannot set TP/SL/TS for zero-position + '30025': InvalidOrder, // trigger price should bigger than 10% of last price + '30026': InvalidOrder, // price too high + '30027': InvalidOrder, // price set for Take profit should be higher than Last Traded Price + '30028': InvalidOrder, // price set for Stop loss should be between Liquidation price and Last Traded Price + '30029': InvalidOrder, // price set for Stop loss should be between Last Traded Price and Liquidation price + '30030': InvalidOrder, // price set for Take profit should be lower than Last Traded Price + '30031': InsufficientFunds, // insufficient available balance for order cost + '30032': InvalidOrder, // order has been filled or cancelled + '30033': RateLimitExceeded, // The number of stop orders exceeds maximum limit allowed + '30034': OrderNotFound, // no order found + '30035': RateLimitExceeded, // too fast to cancel + '30036': ExchangeError, // the expected position value after order execution exceeds the current risk limit + '30037': InvalidOrder, // order already cancelled + '30041': ExchangeError, // no position found + '30042': InsufficientFunds, // insufficient wallet balance + '30043': PermissionDenied, // operation not allowed as position is undergoing liquidation + '30044': PermissionDenied, // operation not allowed as position is undergoing AD + '30045': PermissionDenied, // operation not allowed as position is not normal status + '30049': InsufficientFunds, // insufficient available balance + '30050': ExchangeError, // any adjustments made will trigger immediate liquidation + '30051': ExchangeError, // due to risk limit, cannot adjust leverage + '30052': ExchangeError, // leverage can not less than 1 + '30054': ExchangeError, // position margin is invalid + '30057': ExchangeError, // requested quantity of contracts exceeds risk limit + '30063': ExchangeError, // reduce-only rule not satisfied + '30067': InsufficientFunds, // insufficient available balance + '30068': ExchangeError, // exit value must be positive + '34026': ExchangeError, // the limit is no change + }, + 'broad': { + 'unknown orderInfo': OrderNotFound, // {"ret_code":-1,"ret_msg":"unknown orderInfo","ext_code":"","ext_info":"","result":null,"time_now":"1584030414.005545","rate_limit_status":99,"rate_limit_reset_ms":1584030414003,"rate_limit":100} + 'invalid api_key': AuthenticationError, // {"ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797"} + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'marketTypes': { + 'BTC/USDT': 'linear', + 'ETH/USDT': 'linear', + 'LTC/USDT': 'linear', + 'XTZ/USDT': 'linear', + 'LINK/USDT': 'linear', + }, + 'code': 'BTC', + 'cancelAllOrders': { + 'method': 'privatePostOrderCancelAll', // privatePostStopOrderCancelAll + }, + 'recvWindow': 5 * 1000, // 5 sec default + 'timeDifference': 0, // the difference between system clock and Binance clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.00075, + 'maker': -0.00025, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async loadTimeDifference (params = {}) { + const serverTime = await this.fetchTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = after - serverTime; + return this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: {}, + // time_now: '1583933682.448826' + // } + // + return this.safeTimestamp (response, 'time_now'); + } + + async fetchMarkets (params = {}) { + if (this.options['adjustForTimeDifference']) { + await this.loadTimeDifference (); + } + const response = await this.publicGetSymbols (params); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // name: 'BTCUSD', + // base_currency: 'BTC', + // quote_currency: 'USD', + // price_scale: 2, + // taker_fee: '0.00075', + // maker_fee: '-0.00025', + // leverage_filter: { min_leverage: 1, max_leverage: 100, leverage_step: '0.01' }, + // price_filter: { min_price: '0.5', max_price: '999999.5', tick_size: '0.5' }, + // lot_size_filter: { max_trading_qty: 1000000, min_trading_qty: 1, qty_step: 1 } + // }, + // ], + // time_now: '1583930495.454196' + // } + // + // sandbox/testnet + // + // { + // "ret_code":0, + // "ret_msg":"OK", + // "ext_code":"", + // "ext_info":"", + // "result":[ + // { + // "symbol":"BTCUSD", + // "symbol_alias":"BTCUSD", + // "status":"Trading", + // "base_currency":"BTC", + // "quote_currency":"USD", + // "price_scale":2, + // "taker_fee":"0.00075", + // "maker_fee":"-0.00025", + // "leverage_filter":{"min_leverage":1,"max_leverage":100,"leverage_step":"0.01"}, + // "price_filter":{"min_price":"0.5","max_price":"999999.5","tick_size":"0.5"}, + // "lot_size_filter":{"max_trading_qty":1000000,"min_trading_qty":1,"qty_step":1} + // } + // ], + // "time_now":"1605916574.118500" + // } + // + const markets = this.safeValue (response, 'result', []); + const options = this.safeValue (this.options, 'fetchMarkets', {}); + const linearQuoteCurrencies = this.safeValue (options, 'linear', { 'USDT': true }); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString2 (market, 'name', 'symbol'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const linear = (quote in linearQuoteCurrencies); + const inverse = !linear; + let symbol = base + '/' + quote; + const baseQuote = base + quote; + if (baseQuote !== id) { + symbol = id; + } + const lotSizeFilter = this.safeValue (market, 'lot_size_filter', {}); + const priceFilter = this.safeValue (market, 'price_filter', {}); + const precision = { + 'amount': this.safeFloat (lotSizeFilter, 'qty_step'), + 'price': this.safeFloat (priceFilter, 'tick_size'), + }; + const status = this.safeString (market, 'status'); + let active = undefined; + if (status !== undefined) { + active = (status === 'Trading'); + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'taker': this.safeFloat (market, 'taker_fee'), + 'maker': this.safeFloat (market, 'maker_fee'), + 'type': 'future', + 'spot': false, + 'future': true, + 'option': false, + 'linear': linear, + 'inverse': inverse, + 'limits': { + 'amount': { + 'min': this.safeFloat (lotSizeFilter, 'min_trading_qty'), + 'max': this.safeFloat (lotSizeFilter, 'max_trading_qty'), + }, + 'price': { + 'min': this.safeFloat (priceFilter, 'min_price'), + 'max': this.safeFloat (priceFilter, 'max_price'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = {}; + const coin = this.safeString (params, 'coin'); + const code = this.safeString (params, 'code'); + if (coin !== undefined) { + request['coin'] = coin; + } else if (code !== undefined) { + const currency = this.currency (code); + request['coin'] = currency['id']; + } + const response = await this.privateGetWalletBalance (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: { + // BTC: { + // equity: 0, + // available_balance: 0, + // used_margin: 0, + // order_margin: 0, + // position_margin: 0, + // occ_closing_fee: 0, + // occ_funding_fee: 0, + // wallet_balance: 0, + // realised_pnl: 0, + // unrealised_pnl: 0, + // cum_realised_pnl: 0, + // given_cash: 0, + // service_cash: 0 + // } + // }, + // time_now: '1583937810.370020', + // rate_limit_status: 119, + // rate_limit_reset_ms: 1583937810367, + // rate_limit: 120 + // } + // + const result = { + 'info': response, + }; + const balances = this.safeValue (response, 'result', {}); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available_balance'); + account['used'] = this.safeFloat (balance, 'used_margin'); + account['total'] = this.safeFloat (balance, 'equity'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // symbol: 'BTCUSD', + // bid_price: '7680', + // ask_price: '7680.5', + // last_price: '7680.00', + // last_tick_direction: 'MinusTick', + // prev_price_24h: '7870.50', + // price_24h_pcnt: '-0.024204', + // high_price_24h: '8035.00', + // low_price_24h: '7671.00', + // prev_price_1h: '7780.00', + // price_1h_pcnt: '-0.012853', + // mark_price: '7683.27', + // index_price: '7682.74', + // open_interest: 188829147, + // open_value: '23670.06', + // total_turnover: '25744224.90', + // turnover_24h: '102997.83', + // total_volume: 225448878806, + // volume_24h: 809919408, + // funding_rate: '0.0001', + // predicted_funding_rate: '0.0001', + // next_funding_time: '2020-03-12T00:00:00Z', + // countdown_hour: 7 + // } + // + const timestamp = undefined; + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'last_price'); + const open = this.safeFloat (ticker, 'prev_price_24h'); + let percentage = this.safeFloat (ticker, 'price_24h_pcnt'); + if (percentage !== undefined) { + percentage *= 100; + } + let change = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + average = this.sum (open, last) / 2; + } + const baseVolume = this.safeFloat (ticker, 'turnover_24h'); + const quoteVolume = this.safeFloat (ticker, 'volume_24h'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_price_24h'), + 'low': this.safeFloat (ticker, 'low_price_24h'), + 'bid': this.safeFloat (ticker, 'bid_price'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask_price'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // symbol: 'BTCUSD', + // bid_price: '7680', + // ask_price: '7680.5', + // last_price: '7680.00', + // last_tick_direction: 'MinusTick', + // prev_price_24h: '7870.50', + // price_24h_pcnt: '-0.024204', + // high_price_24h: '8035.00', + // low_price_24h: '7671.00', + // prev_price_1h: '7780.00', + // price_1h_pcnt: '-0.012853', + // mark_price: '7683.27', + // index_price: '7682.74', + // open_interest: 188829147, + // open_value: '23670.06', + // total_turnover: '25744224.90', + // turnover_24h: '102997.83', + // total_volume: 225448878806, + // volume_24h: 809919408, + // funding_rate: '0.0001', + // predicted_funding_rate: '0.0001', + // next_funding_time: '2020-03-12T00:00:00Z', + // countdown_hour: 7 + // } + // ], + // time_now: '1583948195.818255' + // } + // + const result = this.safeValue (response, 'result', []); + const first = this.safeValue (result, 0); + const timestamp = this.safeTimestamp (response, 'time_now'); + const ticker = this.parseTicker (first, market); + ticker['timestamp'] = timestamp; + ticker['datetime'] = this.iso8601 (timestamp); + return ticker; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // symbol: 'BTCUSD', + // bid_price: '7680', + // ask_price: '7680.5', + // last_price: '7680.00', + // last_tick_direction: 'MinusTick', + // prev_price_24h: '7870.50', + // price_24h_pcnt: '-0.024204', + // high_price_24h: '8035.00', + // low_price_24h: '7671.00', + // prev_price_1h: '7780.00', + // price_1h_pcnt: '-0.012853', + // mark_price: '7683.27', + // index_price: '7682.74', + // open_interest: 188829147, + // open_value: '23670.06', + // total_turnover: '25744224.90', + // turnover_24h: '102997.83', + // total_volume: 225448878806, + // volume_24h: 809919408, + // funding_rate: '0.0001', + // predicted_funding_rate: '0.0001', + // next_funding_time: '2020-03-12T00:00:00Z', + // countdown_hour: 7 + // } + // ], + // time_now: '1583948195.818255' + // } + // + const result = this.safeValue (response, 'result', []); + const tickers = {}; + for (let i = 0; i < result.length; i++) { + const ticker = this.parseTicker (result[i]); + const symbol = ticker['symbol']; + tickers[symbol] = ticker; + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // inverse perpetual BTC/USD + // + // { + // symbol: 'BTCUSD', + // interval: '1', + // open_time: 1583952540, + // open: '7760.5', + // high: '7764', + // low: '7757', + // close: '7763.5', + // volume: '1259766', + // turnover: '162.32773718999994' + // } + // + // linear perpetual BTC/USDT + // + // { + // "id":143536, + // "symbol":"BTCUSDT", + // "period":"15", + // "start_at":1587883500, + // "volume":1.035, + // "open":7540.5, + // "high":7541, + // "low":7540.5, + // "close":7541 + // } + // + return [ + this.safeTimestamp2 (ohlcv, 'open_time', 'start_at'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat2 (ohlcv, 'turnover', 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + const now = this.seconds (); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + request['from'] = now - limit * duration; + } + } else { + request['from'] = parseInt (since / 1000); + } + if (limit !== undefined) { + request['limit'] = limit; // max 200, default 200 + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const method = (marketType === 'linear') ? 'publicLinearGetKline' : 'publicGetKlineList'; + const response = await this[method] (this.extend (request, params)); + // + // inverse perpetual BTC/USD + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // symbol: 'BTCUSD', + // interval: '1', + // open_time: 1583952540, + // open: '7760.5', + // high: '7764', + // low: '7757', + // close: '7763.5', + // volume: '1259766', + // turnover: '162.32773718999994' + // }, + // ], + // time_now: '1583953082.397330' + // } + // + // linear perpetual BTC/USDT + // + // { + // "ret_code":0, + // "ret_msg":"OK", + // "ext_code":"", + // "ext_info":"", + // "result":[ + // { + // "id":143536, + // "symbol":"BTCUSDT", + // "period":"15", + // "start_at":1587883500, + // "volume":1.035, + // "open":7540.5, + // "high":7541, + // "low":7540.5, + // "close":7541 + // } + // ], + // "time_now":"1587884120.168077" + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // id: 43785688, + // symbol: 'BTCUSD', + // price: 7786, + // qty: 67, + // side: 'Sell', + // time: '2020-03-11T19:18:30.123Z' + // } + // + // fetchMyTrades, fetchOrderTrades (private) + // + // { + // "closed_size": 0, + // "cross_seq": 277136382, + // "exec_fee": "0.0000001", + // "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68", + // "exec_price": "8178.5", + // "exec_qty": 1, + // // the docs say the exec_time field is "abandoned" now + // // the user should use "trade_time_ms" + // "exec_time": "1571676941.70682", + // "exec_type": "Trade", //Exec Type Enum + // "exec_value": "0.00012227", + // "fee_rate": "0.00075", + // "last_liquidity_ind": "RemovedLiquidity", //Liquidity Enum + // "leaves_qty": 0, + // "nth_fill": 2, + // "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029", + // "order_link_id": "", + // "order_price": "8178", + // "order_qty": 1, + // "order_type": "Market", //Order Type Enum + // "side": "Buy", //Side Enum + // "symbol": "BTCUSD", //Symbol Enum + // "user_id": 1, + // "trade_time_ms": 1577480599000 + // } + // + const id = this.safeString2 (trade, 'id', 'exec_id'); + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const amount = this.safeFloat2 (trade, 'qty', 'exec_qty'); + let cost = this.safeFloat (trade, 'exec_value'); + const price = this.safeFloat2 (trade, 'exec_price', 'price'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + let timestamp = this.parse8601 (this.safeString (trade, 'time')); + if (timestamp === undefined) { + timestamp = this.safeInteger (trade, 'trade_time_ms'); + } + const side = this.safeStringLower (trade, 'side'); + const lastLiquidityInd = this.safeString (trade, 'last_liquidity_ind'); + const takerOrMaker = (lastLiquidityInd === 'AddedLiquidity') ? 'maker' : 'taker'; + const feeCost = this.safeFloat (trade, 'exec_fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyCode = market['inverse'] ? market['base'] : market['quote']; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': this.safeFloat (trade, 'fee_rate'), + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': this.safeString (trade, 'order_id'), + 'type': this.safeStringLower (trade, 'order_type'), + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'from': 123, // from id + }; + if (limit !== undefined) { + request['count'] = limit; // default 500, max 1000 + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const method = (marketType === 'linear') ? 'publicLinearGetRecentTradingRecords' : 'publicGetTradingRecords'; + const response = await this[method] (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // id: 43785688, + // symbol: 'BTCUSD', + // price: 7786, + // qty: 67, + // side: 'Sell', + // time: '2020-03-11T19:18:30.123Z' + // }, + // ], + // time_now: '1583954313.393362' + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTrades (result, market, since, limit); + } + + parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'Buy', asksKey = 'Sell', priceKey = 'price', amountKey = 'size') { + const bids = []; + const asks = []; + for (let i = 0; i < orderbook.length; i++) { + const bidask = orderbook[i]; + const side = this.safeString (bidask, 'side'); + if (side === 'Buy') { + bids.push (this.parseBidAsk (bidask, priceKey, amountKey)); + } else if (side === 'Sell') { + asks.push (this.parseBidAsk (bidask, priceKey, amountKey)); + } else { + throw new ExchangeError (this.id + ' parseOrderBook encountered an unrecognized bidask format: ' + this.json (bidask)); + } + } + return { + 'bids': this.sortBy (bids, 0, true), + 'asks': this.sortBy (asks, 0), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetOrderBookL2 (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { symbol: 'BTCUSD', price: '7767.5', size: 677956, side: 'Buy' }, + // { symbol: 'BTCUSD', price: '7767', size: 580690, side: 'Buy' }, + // { symbol: 'BTCUSD', price: '7766.5', size: 475252, side: 'Buy' }, + // { symbol: 'BTCUSD', price: '7768', size: 330847, side: 'Sell' }, + // { symbol: 'BTCUSD', price: '7768.5', size: 97159, side: 'Sell' }, + // { symbol: 'BTCUSD', price: '7769', size: 6508, side: 'Sell' }, + // ], + // time_now: '1583954829.874823' + // } + // + const result = this.safeValue (response, 'result', []); + const timestamp = this.safeTimestamp (response, 'time_now'); + return this.parseOrderBook (result, timestamp, 'Buy', 'Sell', 'price', 'size'); + } + + parseOrderStatus (status) { + const statuses = { + // basic orders + 'Created': 'open', + 'Rejected': 'rejected', // order is triggered but failed upon being placed + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'Cancelled': 'canceled', + 'PendingCancel': 'canceling', // the engine has received the cancellation but there is no guarantee that it will be successful + // conditional orders + 'Active': 'open', // order is triggered and placed successfully + 'Untriggered': 'open', // order waits to be triggered + 'Triggered': 'closed', // order is triggered + // 'Cancelled': 'canceled', // order is cancelled + // 'Rejected': 'rejected', // order is triggered but fail to be placed + 'Deactivated': 'canceled', // conditional order was cancelled before triggering + }; + return this.safeString (statuses, status, status); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GoodTillCancel': 'GTC', + 'ImmediateOrCancel': 'IOC', + 'FillOrKill': 'FOK', + 'PostOnly': 'PO', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "user_id": 1, + // "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b", + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8800, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "order_status": "Created", + // "last_exec_time": 0, + // "last_exec_price": 0, + // "leaves_qty": 1, + // "cum_exec_qty": 0, // in contracts, where 1 contract = 1 quote currency unit (USD for inverse contracts) + // "cum_exec_value": 0, // in contract's underlying currency (BTC for inverse contracts) + // "cum_exec_fee": 0, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-11-30T11:03:43.452Z", + // "updated_at": "2019-11-30T11:03:43.455Z" + // } + // + // fetchOrder + // + // { + // "user_id" : 599946, + // "symbol" : "BTCUSD", + // "side" : "Buy", + // "order_type" : "Limit", + // "price" : "7948", + // "qty" : 10, + // "time_in_force" : "GoodTillCancel", + // "order_status" : "Filled", + // "ext_fields" : { + // "o_req_num" : -1600687220498, + // "xreq_type" : "x_create" + // }, + // "last_exec_time" : "1588150113.968422", + // "last_exec_price" : "7948", + // "leaves_qty" : 0, + // "leaves_value" : "0", + // "cum_exec_qty" : 10, + // "cum_exec_value" : "0.00125817", + // "cum_exec_fee" : "-0.00000031", + // "reject_reason" : "", + // "cancel_type" : "", + // "order_link_id" : "", + // "created_at" : "2020-04-29T08:45:24.399146Z", + // "updated_at" : "2020-04-29T08:48:33.968422Z", + // "order_id" : "dd2504b9-0157-406a-99e1-efa522373944" + // } + // + // conditional order + // + // { + // "user_id":##, + // "symbol":"BTCUSD", + // "side":"Buy", + // "order_type":"Market", + // "price":0, + // "qty":10, + // "time_in_force":"GoodTillCancel", + // "stop_order_type":"Stop", + // "trigger_by":"LastPrice", + // "base_price":11833, + // "order_status":"Untriggered", + // "ext_fields":{ + // "stop_order_type":"Stop", + // "trigger_by":"LastPrice", + // "base_price":11833, + // "expected_direction":"Rising", + // "trigger_price":12400, + // "close_on_trigger":true, + // "op_from":"api", + // "remark":"145.53.159.48", + // "o_req_num":0 + // }, + // "leaves_qty":10, + // "leaves_value":0.00080645, + // "reject_reason":null, + // "cross_seq":-1, + // "created_at":"2020-08-21T09:18:48.000Z", + // "updated_at":"2020-08-21T09:18:48.000Z", + // "stop_px":12400, + // "stop_order_id":"3f3b54b1-3379-42c7-8510-44f4d9915be0" + // } + // + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market); + let symbol = undefined; + let base = undefined; + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const id = this.safeString2 (order, 'order_id', 'stop_order_id'); + const type = this.safeStringLower (order, 'order_type'); + let price = this.safeFloat (order, 'price'); + if (price === 0.0) { + price = undefined; + } + let average = this.safeFloat (order, 'average_price'); + const amount = this.safeFloat (order, 'qty'); + let cost = this.safeFloat (order, 'cum_exec_value'); + let filled = this.safeFloat (order, 'cum_exec_qty'); + let remaining = this.safeFloat (order, 'leaves_qty'); + if (market !== undefined) { + symbol = market['symbol']; + base = market['base']; + } + let lastTradeTimestamp = this.safeTimestamp (order, 'last_exec_time'); + if (lastTradeTimestamp === 0) { + lastTradeTimestamp = undefined; + } + if ((filled === undefined) && (amount !== undefined) && (remaining !== undefined)) { + filled = amount - remaining; + } + if (filled !== undefined) { + if ((remaining === undefined) && (amount !== undefined)) { + remaining = amount - filled; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } + if ((type === 'market') && (cost !== undefined) && (cost > 0)) { + price = undefined; + if (average === undefined) { + average = filled / cost; + } + } + } + const status = this.parseOrderStatus (this.safeString2 (order, 'order_status', 'stop_order_status')); + const side = this.safeStringLower (order, 'side'); + let feeCost = this.safeFloat (order, 'cum_exec_fee'); + let fee = undefined; + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + fee = { + 'cost': feeCost, + 'currency': base, + }; + } + let clientOrderId = this.safeString (order, 'order_link_id'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force')); + const stopPrice = this.safeFloat (order, 'stop_px'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'order_link_id': 'string', // one of order_id, stop_order_id or order_link_id is required + // regular orders --------------------------------------------- + // 'order_id': id, // one of order_id or order_link_id is required for regular orders + // conditional orders --------------------------------------------- + // 'stop_order_id': id, // one of stop_order_id or order_link_id is required for conditional orders + }; + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + let method = (marketType === 'linear') ? 'privateLinearGetOrderSearch' : 'privateGetOrder'; + const stopOrderId = this.safeString (params, 'stop_order_id'); + if (stopOrderId === undefined) { + const orderLinkId = this.safeString (params, 'order_link_id'); + if (orderLinkId === undefined) { + request['order_id'] = id; + } + } else { + method = (marketType === 'linear') ? 'privateLinearGetStopOrderSearch' : 'privateGetStopOrder'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Sell", + // "order_type": "Limit", + // "price": "8083", + // "qty": 10, + // "time_in_force": "GoodTillCancel", + // "order_status": "New", + // "ext_fields": { "o_req_num": -308787, "xreq_type": "x_create", "xreq_offset": 4154640 }, + // "leaves_qty": 10, + // "leaves_value": "0.00123716", + // "cum_exec_qty": 0, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-10-21T07:28:19.396246Z", + // "updated_at": "2019-10-21T07:28:19.396246Z", + // "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93" + // }, + // "time_now": "1571651135.291930", + // "rate_limit_status": 99, // The remaining number of accesses in one minute + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": "8000", + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "order_status": "Untriggered", + // "ext_fields": {}, + // "leaves_qty": 1, + // "leaves_value": "0.00013333", + // "cum_exec_qty": 0, + // "cum_exec_value": null, + // "cum_exec_fee": null, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-12-27T19:56:24.052194Z", + // "updated_at": "2019-12-27T19:56:24.052194Z", + // "order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36" + // }, + // "time_now": "1577476584.386958", + // "rate_limit_status": 99, + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result, market); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let qty = this.amountToPrecision (symbol, amount); + if (market['inverse']) { + qty = parseInt (qty); + } else { + qty = parseFloat (qty); + } + const request = { + // orders --------------------------------------------------------- + 'side': this.capitalize (side), + 'symbol': market['id'], + 'order_type': this.capitalize (type), + 'qty': qty, // order quantity in USD, integer only + // 'price': parseFloat (this.priceToPrecision (symbol, price)), // required for limit orders + 'time_in_force': 'GoodTillCancel', // ImmediateOrCancel, FillOrKill, PostOnly + // 'take_profit': 123.45, // take profit price, only take effect upon opening the position + // 'stop_loss': 123.45, // stop loss price, only take effect upon opening the position + // 'reduce_only': false, // reduce only, required for linear orders + // when creating a closing order, bybit recommends a True value for + // close_on_trigger to avoid failing due to insufficient available margin + // 'close_on_trigger': false, required for linear orders + // 'order_link_id': 'string', // unique client order id, max 36 characters + // conditional orders --------------------------------------------- + // base_price is used to compare with the value of stop_px, to decide + // whether your conditional order will be triggered by crossing trigger + // price from upper side or lower side, mainly used to identify the + // expected direction of the current conditional order + // 'base_price': 123.45, // required for conditional orders + // 'stop_px': 123.45, // trigger price, required for conditional orders + // 'trigger_by': 'LastPrice', // IndexPrice, MarkPrice + }; + let priceIsRequired = false; + if (type === 'limit') { + priceIsRequired = true; + } + if (priceIsRequired) { + if (price !== undefined) { + request['price'] = parseFloat (this.priceToPrecision (symbol, price)); + } else { + throw new ArgumentsRequired (this.id + ' createOrder requires a price argument for a ' + type + ' order'); + } + } + const clientOrderId = this.safeString2 (params, 'order_link_id', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['order_link_id'] = clientOrderId; + params = this.omit (params, [ 'order_link_id', 'clientOrderId' ]); + } + const stopPx = this.safeValue2 (params, 'stop_px', 'stopPrice'); + const basePrice = this.safeValue (params, 'base_price'); + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + let method = (marketType === 'linear') ? 'privateLinearPostOrderCreate' : 'privatePostOrderCreate'; + if (marketType === 'linear') { + method = 'privateLinearPostOrderCreate'; + request['reduce_only'] = false; + request['close_on_trigger'] = false; + } + if (stopPx !== undefined) { + if (basePrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order'); + } else { + method = (marketType === 'linear') ? 'privateLinearPostStopOrderCreate' : 'openapiPostStopOrderCreate'; + request['stop_px'] = parseFloat (this.priceToPrecision (symbol, stopPx)); + request['base_price'] = parseFloat (this.priceToPrecision (symbol, basePrice)); + params = this.omit (params, [ 'stop_px', 'stopPrice', 'base_price' ]); + } + } else if (basePrice !== undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order'); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "user_id": 1, + // "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b", + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8800, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "order_status": "Created", + // "last_exec_time": 0, + // "last_exec_price": 0, + // "leaves_qty": 1, + // "cum_exec_qty": 0, + // "cum_exec_value": 0, + // "cum_exec_fee": 0, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-11-30T11:03:43.452Z", + // "updated_at": "2019-11-30T11:03:43.455Z" + // }, + // "time_now": "1575111823.458705", + // "rate_limit_status": 98, + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8000, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "stop_order_type": "Stop", + // "trigger_by": "LastPrice", + // "base_price": 7000, + // "order_status": "Untriggered", + // "ext_fields": { + // "stop_order_type": "Stop", + // "trigger_by": "LastPrice", + // "base_price": 7000, + // "expected_direction": "Rising", + // "trigger_price": 7500, + // "op_from": "api", + // "remark": "127.0.01", + // "o_req_num": 0 + // }, + // "leaves_qty": 1, + // "leaves_value": 0.00013333, + // "reject_reason": null, + // "cross_seq": -1, + // "created_at": "2019-12-27T12:48:24.000Z", + // "updated_at": "2019-12-27T12:48:24.000Z", + // "stop_px": 7500, + // "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5" + // }, + // "ext_info": null, + // "time_now": "1577450904.327654", + // "rate_limit_status": 99, + // "rate_limit_reset_ms": 1577450904335, + // "rate_limit": "100" + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires an symbol argument'); + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + // 'order_id': id, // only for non-conditional orders + 'symbol': market['id'], + // 'p_r_qty': this.amountToPrecision (symbol, amount), // new order quantity, optional + // 'p_r_price' this.priceToprecision (symbol, price), // new order price, optional + // ---------------------------------------------------------------- + // conditional orders + // 'stop_order_id': id, // only for conditional orders + // 'p_r_trigger_price': 123.45, // new trigger price also known as stop_px + }; + let method = (marketType === 'linear') ? 'privateLinearPostOrderReplace' : 'openapiPostOrderReplace'; + const stopOrderId = this.safeString (params, 'stop_order_id'); + if (stopOrderId !== undefined) { + method = (marketType === 'linear') ? 'privateLinearPostStopOrderReplace' : 'openapiPostStopOrderReplace'; + request['stop_order_id'] = stopOrderId; + params = this.omit (params, [ 'stop_order_id' ]); + } else { + request['order_id'] = id; + } + if (amount !== undefined) { + request['p_r_qty'] = parseInt (this.amountToPrecision (symbol, amount)); + } + if (price !== undefined) { + request['p_r_price'] = parseFloat (this.priceToPrecision (symbol, price)); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93" }, + // "time_now": "1539778407.210858", + // "rate_limit_status": 99, // remaining number of accesses in one minute + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { "stop_order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36" }, + // "ext_info": null, + // "time_now": "1577475760.604942", + // "rate_limit_status": 96, + // "rate_limit_reset_ms": 1577475760612, + // "rate_limit": "100" + // } + // + const result = this.safeValue (response, 'result', {}); + return { + 'info': response, + 'id': this.safeString2 (result, 'order_id', 'stop_order_id'), + 'order_id': this.safeString (result, 'order_id'), + 'stop_order_id': this.safeString (result, 'stop_order_id'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'order_link_id': 'string', // one of order_id, stop_order_id or order_link_id is required + // regular orders --------------------------------------------- + // 'order_id': id, // one of order_id or order_link_id is required for regular orders + // conditional orders --------------------------------------------- + // 'stop_order_id': id, // one of stop_order_id or order_link_id is required for conditional orders + }; + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeValue (marketTypes, symbol); + let method = (marketType === 'linear') ? 'privateLinearPostOrderCancel' : 'privatePostOrderCancel'; + const stopOrderId = this.safeString (params, 'stop_order_id'); + if (stopOrderId === undefined) { + const orderLinkId = this.safeString (params, 'order_link_id'); + if (orderLinkId === undefined) { + request['order_id'] = id; + } + } else { + method = (marketType === 'linear') ? 'privateLinearPostStopOrderCancel' : 'openapiPostStopOrderCancel'; + } + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const options = this.safeValue (this.options, 'cancelAllOrders'); + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const defaultMethod = (marketType === 'linear') ? 'privateLinearPostOrderCancelAll' : 'privatePostOrderCancelAll'; + const method = this.safeString (options, 'method', defaultMethod); + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'order_id': 'string' + // 'order_link_id': 'string', // unique client order id, max 36 characters + // 'symbol': market['id'], // default BTCUSD + // 'order': 'desc', // asc + // 'page': 1, + // 'limit': 20, // max 50 + // 'order_status': 'Created,New' + // conditional orders --------------------------------------------- + // 'stop_order_id': 'string', + // 'stop_order_status': 'Untriggered', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const options = this.safeValue (this.options, 'fetchOrders', {}); + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + let defaultMethod = (marketType === 'linear') ? 'privateLinearGetOrderList' : 'openapiGetOrderList'; + let query = params; + if (('stop_order_id' in params) || ('stop_order_status' in params)) { + let stopOrderStatus = this.safeValue (params, 'stopOrderStatus'); + if (stopOrderStatus !== undefined) { + if (Array.isArray (stopOrderStatus)) { + stopOrderStatus = stopOrderStatus.join (','); + } + request['stop_order_status'] = stopOrderStatus; + query = this.omit (params, 'stop_order_status'); + } + defaultMethod = (marketType === 'linear') ? 'privateLinearGetStopOrderList' : 'openapiGetStopOrderList'; + } + const method = this.safeString (options, 'method', defaultMethod); + const response = await this[method] (this.extend (request, query)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "current_page": 1, + // "last_page": 6, + // "data": [ + // { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Sell", + // "order_type": "Market", + // "price": 7074, + // "qty": 2, + // "time_in_force": "ImmediateOrCancel", + // "order_status": "Filled", + // "ext_fields": { + // "close_on_trigger": true, + // "orig_order_type": "BLimit", + // "prior_x_req_price": 5898.5, + // "op_from": "pc", + // "remark": "127.0.0.1", + // "o_req_num": -34799032763, + // "xreq_type": "x_create" + // }, + // "last_exec_time": "1577448481.696421", + // "last_exec_price": 7070.5, + // "leaves_qty": 0, + // "leaves_value": 0, + // "cum_exec_qty": 2, + // "cum_exec_value": 0.00028283, + // "cum_exec_fee": 0.00002, + // "reject_reason": "NoError", + // "order_link_id": "", + // "created_at": "2019-12-27T12:08:01.000Z", + // "updated_at": "2019-12-27T12:08:01.000Z", + // "order_id": "f185806b-b801-40ff-adec-52289370ed62" + // } + // ] + // }, + // "ext_info": null, + // "time_now": "1577448922.437871", + // "rate_limit_status": 98, + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "current_page": 1, + // "last_page": 1, + // "data": [ + // { + // "user_id": 1, + // "stop_order_status": "Untriggered", + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8000, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "stop_order_type": "Stop", + // "trigger_by": "LastPrice", + // "base_price": 7000, + // "order_link_id": "", + // "created_at": "2019-12-27T12:48:24.000Z", + // "updated_at": "2019-12-27T12:48:24.000Z", + // "stop_px": 7500, + // "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5" + // }, + // ] + // }, + // "ext_info": null, + // "time_now": "1577451658.755468", + // "rate_limit_status": 599, + // "rate_limit_reset_ms": 1577451658762, + // "rate_limit": 600 + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultStatuses = [ + 'Rejected', + 'Filled', + 'Cancelled', + // conditional orders + // 'Active', + // 'Triggered', + // 'Cancelled', + // 'Rejected', + // 'Deactivated', + ]; + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + let status = this.safeValue (options, 'order_status', defaultStatuses); + if (Array.isArray (status)) { + status = status.join (','); + } + const request = {}; + const stopOrderStatus = this.safeValue (params, 'stop_order_status'); + if (stopOrderStatus === undefined) { + request['order_status'] = status; + } else { + request['stop_order_status'] = stopOrderStatus; + } + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultStatuses = [ + 'Created', + 'New', + 'PartiallyFilled', + 'PendingCancel', + // conditional orders + // 'Untriggered', + ]; + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + let status = this.safeValue (options, 'order_status', defaultStatuses); + if (Array.isArray (status)) { + status = status.join (','); + } + const request = {}; + const stopOrderStatus = this.safeValue (params, 'stop_order_status'); + if (stopOrderStatus === undefined) { + request['order_status'] = status; + } else { + request['stop_order_status'] = stopOrderStatus; + } + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params)); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'order_id': 'f185806b-b801-40ff-adec-52289370ed62', // if not provided will return user's trading records + // 'symbol': market['id'], + // 'start_time': parseInt (since / 1000), + // 'page': 1, + // 'limit' 20, // max 50 + }; + let market = undefined; + if (symbol === undefined) { + const orderId = this.safeString (params, 'order_id'); + if (orderId === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument or an order_id param'); + } else { + request['order_id'] = orderId; + params = this.omit (params, 'order_id'); + } + } else { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 50 + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const method = (marketType === 'linear') ? 'privateLinearGetTradeExecutionList' : 'privateGetExecutionList'; + const response = await this[method] (this.extend (request, params)); + // + // inverse + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "order_id": "Abandoned!!", // Abandoned!! + // "trade_list": [ + // { + // "closed_size": 0, + // "cross_seq": 277136382, + // "exec_fee": "0.0000001", + // "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68", + // "exec_price": "8178.5", + // "exec_qty": 1, + // "exec_time": "1571676941.70682", + // "exec_type": "Trade", //Exec Type Enum + // "exec_value": "0.00012227", + // "fee_rate": "0.00075", + // "last_liquidity_ind": "RemovedLiquidity", //Liquidity Enum + // "leaves_qty": 0, + // "nth_fill": 2, + // "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029", + // "order_link_id": "", + // "order_price": "8178", + // "order_qty": 1, + // "order_type": "Market", //Order Type Enum + // "side": "Buy", //Side Enum + // "symbol": "BTCUSD", //Symbol Enum + // "user_id": 1 + // } + // ] + // }, + // "time_now": "1577483699.281488", + // "rate_limit_status": 118, + // "rate_limit_reset_ms": 1577483699244737, + // "rate_limit": 120 + // } + // + // linear + // + // { + // "ret_code":0, + // "ret_msg":"OK", + // "ext_code":"", + // "ext_info":"", + // "result":{ + // "current_page":1, + // "data":[ + // { + // "order_id":"b59418ec-14d4-4ef9-b9f4-721d5d576974", + // "order_link_id":"", + // "side":"Sell", + // "symbol":"BTCUSDT", + // "exec_id":"0327284d-faec-5191-bd89-acc5b4fafda9", + // "price":0.5, + // "order_price":0.5, + // "order_qty":0.01, + // "order_type":"Market", + // "fee_rate":0.00075, + // "exec_price":9709.5, + // "exec_type":"Trade", + // "exec_qty":0.01, + // "exec_fee":0.07282125, + // "exec_value":97.095, + // "leaves_qty":0, + // "closed_size":0.01, + // "last_liquidity_ind":"RemovedLiquidity", + // "trade_time":1591648052, + // "trade_time_ms":1591648052861 + // } + // ] + // }, + // "time_now":"1591736501.979264", + // "rate_limit_status":119, + // "rate_limit_reset_ms":1591736501974, + // "rate_limit":120 + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue2 (result, 'trade_list', 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetDeposits (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 5611, + // "result": { + // "count": 1, + // "data": [ + // { + // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", + // "amount": 5, + // "currency": "BTC", + // "received_timestamp": 1549295017670, + // "state": "completed", + // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", + // "updated_timestamp": 1549295130159 + // } + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'coin': currency['id'], + // 'start_date': this.iso8601 (since), + // 'end_date': this.iso8601 (till), + // 'status': 'Pending', // ToBeConfirmed, UnderReview, Pending, Success, CancelByUser, Reject, Expire + // 'page': 1, + // 'limit': 20, // max 50 + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['coin'] = currency['id']; + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.openapiGetWalletWithdrawList (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "data": [ + // { + // "id": 137, + // "user_id": 1, + // "coin": "XRP", // Coin Enum + // "status": "Pending", // Withdraw Status Enum + // "amount": "20.00000000", + // "fee": "0.25000000", + // "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM", + // "tx_id": "", + // "submited_at": "2019-06-11T02:20:24.000Z", + // "updated_at": "2019-06-11T02:20:24.000Z" + // }, + // ], + // "current_page": 1, + // "last_page": 1 + // }, + // "ext_info": null, + // "time_now": "1577482295.125488", + // "rate_limit_status": 119, + // "rate_limit_reset_ms": 1577482295132, + // "rate_limit": 120 + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + 'ToBeConfirmed': 'pending', + 'UnderReview': 'pending', + 'Pending': 'pending', + 'Success': 'ok', + 'CancelByUser': 'canceled', + 'Reject': 'rejected', + 'Expire': 'expired', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchWithdrawals + // + // { + // "id": 137, + // "user_id": 1, + // "coin": "XRP", // Coin Enum + // "status": "Pending", // Withdraw Status Enum + // "amount": "20.00000000", + // "fee": "0.25000000", + // "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM", + // "tx_id": "", + // "submited_at": "2019-06-11T02:20:24.000Z", + // "updated_at": "2019-06-11T02:20:24.000Z" + // } + // + const currencyId = this.safeString (transaction, 'coin'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.parse8601 (this.safeString (transaction, 'submited_at')); + const updated = this.parse8601 (this.safeString (transaction, 'updated_at')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const address = this.safeString (transaction, 'address'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'tx_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': undefined, + 'addressFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'tagFrom': undefined, + 'type': 'withdrawal', + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'coin': currency['id'], + // 'currency': currency['id'], // alias + // 'start_date': this.iso8601 (since), + // 'end_date': this.iso8601 (till), + // 'wallet_fund_type': 'Deposit', // Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit + // 'page': 1, + // 'limit': 20, // max 50 + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['coin'] = currency['id']; + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.openapiGetWalletFundRecords (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "data": [ + // { + // "id": 234467, + // "user_id": 1, + // "coin": "BTC", + // "wallet_id": 27913, + // "type": "Realized P&L", + // "amount": "-0.00000006", + // "tx_id": "", + // "address": "BTCUSD", + // "wallet_balance": "0.03000330", + // "exec_time": "2019-12-09T00:00:25.000Z", + // "cross_seq": 0 + // } + // ] + // }, + // "ext_info": null, + // "time_now": "1577481867.115552", + // "rate_limit_status": 119, + // "rate_limit_reset_ms": 1577481867122, + // "rate_limit": 120 + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseLedger (data, currency, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "id": 234467, + // "user_id": 1, + // "coin": "BTC", + // "wallet_id": 27913, + // "type": "Realized P&L", + // "amount": "-0.00000006", + // "tx_id": "", + // "address": "BTCUSD", + // "wallet_balance": "0.03000330", + // "exec_time": "2019-12-09T00:00:25.000Z", + // "cross_seq": 0 + // } + // + const currencyId = this.safeString (item, 'coin'); + const code = this.safeCurrencyCode (currencyId, currency); + const amount = this.safeFloat (item, 'amount'); + const after = this.safeFloat (item, 'wallet_balance'); + const direction = (amount < 0) ? 'out' : 'in'; + let before = undefined; + if (after !== undefined && amount !== undefined) { + const difference = (direction === 'out') ? amount : -amount; + before = this.sum (after, difference); + } + const timestamp = this.parse8601 (this.safeString (item, 'exec_time')); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const id = this.safeString (item, 'id'); + const referenceId = this.safeString (item, 'tx_id'); + return { + 'id': id, + 'currency': code, + 'account': this.safeString (item, 'wallet_id'), + 'referenceAccount': undefined, + 'referenceId': referenceId, + 'status': undefined, + 'amount': amount, + 'before': before, + 'after': after, + 'fee': undefined, + 'direction': direction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'type': type, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'Deposit': 'transaction', + 'Withdraw': 'transaction', + 'RealisedPNL': 'trade', + 'Commission': 'fee', + 'Refund': 'cashback', + 'Prize': 'prize', // ? + 'ExchangeOrderWithdraw': 'transaction', + 'ExchangeOrderDeposit': 'transaction', + }; + return this.safeString (types, type, type); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api']; + let request = path; + // public v2 + if (api === 'public') { + request = '/' + this.version + '/' + api + '/' + request; + if (Object.keys (params).length) { + request += '?' + this.rawencode (params); + } + } else if (api === 'publicLinear') { + request = '/public/linear/' + request; + if (Object.keys (params).length) { + request += '?' + this.rawencode (params); + } + } else { + this.checkRequiredCredentials (); + if (api === 'openapi') { + request = '/open-api/' + request; + } else if (api === 'private') { + // private v2 + request = '/' + this.version + '/' + api + '/' + request; + } else if (api === 'privateLinear') { + request = '/private/linear/' + request; + } else { + // position, user + request = '/' + api + '/' + request; + } + const timestamp = this.nonce (); + const query = this.extend (params, { + 'api_key': this.apiKey, + 'recv_window': this.options['recvWindow'], + 'timestamp': timestamp, + }); + let auth = this.rawencode (this.keysort (query)); + // https://github.com/ccxt/ccxt/issues/7377 + // https://github.com/ccxt/ccxt/issues/7515 + // bybit encodes whole floats as integers without .0 for conditional stop-orders only + if (path.indexOf ('stop-order') >= 0) { + auth = auth.replace ('.0&', '&'); + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + if (method === 'POST') { + body = this.json (this.extend (query, { + 'sign': signature, + })); + headers = { + 'Content-Type': 'application/json', + }; + } else { + request += '?' + auth + '&sign=' + signature; + } + } + url += request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + // + // { + // ret_code: 10001, + // ret_msg: 'ReadMapCB: expect { or n, but found \u0000, error ' + + // 'found in #0 byte of ...||..., bigger context ' + + // '...||...', + // ext_code: '', + // ext_info: '', + // result: null, + // time_now: '1583934106.590436' + // } + // + const errorCode = this.safeValue (response, 'ret_code'); + if (errorCode !== 0) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],57:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, AuthenticationError, DDoSProtection, BadResponse } = require ('./base/errors'); +const { TRUNCATE, NO_PADDING, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bytetrade extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bytetrade', + 'name': 'ByteTrade', + 'countries': ['HK'], + 'rateLimit': 500, + 'requiresWeb3': true, + 'certified': true, + // new metainfo interface + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '1d': '1d', + '5d': '5d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'test': { + 'market': 'https://api-v2-test.byte-trade.com', + 'public': 'https://api-v2-test.byte-trade.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg', + 'api': { + 'market': 'https://api-v2.byte-trade.com', + 'public': 'https://api-v2.byte-trade.com', + }, + 'www': 'https://www.byte-trade.com', + 'doc': 'https://docs.byte-trade.com/#description', + }, + 'api': { + 'market': { + 'get': [ + 'klines', // Kline of a symbol + 'depth', // Market Depth of a symbol + 'trades', // Trade records of a symbol + 'tickers', + ], + }, + 'public': { + 'get': [ + 'symbols', // Reference information of trading instrument, including base currency, quote precision, etc. + 'currencies', // The list of currencies available + 'balance', // Get the balance of an account + 'orders/open', // Get the open orders of an account + 'orders/closed', // Get the closed orders of an account + 'orders/all', // Get the open and closed orders of an account + 'orders', // Get the details of an order of an account + 'orders/trades', // Get detail match results + 'depositaddress', // Get deposit address + 'withdrawals', // Get withdrawals info + 'deposits', // Get deposit info + 'transfers', // Get transfer info + ], + 'post': [ + 'transaction/createorder', // Post create order transaction to blockchain + 'transaction/cancelorder', // Post cancel order transaction to blockchain + 'transaction/withdraw', // Post withdraw transaction to blockchain + 'transaction/transfer', // Post transfer transaction to blockchain + ], + }, + }, + 'fees': { + 'trading': { + 'taker': 0.0008, + 'maker': 0.0008, + }, + }, + 'commonCurrencies': { + '1': 'ByteTrade', + '44': 'ByteHub', + '48': 'Blocktonic', + '133': 'TerraCredit', + }, + 'exceptions': { + 'vertify error': AuthenticationError, // typo on the exchange side, 'vertify' + 'verify error': AuthenticationError, // private key signature is incorrect + 'transaction already in network': BadRequest, // same transaction submited + 'invalid argument': BadRequest, + }, + }); + } + + async fetchCurrencies (params = {}) { + const currencies = await this.publicGetCurrencies (params); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'code'); + let code = undefined; + if (id in this.commonCurrencies) { + code = this.commonCurrencies[id]; + } else { + code = this.safeString (currency, 'name'); + } + const name = this.safeString (currency, 'fullname'); + // in byte-trade.com DEX, request https://api-v2.byte-trade.com/currencies will return currencies, + // the api doc is https://github.com/Bytetrade/bytetrade-official-api-docs/wiki/rest-api#get-currencies-get-currencys-supported-in-bytetradecom + // we can see the coin name is none-unique in the result, the coin which code is 18 is the CyberMiles ERC20, and the coin which code is 35 is the CyberMiles main chain, but their name is same. + // that is because bytetrade is a DEX, supports people create coin with the same name, but the id(code) of coin is unique, so we should use the id or name and id as the identity of coin. + // For coin name and symbol is same with CCXT, I use name@id as the key of commonCurrencies dict. + // [{ + // "name": "CMT", // currency name, non-unique + // "code": "18", // currency id, unique + // "type": "crypto", + // "fullname": "CyberMiles", + // "active": true, + // "chainType": "ethereum", + // "basePrecision": 18, + // "transferPrecision": 10, + // "externalPrecision": 18, + // "chainContractAddress": "0xf85feea2fdd81d51177f6b8f35f0e6734ce45f5f", + // "limits": { + // "deposit": { + // "min": "0", + // "max": "-1" + // }, + // "withdraw": { + // "min": "0", + // "max": "-1" + // } + // } + // }, + // { + // "name": "CMT", + // "code": "35", + // "type": "crypto", + // "fullname": "CyberMiles", + // "active": true, + // "chainType": "cmt", + // "basePrecision": 18, + // "transferPrecision": 10, + // "externalPrecision": 18, + // "chainContractAddress": "0x0000000000000000000000000000000000000000", + // "limits": { + // "deposit": { + // "min": "1", + // "max": "-1" + // }, + // "withdraw": { + // "min": "10", + // "max": "-1" + // } + // } + // } + // ] + const active = this.safeValue (currency, 'active'); + const limits = this.safeValue (currency, 'limits'); + const deposit = this.safeValue (limits, 'deposit'); + const amountPrecision = this.safeInteger (currency, 'basePrecision'); + let maxDeposit = this.safeFloat (deposit, 'max'); + if (maxDeposit === -1.0) { + maxDeposit = undefined; + } + const withdraw = this.safeValue (limits, 'withdraw'); + let maxWithdraw = this.safeFloat (withdraw, 'max'); + if (maxWithdraw === -1.0) { + maxWithdraw = undefined; + } + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'active': active, + 'precision': amountPrecision, + 'fee': undefined, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'deposit': { + 'min': this.safeFloat (deposit, 'min'), + 'max': maxDeposit, + }, + 'withdraw': { + 'min': this.safeFloat (withdraw, 'min'), + 'max': maxWithdraw, + }, + }, + 'info': currency, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetSymbols (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + let base = this.safeString (market, 'baseName'); + let quote = this.safeString (market, 'quoteName'); + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const normalBase = base.split ('@' + baseId)[0]; + let normalQuote = quote.split ('@' + quoteId)[0]; + if (quoteId === '126') { + normalQuote = 'ZAR'; // The id 126 coin is a special coin whose name on the chain is actually ZAR, but it is changed to ZCN after creation, so it must be changed to ZAR when placing the transaction in the chain + } + const normalSymbol = normalBase + '/' + normalQuote; + if (baseId in this.commonCurrencies) { + base = this.commonCurrencies[baseId]; + } + if (quoteId in this.commonCurrencies) { + quote = this.commonCurrencies[quoteId]; + } + const symbol = base + '/' + quote; + const limits = this.safeValue (market, 'limits', {}); + const amount = this.safeValue (limits, 'amount', {}); + const price = this.safeValue (limits, 'price', {}); + const precision = this.safeValue (market, 'precision', {}); + const active = this.safeString (market, 'active'); + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': { + 'amount': this.safeInteger (precision, 'amount'), + 'price': this.safeInteger (precision, 'price'), + }, + 'normalSymbol': normalSymbol, + 'limits': { + 'amount': { + 'min': this.safeFloat (amount, 'min'), + 'max': this.safeFloat (amount, 'max'), + }, + 'price': { + 'min': this.safeFloat (price, 'min'), + 'max': this.safeFloat (price, 'max'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + result.push (entry); + } + return result; + } + + async fetchBalance (params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const request = { + 'userid': this.apiKey, + }; + const balances = await this.publicGetBalance (this.extend (request, params)); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'code'); + const code = this.safeCurrencyCode (currencyId, undefined); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'used'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = maximum = 100 + } + const response = await this.marketGetDepth (this.extend (request, params)); + const timestamp = this.safeValue (response, 'timestamp'); + const orderbook = this.parseOrderBook (response, timestamp); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeInteger (ticker, 'timestamp'); + // + // [ + // { + // "symbol":"68719476706", + // "name":"ETH/BTC", + // "base":"2", + // "quote":"32", + // "timestamp":1575905991933, + // "datetime":"2019-12-09T15:39:51.933Z", + // "high":"0", + // "low":"0", + // "open":"0", + // "close":"0", + // "last":"0", + // "change":"0", + // "percentage":"0", + // "baseVolume":"0", + // "quoteVolume":"0" + // } + // ] + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'symbol'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (ticker, 'base'); + const quoteId = this.safeString (ticker, 'quote'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), + 'open': this.safeFloat (ticker, 'open'), + 'close': this.safeFloat (ticker, 'close'), + 'last': this.safeFloat (ticker, 'last'), + 'previousClose': undefined, // previous day close + 'change': this.safeFloat (ticker, 'change'), + 'percentage': this.safeFloat (ticker, 'percentage'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetTickers (this.extend (request, params)); + // + // [ + // { + // "symbol":"68719476706", + // "name":"ETH/BTC", + // "base":"2", + // "quote":"32", + // "timestamp":1575905991933, + // "datetime":"2019-12-09T15:39:51.933Z", + // "high":"0", + // "low":"0", + // "open":"0", + // "close":"0", + // "last":"0", + // "change":"0", + // "percentage":"0", + // "baseVolume":"0", + // "quoteVolume":"0" + // } + // ] + // + if (Array.isArray (response)) { + const ticker = this.safeValue (response, 0); + if (ticker === undefined) { + throw new BadResponse (this.id + ' fetchTicker() returned an empty response'); + } + return this.parseTicker (ticker, market); + } + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchBidsAsks (symbols = undefined, params = {}) { + await this.loadMarkets (); + const rawTickers = await this.marketGetDepth (params); + return this.parseTickers (rawTickers, symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const rawTickers = await this.marketGetTickers (params); + return this.parseTickers (rawTickers, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591505760000, + // "242.7", + // "242.76", + // "242.69", + // "242.76", + // "0.1892" + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.marketGetKlines (this.extend (request, params)); + // + // [ + // [1591505760000,"242.7","242.76","242.69","242.76","0.1892"], + // [1591505820000,"242.77","242.83","242.7","242.72","0.6378"], + // [1591505880000,"242.72","242.73","242.61","242.72","0.4141"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeInteger (trade, 'timestamp'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const cost = this.safeFloat (trade, 'cost'); + const id = this.safeString (trade, 'id'); + const type = this.safeString (trade, 'type'); + const takerOrMaker = this.safeString (trade, 'takerOrMaker'); + const side = this.safeString (trade, 'side'); + const datetime = this.iso8601 (timestamp); // this.safeString (trade, 'datetime'); + const order = this.safeString (trade, 'order'); + const fee = this.safeValue (trade, 'fee'); + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeString (trade, 'symbol'); + market = this.safeValue (this.markets_by_id, marketId); + } + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': datetime, + 'symbol': symbol, + 'id': id, + 'order': order, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default = 100, maximum = 500 + } + const response = await this.marketGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOrder (order, market = undefined) { + const status = this.safeString (order, 'status'); + let symbol = undefined; + const marketId = this.safeString (order, 'symbol'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (order, 'base'); + const quoteId = this.safeString (order, 'quote'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (order, 'timestamp'); + const datetime = this.safeString (order, 'datetime'); + const lastTradeTimestamp = this.safeInteger (order, 'lastTradeTimestamp'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled'); + const remaining = this.safeFloat (order, 'remaining'); + const cost = this.safeFloat (order, 'cost'); + const average = this.safeFloat (order, 'average'); + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const fee = this.safeValue (order, 'fee'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': datetime, + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + this.checkRequiredDependencies (); + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('createOrder requires this.apiKey or userid in params'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let sideNum = undefined; + let typeNum = undefined; + if (side === 'sell') { + sideNum = 1; + } else { + sideNum = 2; + } + if (type === 'limit') { + typeNum = 1; + } else { + typeNum = 2; + price = 0; + } + const normalSymbol = market['normalSymbol']; + const baseId = market['baseId']; + const baseCurrency = this.currency (market['base']); + const amountTruncated = this.amountToPrecision (symbol, amount); + const amountChain = this.toWei (amountTruncated, baseCurrency['precision']); + const quoteId = market['quoteId']; + const quoteCurrency = this.currency (market['quote']); + const priceRounded = this.priceToPrecision (symbol, price); + const priceChain = this.toWei (priceRounded, quoteCurrency['precision']); + const now = this.milliseconds (); + const expiration = this.milliseconds (); + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const defaultFee = this.safeString (this.options, 'fee', '300000000000000'); + const totalFeeRate = this.safeString (params, 'totalFeeRate', 8); + const chainFeeRate = this.safeString (params, 'chainFeeRate', 1); + const fee = this.safeString (params, 'fee', defaultFee); + const eightBytes = this.integerPow ('2', '64'); + const allByteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (32, 1), + this.numberToLE (0, 8), + this.numberToLE (fee, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (sideNum, 1), + this.numberToLE (typeNum, 1), + this.numberToLE (normalSymbol.length, 1), + this.stringToBinary (this.encode (normalSymbol)), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (this.integerDivide (priceChain, eightBytes), 8), + this.numberToLE (this.integerModulo (priceChain, eightBytes), 8), + this.numberToLE (0, 2), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (parseInt (chainFeeRate), 2), + this.numberToLE (1, 1), + this.numberToLE (parseInt (totalFeeRate), 2), + this.numberToLE (parseInt (quoteId), 4), + this.numberToLE (parseInt (baseId), 4), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const txByteStringArray = [ + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (32, 1), + this.numberToLE (0, 8), + this.numberToLE (fee, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (sideNum, 1), + this.numberToLE (typeNum, 1), + this.numberToLE (normalSymbol.length, 1), + this.stringToBinary (this.encode (normalSymbol)), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (this.integerDivide (priceChain, eightBytes), 8), + this.numberToLE (this.integerModulo (priceChain, eightBytes), 8), + this.numberToLE (0, 2), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (parseInt (chainFeeRate), 2), + this.numberToLE (1, 1), + this.numberToLE (parseInt (totalFeeRate), 2), + this.numberToLE (parseInt (quoteId), 4), + this.numberToLE (parseInt (baseId), 4), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const txbytestring = this.binaryConcatArray (txByteStringArray); + const txidhash = this.hash (txbytestring, 'sha256', 'hex'); + const txid = txidhash.slice (0, 40); + const orderidByteStringArray = [ + this.numberToLE (txid.length, 1), + this.stringToBinary (this.encode (txid)), + this.numberToBE (0, 4), + ]; + const orderidbytestring = this.binaryConcatArray (orderidByteStringArray); + const orderidhash = this.hash (orderidbytestring, 'sha256', 'hex'); + const orderid = orderidhash.slice (0, 40); + const bytestring = this.binaryConcatArray (allByteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + const operation = { + 'now': datetime, + 'expiration': expirationDatetime, + 'fee': fee, + 'creator': this.apiKey, + 'side': sideNum, + 'order_type': typeNum, + 'market_name': normalSymbol, + 'amount': amountChain, + 'price': priceChain, + 'use_btt_as_fee': false, + 'money_id': parseInt (quoteId), + 'stock_id': parseInt (baseId), + 'custom_no_btt_fee_rate': parseInt (totalFeeRate), + 'custom_btt_fee_rate': parseInt (chainFeeRate), + }; + const fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + 32, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + const request = { + 'trObj': this.json (fatty), + }; + const response = await this.publicPostTransactionCreateorder (request); + const timestamp = this.milliseconds (); + const statusCode = this.safe_string (response, 'code'); + const status = (statusCode === '0') ? 'open' : 'failed'; + return { + 'info': response, + 'id': orderid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': undefined, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchOrder requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const request = { + 'userid': this.apiKey, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.markets[symbol]; + request['symbol'] = market['id']; + } + request['id'] = id; + const response = await this.publicGetOrders (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchOpenOrders requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const request = { + 'userid': this.apiKey, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersOpen (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchClosedOrders requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + let market = undefined; + const request = { + 'userid': this.apiKey, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersClosed (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchOrders requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + let market = undefined; + const request = { + 'userid': this.apiKey, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersAll (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('cancelOrder requires hasAlreadyAuthenticatedSuccessfully'); + } + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const baseId = market['baseId']; + const quoteId = market['quoteId']; + const normalSymbol = market['normalSymbol']; + const feeAmount = '300000000000000'; + const now = this.milliseconds (); + const expiration = 0; + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (expiration, 4), + this.numberToLE (1, 1), + this.numberToLE (33, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (normalSymbol.length, 1), + this.stringToBinary (this.encode (normalSymbol)), + this.base16ToBinary (id), + this.numberToLE (parseInt (quoteId), 4), + this.numberToLE (parseInt (baseId), 4), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const bytestring = this.binaryConcatArray (byteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + const operation = { + 'fee': feeAmount, + 'creator': this.apiKey, + 'order_id': id, + 'market_name': normalSymbol, + 'money_id': parseInt (quoteId), + 'stock_id': parseInt (baseId), + }; + const fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + 33, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + const request = { + 'trObj': this.json (fatty), + }; + const response = await this.publicPostTransactionCancelorder (request); + const timestamp = this.milliseconds (); + const statusCode = this.safe_string (response, 'code'); + const status = (statusCode === '0') ? 'canceled' : 'failed'; + return { + 'info': response, + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': undefined, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async transfer (code, amount, address, message = '', params = {}) { + this.checkRequiredDependencies (); + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('transfer requires this.apiKey'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING); + const amountChain = this.toWei (amountTruncate, currency['precision']); + const assetType = parseInt (currency['id']); + const now = this.milliseconds (); + const expiration = now; + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const feeAmount = '300000000000000'; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const eightBytes = this.integerPow ('2', '64'); + const byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (28, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (address.length, 1), + this.stringToBinary (this.encode (address)), + this.numberToLE (assetType, 4), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (1, 1), + this.numberToLE (message.length, 1), + this.stringToBinary (this.encode (message)), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const bytestring = this.binaryConcatArray (byteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + const operation = { + 'fee': '300000000000000', + 'from': this.apiKey, + 'to': address, + 'asset_type': parseInt (currency['id']), + 'amount': amountChain.toString (), + 'message': message, + }; + const fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + 28, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + const request = { + 'trObj': this.json (fatty), + }; + const response = await this.publicPostTransactionTransfer (request); + const timestamp = this.milliseconds (); + const statusCode = this.safe_string (response, 'code'); + let status = ''; + if (statusCode === '0') { + status = 'submit success'; + } else { + status = 'submit fail'; + } + return { + 'info': response, + 'id': '', + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': undefined, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchMyTrades requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'userid': this.apiKey, + }; + if (symbol !== undefined) { + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchDeposits requires this.apiKey or userid argument'); + } + let currency = undefined; + const request = { + 'userid': this.apiKey, + }; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetDeposits (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchWithdrawals requires this.apiKey or userid argument'); + } + let currency = undefined; + const request = { + 'userid': this.apiKey, + }; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetWithdrawals (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'DEPOSIT_FAILED': 'failed', + 'FEE_SEND_FAILED': 'failed', + 'FEE_FAILED': 'failed', + 'PAY_SEND_FAILED': 'failed', + 'PAY_FAILED': 'failed', + 'BTT_FAILED': 'failed', + 'WITHDDRAW_FAILED': 'failed', + 'USER_FAILED': 'failed', + 'FEE_EXECUED': 'pending', + 'PAY_EXECUED': 'pending', + 'WITHDDRAW_EXECUTED': 'pending', + 'USER_EXECUED': 'pending', + 'BTT_SUCCED': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString (transaction, 'tag'); + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const txid = this.safeValue (transaction, 'txid'); + const currencyId = this.safeString (transaction, 'code'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeInteger (transaction, 'timestamp'); + const datetime = this.safeString (transaction, 'datetime'); + const type = this.safeString (transaction, 'type'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const feeInfo = this.safeValue (transaction, 'fee'); + const feeCost = this.safeFloat (feeInfo, 'cost'); + const feeCurrencyId = this.safeString (feeInfo, 'code'); + const feeCode = this.safeCurrencyCode (feeCurrencyId, currency); + const fee = { + 'cost': feeCost, + 'currency': feeCode, + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': datetime, + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchDepositAddress requires this.apiKey or userid argument'); + } + const currency = this.currency (code); + const request = { + 'userid': this.apiKey, + 'code': currency['id'], + }; + const response = await this.publicGetDepositaddress (request); + const address = this.safeString (response[0], 'address'); + const tag = this.safeString (response[0], 'tag'); + const chainType = this.safeString (response[0], 'chainType'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'chainType': chainType, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkAddress (address); + await this.loadMarkets (); + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('withdraw requires this.apiKey'); + } + const addressResponse = await this.fetchDepositAddress (code); + const chainTypeString = this.safeString (addressResponse, 'chainType'); + const chainId = this.safeString (addressResponse['info'][0], 'chainId'); + let middleAddress = ''; + if (chainTypeString === 'eos') { + middleAddress = address; + } else { + middleAddress = this.safeString (addressResponse, 'address'); + } + let operationId = 18; + if (chainTypeString !== 'ethereum' && chainTypeString !== 'etc' && chainTypeString !== 'eos' && chainTypeString !== 'cmt' && chainTypeString !== 'naka') { + operationId = 26; + } + const now = this.milliseconds (); + const expiration = 0; + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const feeAmount = '300000000000000'; + const currency = this.currency (code); + const coinId = currency['id']; + const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING); + const amountChain = this.toWei (amountTruncate, currency['info']['externalPrecision']); + const eightBytes = this.integerPow ('2', '64'); + let assetFee = 0; + let byteStringArray = []; + if (operationId === 26) { + assetFee = currency['info']['fee']; + byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (operationId, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (address.length, 1), + this.stringToBinary (this.encode (address)), + this.numberToLE (parseInt (coinId), 4), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (1, 1), + this.numberToLE (this.integerDivide (assetFee, eightBytes), 8), + this.numberToLE (this.integerModulo (assetFee, eightBytes), 8), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + } else { + byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (operationId, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (4, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (middleAddress.length, 1), + this.stringToBinary (this.encode (middleAddress)), + this.numberToLE (parseInt (coinId), 4), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + } + const bytestring = this.binaryConcatArray (byteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + let fatty = undefined; + let request = undefined; + let operation = undefined; + const chainContractAddress = this.safeString (currency['info'], 'chainContractAddress'); + if (operationId === 26) { + operation = { + 'fee': feeAmount, + 'from': this.apiKey, + 'to_external_address': address, + 'asset_type': parseInt (coinId), + 'amount': amountChain, + 'asset_fee': assetFee, + }; + fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + operationId, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + request = { + 'chainType': chainId, + 'trObj': this.json (fatty), + 'chainContractAddress': chainContractAddress, + }; + } else { + operation = { + 'fee': feeAmount, + 'from': this.apiKey, + 'to_external_address': middleAddress, + 'asset_type': parseInt (coinId), + 'amount': amountChain, + 'asset_fee': assetFee, + }; + const middle = { + 'fee': feeAmount, + 'proposaler': this.apiKey, + 'expiration_time': datetime, + 'proposed_ops': [{ + 'op': [4, operation], + }], + }; + fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + operationId, + middle, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + if (chainTypeString === 'eos') { + request = { + 'chainType': chainId, + 'toExternalAddress': 'noneed', + 'trObj': this.json (fatty), + 'chainContractAddress': chainContractAddress, + }; + } else { + request = { + 'chainType': chainId, + 'toExternalAddress': address, + 'trObj': this.json (fatty), + 'chainContractAddress': chainContractAddress, + }; + } + } + const response = await this.publicPostTransactionWithdraw (request); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + url += '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code === 503) { + throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body); + } + if (response === undefined) { + return; // fallback to default error handler + } + if ('code' in response) { + const status = this.safeString (response, 'code'); + if (status === '1') { + const message = this.safeString (response, 'msg'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],58:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, NullResponse, InvalidOrder, NotSupported, InsufficientFunds, InvalidNonce, OrderNotFound, RateLimitExceeded, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class cex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'cex', + 'name': 'CEX.IO', + 'countries': [ 'GB', 'EU', 'CY', 'RU' ], + 'rateLimit': 1500, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1m', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg', + 'api': 'https://cex.io/api', + 'www': 'https://cex.io', + 'doc': 'https://cex.io/cex-api', + 'fees': [ + 'https://cex.io/fee-schedule', + 'https://cex.io/limits-commissions', + ], + 'referral': 'https://cex.io/r/0/up105393824/0/', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'currency_profile', + 'currency_limits/', + 'last_price/{pair}/', + 'last_prices/{currencies}/', + 'ohlcv/hd/{yyyymmdd}/{pair}', + 'order_book/{pair}/', + 'ticker/{pair}/', + 'tickers/{currencies}/', + 'trade_history/{pair}/', + ], + 'post': [ + 'convert/{pair}', + 'price_stats/{pair}', + ], + }, + 'private': { + 'post': [ + 'active_orders_status/', + 'archived_orders/{pair}/', + 'balance/', + 'cancel_order/', + 'cancel_orders/{pair}/', + 'cancel_replace_order/{pair}/', + 'close_position/{pair}/', + 'get_address/', + 'get_myfee/', + 'get_order/', + 'get_order_tx/', + 'open_orders/{pair}/', + 'open_orders/', + 'open_position/{pair}/', + 'open_positions/{pair}/', + 'place_order/{pair}/', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.16 / 100, + 'taker': 0.25 / 100, + }, + 'funding': { + 'withdraw': { + // 'USD': undefined, + // 'EUR': undefined, + // 'RUB': undefined, + // 'GBP': undefined, + 'BTC': 0.001, + 'ETH': 0.01, + 'BCH': 0.001, + 'DASH': 0.01, + 'BTG': 0.001, + 'ZEC': 0.001, + 'XRP': 0.02, + }, + 'deposit': { + // 'USD': amount => amount * 0.035 + 0.25, + // 'EUR': amount => amount * 0.035 + 0.24, + // 'RUB': amount => amount * 0.05 + 15.57, + // 'GBP': amount => amount * 0.035 + 0.2, + 'BTC': 0.0, + 'ETH': 0.0, + 'BCH': 0.0, + 'DASH': 0.0, + 'BTG': 0.0, + 'ZEC': 0.0, + 'XRP': 0.0, + 'XLM': 0.0, + }, + }, + }, + 'exceptions': { + 'exact': {}, + 'broad': { + 'Insufficient funds': InsufficientFunds, + 'Nonce must be incremented': InvalidNonce, + 'Invalid Order': InvalidOrder, + 'Order not found': OrderNotFound, + 'limit exceeded': RateLimitExceeded, // {"error":"rate limit exceeded"} + 'Invalid API key': AuthenticationError, + 'There was an error while placing your order': InvalidOrder, + 'Sorry, too many clients already': DDoSProtection, + }, + }, + 'options': { + 'fetchOHLCVWarning': true, + 'createMarketBuyOrderRequiresPrice': true, + 'order': { + 'status': { + 'c': 'canceled', + 'd': 'closed', + 'cd': 'canceled', + 'a': 'open', + }, + }, + }, + }); + } + + async fetchCurrenciesFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetCurrencyProfile (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchCurrencies'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + this.options['currencies'] = { + 'timestamp': this.milliseconds (), + 'response': response, + }; + // + // { + // "e":"currency_profile", + // "ok":"ok", + // "data":{ + // "symbols":[ + // { + // "code":"GHS", + // "contract":true, + // "commodity":true, + // "fiat":false, + // "description":"CEX.IO doesn't provide cloud mining services anymore.", + // "precision":8, + // "scale":0, + // "minimumCurrencyAmount":"0.00000001", + // "minimalWithdrawalAmount":-1 + // }, + // { + // "code":"BTC", + // "contract":false, + // "commodity":false, + // "fiat":false, + // "description":"", + // "precision":8, + // "scale":0, + // "minimumCurrencyAmount":"0.00000001", + // "minimalWithdrawalAmount":0.002 + // }, + // { + // "code":"ETH", + // "contract":false, + // "commodity":false, + // "fiat":false, + // "description":"", + // "precision":8, + // "scale":2, + // "minimumCurrencyAmount":"0.00000100", + // "minimalWithdrawalAmount":0.01 + // } + // ], + // "pairs":[ + // { + // "symbol1":"BTC", + // "symbol2":"USD", + // "pricePrecision":1, + // "priceScale":"/1000000", + // "minLotSize":0.002, + // "minLotSizeS2":20 + // }, + // { + // "symbol1":"ETH", + // "symbol2":"USD", + // "pricePrecision":2, + // "priceScale":"/10000", + // "minLotSize":0.1, + // "minLotSizeS2":20 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', []); + const currencies = this.safeValue (data, 'symbols', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (currency, 'precision'); + const active = true; + result[code] = { + 'id': id, + 'code': code, + 'name': id, + 'active': active, + 'precision': precision, + 'fee': undefined, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'minimumCurrencyAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minimalWithdrawalAmount'), + 'max': undefined, + }, + }, + 'info': currency, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const currenciesResponse = await this.fetchCurrenciesFromCache (params); + const currenciesData = this.safeValue (currenciesResponse, 'data', {}); + const currencies = this.safeValue (currenciesData, 'symbols', []); + const currenciesById = this.indexBy (currencies, 'code'); + const pairs = this.safeValue (currenciesData, 'pairs', []); + const response = await this.publicGetCurrencyLimits (params); + // + // { + // "e":"currency_limits", + // "ok":"ok", + // "data": { + // "pairs":[ + // { + // "symbol1":"BTC", + // "symbol2":"USD", + // "minLotSize":0.002, + // "minLotSizeS2":20, + // "maxLotSize":30, + // "minPrice":"1500", + // "maxPrice":"35000" + // }, + // { + // "symbol1":"BCH", + // "symbol2":"EUR", + // "minLotSize":0.1, + // "minLotSizeS2":20, + // "maxLotSize":null, + // "minPrice":"25", + // "maxPrice":"8192" + // } + // ] + // } + // } + // + const result = []; + const markets = this.safeValue (response['data'], 'pairs'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const baseId = this.safeString (market, 'symbol1'); + const quoteId = this.safeString (market, 'symbol2'); + const id = baseId + '/' + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const baseCurrency = this.safeValue (currenciesById, baseId, {}); + const quoteCurrency = this.safeValue (currenciesById, quoteId, {}); + let pricePrecision = this.safeInteger (quoteCurrency, 'precision', 8); + for (let j = 0; j < pairs.length; j++) { + const pair = pairs[j]; + if ((pair['symbol1'] === baseId) && (pair['symbol2'] === quoteId)) { + // we might need to account for `priceScale` here + pricePrecision = this.safeInteger (pair, 'pricePrecision', pricePrecision); + } + } + const baseCcyPrecision = this.safeInteger (baseCurrency, 'precision', 8); + const baseCcyScale = this.safeInteger (baseCurrency, 'scale', 0); + const amountPrecision = baseCcyPrecision - baseCcyScale; + const precision = { + 'amount': amountPrecision, + 'price': pricePrecision, + }; + result.push ({ + 'id': id, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minLotSize'), + 'max': this.safeFloat (market, 'maxLotSize'), + }, + 'price': { + 'min': this.safeFloat (market, 'minPrice'), + 'max': this.safeFloat (market, 'maxPrice'), + }, + 'cost': { + 'min': this.safeFloat (market, 'minLotSizeS2'), + 'max': undefined, + }, + }, + 'active': undefined, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const ommited = [ 'username', 'timestamp' ]; + const balances = this.omit (response, ommited); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + // https://github.com/ccxt/ccxt/issues/5484 + account['used'] = this.safeFloat (balance, 'orders', 0.0); + const code = this.safeCurrencyCode (currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetOrderBookPair (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591403940, + // 0.024972, + // 0.024972, + // 0.024969, + // 0.024969, + // 0.49999900 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (since === undefined) { + since = this.milliseconds () - 86400000; // yesterday + } else { + if (this.options['fetchOHLCVWarning']) { + throw new ExchangeError (this.id + " fetchOHLCV warning: CEX can return historical candles for a certain date only, this might produce an empty or null reply. Set exchange.options['fetchOHLCVWarning'] = false or add ({ 'options': { 'fetchOHLCVWarning': false }}) to constructor params to suppress this warning message."); + } + } + let ymd = this.ymd (since); + ymd = ymd.split ('-'); + ymd = ymd.join (''); + const request = { + 'pair': market['id'], + 'yyyymmdd': ymd, + }; + try { + const response = await this.publicGetOhlcvHdYyyymmddPair (this.extend (request, params)); + // + // { + // "time":20200606, + // "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]", + // } + // + const key = 'data' + this.timeframes[timeframe]; + const data = this.safeString (response, key); + const ohlcvs = JSON.parse (data); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } catch (e) { + if (e instanceof NullResponse) { + return []; + } + } + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const volume = this.safeFloat (ticker, 'volume'); + const high = this.safeFloat (ticker, 'high'); + const low = this.safeFloat (ticker, 'low'); + const bid = this.safeFloat (ticker, 'bid'); + const ask = this.safeFloat (ticker, 'ask'); + const last = this.safeFloat (ticker, 'last'); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': volume, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const currencies = Object.keys (this.currencies); + const request = { + 'currencies': currencies.join ('/'), + }; + const response = await this.publicGetTickersCurrencies (this.extend (request, params)); + const tickers = response['data']; + const result = {}; + for (let t = 0; t < tickers.length; t++) { + const ticker = tickers[t]; + const symbol = ticker['pair'].replace (':', '/'); + const market = this.markets[symbol]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const ticker = await this.publicGetTickerPair (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const type = undefined; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTradeHistoryPair (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + // for market buy it requires the amount of quote currency to spend + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + amount = amount * price; + } + } + } + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + 'type': side, + 'amount': amount, + }; + if (type === 'limit') { + request['price'] = price; + } else { + request['order_type'] = type; + } + const response = await this.privatePostPlaceOrderPair (this.extend (request, params)); + // + // { + // "id": "12978363524", + // "time": 1586610022259, + // "type": "buy", + // "price": "0.033934", + // "amount": "0.10722802", + // "pending": "0.10722802", + // "complete": false + // } + // + const placedAmount = this.safeFloat (response, 'amount'); + const remaining = this.safeFloat (response, 'pending'); + const timestamp = this.safeValue (response, 'time'); + const complete = this.safeValue (response, 'complete'); + const status = complete ? 'closed' : 'open'; + let filled = undefined; + if ((placedAmount !== undefined) && (remaining !== undefined)) { + filled = Math.max (placedAmount - remaining, 0); + } + return { + 'id': this.safeString (response, 'id'), + 'info': response, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'type': type, + 'side': this.safeString (response, 'type'), + 'symbol': symbol, + 'status': status, + 'price': this.safeFloat (response, 'price'), + 'amount': placedAmount, + 'cost': undefined, + 'average': undefined, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + 'trades': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrder (order, market = undefined) { + // Depending on the call, 'time' can be a unix int, unix string or ISO string + // Yes, really + let timestamp = this.safeValue (order, 'time'); + if (typeof timestamp === 'string' && timestamp.indexOf ('T') >= 0) { + // ISO8601 string + timestamp = this.parse8601 (timestamp); + } else { + // either integer or string integer + timestamp = parseInt (timestamp); + } + let symbol = undefined; + if (market === undefined) { + const baseId = this.safeString (order, 'symbol1'); + const quoteId = this.safeString (order, 'symbol2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + if (symbol in this.markets) { + market = this.market (symbol); + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const price = this.safeFloat (order, 'price'); + let amount = this.safeFloat (order, 'amount'); + // sell orders can have a negative amount + // https://github.com/ccxt/ccxt/issues/5338 + if (amount !== undefined) { + amount = Math.abs (amount); + } + const remaining = this.safeFloat2 (order, 'pending', 'remains'); + const filled = amount - remaining; + let fee = undefined; + let cost = undefined; + if (market !== undefined) { + symbol = market['symbol']; + cost = this.safeFloat (order, 'ta:' + market['quote']); + if (cost === undefined) { + cost = this.safeFloat (order, 'tta:' + market['quote']); + } + const baseFee = 'fa:' + market['base']; + const baseTakerFee = 'tfa:' + market['base']; + const quoteFee = 'fa:' + market['quote']; + const quoteTakerFee = 'tfa:' + market['quote']; + let feeRate = this.safeFloat (order, 'tradingFeeMaker'); + if (!feeRate) { + feeRate = this.safeFloat (order, 'tradingFeeTaker', feeRate); + } + if (feeRate) { + feeRate /= 100.0; // convert to mathematically-correct percentage coefficients: 1.0 = 100% + } + if ((baseFee in order) || (baseTakerFee in order)) { + const baseFeeCost = this.safeFloat2 (order, baseFee, baseTakerFee); + fee = { + 'currency': market['base'], + 'rate': feeRate, + 'cost': baseFeeCost, + }; + } else if ((quoteFee in order) || (quoteTakerFee in order)) { + const quoteFeeCost = this.safeFloat2 (order, quoteFee, quoteTakerFee); + fee = { + 'currency': market['quote'], + 'rate': feeRate, + 'cost': quoteFeeCost, + }; + } + } + if (!cost) { + cost = price * filled; + } + const side = order['type']; + let trades = undefined; + const orderId = order['id']; + if ('vtx' in order) { + trades = []; + for (let i = 0; i < order['vtx'].length; i++) { + const item = order['vtx'][i]; + const tradeSide = this.safeString (item, 'type'); + if (tradeSide === 'cancel') { + // looks like this might represent the cancelled part of an order + // { id: '4426729543', + // type: 'cancel', + // time: '2017-09-22T00:24:30.476Z', + // user: 'up106404164', + // c: 'user:up106404164:a:BCH', + // d: 'order:4426728375:a:BCH', + // a: '0.09935956', + // amount: '0.09935956', + // balance: '0.42580261', + // symbol: 'BCH', + // order: '4426728375', + // buy: null, + // sell: null, + // pair: null, + // pos: null, + // cs: '0.42580261', + // ds: 0 } + continue; + } + const tradePrice = this.safeFloat (item, 'price'); + if (tradePrice === undefined) { + // this represents the order + // { + // "a": "0.47000000", + // "c": "user:up106404164:a:EUR", + // "d": "order:6065499239:a:EUR", + // "cs": "1432.93", + // "ds": "476.72", + // "id": "6065499249", + // "buy": null, + // "pos": null, + // "pair": null, + // "sell": null, + // "time": "2018-04-22T13:07:22.152Z", + // "type": "buy", + // "user": "up106404164", + // "order": "6065499239", + // "amount": "-715.97000000", + // "symbol": "EUR", + // "balance": "1432.93000000" } + continue; + } + // todo: deal with these + if (tradeSide === 'costsNothing') { + continue; + } + // -- + // if (side !== tradeSide) + // throw new Error (JSON.stringify (order, null, 2)); + // if (orderId !== item['order']) + // throw new Error (JSON.stringify (order, null, 2)); + // -- + // partial buy trade + // { + // "a": "0.01589885", + // "c": "user:up106404164:a:BTC", + // "d": "order:6065499239:a:BTC", + // "cs": "0.36300000", + // "ds": 0, + // "id": "6067991213", + // "buy": "6065499239", + // "pos": null, + // "pair": null, + // "sell": "6067991206", + // "time": "2018-04-22T23:09:11.773Z", + // "type": "buy", + // "user": "up106404164", + // "order": "6065499239", + // "price": 7146.5, + // "amount": "0.01589885", + // "symbol": "BTC", + // "balance": "0.36300000", + // "symbol2": "EUR", + // "fee_amount": "0.19" } + // -- + // trade with zero amount, but non-zero fee + // { + // "a": "0.00000000", + // "c": "user:up106404164:a:EUR", + // "d": "order:5840654423:a:EUR", + // "cs": 559744, + // "ds": 0, + // "id": "5840654429", + // "buy": "5807238573", + // "pos": null, + // "pair": null, + // "sell": "5840654423", + // "time": "2018-03-15T03:20:14.010Z", + // "type": "sell", + // "user": "up106404164", + // "order": "5840654423", + // "price": 730, + // "amount": "0.00000000", + // "symbol": "EUR", + // "balance": "5597.44000000", + // "symbol2": "BCH", + // "fee_amount": "0.01" } + // -- + // trade which should have an amount of exactly 0.002BTC + // { + // "a": "16.70000000", + // "c": "user:up106404164:a:GBP", + // "d": "order:9927386681:a:GBP", + // "cs": "86.90", + // "ds": 0, + // "id": "9927401610", + // "buy": "9927401601", + // "pos": null, + // "pair": null, + // "sell": "9927386681", + // "time": "2019-08-21T15:25:37.777Z", + // "type": "sell", + // "user": "up106404164", + // "order": "9927386681", + // "price": 8365, + // "amount": "16.70000000", + // "office": "UK", + // "symbol": "GBP", + // "balance": "86.90000000", + // "symbol2": "BTC", + // "fee_amount": "0.03" + // } + const tradeTimestamp = this.parse8601 (this.safeString (item, 'time')); + const tradeAmount = this.safeFloat (item, 'amount'); + const feeCost = this.safeFloat (item, 'fee_amount'); + let absTradeAmount = (tradeAmount < 0) ? -tradeAmount : tradeAmount; + let tradeCost = undefined; + if (tradeSide === 'sell') { + tradeCost = absTradeAmount; + absTradeAmount = this.sum (feeCost, tradeCost) / tradePrice; + } else { + tradeCost = absTradeAmount * tradePrice; + } + trades.push ({ + 'id': this.safeString (item, 'id'), + 'timestamp': tradeTimestamp, + 'datetime': this.iso8601 (tradeTimestamp), + 'order': orderId, + 'symbol': symbol, + 'price': tradePrice, + 'amount': absTradeAmount, + 'cost': tradeCost, + 'side': tradeSide, + 'fee': { + 'cost': feeCost, + 'currency': market['quote'], + }, + 'info': item, + 'type': undefined, + 'takerOrMaker': undefined, + }); + } + } + return { + 'id': orderId, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': (price === undefined) ? 'market' : 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'privatePostOpenOrders'; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + method += 'Pair'; + } + const orders = await this[method] (this.extend (request, params)); + for (let i = 0; i < orders.length; i++) { + orders[i] = this.extend (orders[i], { 'status': 'open' }); + } + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostArchivedOrdersPair'; + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + const market = this.market (symbol); + const request = { 'pair': market['id'] }; + const response = await this[method] (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id.toString (), + }; + const response = await this.privatePostGetOrderTx (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + // + // { + // "id": "5442731603", + // "type": "sell", + // "time": 1516132358071, + // "lastTxTime": 1516132378452, + // "lastTx": "5442734452", + // "pos": null, + // "user": "up106404164", + // "status": "d", + // "symbol1": "ETH", + // "symbol2": "EUR", + // "amount": "0.50000000", + // "kind": "api", + // "price": "923.3386", + // "tfacf": "1", + // "fa:EUR": "0.55", + // "ta:EUR": "369.77", + // "remains": "0.00000000", + // "tfa:EUR": "0.22", + // "tta:EUR": "91.95", + // "a:ETH:cds": "0.50000000", + // "a:EUR:cds": "461.72", + // "f:EUR:cds": "0.77", + // "tradingFeeMaker": "0.15", + // "tradingFeeTaker": "0.23", + // "tradingFeeStrategy": "userVolumeAmount", + // "tradingFeeUserVolumeAmount": "2896912572", + // "orderId": "5442731603", + // "next": false, + // "vtx": [ + // { + // "id": "5442734452", + // "type": "sell", + // "time": "2018-01-16T19:52:58.452Z", + // "user": "up106404164", + // "c": "user:up106404164:a:EUR", + // "d": "order:5442731603:a:EUR", + // "a": "104.53000000", + // "amount": "104.53000000", + // "balance": "932.71000000", + // "symbol": "EUR", + // "order": "5442731603", + // "buy": "5442734443", + // "sell": "5442731603", + // "pair": null, + // "pos": null, + // "office": null, + // "cs": "932.71", + // "ds": 0, + // "price": 923.3386, + // "symbol2": "ETH", + // "fee_amount": "0.16" + // }, + // { + // "id": "5442731609", + // "type": "sell", + // "time": "2018-01-16T19:52:38.071Z", + // "user": "up106404164", + // "c": "user:up106404164:a:EUR", + // "d": "order:5442731603:a:EUR", + // "a": "91.73000000", + // "amount": "91.73000000", + // "balance": "563.49000000", + // "symbol": "EUR", + // "order": "5442731603", + // "buy": "5442618127", + // "sell": "5442731603", + // "pair": null, + // "pos": null, + // "office": null, + // "cs": "563.49", + // "ds": 0, + // "price": 924.0092, + // "symbol2": "ETH", + // "fee_amount": "0.22" + // }, + // { + // "id": "5442731604", + // "type": "sell", + // "time": "2018-01-16T19:52:38.071Z", + // "user": "up106404164", + // "c": "order:5442731603:a:ETH", + // "d": "user:up106404164:a:ETH", + // "a": "0.50000000", + // "amount": "-0.50000000", + // "balance": "15.80995000", + // "symbol": "ETH", + // "order": "5442731603", + // "buy": null, + // "sell": null, + // "pair": null, + // "pos": null, + // "office": null, + // "cs": "0.50000000", + // "ds": "15.80995000" + // } + // ] + // } + // + return this.parseOrder (data); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'limit': limit, + 'pair': market['id'], + 'dateFrom': since, + }; + const response = await this.privatePostArchivedOrdersPair (this.extend (request, params)); + const results = []; + for (let i = 0; i < response.length; i++) { + // cancelled (unfilled): + // { id: '4005785516', + // type: 'sell', + // time: '2017-07-18T19:08:34.223Z', + // lastTxTime: '2017-07-18T19:08:34.396Z', + // lastTx: '4005785522', + // pos: null, + // status: 'c', + // symbol1: 'ETH', + // symbol2: 'GBP', + // amount: '0.20000000', + // price: '200.5625', + // remains: '0.20000000', + // 'a:ETH:cds': '0.20000000', + // tradingFeeMaker: '0', + // tradingFeeTaker: '0.16', + // tradingFeeUserVolumeAmount: '10155061217', + // orderId: '4005785516' } + // -- + // cancelled (partially filled buy): + // { id: '4084911657', + // type: 'buy', + // time: '2017-08-05T03:18:39.596Z', + // lastTxTime: '2019-03-19T17:37:46.404Z', + // lastTx: '8459265833', + // pos: null, + // status: 'cd', + // symbol1: 'BTC', + // symbol2: 'GBP', + // amount: '0.05000000', + // price: '2241.4692', + // tfacf: '1', + // remains: '0.03910535', + // 'tfa:GBP': '0.04', + // 'tta:GBP': '24.39', + // 'a:BTC:cds': '0.01089465', + // 'a:GBP:cds': '112.26', + // 'f:GBP:cds': '0.04', + // tradingFeeMaker: '0', + // tradingFeeTaker: '0.16', + // tradingFeeUserVolumeAmount: '13336396963', + // orderId: '4084911657' } + // -- + // cancelled (partially filled sell): + // { id: '4426728375', + // type: 'sell', + // time: '2017-09-22T00:24:20.126Z', + // lastTxTime: '2017-09-22T00:24:30.476Z', + // lastTx: '4426729543', + // pos: null, + // status: 'cd', + // symbol1: 'BCH', + // symbol2: 'BTC', + // amount: '0.10000000', + // price: '0.11757182', + // tfacf: '1', + // remains: '0.09935956', + // 'tfa:BTC': '0.00000014', + // 'tta:BTC': '0.00007537', + // 'a:BCH:cds': '0.10000000', + // 'a:BTC:cds': '0.00007537', + // 'f:BTC:cds': '0.00000014', + // tradingFeeMaker: '0', + // tradingFeeTaker: '0.18', + // tradingFeeUserVolumeAmount: '3466715450', + // orderId: '4426728375' } + // -- + // filled: + // { id: '5342275378', + // type: 'sell', + // time: '2018-01-04T00:28:12.992Z', + // lastTxTime: '2018-01-04T00:28:12.992Z', + // lastTx: '5342275393', + // pos: null, + // status: 'd', + // symbol1: 'BCH', + // symbol2: 'BTC', + // amount: '0.10000000', + // kind: 'api', + // price: '0.17', + // remains: '0.00000000', + // 'tfa:BTC': '0.00003902', + // 'tta:BTC': '0.01699999', + // 'a:BCH:cds': '0.10000000', + // 'a:BTC:cds': '0.01699999', + // 'f:BTC:cds': '0.00003902', + // tradingFeeMaker: '0.15', + // tradingFeeTaker: '0.23', + // tradingFeeUserVolumeAmount: '1525951128', + // orderId: '5342275378' } + // -- + // market order (buy): + // { "id": "6281946200", + // "pos": null, + // "time": "2018-05-23T11:55:43.467Z", + // "type": "buy", + // "amount": "0.00000000", + // "lastTx": "6281946210", + // "status": "d", + // "amount2": "20.00", + // "orderId": "6281946200", + // "remains": "0.00000000", + // "symbol1": "ETH", + // "symbol2": "EUR", + // "tfa:EUR": "0.05", + // "tta:EUR": "19.94", + // "a:ETH:cds": "0.03764100", + // "a:EUR:cds": "20.00", + // "f:EUR:cds": "0.05", + // "lastTxTime": "2018-05-23T11:55:43.467Z", + // "tradingFeeTaker": "0.25", + // "tradingFeeUserVolumeAmount": "55998097" } + // -- + // market order (sell): + // { "id": "6282200948", + // "pos": null, + // "time": "2018-05-23T12:42:58.315Z", + // "type": "sell", + // "amount": "-0.05000000", + // "lastTx": "6282200958", + // "status": "d", + // "orderId": "6282200948", + // "remains": "0.00000000", + // "symbol1": "ETH", + // "symbol2": "EUR", + // "tfa:EUR": "0.07", + // "tta:EUR": "26.49", + // "a:ETH:cds": "0.05000000", + // "a:EUR:cds": "26.49", + // "f:EUR:cds": "0.07", + // "lastTxTime": "2018-05-23T12:42:58.315Z", + // "tradingFeeTaker": "0.25", + // "tradingFeeUserVolumeAmount": "56294576" } + const order = response[i]; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const baseId = this.safeString (order, 'symbol1'); + const quoteId = this.safeString (order, 'symbol2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const side = this.safeString (order, 'type'); + const baseAmount = this.safeFloat (order, 'a:' + baseId + ':cds'); + const quoteAmount = this.safeFloat (order, 'a:' + quoteId + ':cds'); + const fee = this.safeFloat (order, 'f:' + quoteId + ':cds'); + const amount = this.safeFloat (order, 'amount'); + const price = this.safeFloat (order, 'price'); + const remaining = this.safeFloat (order, 'remains'); + const filled = amount - remaining; + let orderAmount = undefined; + let cost = undefined; + let average = undefined; + let type = undefined; + if (!price) { + type = 'market'; + orderAmount = baseAmount; + cost = quoteAmount; + average = orderAmount / cost; + } else { + const ta = this.safeFloat (order, 'ta:' + quoteId, 0); + const tta = this.safeFloat (order, 'tta:' + quoteId, 0); + const fa = this.safeFloat (order, 'fa:' + quoteId, 0); + const tfa = this.safeFloat (order, 'tfa:' + quoteId, 0); + if (side === 'sell') { + cost = this.sum (this.sum (ta, tta), this.sum (fa, tfa)); + } else { + cost = this.sum (ta, tta) - this.sum (fa, tfa); + } + type = 'limit'; + orderAmount = amount; + average = cost / filled; + } + const time = this.safeString (order, 'time'); + const lastTxTime = this.safeString (order, 'lastTxTime'); + const timestamp = this.parse8601 (time); + results.push ({ + 'id': this.safeString (order, 'id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastUpdated': this.parse8601 (lastTxTime), + 'status': status, + 'symbol': symbol, + 'side': side, + 'price': price, + 'amount': orderAmount, + 'average': average, + 'type': type, + 'filled': filled, + 'cost': cost, + 'remaining': remaining, + 'fee': { + 'cost': fee, + 'currency': quote, + }, + 'info': order, + }); + } + return results; + } + + parseOrderStatus (status) { + return this.safeString (this.options['order']['status'], status, status); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (amount === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires a amount argument'); + } + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires a price argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + // see: https://cex.io/rest-api#/definitions/CancelReplaceOrderRequest + const request = { + 'pair': market['id'], + 'type': side, + 'amount': amount, + 'price': price, + 'order_id': id, + }; + const response = await this.privatePostCancelReplaceOrderPair (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchDepositAddress (code, params = {}) { + if (code === 'XRP' || code === 'XLM') { + // https://github.com/ccxt/ccxt/pull/2327#issuecomment-375204856 + throw new NotSupported (this.id + ' fetchDepositAddress does not support XRP and XLM addresses yet (awaiting docs from CEX.io)'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostGetAddress (this.extend (request, params)); + const address = this.safeString (response, 'data'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.json (this.extend ({ + 'key': this.apiKey, + 'signature': signature.toUpperCase (), + 'nonce': nonce, + }, query)); + headers = { + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (Array.isArray (response)) { + return response; // public endpoints may return []-arrays + } + if (body === 'true') { + return; + } + if (response === undefined) { + throw new NullResponse (this.id + ' returned ' + this.json (response)); + } + if ('e' in response) { + if ('ok' in response) { + if (response['ok'] === 'ok') { + return; + } + } + } + if ('error' in response) { + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],59:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const foxbit = require ('./foxbit.js'); + +// --------------------------------------------------------------------------- + +module.exports = class chilebit extends foxbit { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'chilebit', + 'name': 'ChileBit', + 'countries': [ 'CL' ], + 'has': { + 'CORS': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27991414-1298f0d8-647f-11e7-9c40-d56409266336.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://chilebit.net', + 'doc': 'https://blinktrade.com/docs', + }, + 'options': { + 'brokerId': '9', // https://blinktrade.com/docs/#brokers + }, + }); + } +}; + +},{"./foxbit.js":84}],60:[function(require,module,exports){ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, RateLimitExceeded } = require ('./base/errors'); + +// ---------------------------------------------------------------------------- + +module.exports = class coinbase extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinbase', + 'name': 'Coinbase', + 'countries': [ 'US' ], + 'rateLimit': 400, // 10k calls per hour + 'version': 'v2', + 'userAgent': this.userAgents['chrome'], + 'headers': { + 'CB-VERSION': '2018-05-30', + }, + 'has': { + 'CORS': true, + 'cancelOrder': false, + 'createDepositAddress': true, + 'createOrder': false, + 'deposit': false, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': false, + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': false, + 'fetchL2OrderBook': false, + 'fetchLedger': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTime': true, + 'fetchBidsAsks': false, + 'fetchTrades': false, + 'withdraw': false, + 'fetchTransactions': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchMySells': true, + 'fetchMyBuys': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg', + 'api': 'https://api.coinbase.com', + 'www': 'https://www.coinbase.com', + 'doc': 'https://developers.coinbase.com/api/v2', + 'fees': 'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees', + 'referral': 'https://www.coinbase.com/join/58cbe25a355148797479dbd2', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'time', + 'exchange-rates', + 'users/{user_id}', + 'prices/{symbol}/buy', + 'prices/{symbol}/sell', + 'prices/{symbol}/spot', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/{account_id}', + 'accounts/{account_id}/addresses', + 'accounts/{account_id}/addresses/{address_id}', + 'accounts/{account_id}/addresses/{address_id}/transactions', + 'accounts/{account_id}/transactions', + 'accounts/{account_id}/transactions/{transaction_id}', + 'accounts/{account_id}/buys', + 'accounts/{account_id}/buys/{buy_id}', + 'accounts/{account_id}/sells', + 'accounts/{account_id}/sells/{sell_id}', + 'accounts/{account_id}/deposits', + 'accounts/{account_id}/deposits/{deposit_id}', + 'accounts/{account_id}/withdrawals', + 'accounts/{account_id}/withdrawals/{withdrawal_id}', + 'payment-methods', + 'payment-methods/{payment_method_id}', + 'user', + 'user/auth', + ], + 'post': [ + 'accounts', + 'accounts/{account_id}/primary', + 'accounts/{account_id}/addresses', + 'accounts/{account_id}/transactions', + 'accounts/{account_id}/transactions/{transaction_id}/complete', + 'accounts/{account_id}/transactions/{transaction_id}/resend', + 'accounts/{account_id}/buys', + 'accounts/{account_id}/buys/{buy_id}/commit', + 'accounts/{account_id}/sells', + 'accounts/{account_id}/sells/{sell_id}/commit', + 'accounts/{account_id}/deposists', + 'accounts/{account_id}/deposists/{deposit_id}/commit', + 'accounts/{account_id}/withdrawals', + 'accounts/{account_id}/withdrawals/{withdrawal_id}/commit', + ], + 'put': [ + 'accounts/{account_id}', + 'user', + ], + 'delete': [ + 'accounts/{id}', + 'accounts/{account_id}/transactions/{transaction_id}', + ], + }, + }, + 'exceptions': { + 'two_factor_required': AuthenticationError, // 402 When sending money over 2fa limit + 'param_required': ExchangeError, // 400 Missing parameter + 'validation_error': ExchangeError, // 400 Unable to validate POST/PUT + 'invalid_request': ExchangeError, // 400 Invalid request + 'personal_details_required': AuthenticationError, // 400 User’s personal detail required to complete this request + 'identity_verification_required': AuthenticationError, // 400 Identity verification is required to complete this request + 'jumio_verification_required': AuthenticationError, // 400 Document verification is required to complete this request + 'jumio_face_match_verification_required': AuthenticationError, // 400 Document verification including face match is required to complete this request + 'unverified_email': AuthenticationError, // 400 User has not verified their email + 'authentication_error': AuthenticationError, // 401 Invalid auth (generic) + 'invalid_token': AuthenticationError, // 401 Invalid Oauth token + 'revoked_token': AuthenticationError, // 401 Revoked Oauth token + 'expired_token': AuthenticationError, // 401 Expired Oauth token + 'invalid_scope': AuthenticationError, // 403 User hasn’t authenticated necessary scope + 'not_found': ExchangeError, // 404 Resource not found + 'rate_limit_exceeded': RateLimitExceeded, // 429 Rate limit exceeded + 'internal_server_error': ExchangeError, // 500 Internal server error + }, + 'commonCurrencies': { + 'CGLD': 'CELO', + }, + 'options': { + 'fetchCurrencies': { + 'expires': 5000, + }, + 'accounts': [ + 'wallet', + 'fiat', + // 'vault', + ], + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "data": { + // "epoch": 1589295679, + // "iso": "2020-05-12T15:01:19Z" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.safeTimestamp (data, 'epoch'); + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccounts (params); + // + // { + // "id": "XLM", + // "name": "XLM Wallet", + // "primary": false, + // "type": "wallet", + // "currency": { + // "code": "XLM", + // "name": "Stellar Lumens", + // "color": "#000000", + // "sort_index": 127, + // "exponent": 7, + // "type": "crypto", + // "address_regex": "^G[A-Z2-7]{55}$", + // "asset_id": "13b83335-5ede-595b-821e-5bcdfa80560f", + // "destination_tag_name": "XLM Memo ID", + // "destination_tag_regex": "^[ -~]{1,28}$" + // }, + // "balance": { + // "amount": "0.0000000", + // "currency": "XLM" + // }, + // "created_at": null, + // "updated_at": null, + // "resource": "account", + // "resource_path": "/v2/accounts/XLM", + // "allow_deposits": true, + // "allow_withdrawals": true + // } + // + const data = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const currency = this.safeValue (account, 'currency', {}); + const currencyId = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': this.safeString (account, 'id'), + 'type': this.safeString (account, 'type'), + 'code': code, + 'info': account, + }); + } + return result; + } + + async createDepositAddress (code, params = {}) { + let accountId = this.safeString (params, 'account_id'); + params = this.omit (params, 'account_id'); + if (accountId === undefined) { + await this.loadAccounts (); + for (let i = 0; i < this.accounts.length; i++) { + const account = this.accounts[i]; + if (account['code'] === code && account['type'] === 'wallet') { + accountId = account['id']; + break; + } + } + } + if (accountId === undefined) { + throw new ExchangeError (this.id + ' createDepositAddress could not find the account with matching currency code, specify an `account_id` extra param'); + } + const request = { + 'account_id': accountId, + }; + const response = await this.privatePostAccountsAccountIdAddresses (this.extend (request, params)); + // + // { + // "data": { + // "id": "05b1ebbf-9438-5dd4-b297-2ddedc98d0e4", + // "address": "coinbasebase", + // "address_info": { + // "address": "coinbasebase", + // "destination_tag": "287594668" + // }, + // "name": null, + // "created_at": "2019-07-01T14:39:29Z", + // "updated_at": "2019-07-01T14:39:29Z", + // "network": "eosio", + // "uri_scheme": "eosio", + // "resource": "address", + // "resource_path": "/v2/accounts/14cfc769-e852-52f3-b831-711c104d194c/addresses/05b1ebbf-9438-5dd4-b297-2ddedc98d0e4", + // "warnings": [ + // { + // "title": "Only send EOS (EOS) to this address", + // "details": "Sending any other cryptocurrency will result in permanent loss.", + // "image_url": "https://dynamic-assets.coinbase.com/deaca3d47b10ed4a91a872e9618706eec34081127762d88f2476ac8e99ada4b48525a9565cf2206d18c04053f278f693434af4d4629ca084a9d01b7a286a7e26/asset_icons/1f8489bb280fb0a0fd643c1161312ba49655040e9aaaced5f9ad3eeaf868eadc.png" + // }, + // { + // "title": "Both an address and EOS memo are required to receive EOS", + // "details": "If you send funds without an EOS memo or with an incorrect EOS memo, your funds cannot be credited to your account.", + // "image_url": "https://www.coinbase.com/assets/receive-warning-2f3269d83547a7748fb39d6e0c1c393aee26669bfea6b9f12718094a1abff155.png" + // } + // ], + // "warning_title": "Only send EOS (EOS) to this address", + // "warning_details": "Sending any other cryptocurrency will result in permanent loss.", + // "destination_tag": "287594668", + // "deposit_uri": "eosio:coinbasebase?dt=287594668", + // "callback_url": null + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const tag = this.safeString (data, 'destination_tag'); + const address = this.safeString (data, 'address'); + return { + 'currency': code, + 'tag': tag, + 'address': address, + 'info': response, + }; + } + + async fetchMySells (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // they don't have an endpoint for all historical trades + const request = await this.prepareAccountRequest (limit, params); + await this.loadMarkets (); + const query = this.omit (params, [ 'account_id', 'accountId' ]); + const sells = await this.privateGetAccountsAccountIdSells (this.extend (request, query)); + return this.parseTrades (sells['data'], undefined, since, limit); + } + + async fetchMyBuys (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // they don't have an endpoint for all historical trades + const request = await this.prepareAccountRequest (limit, params); + await this.loadMarkets (); + const query = this.omit (params, [ 'account_id', 'accountId' ]); + const buys = await this.privateGetAccountsAccountIdBuys (this.extend (request, query)); + return this.parseTrades (buys['data'], undefined, since, limit); + } + + async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) { + const request = await this.prepareAccountRequestWithCurrencyCode (code, limit, params); + await this.loadMarkets (); + const query = this.omit (params, [ 'account_id', 'accountId' ]); + const response = await this[method] (this.extend (request, query)); + return this.parseTransactions (response['data'], undefined, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + // fiat only, for crypto transactions use fetchLedger + return await this.fetchTransactionsWithMethod ('privateGetAccountsAccountIdWithdrawals', code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + // fiat only, for crypto transactions use fetchLedger + return await this.fetchTransactionsWithMethod ('privateGetAccountsAccountIdDeposits', code, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + 'created': 'pending', + 'completed': 'ok', + 'canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, market = undefined) { + // + // fiat deposit + // + // { + // "id": "f34c19f3-b730-5e3d-9f72", + // "status": "completed", + // "payment_method": { + // "id": "a022b31d-f9c7-5043-98f2", + // "resource": "payment_method", + // "resource_path": "/v2/payment-methods/a022b31d-f9c7-5043-98f2" + // }, + // "transaction": { + // "id": "04ed4113-3732-5b0c-af86-b1d2146977d0", + // "resource": "transaction", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86" + // }, + // "user_reference": "2VTYTH", + // "created_at": "2017-02-09T07:01:18Z", + // "updated_at": "2017-02-09T07:01:26Z", + // "resource": "deposit", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72", + // "committed": true, + // "payout_at": "2017-02-12T07:01:17Z", + // "instant": false, + // "fee": { "amount": "0.00", "currency": "EUR" }, + // "amount": { "amount": "114.02", "currency": "EUR" }, + // "subtotal": { "amount": "114.02", "currency": "EUR" }, + // "hold_until": null, + // "hold_days": 0, + // "hold_business_days": 0, + // "next_step": null + // } + // + // fiat_withdrawal + // + // { + // "id": "cfcc3b4a-eeb6-5e8c-8058", + // "status": "completed", + // "payment_method": { + // "id": "8b94cfa4-f7fd-5a12-a76a", + // "resource": "payment_method", + // "resource_path": "/v2/payment-methods/8b94cfa4-f7fd-5a12-a76a" + // }, + // "transaction": { + // "id": "fcc2550b-5104-5f83-a444", + // "resource": "transaction", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/fcc2550b-5104-5f83-a444" + // }, + // "user_reference": "MEUGK", + // "created_at": "2018-07-26T08:55:12Z", + // "updated_at": "2018-07-26T08:58:18Z", + // "resource": "withdrawal", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/cfcc3b4a-eeb6-5e8c-8058", + // "committed": true, + // "payout_at": "2018-07-31T08:55:12Z", + // "instant": false, + // "fee": { "amount": "0.15", "currency": "EUR" }, + // "amount": { "amount": "13130.69", "currency": "EUR" }, + // "subtotal": { "amount": "13130.84", "currency": "EUR" }, + // "idem": "e549dee5-63ed-4e79-8a96", + // "next_step": null + // } + // + const subtotalObject = this.safeValue (transaction, 'subtotal', {}); + const feeObject = this.safeValue (transaction, 'fee', {}); + const id = this.safeString (transaction, 'id'); + const timestamp = this.parse8601 (this.safeValue (transaction, 'created_at')); + const updated = this.parse8601 (this.safeValue (transaction, 'updated_at')); + const type = this.safeString (transaction, 'resource'); + const amount = this.safeFloat (subtotalObject, 'amount'); + const currencyId = this.safeString (subtotalObject, 'currency'); + const currency = this.safeCurrencyCode (currencyId); + const feeCost = this.safeFloat (feeObject, 'amount'); + const feeCurrencyId = this.safeString (feeObject, 'currency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + let status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + if (status === undefined) { + const committed = this.safeValue (transaction, 'committed'); + status = committed ? 'ok' : 'pending'; + } + return { + 'info': transaction, + 'id': id, + 'txid': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': undefined, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': currency, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTrade (trade, market = undefined) { + // + // { + // "id": "67e0eaec-07d7-54c4-a72c-2e92826897df", + // "status": "completed", + // "payment_method": { + // "id": "83562370-3e5c-51db-87da-752af5ab9559", + // "resource": "payment_method", + // "resource_path": "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559" + // }, + // "transaction": { + // "id": "441b9494-b3f0-5b98-b9b0-4d82c21c252a", + // "resource": "transaction", + // "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a" + // }, + // "amount": { "amount": "1.00000000", "currency": "BTC" }, + // "total": { "amount": "10.25", "currency": "USD" }, + // "subtotal": { "amount": "10.10", "currency": "USD" }, + // "created_at": "2015-01-31T20:49:02Z", + // "updated_at": "2015-02-11T16:54:02-08:00", + // "resource": "buy", + // "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/buys/67e0eaec-07d7-54c4-a72c-2e92826897df", + // "committed": true, + // "instant": false, + // "fee": { "amount": "0.15", "currency": "USD" }, + // "payout_at": "2015-02-18T16:54:00-08:00" + // } + // + let symbol = undefined; + const totalObject = this.safeValue (trade, 'total', {}); + const amountObject = this.safeValue (trade, 'amount', {}); + const subtotalObject = this.safeValue (trade, 'subtotal', {}); + const feeObject = this.safeValue (trade, 'fee', {}); + const id = this.safeString (trade, 'id'); + const timestamp = this.parse8601 (this.safeValue (trade, 'created_at')); + if (market === undefined) { + const baseId = this.safeString (amountObject, 'currency'); + const quoteId = this.safeString (totalObject, 'currency'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + const orderId = undefined; + const side = this.safeString (trade, 'resource'); + const type = undefined; + const cost = this.safeFloat (subtotalObject, 'amount'); + const amount = this.safeFloat (amountObject, 'amount'); + let price = undefined; + if (cost !== undefined) { + if ((amount !== undefined) && (amount > 0)) { + price = cost / amount; + } + } + const feeCost = this.safeFloat (feeObject, 'amount'); + const feeCurrencyId = this.safeString (feeObject, 'currency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + const currencies = this.safeValue (response, 'currencies', {}); + const exchangeRates = this.safeValue (response, 'exchangeRates', {}); + const data = this.safeValue (currencies, 'data', []); + const dataById = this.indexBy (data, 'id'); + const rates = this.safeValue (this.safeValue (exchangeRates, 'data', {}), 'rates', {}); + const baseIds = Object.keys (rates); + const result = []; + for (let i = 0; i < baseIds.length; i++) { + const baseId = baseIds[i]; + const base = this.safeCurrencyCode (baseId); + const type = (baseId in dataById) ? 'fiat' : 'crypto'; + // https://github.com/ccxt/ccxt/issues/6066 + if (type === 'crypto') { + for (let j = 0; j < data.length; j++) { + const quoteCurrency = data[j]; + const quoteId = this.safeString (quoteCurrency, 'id'); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = baseId + '-' + quoteId; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'info': quoteCurrency, + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (quoteCurrency, 'min_size'), + 'max': undefined, + }, + }, + }); + } + } + } + return result; + } + + async fetchCurrenciesFromCache (params = {}) { + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const currencies = await this.publicGetCurrencies (params); + const exchangeRates = await this.publicGetExchangeRates (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'currencies': currencies, + 'exchangeRates': exchangeRates, + 'timestamp': now, + }); + } + return this.safeValue (this.options, 'fetchCurrencies', {}); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + const currencies = this.safeValue (response, 'currencies', {}); + // + // { + // "data":[ + // {"id":"AED","name":"United Arab Emirates Dirham","min_size":"0.01000000"}, + // {"id":"AFN","name":"Afghan Afghani","min_size":"0.01000000"}, + // {"id":"ALL","name":"Albanian Lek","min_size":"0.01000000"}, + // {"id":"AMD","name":"Armenian Dram","min_size":"0.01000000"}, + // {"id":"ANG","name":"Netherlands Antillean Gulden","min_size":"0.01000000"}, + // // ... + // ], + // } + // + const exchangeRates = this.safeValue (response, 'exchangeRates', {}); + // + // { + // "data":{ + // "currency":"USD", + // "rates":{ + // "AED":"3.67", + // "AFN":"78.21", + // "ALL":"110.42", + // "AMD":"474.18", + // "ANG":"1.75", + // // ... + // }, + // } + // } + // + const data = this.safeValue (currencies, 'data', []); + const dataById = this.indexBy (data, 'id'); + const rates = this.safeValue (this.safeValue (exchangeRates, 'data', {}), 'rates', {}); + const keys = Object.keys (rates); + const result = {}; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const type = (key in dataById) ? 'fiat' : 'crypto'; + const currency = this.safeValue (dataById, key, {}); + const id = this.safeString (currency, 'id', key); + const name = this.safeString (currency, 'name'); + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, // the original payload + 'type': type, + 'name': name, + 'active': true, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'min_size'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const timestamp = this.seconds (); + const market = this.market (symbol); + const request = this.extend ({ + 'symbol': market['id'], + }, params); + const buy = await this.publicGetPricesSymbolBuy (request); + const sell = await this.publicGetPricesSymbolSell (request); + const spot = await this.publicGetPricesSymbolSpot (request); + const ask = this.safeFloat (buy['data'], 'amount'); + const bid = this.safeFloat (sell['data'], 'amount'); + const last = this.safeFloat (spot['data'], 'amount'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'bid': bid, + 'ask': ask, + 'last': last, + 'high': undefined, + 'low': undefined, + 'bidVolume': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': { + 'buy': buy, + 'sell': sell, + 'spot': spot, + }, + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + const balances = this.safeValue (response, 'data'); + const accounts = this.safeValue (params, 'type', this.options['accounts']); + const result = { 'info': response }; + for (let b = 0; b < balances.length; b++) { + const balance = balances[b]; + if (this.inArray (balance['type'], accounts)) { + const currencyId = this.safeString (balance['balance'], 'currency'); + const code = this.safeCurrencyCode (currencyId); + const total = this.safeFloat (balance['balance'], 'amount'); + const free = total; + const used = undefined; + if (code in result) { + result[code]['free'] = this.sum (result[code]['free'], total); + result[code]['total'] = this.sum (result[code]['total'], total); + } else { + const account = { + 'free': free, + 'used': used, + 'total': total, + }; + result[code] = account; + } + } + } + return this.parseBalance (result); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = await this.prepareAccountRequestWithCurrencyCode (code, limit, params); + const query = this.omit (params, ['account_id', 'accountId']); + // for pagination use parameter 'starting_after' + // the value for the next page can be obtained from the result of the previous call in the 'pagination' field + // eg: instance.last_json_response.pagination.next_starting_after + const response = await this.privateGetAccountsAccountIdTransactions (this.extend (request, query)); + return this.parseLedger (response['data'], currency, since, limit); + } + + parseLedgerEntryStatus (status) { + const types = { + 'completed': 'ok', + }; + return this.safeString (types, status, status); + } + + parseLedgerEntryType (type) { + const types = { + 'buy': 'trade', + 'sell': 'trade', + 'fiat_deposit': 'transaction', + 'fiat_withdrawal': 'transaction', + 'exchange_deposit': 'transaction', // fiat withdrawal (from coinbase to coinbasepro) + 'exchange_withdrawal': 'transaction', // fiat deposit (to coinbase from coinbasepro) + 'send': 'transaction', // crypto deposit OR withdrawal + 'pro_deposit': 'transaction', // crypto withdrawal (from coinbase to coinbasepro) + 'pro_withdrawal': 'transaction', // crypto deposit (to coinbase from coinbasepro) + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // crypto deposit transaction + // + // { + // id: '34e4816b-4c8c-5323-a01c-35a9fa26e490', + // type: 'send', + // status: 'completed', + // amount: { amount: '28.31976528', currency: 'BCH' }, + // native_amount: { amount: '2799.65', currency: 'GBP' }, + // description: null, + // created_at: '2019-02-28T12:35:20Z', + // updated_at: '2019-02-28T12:43:24Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/34e4816b-4c8c-5323-a01c-35a9fa26e490', + // instant_exchange: false, + // network: { + // status: 'confirmed', + // hash: '56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701', + // transaction_url: 'https://bch.btc.com/56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701' + // }, + // from: { resource: 'bitcoin_cash_network', currency: 'BCH' }, + // details: { title: 'Received Bitcoin Cash', subtitle: 'From Bitcoin Cash address' } + // } + // + // crypto withdrawal transaction + // + // { + // id: '459aad99-2c41-5698-ac71-b6b81a05196c', + // type: 'send', + // status: 'completed', + // amount: { amount: '-0.36775642', currency: 'BTC' }, + // native_amount: { amount: '-1111.65', currency: 'GBP' }, + // description: null, + // created_at: '2019-03-20T08:37:07Z', + // updated_at: '2019-03-20T08:49:33Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/459aad99-2c41-5698-ac71-b6b81a05196c', + // instant_exchange: false, + // network: { + // status: 'confirmed', + // hash: '2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b', + // transaction_url: 'https://blockchain.info/tx/2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b', + // transaction_fee: { amount: '0.00000000', currency: 'BTC' }, + // transaction_amount: { amount: '0.36775642', currency: 'BTC' }, + // confirmations: 15682 + // }, + // to: { + // resource: 'bitcoin_address', + // address: '1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX', + // currency: 'BTC', + // address_info: { address: '1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX' } + // }, + // idem: 'da0a2f14-a2af-4c5a-a37e-d4484caf582bsend', + // application: { + // id: '5756ab6e-836b-553b-8950-5e389451225d', + // resource: 'application', + // resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d' + // }, + // details: { title: 'Sent Bitcoin', subtitle: 'To Bitcoin address' } + // } + // + // withdrawal transaction from coinbase to coinbasepro + // + // { + // id: '5b1b9fb8-5007-5393-b923-02903b973fdc', + // type: 'pro_deposit', + // status: 'completed', + // amount: { amount: '-0.00001111', currency: 'BCH' }, + // native_amount: { amount: '0.00', currency: 'GBP' }, + // description: null, + // created_at: '2019-02-28T13:31:58Z', + // updated_at: '2019-02-28T13:31:58Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/5b1b9fb8-5007-5393-b923-02903b973fdc', + // instant_exchange: false, + // application: { + // id: '5756ab6e-836b-553b-8950-5e389451225d', + // resource: 'application', + // resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d' + // }, + // details: { title: 'Transferred Bitcoin Cash', subtitle: 'To Coinbase Pro' } + // } + // + // withdrawal transaction from coinbase to gdax + // + // { + // id: 'badb7313-a9d3-5c07-abd0-00f8b44199b1', + // type: 'exchange_deposit', + // status: 'completed', + // amount: { amount: '-0.43704149', currency: 'BCH' }, + // native_amount: { amount: '-51.90', currency: 'GBP' }, + // description: null, + // created_at: '2019-03-19T10:30:40Z', + // updated_at: '2019-03-19T10:30:40Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/badb7313-a9d3-5c07-abd0-00f8b44199b1', + // instant_exchange: false, + // details: { title: 'Transferred Bitcoin Cash', subtitle: 'To GDAX' } + // } + // + // deposit transaction from gdax to coinbase + // + // { + // id: '9c4b642c-8688-58bf-8962-13cef64097de', + // type: 'exchange_withdrawal', + // status: 'completed', + // amount: { amount: '0.57729420', currency: 'BTC' }, + // native_amount: { amount: '4418.72', currency: 'GBP' }, + // description: null, + // created_at: '2018-02-17T11:33:33Z', + // updated_at: '2018-02-17T11:33:33Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/9c4b642c-8688-58bf-8962-13cef64097de', + // instant_exchange: false, + // details: { title: 'Transferred Bitcoin', subtitle: 'From GDAX' } + // } + // + // deposit transaction from coinbasepro to coinbase + // + // { + // id: '8d6dd0b9-3416-568a-889d-8f112fae9e81', + // type: 'pro_withdrawal', + // status: 'completed', + // amount: { amount: '0.40555386', currency: 'BTC' }, + // native_amount: { amount: '1140.27', currency: 'GBP' }, + // description: null, + // created_at: '2019-03-04T19:41:58Z', + // updated_at: '2019-03-04T19:41:58Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/8d6dd0b9-3416-568a-889d-8f112fae9e81', + // instant_exchange: false, + // application: { + // id: '5756ab6e-836b-553b-8950-5e389451225d', + // resource: 'application', + // resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d' + // }, + // details: { title: 'Transferred Bitcoin', subtitle: 'From Coinbase Pro' } + // } + // + // sell trade + // + // { + // id: 'a9409207-df64-585b-97ab-a50780d2149e', + // type: 'sell', + // status: 'completed', + // amount: { amount: '-9.09922880', currency: 'BTC' }, + // native_amount: { amount: '-7285.73', currency: 'GBP' }, + // description: null, + // created_at: '2017-03-27T15:38:34Z', + // updated_at: '2017-03-27T15:38:34Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/a9409207-df64-585b-97ab-a50780d2149e', + // instant_exchange: false, + // sell: { + // id: 'e3550b4d-8ae6-5de3-95fe-1fb01ba83051', + // resource: 'sell', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/sells/e3550b4d-8ae6-5de3-95fe-1fb01ba83051' + // }, + // details: { + // title: 'Sold Bitcoin', + // subtitle: 'Using EUR Wallet', + // payment_method_name: 'EUR Wallet' + // } + // } + // + // buy trade + // + // { + // id: '63eeed67-9396-5912-86e9-73c4f10fe147', + // type: 'buy', + // status: 'completed', + // amount: { amount: '2.39605772', currency: 'ETH' }, + // native_amount: { amount: '98.31', currency: 'GBP' }, + // description: null, + // created_at: '2017-03-27T09:07:56Z', + // updated_at: '2017-03-27T09:07:57Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/transactions/63eeed67-9396-5912-86e9-73c4f10fe147', + // instant_exchange: false, + // buy: { + // id: '20b25b36-76c6-5353-aa57-b06a29a39d82', + // resource: 'buy', + // resource_path: '/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/buys/20b25b36-76c6-5353-aa57-b06a29a39d82' + // }, + // details: { + // title: 'Bought Ethereum', + // subtitle: 'Using EUR Wallet', + // payment_method_name: 'EUR Wallet' + // } + // } + // + // fiat deposit transaction + // + // { + // id: '04ed4113-3732-5b0c-af86-b1d2146977d0', + // type: 'fiat_deposit', + // status: 'completed', + // amount: { amount: '114.02', currency: 'EUR' }, + // native_amount: { amount: '97.23', currency: 'GBP' }, + // description: null, + // created_at: '2017-02-09T07:01:21Z', + // updated_at: '2017-02-09T07:01:22Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86-b1d2146977d0', + // instant_exchange: false, + // fiat_deposit: { + // id: 'f34c19f3-b730-5e3d-9f72-96520448677a', + // resource: 'fiat_deposit', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72-96520448677a' + // }, + // details: { + // title: 'Deposited funds', + // subtitle: 'From SEPA Transfer (GB47 BARC 20..., reference CBADVI)', + // payment_method_name: 'SEPA Transfer (GB47 BARC 20..., reference CBADVI)' + // } + // } + // + // fiat withdrawal transaction + // + // { + // id: '957d98e2-f80e-5e2f-a28e-02945aa93079', + // type: 'fiat_withdrawal', + // status: 'completed', + // amount: { amount: '-11000.00', currency: 'EUR' }, + // native_amount: { amount: '-9698.22', currency: 'GBP' }, + // description: null, + // created_at: '2017-12-06T13:19:19Z', + // updated_at: '2017-12-06T13:19:19Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/957d98e2-f80e-5e2f-a28e-02945aa93079', + // instant_exchange: false, + // fiat_withdrawal: { + // id: 'f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7', + // resource: 'fiat_withdrawal', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7' + // }, + // details: { + // title: 'Withdrew funds', + // subtitle: 'To HSBC BANK PLC (GB74 MIDL...)', + // payment_method_name: 'HSBC BANK PLC (GB74 MIDL...)' + // } + // } + // + const amountInfo = this.safeValue (item, 'amount', {}); + let amount = this.safeFloat (amountInfo, 'amount'); + let direction = undefined; + if (amount < 0) { + direction = 'out'; + amount = -amount; + } else { + direction = 'in'; + } + const currencyId = this.safeString (amountInfo, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + // + // the address and txid do not belong to the unified ledger structure + // + // let address = undefined; + // if (item['to']) { + // address = this.safeString (item['to'], 'address'); + // } + // let txid = undefined; + // + let fee = undefined; + const networkInfo = this.safeValue (item, 'network', {}); + // txid = network['hash']; // txid does not belong to the unified ledger structure + const feeInfo = this.safeValue (networkInfo, 'transaction_fee'); + if (feeInfo !== undefined) { + const feeCurrencyId = this.safeString (feeInfo, 'currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId, currency); + const feeAmount = this.safeFloat (feeInfo, 'amount'); + fee = { + 'cost': feeAmount, + 'currency': feeCurrencyCode, + }; + } + const timestamp = this.parse8601 (this.safeValue (item, 'created_at')); + const id = this.safeString (item, 'id'); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const status = this.parseLedgerEntryStatus (this.safeString (item, 'status')); + const path = this.safeString (item, 'resource_path'); + let accountId = undefined; + if (path !== undefined) { + const parts = path.split ('/'); + const numParts = parts.length; + if (numParts > 3) { + accountId = parts[3]; + } + } + return { + 'info': item, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'direction': direction, + 'account': accountId, + 'referenceId': undefined, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': undefined, + 'after': undefined, + 'status': status, + 'fee': fee, + }; + } + + async findAccountId (code) { + await this.loadMarkets (); + await this.loadAccounts (); + for (let i = 0; i < this.accounts.length; i++) { + const account = this.accounts[i]; + if (account['code'] === code) { + return account['id']; + } + } + return undefined; + } + + prepareAccountRequest (limit = undefined, params = {}) { + const accountId = this.safeString2 (params, 'account_id', 'accountId'); + if (accountId === undefined) { + throw new ArgumentsRequired (this.id + ' method requires an account_id (or accountId) parameter'); + } + const request = { + 'account_id': accountId, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + return request; + } + + async prepareAccountRequestWithCurrencyCode (code = undefined, limit = undefined, params = {}) { + let accountId = this.safeString2 (params, 'account_id', 'accountId'); + if (accountId === undefined) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' method requires an account_id (or accountId) parameter OR a currency code argument'); + } + accountId = await this.findAccountId (code); + if (accountId === undefined) { + throw new ExchangeError (this.id + ' could not find account id for ' + code); + } + } + const request = { + 'account_id': accountId, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + return request; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let fullPath = '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + fullPath += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + fullPath; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let payload = ''; + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + const auth = nonce + method + fullPath + payload; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers = { + 'CB-ACCESS-KEY': this.apiKey, + 'CB-ACCESS-SIGN': signature, + 'CB-ACCESS-TIMESTAMP': nonce, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const feedback = this.id + ' ' + body; + // + // {"error": "invalid_request", "error_description": "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."} + // + // or + // + // { + // "errors": [ + // { + // "id": "not_found", + // "message": "Not found" + // } + // ] + // } + // + let errorCode = this.safeString (response, 'error'); + if (errorCode !== undefined) { + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + const errors = this.safeValue (response, 'errors'); + if (errors !== undefined) { + if (Array.isArray (errors)) { + const numErrors = errors.length; + if (numErrors > 0) { + errorCode = this.safeString (errors[0], 'id'); + if (errorCode !== undefined) { + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + } + } + } + const data = this.safeValue (response, 'data'); + if (data === undefined) { + throw new ExchangeError (this.id + ' failed due to a malformed response ' + this.json (response)); + } + } +}; + + +},{"./base/Exchange":5,"./base/errors":7}],61:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const coinbasepro = require ('./coinbasepro.js'); + +// --------------------------------------------------------------------------- + +module.exports = class coinbaseprime extends coinbasepro { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinbaseprime', + 'name': 'Coinbase Prime', + 'pro': true, + 'urls': { + 'test': { + 'public': 'https://api-public.sandbox.prime.coinbase.com', + 'private': 'https://api-public.sandbox.prime.coinbase.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/44539184-29f26e00-a70c-11e8-868f-e907fc236a7c.jpg', + 'api': { + 'public': 'https://api.prime.coinbase.com', + 'private': 'https://api.prime.coinbase.com', + }, + 'www': 'https://prime.coinbase.com', + 'doc': 'https://docs.prime.coinbase.com', + 'fees': 'https://support.prime.coinbase.com/customer/en/portal/articles/2945629-fees?b_id=17475', + }, + }); + } +}; + +},{"./coinbasepro.js":62}],62:[function(require,module,exports){ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InsufficientFunds, ArgumentsRequired, ExchangeError, InvalidOrder, InvalidAddress, AuthenticationError, NotSupported, OrderNotFound, OnMaintenance, PermissionDenied, RateLimitExceeded } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class coinbasepro extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinbasepro', + 'name': 'Coinbase Pro', + 'countries': [ 'US' ], + 'rateLimit': 1000, + 'userAgent': this.userAgents['chrome'], + 'pro': true, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createOrder': true, + 'deposit': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': false, // the exchange does not have this method, only createDepositAddress, see https://github.com/ccxt/ccxt/pull/7405 + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTime': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'withdraw': true, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': 60, + '5m': 300, + '15m': 900, + '1h': 3600, + '6h': 21600, + '1d': 86400, + }, + 'urls': { + 'test': { + 'public': 'https://api-public.sandbox.pro.coinbase.com', + 'private': 'https://api-public.sandbox.pro.coinbase.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/41764625-63b7ffde-760a-11e8-996d-a6328fa9347a.jpg', + 'api': { + 'public': 'https://api.pro.coinbase.com', + 'private': 'https://api.pro.coinbase.com', + }, + 'www': 'https://pro.coinbase.com/', + 'doc': 'https://docs.pro.coinbase.com', + 'fees': [ + 'https://docs.pro.coinbase.com/#fees', + 'https://support.pro.coinbase.com/customer/en/portal/articles/2945310-fees', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'products', + 'products/{id}', + 'products/{id}/book', + 'products/{id}/candles', + 'products/{id}/stats', + 'products/{id}/ticker', + 'products/{id}/trades', + 'time', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/{id}', + 'accounts/{id}/holds', + 'accounts/{id}/ledger', + 'accounts/{id}/transfers', + 'coinbase-accounts', + 'fills', + 'funding', + 'fees', + 'margin/profile_information', + 'margin/buying_power', + 'margin/withdrawal_power', + 'margin/withdrawal_power_all', + 'margin/exit_plan', + 'margin/liquidation_history', + 'margin/position_refresh_amounts', + 'margin/status', + 'oracle', + 'orders', + 'orders/{id}', + 'orders/client:{client_oid}', + 'otc/orders', + 'payment-methods', + 'position', + 'profiles', + 'profiles/{id}', + 'reports/{report_id}', + 'transfers', + 'transfers/{transfer_id}', + 'users/self/trailing-volume', + 'users/self/exchange-limits', + 'withdrawals/fee-estimate', + ], + 'post': [ + 'conversions', + 'deposits/coinbase-account', + 'deposits/payment-method', + 'coinbase-accounts/{id}/addresses', + 'funding/repay', + 'orders', + 'position/close', + 'profiles/margin-transfer', + 'profiles/transfer', + 'reports', + 'withdrawals/coinbase', + 'withdrawals/coinbase-account', + 'withdrawals/crypto', + 'withdrawals/payment-method', + ], + 'delete': [ + 'orders', + 'orders/client:{client_oid}', + 'orders/{id}', + ], + }, + }, + 'commonCurrencies': { + 'CGLD': 'CELO', + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': true, // complicated tier system per coin + 'percentage': true, + 'maker': 0.5 / 100, // highest fee of all tiers + 'taker': 0.5 / 100, // highest fee of all tiers + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BCH': 0, + 'BTC': 0, + 'LTC': 0, + 'ETH': 0, + 'EUR': 0.15, + 'USD': 25, + }, + 'deposit': { + 'BCH': 0, + 'BTC': 0, + 'LTC': 0, + 'ETH': 0, + 'EUR': 0.15, + 'USD': 10, + }, + }, + }, + 'exceptions': { + 'exact': { + 'Insufficient funds': InsufficientFunds, + 'NotFound': OrderNotFound, + 'Invalid API Key': AuthenticationError, + 'invalid signature': AuthenticationError, + 'Invalid Passphrase': AuthenticationError, + 'Invalid order id': InvalidOrder, + 'Private rate limit exceeded': RateLimitExceeded, + 'Trading pair not available': PermissionDenied, + 'Product not found': InvalidOrder, + }, + 'broad': { + 'Order already done': OrderNotFound, + 'order not found': OrderNotFound, + 'price too small': InvalidOrder, + 'price too precise': InvalidOrder, + 'under maintenance': OnMaintenance, + 'size is too small': InvalidOrder, + 'Cancel only mode': OnMaintenance, // https://github.com/ccxt/ccxt/issues/7690 + }, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // id: 'XTZ', + // name: 'Tezos', + // min_size: '0.000001', + // status: 'online', + // message: '', + // max_precision: '0.000001', + // convertible_to: [], + // details: { + // type: 'crypto', + // symbol: 'Τ', + // network_confirmations: 60, + // sort_order: 53, + // crypto_address_link: 'https://tzstats.com/{{address}}', + // crypto_transaction_link: 'https://tzstats.com/{{txId}}', + // push_payment_methods: [ 'crypto' ], + // group_types: [], + // display_name: '', + // processing_time_seconds: 0, + // min_withdrawal_amount: 1 + // } + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'id'); + const name = this.safeString (currency, 'name'); + const code = this.safeCurrencyCode (id); + const details = this.safeValue (currency, 'details', {}); + const precision = this.safeFloat (currency, 'max_precision'); + const status = this.safeString (currency, 'status'); + const active = (status === 'online'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': this.safeString (details, 'type'), + 'name': name, + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (details, 'min_size'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (details, 'min_withdrawal_amount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetProducts (params); + // + // [ + // { + // "id":"ZEC-BTC", + // "base_currency":"ZEC", + // "quote_currency":"BTC", + // "base_min_size":"0.01000000", + // "base_max_size":"1500.00000000", + // "quote_increment":"0.00000100", + // "base_increment":"0.00010000", + // "display_name":"ZEC/BTC", + // "min_market_funds":"0.001", + // "max_market_funds":"30", + // "margin_enabled":false, + // "post_only":false, + // "limit_only":false, + // "cancel_only":false, + // "trading_disabled":false, + // "status":"online", + // "status_message":"" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const priceLimits = { + 'min': this.safeFloat (market, 'quote_increment'), + 'max': undefined, + }; + const precision = { + 'amount': this.safeFloat (market, 'base_increment'), + 'price': this.safeFloat (market, 'quote_increment'), + }; + const status = this.safeString (market, 'status'); + const active = (status === 'online'); + result.push (this.extend (this.fees['trading'], { + 'id': id, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'base_min_size'), + 'max': this.safeFloat (market, 'base_max_size'), + }, + 'price': priceLimits, + 'cost': { + 'min': this.safeFloat (market, 'min_market_funds'), + 'max': this.safeFloat (market, 'max_market_funds'), + }, + }, + 'active': active, + 'info': market, + })); + } + return result; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccounts (params); + // + // [ + // { + // id: '4aac9c60-cbda-4396-9da4-4aa71e95fba0', + // currency: 'BTC', + // balance: '0.0000000000000000', + // available: '0', + // hold: '0.0000000000000000', + // profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da' + // }, + // { + // id: 'f75fa69a-1ad1-4a80-bd61-ee7faa6135a3', + // currency: 'USDC', + // balance: '0.0000000000000000', + // available: '0', + // hold: '0.0000000000000000', + // profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da' + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const account = response[i]; + const accountId = this.safeString (account, 'id'); + const currencyId = this.safeString (account, 'currency'); + const code = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': accountId, + 'type': undefined, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'hold'), + 'total': this.safeFloat (balance, 'balance'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + // level 1 - only the best bid and ask + // level 2 - top 50 bids and asks (aggregated) + // level 3 - full order book (non aggregated) + const request = { + 'id': this.marketId (symbol), + 'level': 2, // 1 best bidask, 2 aggregated, 3 full + }; + const response = await this.publicGetProductsIdBook (this.extend (request, params)); + // + // { + // "sequence":1924393896, + // "bids":[ + // ["0.01825","24.34811287",2], + // ["0.01824","72.5463",3], + // ["0.01823","424.54298049",6], + // ], + // "asks":[ + // ["0.01826","171.10414904",4], + // ["0.01827","22.60427028",1], + // ["0.01828","397.46018784",7], + // ] + // } + // + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'sequence'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // publicGetProductsIdTicker + // + // { + // "trade_id":843439, + // "price":"0.997999", + // "size":"80.29769", + // "time":"2020-01-28T02:13:33.012523Z", + // "bid":"0.997094", + // "ask":"0.998", + // "volume":"1903188.03750000" + // } + // + // publicGetProductsIdStats + // + // { + // "open": "34.19000000", + // "high": "95.70000000", + // "low": "7.06000000", + // "volume": "2.41000000" + // } + // + const timestamp = this.parse8601 (this.safeValue (ticker, 'time')); + const bid = this.safeFloat (ticker, 'bid'); + const ask = this.safeFloat (ticker, 'ask'); + const last = this.safeFloat (ticker, 'price'); + const symbol = (market === undefined) ? undefined : market['symbol']; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + // publicGetProductsIdTicker or publicGetProductsIdStats + const method = this.safeString (this.options, 'fetchTickerMethod', 'publicGetProductsIdTicker'); + const response = await this[method] (this.extend (request, params)); + // + // publicGetProductsIdTicker + // + // { + // "trade_id":843439, + // "price":"0.997999", + // "size":"80.29769", + // "time":"2020-01-28T02:13:33.012523Z", + // "bid":"0.997094", + // "ask":"0.998", + // "volume":"1903188.03750000" + // } + // + // publicGetProductsIdStats + // + // { + // "open": "34.19000000", + // "high": "95.70000000", + // "low": "7.06000000", + // "volume": "2.41000000" + // } + // + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // + // { + // type: 'match', + // trade_id: 82047307, + // maker_order_id: '0f358725-2134-435e-be11-753912a326e0', + // taker_order_id: '252b7002-87a3-425c-ac73-f5b9e23f3caf', + // side: 'sell', + // size: '0.00513192', + // price: '9314.78', + // product_id: 'BTC-USD', + // sequence: 12038915443, + // time: '2020-01-31T20:03:41.158814Z' + // } + // + const timestamp = this.parse8601 (this.safeString2 (trade, 'time', 'created_at')); + const marketId = this.safeString (trade, 'product_id'); + const symbol = this.safeSymbol (marketId, market, '-'); + let feeRate = undefined; + let feeCurrency = undefined; + let takerOrMaker = undefined; + if (market !== undefined) { + feeCurrency = market['quote']; + if ('liquidity' in trade) { + takerOrMaker = (trade['liquidity'] === 'T') ? 'taker' : 'maker'; + feeRate = market[takerOrMaker]; + } + } + const feeCost = this.safeFloat2 (trade, 'fill_fees', 'fee'); + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }; + const type = undefined; + const id = this.safeString (trade, 'trade_id'); + let side = (trade['side'] === 'buy') ? 'sell' : 'buy'; + const orderId = this.safeString (trade, 'order_id'); + // Coinbase Pro returns inverted side to fetchMyTrades vs fetchTrades + if (orderId !== undefined) { + side = (trade['side'] === 'buy') ? 'buy' : 'sell'; + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + return { + 'id': id, + 'order': orderId, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'fee': fee, + 'cost': price * amount, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // as of 2018-08-23 + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_id': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetFills (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], // fixes issue #2 + }; + const response = await this.publicGetProductsIdTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591514160, + // 0.02507, + // 0.02507, + // 0.02507, + // 0.02507, + // 0.02816506 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const granularity = this.timeframes[timeframe]; + const request = { + 'id': market['id'], + 'granularity': granularity, + }; + if (since !== undefined) { + request['start'] = this.iso8601 (since); + if (limit === undefined) { + // https://docs.pro.coinbase.com/#get-historic-rates + limit = 300; // max = 300 + } + request['end'] = this.iso8601 (this.sum ((limit - 1) * granularity * 1000, since)); + } + const response = await this.publicGetProductsIdCandles (this.extend (request, params)); + // + // [ + // [1591514160,0.02507,0.02507,0.02507,0.02507,0.02816506], + // [1591514100,0.02507,0.02507,0.02507,0.02507,1.63830323], + // [1591514040,0.02505,0.02507,0.02505,0.02507,0.19918178] + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "iso":"2020-05-12T08:00:51.504Z", + // "epoch":1589270451.504 + // } + // + return this.safeTimestamp (response, 'epoch'); + } + + parseOrderStatus (status) { + const statuses = { + 'pending': 'open', + 'active': 'open', + 'open': 'open', + 'done': 'closed', + 'canceled': 'canceled', + 'canceling': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const marketId = this.safeString (order, 'product_id'); + market = this.safeMarket (marketId, market, '-'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const price = this.safeFloat (order, 'price'); + const filled = this.safeFloat (order, 'filled_size'); + const amount = this.safeFloat (order, 'size', filled); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + const cost = this.safeFloat (order, 'executed_value'); + const feeCost = this.safeFloat (order, 'fill_fees'); + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrencyCode = undefined; + if (market !== undefined) { + feeCurrencyCode = market['quote']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': undefined, + }; + } + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'order_id': id, + }; + const response = await this.privateGetFills (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': 'all', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': 'done', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + // let oid = this.nonce ().toString (); + const request = { + 'product_id': this.marketId (symbol), + 'side': side, + 'size': this.amountToPrecision (symbol, amount), + 'type': type, + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + return await this.privateDeleteOrdersId ({ 'id': id }); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + return await this.privateDeleteOrders (params); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + const cost = amount * price; + const currency = market['quote']; + return { + 'type': takerOrMaker, + 'currency': currency, + 'rate': rate, + 'cost': parseFloat (this.currencyToPrecision (currency, rate * cost)), + }; + } + + async fetchPaymentMethods (params = {}) { + return await this.privateGetPaymentMethods (params); + } + + async deposit (code, amount, address, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + }; + let method = 'privatePostDeposits'; + if ('payment_method_id' in params) { + // deposit from a payment_method, like a bank account + method += 'PaymentMethod'; + } else if ('coinbase_account_id' in params) { + // deposit into Coinbase Pro account from a Coinbase account + method += 'CoinbaseAccount'; + } else { + // deposit methodotherwise we did not receive a supported deposit location + // relevant docs link for the Googlers + // https://docs.pro.coinbase.com/#deposits + throw new NotSupported (this.id + ' deposit() requires one of `coinbase_account_id` or `payment_method_id` extra params'); + } + const response = await this[method] (this.extend (request, params)); + if (!response) { + throw new ExchangeError (this.id + ' deposit() error: ' + this.json (response)); + } + return { + 'info': response, + 'id': response['id'], + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + }; + let method = 'privatePostWithdrawals'; + if ('payment_method_id' in params) { + method += 'PaymentMethod'; + } else if ('coinbase_account_id' in params) { + method += 'CoinbaseAccount'; + } else { + method += 'Crypto'; + request['crypto_address'] = address; + } + const response = await this[method] (this.extend (request, params)); + if (!response) { + throw new ExchangeError (this.id + ' withdraw() error: ' + this.json (response)); + } + return { + 'info': response, + 'id': response['id'], + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let currency = undefined; + let id = this.safeString (params, 'id'); // account id + if (id === undefined) { + if (code !== undefined) { + currency = this.currency (code); + const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency'); + const account = this.safeValue (accountsByCurrencyCode, code); + if (account === undefined) { + throw new ExchangeError (this.id + ' fetchTransactions() could not find account id for ' + code); + } + id = account['id']; + } + } + const request = {}; + if (id !== undefined) { + request['id'] = id; + } + if (limit !== undefined) { + request['limit'] = limit; + } + let response = undefined; + if (id === undefined) { + response = await this.privateGetTransfers (this.extend (request, params)); + for (let i = 0; i < response.length; i++) { + const account_id = this.safeString (response[i], 'account_id'); + const account = this.safeValue (this.accountsById, account_id); + const code = this.safeString (account, 'currency'); + response[i]['currency'] = code; + } + } else { + response = await this.privateGetAccountsIdTransfers (this.extend (request, params)); + for (let i = 0; i < response.length; i++) { + response[i]['currency'] = code; + } + } + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchTransactions (code, since, limit, this.extend (params, { 'type': 'deposit' })); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchTransactions (code, since, limit, this.extend (params, { 'type': 'withdraw' })); + } + + parseTransactionStatus (transaction) { + const canceled = this.safeValue (transaction, 'canceled_at'); + if (canceled) { + return 'canceled'; + } + const processed = this.safeValue (transaction, 'processed_at'); + const completed = this.safeValue (transaction, 'completed_at'); + if (completed) { + return 'ok'; + } else if (processed && !completed) { + return 'failed'; + } else { + return 'pending'; + } + } + + parseTransaction (transaction, currency = undefined) { + const details = this.safeValue (transaction, 'details', {}); + const id = this.safeString (transaction, 'id'); + const txid = this.safeString (details, 'crypto_transaction_hash'); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const updated = this.parse8601 (this.safeString (transaction, 'processed_at')); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (transaction); + const amount = this.safeFloat (transaction, 'amount'); + let type = this.safeString (transaction, 'type'); + let address = this.safeString (details, 'crypto_address'); + const tag = this.safeString (details, 'destination_tag'); + address = this.safeString (transaction, 'crypto_address', address); + let fee = undefined; + if (type === 'withdraw') { + type = 'withdrawal'; + address = this.safeString (details, 'sent_to_address', address); + const feeCost = this.safeFloat (details, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'code': code, + }; + } + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'][api] + request; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let payload = ''; + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + const what = nonce + method + request + payload; + const secret = this.base64ToBinary (this.secret); + const signature = this.hmac (this.encode (what), secret, 'sha256', 'base64'); + headers = { + 'CB-ACCESS-KEY': this.apiKey, + 'CB-ACCESS-SIGN': signature, + 'CB-ACCESS-TIMESTAMP': nonce, + 'CB-ACCESS-PASSPHRASE': this.password, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + let accounts = this.safeValue (this.options, 'coinbaseAccounts'); + if (accounts === undefined) { + accounts = await this.privateGetCoinbaseAccounts (); + this.options['coinbaseAccounts'] = accounts; // cache it + this.options['coinbaseAccountsByCurrencyId'] = this.indexBy (accounts, 'currency'); + } + const currencyId = currency['id']; + const account = this.safeValue (this.options['coinbaseAccountsByCurrencyId'], currencyId); + if (account === undefined) { + // eslint-disable-next-line quotes + throw new InvalidAddress (this.id + " fetchDepositAddress() could not find currency code " + code + " with id = " + currencyId + " in this.options['coinbaseAccountsByCurrencyId']"); + } + const request = { + 'id': account['id'], + }; + const response = await this.privatePostCoinbaseAccountsIdAddresses (this.extend (request, params)); + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'destination_tag'); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 400) || (code === 404)) { + if (body[0] === '{') { + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + throw new ExchangeError (this.id + ' ' + body); + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (typeof response !== 'string') { + if ('message' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],63:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coincheck extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coincheck', + 'name': 'coincheck', + 'countries': [ 'JP', 'ID' ], + 'rateLimit': 1500, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMyTrades': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182088-1d6d6380-c2ec-11ea-9c64-8ab9f9b289f5.jpg', + 'api': 'https://coincheck.com/api', + 'www': 'https://coincheck.com', + 'doc': 'https://coincheck.com/documents/exchange/api', + 'fees': [ + 'https://coincheck.com/exchange/fee', + 'https://coincheck.com/info/fee', + ], + }, + 'api': { + 'public': { + 'get': [ + 'exchange/orders/rate', + 'order_books', + 'rate/{pair}', + 'ticker', + 'trades', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/balance', + 'accounts/leverage_balance', + 'bank_accounts', + 'deposit_money', + 'exchange/orders/opens', + 'exchange/orders/transactions', + 'exchange/orders/transactions_pagination', + 'exchange/leverage/positions', + 'lending/borrows/matches', + 'send_money', + 'withdraws', + ], + 'post': [ + 'bank_accounts', + 'deposit_money/{id}/fast', + 'exchange/orders', + 'exchange/transfers/to_leverage', + 'exchange/transfers/from_leverage', + 'lending/borrows', + 'lending/borrows/{id}/repay', + 'send_money', + 'withdraws', + ], + 'delete': [ + 'bank_accounts/{id}', + 'exchange/orders/{id}', + 'withdraws/{id}', + ], + }, + }, + 'markets': { + 'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' }, // the only real pair + // 'ETH/JPY': { 'id': 'eth_jpy', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy' }, + // 'ETC/JPY': { 'id': 'etc_jpy', 'symbol': 'ETC/JPY', 'base': 'ETC', 'quote': 'JPY', 'baseId': 'etc', 'quoteId': 'jpy' }, + // 'DAO/JPY': { 'id': 'dao_jpy', 'symbol': 'DAO/JPY', 'base': 'DAO', 'quote': 'JPY', 'baseId': 'dao', 'quoteId': 'jpy' }, + // 'LSK/JPY': { 'id': 'lsk_jpy', 'symbol': 'LSK/JPY', 'base': 'LSK', 'quote': 'JPY', 'baseId': 'lsk', 'quoteId': 'jpy' }, + // 'FCT/JPY': { 'id': 'fct_jpy', 'symbol': 'FCT/JPY', 'base': 'FCT', 'quote': 'JPY', 'baseId': 'fct', 'quoteId': 'jpy' }, + // 'XMR/JPY': { 'id': 'xmr_jpy', 'symbol': 'XMR/JPY', 'base': 'XMR', 'quote': 'JPY', 'baseId': 'xmr', 'quoteId': 'jpy' }, + // 'REP/JPY': { 'id': 'rep_jpy', 'symbol': 'REP/JPY', 'base': 'REP', 'quote': 'JPY', 'baseId': 'rep', 'quoteId': 'jpy' }, + // 'XRP/JPY': { 'id': 'xrp_jpy', 'symbol': 'XRP/JPY', 'base': 'XRP', 'quote': 'JPY', 'baseId': 'xrp', 'quoteId': 'jpy' }, + // 'ZEC/JPY': { 'id': 'zec_jpy', 'symbol': 'ZEC/JPY', 'base': 'ZEC', 'quote': 'JPY', 'baseId': 'zec', 'quoteId': 'jpy' }, + // 'XEM/JPY': { 'id': 'xem_jpy', 'symbol': 'XEM/JPY', 'base': 'XEM', 'quote': 'JPY', 'baseId': 'xem', 'quoteId': 'jpy' }, + // 'LTC/JPY': { 'id': 'ltc_jpy', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy' }, + // 'DASH/JPY': { 'id': 'dash_jpy', 'symbol': 'DASH/JPY', 'base': 'DASH', 'quote': 'JPY', 'baseId': 'dash', 'quoteId': 'jpy' }, + // 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc' }, + // 'ETC/BTC': { 'id': 'etc_btc', 'symbol': 'ETC/BTC', 'base': 'ETC', 'quote': 'BTC', 'baseId': 'etc', 'quoteId': 'btc' }, + // 'LSK/BTC': { 'id': 'lsk_btc', 'symbol': 'LSK/BTC', 'base': 'LSK', 'quote': 'BTC', 'baseId': 'lsk', 'quoteId': 'btc' }, + // 'FCT/BTC': { 'id': 'fct_btc', 'symbol': 'FCT/BTC', 'base': 'FCT', 'quote': 'BTC', 'baseId': 'fct', 'quoteId': 'btc' }, + // 'XMR/BTC': { 'id': 'xmr_btc', 'symbol': 'XMR/BTC', 'base': 'XMR', 'quote': 'BTC', 'baseId': 'xmr', 'quoteId': 'btc' }, + // 'REP/BTC': { 'id': 'rep_btc', 'symbol': 'REP/BTC', 'base': 'REP', 'quote': 'BTC', 'baseId': 'rep', 'quoteId': 'btc' }, + // 'XRP/BTC': { 'id': 'xrp_btc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc' }, + // 'ZEC/BTC': { 'id': 'zec_btc', 'symbol': 'ZEC/BTC', 'base': 'ZEC', 'quote': 'BTC', 'baseId': 'zec', 'quoteId': 'btc' }, + // 'XEM/BTC': { 'id': 'xem_btc', 'symbol': 'XEM/BTC', 'base': 'XEM', 'quote': 'BTC', 'baseId': 'xem', 'quoteId': 'btc' }, + // 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' }, + // 'DASH/BTC': { 'id': 'dash_btc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC', 'baseId': 'dash', 'quoteId': 'btc' }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0, + 'taker': 0, + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privateGetAccountsBalance (params); + const result = { 'info': balances }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currencyId = this.currencyId (code); + if (currencyId in balances) { + const account = this.account (); + const reserved = currencyId + '_reserved'; + account['free'] = this.safeFloat (balances, currencyId); + account['used'] = this.safeFloat (balances, reserved); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // Only BTC/JPY is meaningful + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privateGetExchangeOrdersOpens (params); + const rawOrders = this.safeValue (response, 'orders', []); + const parsedOrders = this.parseOrders (rawOrders, market, since, limit); + const result = []; + for (let i = 0; i < parsedOrders.length; i++) { + result.push (this.extend (parsedOrders[i], { 'status': 'open' })); + } + return result; + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrders + // + // { id: 202835, + // order_type: "buy", + // rate: 26890, + // pair: "btc_jpy", + // pending_amount: "0.5527", + // pending_market_buy_amount: null, + // stop_loss_rate: null, + // created_at: "2015-01-10T05:55:38.000Z" } + // + // todo: add formats for fetchOrder, fetchClosedOrders here + // + const id = this.safeString (order, 'id'); + const side = this.safeString (order, 'order_type'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const amount = this.safeFloat (order, 'pending_amount'); + const remaining = this.safeFloat (order, 'pending_amount'); + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if (remaining !== undefined) { + if (amount !== undefined) { + filled = Math.max (amount - remaining, 0); + if (price !== undefined) { + cost = filled * price; + } + } + } + const status = undefined; + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'side': side, + 'type': undefined, + 'timeInForce': undefined, + 'status': status, + 'symbol': symbol, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'fee': undefined, + 'info': order, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + if (symbol !== 'BTC/JPY') { + throw new BadSymbol (this.id + ' fetchOrderBook () supports BTC/JPY only'); + } + await this.loadMarkets (); + const response = await this.publicGetOrderBooks (params); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + if (symbol !== 'BTC/JPY') { + throw new BadSymbol (this.id + ' fetchTicker () supports BTC/JPY only'); + } + await this.loadMarkets (); + const ticker = await this.publicGetTicker (params); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'rate'); + const marketId = this.safeString (trade, 'pair'); + market = this.safeValue (this.markets_by_id, marketId, market); + let symbol = undefined; + let baseId = undefined; + let quoteId = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + baseId = market['baseId']; + quoteId = market['quoteId']; + symbol = market['symbol']; + } else { + const ids = marketId.split ('_'); + baseId = ids[0]; + quoteId = ids[1]; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } + } + let takerOrMaker = undefined; + let amount = undefined; + let cost = undefined; + let side = undefined; + let fee = undefined; + let orderId = undefined; + if ('liquidity' in trade) { + if (this.safeString (trade, 'liquidity') === 'T') { + takerOrMaker = 'taker'; + } else if (this.safeString (trade, 'liquidity') === 'M') { + takerOrMaker = 'maker'; + } + const funds = this.safeValue (trade, 'funds', {}); + amount = this.safeFloat (funds, baseId); + cost = this.safeFloat (funds, quoteId); + fee = { + 'currency': this.safeString (trade, 'fee_currency'), + 'cost': this.safeFloat (trade, 'fee'), + }; + side = this.safeString (trade, 'side'); + orderId = this.safeString (trade, 'order_id'); + } else { + amount = this.safeFloat (trade, 'amount'); + side = this.safeString (trade, 'order_type'); + } + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + return { + 'id': id, + 'info': trade, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': orderId, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.privateGetExchangeOrdersTransactions (this.extend ({}, params)); + const transactions = this.safeValue (response, 'transactions', []); + return this.parseTrades (transactions, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + if (symbol !== 'BTC/JPY') { + throw new BadSymbol (this.id + ' fetchTrades () supports BTC/JPY only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + if (type === 'market') { + const order_type = type + '_' + side; + request['order_type'] = order_type; + const prefix = (side === 'buy') ? (order_type + '_') : ''; + request[prefix + 'amount'] = amount; + } else { + request['order_type'] = side; + request['rate'] = price; + request['amount'] = amount; + } + const response = await this.privatePostExchangeOrders (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privateDeleteExchangeOrdersId (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let queryString = ''; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (this.keysort (query)); + } + } else { + if (Object.keys (query).length) { + body = this.urlencode (this.keysort (query)); + queryString = body; + } + } + const auth = nonce + url + queryString; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'ACCESS-KEY': this.apiKey, + 'ACCESS-NONCE': nonce, + 'ACCESS-SIGNATURE': this.hmac (this.encode (auth), this.encode (this.secret)), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (api === 'public') { + return response; + } + if ('success' in response) { + if (response['success']) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],64:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, InvalidNonce, InsufficientFunds, InvalidOrder, OrderNotFound, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinegg extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinegg', + 'name': 'CoinEgg', + 'countries': [ 'CN', 'UK' ], + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOpenOrders': 'emulated', + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/36770310-adfa764e-1c5a-11e8-8e09-449daac3d2fb.jpg', + 'api': { + 'web': 'https://trade.coinegg.com/web', + 'rest': 'https://api.coinegg.com/api/v1', + }, + 'www': 'https://www.coinegg.com', + 'doc': 'https://www.coinegg.com/explain.api.html', + 'fees': 'https://www.coinegg.com/fee.html', + 'referral': 'https://www.coinegg.com/user/register?invite=523218', + }, + 'api': { + 'web': { + 'get': [ + 'symbol/ticker?right_coin={quote}', + '{quote}/trends', + '{quote}/{base}/order', + '{quote}/{base}/trades', + '{quote}/{base}/depth.js', + ], + }, + 'public': { + 'get': [ + 'ticker/region/{quote}', + 'depth/region/{quote}', + 'orders/region/{quote}', + ], + }, + 'private': { + 'post': [ + 'balance', + 'trade_add/region/{quote}', + 'trade_cancel/region/{quote}', + 'trade_view/region/{quote}', + 'trade_list/region/{quote}', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.008, + 'BCH': 0.002, + 'LTC': 0.001, + 'ETH': 0.01, + 'ETC': 0.01, + 'NEO': 0, + 'QTUM': '1%', + 'XRP': '1%', + 'DOGE': '1%', + 'LSK': '1%', + 'XAS': '1%', + 'BTS': '1%', + 'GAME': '1%', + 'GOOC': '1%', + 'NXT': '1%', + 'IFC': '1%', + 'DNC': '1%', + 'BLK': '1%', + 'VRC': '1%', + 'XPM': '1%', + 'VTC': '1%', + 'TFC': '1%', + 'PLC': '1%', + 'EAC': '1%', + 'PPC': '1%', + 'FZ': '1%', + 'ZET': '1%', + 'RSS': '1%', + 'PGC': '1%', + 'SKT': '1%', + 'JBC': '1%', + 'RIO': '1%', + 'LKC': '1%', + 'ZCC': '1%', + 'MCC': '1%', + 'QEC': '1%', + 'MET': '1%', + 'YTC': '1%', + 'HLB': '1%', + 'MRYC': '1%', + 'MTC': '1%', + 'KTC': 0, + }, + }, + }, + 'exceptions': { + '103': AuthenticationError, + '104': AuthenticationError, + '105': AuthenticationError, + '106': InvalidNonce, + '200': InsufficientFunds, + '201': InvalidOrder, + '202': InvalidOrder, + '203': OrderNotFound, + '402': DDoSProtection, + }, + 'errorMessages': { + '100': 'Required parameters can not be empty', + '101': 'Illegal parameter', + '102': 'coin does not exist', + '103': 'Key does not exist', + '104': 'Signature does not match', + '105': 'Insufficient permissions', + '106': 'Request expired(nonce error)', + '200': 'Lack of balance', + '201': 'Too small for the number of trading', + '202': 'Price must be in 0 - 1000000', + '203': 'Order does not exist', + '204': 'Pending order amount must be above 0.001 BTC', + '205': 'Restrict pending order prices', + '206': 'Decimal place error', + '401': 'System error', + '402': 'Requests are too frequent', + '403': 'Non-open API', + '404': 'IP restriction does not request the resource', + '405': 'Currency transactions are temporarily closed', + }, + 'options': { + 'quoteIds': [ 'btc', 'eth', 'usc', 'usdt' ], + }, + 'commonCurrencies': { + 'JBC': 'JubaoCoin', + }, + }); + } + + async fetchMarkets (params = {}) { + const quoteIds = this.options['quoteIds']; + const result = []; + for (let b = 0; b < quoteIds.length; b++) { + const quoteId = quoteIds[b]; + const response = await this.webGetSymbolTickerRightCoinQuote ({ + 'quote': quoteId, + }); + const tickers = this.safeValue (response, 'data', []); + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const id = ticker['symbol']; + const baseId = id.split ('_')[0]; + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': ticker, + }); + } + } + return result; + } + + parseTicker (ticker, market = undefined) { + const symbol = market['symbol']; + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'change'); + let open = undefined; + let change = undefined; + let average = undefined; + if (percentage !== undefined) { + const relativeChange = percentage / 100; + open = last / this.sum (1, relativeChange); + change = last - open; + average = this.sum (last, open) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVol'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.publicGetTickerRegionQuote (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.publicGetDepthRegionQuote (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const symbol = market['symbol']; + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = this.costToPrecision (symbol, price * amount); + } + } + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, 'tid'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.publicGetOrdersRegionQuote (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const data = this.safeValue (response, 'data', {}); + const balances = this.omit (data, 'uid'); + const keys = Object.keys (balances); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const [ currencyId, accountType ] = key.split ('_'); + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + result[code] = this.account (); + } + const type = (accountType === 'lock') ? 'used' : 'free'; + result[code][type] = this.safeFloat (balances, key); + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.parse8601 (this.safeString (order, 'datetime')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount_original'); + const remaining = this.safeFloat (order, 'amount_outstanding'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + let status = this.safeString (order, 'status'); + if (status === 'cancelled') { + status = 'canceled'; + } else { + status = remaining ? 'open' : 'closed'; + } + const info = this.safeValue (order, 'info', order); + const type = 'limit'; + const side = this.safeString (order, 'type'); + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': info, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + 'type': side, + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostTradeAddRegionQuote (this.extend (request, params)); + const id = this.safeString (response, 'id'); + const order = this.parseOrder ({ + 'id': id, + 'datetime': this.ymdhms (this.milliseconds ()), + 'amount_original': amount, + 'amount_outstanding': amount, + 'price': price, + 'type': side, + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + return await this.privatePostTradeCancelRegionQuote (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.privatePostTradeViewRegionQuote (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + if (since !== undefined) { + request['since'] = since / 1000; + } + const response = await this.privatePostTradeListRegionQuote (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'open', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let apiType = 'rest'; + if (api === 'web') { + apiType = api; + } + let url = this.urls['api'][apiType] + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'public' || api === 'web') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + query = this.urlencode (this.extend ({ + 'key': this.apiKey, + 'nonce': this.nonce (), + }, query)); + const secret = this.hash (this.encode (this.secret)); + const signature = this.hmac (this.encode (query), this.encode (secret)); + query += '&' + 'signature=' + signature; + if (method === 'GET') { + url += '?' + query; + } else { + headers = { + 'Content-type': 'application/x-www-form-urlencoded', + }; + body = query; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // private endpoints return the following structure: + // {"result":true,"data":{...}} - success + // {"result":false,"code":"103"} - failure + // {"code":0,"msg":"Suceess","data":{"uid":"2716039","btc_balance":"0.00000000","btc_lock":"0.00000000","xrp_balance":"0.00000000","xrp_lock":"0.00000000"}} + const result = this.safeValue (response, 'result'); + if (result === undefined) { + // public endpoint ← this comment left here by the contributor, in fact a missing result does not necessarily mean a public endpoint... + // we should just check the code and don't rely on the result at all here... + return; + } + if (result === true) { + // success + return; + } + const errorCode = this.safeString (response, 'code'); + const errorMessages = this.errorMessages; + const message = this.safeString (errorMessages, errorCode, 'Unknown Error'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (this.id + ' ' + message); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],65:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InsufficientFunds, OrderNotFound, InvalidOrder, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinex', + 'name': 'CoinEx', + 'version': 'v1', + 'countries': [ 'CN' ], + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '3m': '3min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '12h': '12hour', + '1d': '1day', + '3d': '3day', + '1w': '1week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182089-1e05fa00-c2ec-11ea-8da9-cc73b45abbbc.jpg', + 'api': 'https://api.coinex.com', + 'www': 'https://www.coinex.com', + 'doc': 'https://github.com/coinexcom/coinex_exchange_api/wiki', + 'fees': 'https://www.coinex.com/fees', + 'referral': 'https://www.coinex.com/register?refer_code=yw5fz', + }, + 'api': { + 'public': { + 'get': [ + 'common/currency/rate', + 'common/asset/config', + 'market/info', + 'market/list', + 'market/ticker', + 'market/ticker/all', + 'market/depth', + 'market/deals', + 'market/kline', + ], + }, + 'private': { + 'get': [ + 'balance/coin/deposit', + 'balance/coin/withdraw', + 'balance/info', + 'future/account', + 'future/config', + 'future/limitprice', + 'future/loan/history', + 'future/market', + 'margin/account', + 'margin/config', + 'margin/loan/history', + 'margin/market', + 'order', + 'order/deals', + 'order/finished', + 'order/finished/{id}', + 'order/pending', + 'order/status', + 'order/status/batch', + 'order/user/deals', + ], + 'post': [ + 'balance/coin/withdraw', + 'future/flat', + 'future/loan', + 'future/transfer', + 'margin/flat', + 'margin/loan', + 'margin/transfer', + 'order/batchlimit', + 'order/ioc', + 'order/limit', + 'order/market', + 'sub_account/transfer', + ], + 'delete': [ + 'balance/coin/withdraw', + 'order/pending/batch', + 'order/pending', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.001, + 'taker': 0.001, + }, + 'funding': { + 'withdraw': { + 'BCH': 0.0, + 'BTC': 0.001, + 'LTC': 0.001, + 'ETH': 0.001, + 'ZEC': 0.0001, + 'DASH': 0.0001, + }, + }, + }, + 'limits': { + 'amount': { + 'min': 0.001, + 'max': undefined, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarketInfo (params); + // + // { + // "code": 0, + // "data": { + // "WAVESBTC": { + // "name": "WAVESBTC", + // "min_amount": "1", + // "maker_fee_rate": "0.001", + // "taker_fee_rate": "0.001", + // "pricing_name": "BTC", + // "pricing_decimal": 8, + // "trading_name": "WAVES", + // "trading_decimal": 8 + // } + // } + // } + // + const markets = this.safeValue (response, 'data', {}); + const result = []; + const keys = Object.keys (markets); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const market = markets[key]; + const id = this.safeString (market, 'name'); + const tradingName = this.safeString (market, 'trading_name'); + const baseId = tradingName; + const quoteId = this.safeString (market, 'pricing_name'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + if (tradingName === id) { + symbol = id; + } + const precision = { + 'amount': this.safeInteger (market, 'trading_decimal'), + 'price': this.safeInteger (market, 'pricing_decimal'), + }; + const active = undefined; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'taker': this.safeFloat (market, 'taker_fee_rate'), + 'maker': this.safeFloat (market, 'maker_fee_rate'), + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeInteger (ticker, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + ticker = this.safeValue (ticker, 'ticker', {}); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat2 (ticker, 'vol', 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketTicker (this.extend (request, params)); + return this.parseTicker (response['data'], market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketTickerAll (params); + const data = this.safeValue (response, 'data'); + const timestamp = this.safeInteger (data, 'date'); + const tickers = this.safeValue (data, 'ticker'); + const marketIds = Object.keys (tickers); + const result = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + const ticker = this.parseTicker ({ + 'date': timestamp, + 'ticker': tickers[marketId], + }, market); + ticker['symbol'] = symbol; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = 20, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 20; // default + } + const request = { + 'market': this.marketId (symbol), + 'merge': '0.0000000001', + 'limit': limit.toString (), + }; + const response = await this.publicGetMarketDepth (this.extend (request, params)); + return this.parseOrderBook (response['data']); + } + + parseTrade (trade, market = undefined) { + // this method parses both public and private trades + let timestamp = this.safeTimestamp (trade, 'create_time'); + if (timestamp === undefined) { + timestamp = this.safeInteger (trade, 'date_ms'); + } + const tradeId = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'order_id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const marketId = this.safeString (trade, 'market'); + const symbol = this.safeSymbol (marketId, market); + let cost = this.safeFloat (trade, 'deal_money'); + if (!cost) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_asset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const takerOrMaker = this.safeString (trade, 'role'); + const side = this.safeString (trade, 'type'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': tradeId, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketDeals (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591484400, + // "0.02505349", + // "0.02506988", + // "0.02507000", + // "0.02505304", + // "343.19716223", + // "8.6021323866383196", + // "ETHBTC" + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'type': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketKline (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // [1591484400, "0.02505349", "0.02506988", "0.02507000", "0.02505304", "343.19716223", "8.6021323866383196", "ETHBTC"], + // [1591484700, "0.02506990", "0.02508109", "0.02508109", "0.02506979", "91.59841581", "2.2972047780447000", "ETHBTC"], + // [1591485000, "0.02508106", "0.02507996", "0.02508106", "0.02507500", "65.15307697", "1.6340597822306000", "ETHBTC"], + // ], + // "message": "OK" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalanceInfo (params); + // + // { + // "code": 0, + // "data": { + // "BCH": { # BCH account + // "available": "13.60109", # Available BCH + // "frozen": "0.00000" # Frozen BCH + // }, + // "BTC": { # BTC account + // "available": "32590.16", # Available BTC + // "frozen": "7000.00" # Frozen BTC + // }, + // "ETH": { # ETH account + // "available": "5.06000", # Available ETH + // "frozen": "0.00000" # Frozen ETH + // } + // }, + // "message": "Ok" + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'frozen'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'not_deal': 'open', + 'part_deal': 'open', + 'done': 'closed', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // "amount": "0.1", + // "asset_fee": "0.22736197736197736197", + // "avg_price": "196.85000000000000000000", + // "create_time": 1537270135, + // "deal_amount": "0.1", + // "deal_fee": "0", + // "deal_money": "19.685", + // "fee_asset": "CET", + // "fee_discount": "0.5", + // "id": 1788259447, + // "left": "0", + // "maker_fee_rate": "0", + // "market": "ETHUSDT", + // "order_type": "limit", + // "price": "170.00000000", + // "status": "done", + // "taker_fee_rate": "0.0005", + // "type": "sell", + // } + // + const timestamp = this.safeTimestamp (order, 'create_time'); + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'deal_money'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'deal_amount'); + const average = this.safeFloat (order, 'avg_price'); + let symbol = undefined; + const marketId = this.safeString (order, 'market'); + market = this.safeMarket (marketId, market); + const feeCurrencyId = this.safeString (order, 'fee_asset'); + let feeCurrency = this.safeCurrencyCode (feeCurrencyId); + if (market !== undefined) { + symbol = market['symbol']; + if (feeCurrency === undefined) { + feeCurrency = market['quote']; + } + } + const remaining = this.safeFloat (order, 'left'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const type = this.safeString (order, 'order_type'); + const side = this.safeString (order, 'type'); + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': { + 'currency': feeCurrency, + 'cost': this.safeFloat (order, 'deal_fee'), + }, + 'info': order, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostOrder' + this.capitalize (type); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'type': side, + }; + amount = parseFloat (amount); + // for market buy it requires the amount of quote currency to spend + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + price = parseFloat (price); + request['amount'] = this.costToPrecision (symbol, amount * price); + } + } else { + request['amount'] = this.costToPrecision (symbol, amount); + } + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if ((type === 'limit') || (type === 'ioc')) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'market': market['id'], + }; + const response = await this.privateDeleteOrderPending (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'market': market['id'], + }; + const response = await this.privateGetOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "amount": "0.1", + // "asset_fee": "0.22736197736197736197", + // "avg_price": "196.85000000000000000000", + // "create_time": 1537270135, + // "deal_amount": "0.1", + // "deal_fee": "0", + // "deal_money": "19.685", + // "fee_asset": "CET", + // "fee_discount": "0.5", + // "id": 1788259447, + // "left": "0", + // "maker_fee_rate": "0", + // "market": "ETHUSDT", + // "order_type": "limit", + // "price": "170.00000000", + // "status": "done", + // "taker_fee_rate": "0.0005", + // "type": "sell", + // }, + // "message": "Ok" + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const request = { + 'page': 1, + 'limit': limit, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const method = 'privateGetOrder' + this.capitalize (status); + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const orders = this.safeValue (data, 'data', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('pending', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('finished', symbol, since, limit, params); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const request = { + 'page': 1, + 'limit': limit, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.privateGetOrderUserDeals (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const trades = this.safeValue (data, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag) { + address = address + ':' + tag; + } + const request = { + 'coin_type': currency['id'], + 'coin_address': address, // must be authorized, inter-user transfer by a registered mobile phone number or an email address is supported + 'actual_amount': parseFloat (amount), // the actual amount without fees, https://www.coinex.com/fees + 'transfer_method': 'onchain', // onchain, local + }; + const response = await this.privatePostBalanceCoinWithdraw (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "actual_amount": "1.00000000", + // "amount": "1.00000000", + // "coin_address": "1KAv3pazbTk2JnQ5xTo6fpKK7p1it2RzD4", + // "coin_type": "BCH", + // "coin_withdraw_id": 206, + // "confirmations": 0, + // "create_time": 1524228297, + // "status": "audit", + // "tx_fee": "0", + // "tx_id": "" + // }, + // "message": "Ok" + // } + // + const transaction = this.safeValue (response, 'data', {}); + return this.parseTransaction (transaction, currency); + } + + parseTransactionStatus (status) { + const statuses = { + 'audit': 'pending', + 'pass': 'pending', + 'processing': 'pending', + 'confirming': 'pending', + 'not_pass': 'failed', + 'cancel': 'canceled', + 'finish': 'ok', + 'fail': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "actual_amount": "120.00000000", + // "actual_amount_display": "120", + // "add_explorer": "XXX", + // "amount": "120.00000000", + // "amount_display": "120", + // "coin_address": "XXXXXXXX", + // "coin_address_display": "XXXXXXXX", + // "coin_deposit_id": 1866, + // "coin_type": "USDT", + // "confirmations": 0, + // "create_time": 1539595701, + // "explorer": "", + // "remark": "", + // "status": "finish", + // "status_display": "finish", + // "transfer_method": "local", + // "tx_id": "", + // "tx_id_display": "XXXXXXXXXX" + // } + // + // fetchWithdrawals + // + // { + // "actual_amount": "0.10000000", + // "amount": "0.10000000", + // "coin_address": "15sr1VdyXQ6sVLqeJUJ1uPzLpmQtgUeBSB", + // "coin_type": "BCH", + // "coin_withdraw_id": 203, + // "confirmations": 11, + // "create_time": 1515806440, + // "status": "finish", + // "tx_fee": "0", + // "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6" + // } + // + const id = this.safeString2 (transaction, 'coin_withdraw_id', 'coin_deposit_id'); + const address = this.safeString (transaction, 'coin_address'); + let tag = this.safeString (transaction, 'remark'); // set but unused + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + let txid = this.safeValue (transaction, 'tx_id'); + if (txid !== undefined) { + if (txid.length < 1) { + txid = undefined; + } + } + const currencyId = this.safeString (transaction, 'coin_type'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeTimestamp (transaction, 'create_time'); + const type = ('coin_withdraw_id' in transaction) ? 'withdraw' : 'deposit'; + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + let feeCost = this.safeFloat (transaction, 'tx_fee'); + if (type === 'deposit') { + feeCost = 0; + } + const fee = { + 'cost': feeCost, + 'currency': code, + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin_type': currency['id'], + }; + if (limit !== undefined) { + request['Limit'] = limit; + } + const response = await this.privateGetBalanceCoinWithdraw (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "has_next": true, + // "curr_page": 1, + // "count": 10, + // "data": [ + // { + // "coin_withdraw_id": 203, + // "create_time": 1513933541, + // "actual_amount": "0.00100000", + // "actual_amount_display": "***", + // "amount": "0.00100000", + // "amount_display": "******", + // "coin_address": "1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF", + // "app_coin_address_display": "**********", + // "coin_address_display": "****************", + // "add_explorer": "https://explorer.viawallet.com/btc/address/1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF", + // "coin_type": "BTC", + // "confirmations": 6, + // "explorer": "https://explorer.viawallet.com/btc/tx/1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF", + // "fee": "0", + // "remark": "", + // "smart_contract_name": "BTC", + // "status": "finish", + // "status_display": "finish", + // "transfer_method": "onchain", + // "tx_fee": "0", + // "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6" + // }, /* many more data points */ + // ], + // "total": ***, + // "total_page":*** + // }, + // "message": "Success" + // } + // + let data = this.safeValue (response, 'data'); + if (!Array.isArray (data)) { + data = this.safeValue (data, 'data', []); + } + return this.parseTransactions (data, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin_type': currency['id'], + }; + if (limit !== undefined) { + request['Limit'] = limit; + } + const response = await this.privateGetBalanceCoinDeposit (this.extend (request, params)); + // { + // "code": 0, + // "data": [ + // { + // "actual_amount": "4.65397682", + // "actual_amount_display": "4.65397682", + // "add_explorer": "https://etherscan.io/address/0x361XXXXXX", + // "amount": "4.65397682", + // "amount_display": "4.65397682", + // "coin_address": "0x36dabcdXXXXXX", + // "coin_address_display": "0x361X*****XXXXX", + // "coin_deposit_id": 966191, + // "coin_type": "ETH", + // "confirmations": 30, + // "create_time": 1531661445, + // "explorer": "https://etherscan.io/tx/0x361XXXXXX", + // "remark": "", + // "status": "finish", + // "status_display": "finish", + // "transfer_method": "onchain", + // "tx_id": "0x361XXXXXX", + // "tx_id_display": "0x361XXXXXX" + // } + // ], + // "message": "Ok" + // } + // + let data = this.safeValue (response, 'data'); + if (!Array.isArray (data)) { + data = this.safeValue (data, 'data', []); + } + return this.parseTransactions (data, currency, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + path = this.implodeParams (path, params); + let url = this.urls['api'] + '/' + this.version + '/' + path; + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + query = this.extend ({ + 'access_id': this.apiKey, + 'tonce': nonce.toString (), + }, query); + query = this.keysort (query); + const urlencoded = this.urlencode (query); + const signature = this.hash (this.encode (urlencoded + '&secret_key=' + this.secret)); + headers = { + 'Authorization': signature.toUpperCase (), + 'Content-Type': 'application/json', + }; + if ((method === 'GET') || (method === 'DELETE')) { + url += '?' + urlencoded; + } else { + body = this.json (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const code = this.safeString (response, 'code'); + const data = this.safeValue (response, 'data'); + const message = this.safeString (response, 'message'); + if ((code !== '0') || (data === undefined) || ((message !== 'Ok') && !data)) { + const responseCodes = { + '24': AuthenticationError, + '25': AuthenticationError, + '107': InsufficientFunds, + '600': OrderNotFound, + '601': InvalidOrder, + '602': InvalidOrder, + '606': InvalidOrder, + }; + const ErrorClass = this.safeValue (responseCodes, code, ExchangeError); + throw new ErrorClass (response['message']); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],66:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, RateLimitExceeded, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinfalcon extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinfalcon', + 'name': 'CoinFalcon', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/41822275-ed982188-77f5-11e8-92bb-496bcd14ca52.jpg', + 'api': 'https://coinfalcon.com', + 'www': 'https://coinfalcon.com', + 'doc': 'https://docs.coinfalcon.com', + 'fees': 'https://coinfalcon.com/fees', + 'referral': 'https://coinfalcon.com/?ref=CFJSVGTUPASB', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'markets/{market}/orders', + 'markets/{market}/trades', + ], + }, + 'private': { + 'get': [ + 'user/accounts', + 'user/orders', + 'user/orders/{id}', + 'user/trades', + ], + 'post': [ + 'user/orders', + ], + 'delete': [ + 'user/orders/{id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'maker': 0.0, + 'taker': 0.002, // tiered fee starts at 0.2% + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const markets = this.safeValue (response, 'data'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const [ baseId, quoteId ] = market['name'].split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'size_precision'), + 'price': this.safeInteger (market, 'price_precision'), + }; + result.push ({ + 'id': market['name'], + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const marketId = this.safeString (ticker, 'name'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.milliseconds (); + const last = parseFloat (ticker['last_price']); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'change_in_24h'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const tickers = await this.fetchTickers (params); + return tickers[symbol]; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarkets (params); + const tickers = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'level': '3', + }; + const response = await this.publicGetMarketsMarketOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrderBook (data, undefined, 'bids', 'asks', 'price', 'size'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + const symbol = market['symbol']; + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const tradeId = this.safeString (trade, 'id'); + const side = this.safeString (trade, 'side'); + const orderId = this.safeString (trade, 'order_id'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = this.safeString (trade, 'fee_currency_code'); + fee = { + 'cost': feeCost, + 'currency': this.safeCurrencyCode (feeCurrencyCode), + }; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': tradeId, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (since !== undefined) { + request['start_time'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetUserTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (since !== undefined) { + request['since'] = this.iso8601 (since); + } + const response = await this.publicGetMarketsMarketTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUserAccounts (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available_balance'), + 'used': this.safeFloat (balance, 'hold_balance'), + 'total': this.safeFloat (balance, 'balance'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'fulfilled': 'closed', + 'canceled': 'canceled', + 'pending': 'open', + 'open': 'open', + 'partially_filled': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id":"8bdd79f4-8414-40a2-90c3-e9f4d6d1eef4" + // "market":"IOT-BTC" + // "price":"0.0000003" + // "size":"4.0" + // "size_filled":"3.0" + // "fee":"0.0075" + // "fee_currency_code":"iot" + // "funds":"0.0" + // "status":"canceled" + // "order_type":"buy" + // "post_only":false + // "operation_type":"market_order" + // "created_at":"2018-01-12T21:14:06.747828Z" + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'size_filled'); + let remaining = undefined; + let cost = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + if (price !== undefined) { + cost = filled * price; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let type = this.safeString (order, 'operation_type'); + if (type !== undefined) { + type = type.split ('_'); + type = type[0]; + } + const side = this.safeString (order, 'order_type'); + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'lastTradeTimestamp': undefined, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // price/size must be string + const request = { + 'market': market['id'], + 'size': this.amountToPrecision (symbol, amount), + 'order_type': side, + }; + if (type === 'limit') { + price = this.priceToPrecision (symbol, price); + request['price'] = price.toString (); + } + request['operation_type'] = type + '_order'; + const response = await this.privatePostUserOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateDeleteUserOrdersId (this.extend (request, params)); + const market = this.market (symbol); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetUserOrdersId (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['since_time'] = this.iso8601 (since); + } + // TODO: test status=all if it works for closed orders too + const response = await this.privateGetUserOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + const orders = this.filterByArray (data, 'status', [ 'pending', 'open', 'partially_filled' ], false); + return this.parseOrders (orders, market, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/api/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + } + const seconds = this.seconds ().toString (); + let payload = [ seconds, method, request ].join ('|'); + if (body) { + payload += '|' + body; + } + const signature = this.hmac (this.encode (payload), this.encode (this.secret)); + headers = { + 'CF-API-KEY': this.apiKey, + 'CF-API-TIMESTAMP': seconds, + 'CF-API-SIGNATURE': signature, + 'Content-Type': 'application/json', + }; + } + const url = this.urls['api'] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code < 400) { + return; + } + const ErrorClass = this.safeValue ({ + '401': AuthenticationError, + '429': RateLimitExceeded, + }, code, ExchangeError); + throw new ErrorClass (body); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],67:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InsufficientFunds, ExchangeError, InvalidNonce, InvalidOrder, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinfloor extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinfloor', + 'name': 'coinfloor', + 'rateLimit': 1000, + 'countries': [ 'UK' ], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchLedger': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153925-ef265e80-c2c0-11ea-91b5-020c804b90e0.jpg', + 'api': 'https://webapi.coinfloor.co.uk/bist', + 'www': 'https://www.coinfloor.co.uk', + 'doc': [ + 'https://github.com/coinfloor/api', + 'https://www.coinfloor.co.uk/api', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + 'password': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + '{id}/ticker/', + '{id}/order_book/', + '{id}/transactions/', + ], + }, + 'private': { + 'post': [ + '{id}/balance/', + '{id}/user_transactions/', + '{id}/open_orders/', + '{symbol}/cancel_order/', + '{id}/buy/', + '{id}/sell/', + '{id}/buy_market/', + '{id}/sell_market/', + '{id}/estimate_sell_market/', + '{id}/estimate_buy_market/', + ], + }, + }, + 'markets': { + 'BTC/GBP': { 'id': 'XBT/GBP', 'symbol': 'BTC/GBP', 'base': 'BTC', 'quote': 'GBP', 'baseId': 'XBT', 'quoteId': 'GBP', 'precision': { 'price': 0, 'amount': 4 }}, + 'BTC/EUR': { 'id': 'XBT/EUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'XBT', 'quoteId': 'EUR', 'precision': { 'price': 0, 'amount': 4 }}, + }, + 'exceptions': { + 'exact': { + 'You have insufficient funds.': InsufficientFunds, + 'Tonce is out of sequence.': InvalidNonce, + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + let market = undefined; + let query = params; + const symbol = this.safeString (params, 'symbol'); + if (symbol !== undefined) { + market = this.market (params['symbol']); + query = this.omit (params, 'symbol'); + } + const marketId = this.safeString (params, 'id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + if (market === undefined) { + throw new ArgumentsRequired (this.id + ' fetchBalance requires a symbol param'); + } + const request = { + 'id': market['id'], + }; + const response = await this.privatePostIdBalance (this.extend (request, query)); + const result = { + 'info': response, + }; + // base/quote used for keys e.g. "xbt_reserved" + const base = market['base']; + const quote = market['quote']; + const baseIdLower = this.safeStringLower (market, 'baseId'); + const quoteIdLower = this.safeStringLower (market, 'quoteId'); + result[base] = { + 'free': this.safeFloat (response, baseIdLower + '_available'), + 'used': this.safeFloat (response, baseIdLower + '_reserved'), + 'total': this.safeFloat (response, baseIdLower + '_balance'), + }; + result[quote] = { + 'free': this.safeFloat (response, quoteIdLower + '_available'), + 'used': this.safeFloat (response, quoteIdLower + '_reserved'), + 'total': this.safeFloat (response, quoteIdLower + '_balance'), + }; + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.publicGetIdOrderBook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTicker (ticker, market = undefined) { + // rewrite to get the timestamp from HTTP headers + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetIdTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetIdTransactions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + // code is actually a market symbol in this situation, not a currency code + await this.loadMarkets (); + let market = undefined; + if (code !== undefined) { + market = this.market (code); + if (market === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions requires a code argument (a market symbol)'); + } + } + const request = { + 'id': market['id'], + 'limit': limit, + }; + const response = await this.privatePostIdUserTransactions (this.extend (request, params)); + return this.parseLedger (response, undefined, since, undefined); + } + + parseLedgerEntryStatus (status) { + const types = { + 'completed': 'ok', + }; + return this.safeString (types, status, status); + } + + parseLedgerEntryType (type) { + const types = { + '0': 'transaction', // deposit + '1': 'transaction', // withdrawal + '2': 'trade', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // trade + // + // { + // "datetime": "2017-07-25 06:41:24", + // "id": 1500964884381265, + // "type": 2, + // "xbt": "0.1000", + // "xbt_eur": "2322.00", + // "eur": "-232.20", + // "fee": "0.00", + // "order_id": 84696745 + // } + // + // transaction (withdrawal) + // + // { + // "datetime": "2017-07-25 13:19:46", + // "id": 97669, + // "type": 1, + // "xbt": "-3.0000", + // "xbt_eur": null, + // "eur": "0", + // "fee": "0.0000", + // "order_id": null + // } + // + // transaction (deposit) + // + // { + // "datetime": "2017-07-27 16:44:55", + // "id": 98277, + // "type": 0, + // "xbt": "0", + // "xbt_eur": null, + // "eur": "4970.04", + // "fee": "0.00", + // "order_id": null + // } + // + const keys = Object.keys (item); + let baseId = undefined; + let quoteId = undefined; + let baseAmount = undefined; + let quoteAmount = undefined; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key.indexOf ('_') > 0) { + const parts = key.split ('_'); + const numParts = parts.length; + if (numParts === 2) { + const tmpBaseAmount = this.safeFloat (item, parts[0]); + const tmpQuoteAmount = this.safeFloat (item, parts[1]); + if (tmpBaseAmount !== undefined && tmpQuoteAmount !== undefined) { + baseId = parts[0]; + quoteId = parts[1]; + baseAmount = tmpBaseAmount; + quoteAmount = tmpQuoteAmount; + } + } + } + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const referenceId = this.safeString (item, 'id'); + const timestamp = this.parse8601 (this.safeString (item, 'datetime')); + let fee = undefined; + const feeCost = this.safeFloat (item, 'fee'); + const result = { + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'amount': undefined, + 'direction': undefined, + 'currency': undefined, + 'type': type, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'before': undefined, + 'after': undefined, + 'status': 'ok', + 'fee': fee, + 'info': item, + }; + if (type === 'trade') { + // + // it's a trade so let's make multiple entries, we have several options: + // + // if fee is always in quote currency (the exchange uses this) + // https://github.com/coinfloor/API/blob/master/IMPL-GUIDE.md#how-fees-affect-trade-quantities + // + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': quote, + }; + } + return [ + this.extend (result, { 'currency': base, 'amount': Math.abs (baseAmount), 'direction': (baseAmount > 0) ? 'in' : 'out' }), + this.extend (result, { 'currency': quote, 'amount': Math.abs (quoteAmount), 'direction': (quoteAmount > 0) ? 'in' : 'out', 'fee': fee }), + ]; + // + // if fee is base or quote depending on buy/sell side + // + // const baseFee = (baseAmount > 0) ? { 'currency': base, 'cost': feeCost } : undefined; + // const quoteFee = (quoteAmount > 0) ? { 'currency': quote, 'cost': feeCost } : undefined; + // return [ + // this.extend (result, { 'currency': base, 'amount': baseAmount, 'direction': (baseAmount > 0) ? 'in' : 'out', 'fee': baseFee }), + // this.extend (result, { 'currency': quote, 'amount': quoteAmount, 'direction': (quoteAmount > 0) ? 'in' : 'out', 'fee': quoteFee }), + // ]; + // + // fee as the 3rd item + // + // return [ + // this.extend (result, { 'currency': base, 'amount': baseAmount, 'direction': (baseAmount > 0) ? 'in' : 'out' }), + // this.extend (result, { 'currency': quote, 'amount': quoteAmount, 'direction': (quoteAmount > 0) ? 'in' : 'out' }), + // this.extend (result, { 'currency': feeCurrency, 'amount': feeCost, 'direction': 'out', 'type': 'fee' }), + // ]; + // + } else { + // + // it's a regular transaction (deposit or withdrawal) + // + const amount = (baseAmount === 0) ? quoteAmount : baseAmount; + const code = (baseAmount === 0) ? quote : base; + const direction = (amount > 0) ? 'in' : 'out'; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return this.extend (result, { + 'currency': code, + 'amount': Math.abs (amount), + 'direction': direction, + 'fee': fee, + }); + } + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + let method = 'privatePostId' + this.capitalize (side); + if (type === 'market') { + request['quantity'] = amount; + method += 'Market'; + } else { + request['price'] = price; + request['amount'] = amount; + } + // + // { + // "id":31950584, + // "datetime":"2020-05-21 08:38:18", + // "type":1, + // "price":"9100", + // "amount":"0.0026" + // } + // + const response = await this[method] (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (response, 'datetime')); + return { + 'id': this.safeString (response, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'type': type, + 'price': this.safeFloat (response, 'price'), + 'remaining': this.safeFloat (response, 'amount'), + 'info': response, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'id': id, + }; + const response = await this.privatePostSymbolCancelOrder (request); + if (response === 'false') { + // unfortunately the exchange does not give much info in the response + throw new InvalidOrder (this.id + ' cancel was rejected'); + } + return response; + } + + parseOrder (order, market = undefined) { + const timestamp = this.parse8601 (this.safeString (order, 'datetime')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let side = undefined; + const status = this.safeString (order, 'status'); + if (order['type'] === 0) { + side = 'buy'; + } else if (order['type'] === 1) { + side = 'sell'; + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (order, 'id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': amount, + 'cost': cost, + 'fee': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol param'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.privatePostIdOpenOrders (this.extend (request, params)); + // { + // "amount": "1.0000", + // "datetime": "2019-07-12 13:28:16", + // "id": 233123443, + // "price": "1000.00", + // "type": 0 + // } + return this.parseOrders (response, market, since, limit, { 'status': 'open' }); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code < 400) { + return; + } + if (response === undefined) { + return; + } + const message = this.safeString (response, 'error_msg'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (feedback); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + // curl -k -u '[User ID]/[API key]:[Passphrase]' https://webapi.coinfloor.co.uk:8090/bist/XBT/GBP/balance/ + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, query)); + const auth = this.uid + '/' + this.apiKey + ':' + this.password; + const signature = this.decode (this.stringToBase64 (auth)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],68:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coingi extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coingi', + 'name': 'Coingi', + 'rateLimit': 1000, + 'countries': [ 'PA', 'BG', 'CN', 'US' ], // Panama, Bulgaria, China, US + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'referral': 'https://www.coingi.com/?r=XTPPMC', + 'logo': 'https://user-images.githubusercontent.com/1294454/28619707-5c9232a8-7212-11e7-86d6-98fe5d15cc6e.jpg', + 'api': { + 'www': 'https://coingi.com', + 'current': 'https://api.coingi.com', + 'user': 'https://api.coingi.com', + }, + 'www': 'https://coingi.com', + 'doc': 'https://coingi.docs.apiary.io', + }, + 'api': { + 'www': { + 'get': [ + '', + ], + }, + 'current': { + 'get': [ + 'order-book/{pair}/{askCount}/{bidCount}/{depth}', + 'transactions/{pair}/{maxCount}', + '24hour-rolling-aggregation', + ], + }, + 'user': { + 'post': [ + 'balance', + 'add-order', + 'cancel-order', + 'orders', + 'transactions', + 'create-crypto-withdrawal', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.2 / 100, + 'maker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.001, + 'LTC': 0.01, + 'DOGE': 2, + 'PPC': 0.02, + 'VTC': 0.2, + 'NMC': 2, + 'DASH': 0.002, + 'USD': 10, + 'EUR': 10, + }, + 'deposit': { + 'BTC': 0, + 'LTC': 0, + 'DOGE': 0, + 'PPC': 0, + 'VTC': 0, + 'NMC': 0, + 'DASH': 0, + 'USD': 5, + 'EUR': 1, + }, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.wwwGet (params); + const parts = response.split ('do=currencyPairSelector-selectCurrencyPair" class="active">'); + const currencyParts = parts[1].split ('
'); + const result = []; + for (let i = 1; i < currencyParts.length; i++) { + const currencyPart = currencyParts[i]; + const idParts = currencyPart.split ('
'); + let id = idParts[0]; + id = id.replace ('/', '-'); + id = id.toLowerCase (); + const [ baseId, quoteId ] = id.split ('-'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': id, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const lowercaseCurrencies = []; + const currencies = Object.keys (this.currencies); + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + lowercaseCurrencies.push (currency.toLowerCase ()); + } + const request = { + 'currencies': lowercaseCurrencies.join (','), + }; + const response = await this.userPostBalance (this.extend (request, params)); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance['currency'], 'name'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + const blocked = this.safeFloat (balance, 'blocked'); + const inOrders = this.safeFloat (balance, 'inOrders'); + const withdrawing = this.safeFloat (balance, 'withdrawing'); + account['used'] = this.sum (blocked, inOrders, withdrawing); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = 512, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'depth': 32, // maximum number of depth range steps 1-32 + 'askCount': limit, // maximum returned number of asks 1-512 + 'bidCount': limit, // maximum returned number of bids 1-512 + }; + const orderbook = await this.currentGetOrderBookPairAskCountBidCountDepth (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'baseAmount'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': undefined, + 'last': undefined, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume'), + 'quoteVolume': this.safeFloat (ticker, 'counterVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.currentGet24hourRollingAggregation (params); + const result = {}; + for (let t = 0; t < response.length; t++) { + const ticker = response[t]; + const base = ticker['currencyPair']['base'].toUpperCase (); + const quote = ticker['currencyPair']['counter'].toUpperCase (); + const symbol = base + '/' + quote; + let market = undefined; + if (symbol in this.markets) { + market = this.markets[symbol]; + } + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const tickers = await this.fetchTickers (undefined, params); + if (symbol in tickers) { + return tickers[symbol]; + } + throw new ExchangeError (this.id + ' return did not contain ' + symbol); + } + + parseTrade (trade, market = undefined) { + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const timestamp = this.safeInteger (trade, 'timestamp'); + const id = this.safeString (trade, 'id'); + const marketId = this.safeString (trade, 'currencyPair'); + const symbol = this.safeSymbol (marketId, market); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, // type + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'maxCount': 128, + }; + const response = await this.currentGetTransactionsPairMaxCount (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + 'volume': amount, + 'price': price, + 'orderType': (side === 'buy') ? 0 : 1, + }; + const response = await this.userPostAddOrder (this.extend (request, params)); + return { + 'info': response, + 'id': response['result'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + return await this.userPostCancelOrder (this.extend (request, params)); + } + + sign (path, api = 'current', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api !== 'www') { + url += '/' + api + '/' + this.implodeParams (path, params); + } + const query = this.omit (params, this.extractParams (path)); + if (api === 'current') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'user') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = this.extend ({ + 'token': this.apiKey, + 'nonce': nonce, + }, query); + const auth = nonce.toString () + '$' + this.apiKey; + request['signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.json (request); + headers = { + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'current', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (typeof response !== 'string') { + if ('errors' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],69:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinmarketcap extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinmarketcap', + 'name': 'CoinMarketCap', + 'rateLimit': 10000, + 'version': 'v1', + 'countries': [ 'US' ], + 'has': { + 'cancelOrder': false, + 'CORS': true, + 'createLimitOrder': false, + 'createMarketOrder': false, + 'createOrder': false, + 'editOrder': false, + 'privateAPI': false, + 'fetchBalance': false, + 'fetchCurrencies': true, + 'fetchL2OrderBook': false, + 'fetchMarkets': true, + 'fetchOHLCV': false, + 'fetchOrderBook': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg', + 'api': { + 'public': 'https://api.coinmarketcap.com', + 'files': 'https://files.coinmarketcap.com', + 'charts': 'https://graph.coinmarketcap.com', + }, + 'www': 'https://coinmarketcap.com', + 'doc': 'https://coinmarketcap.com/api', + }, + 'requiredCredentials': { + 'apiKey': false, + 'secret': false, + }, + 'api': { + 'files': { + 'get': [ + 'generated/stats/global.json', + ], + }, + 'graphs': { + 'get': [ + 'currencies/{name}/', + ], + }, + 'public': { + 'get': [ + 'ticker/', + 'ticker/{id}/', + 'global/', + ], + }, + }, + 'currencyCodes': [ + 'AUD', + 'BRL', + 'CAD', + 'CHF', + 'CNY', + 'EUR', + 'GBP', + 'HKD', + 'IDR', + 'INR', + 'JPY', + 'KRW', + 'MXN', + 'RUB', + 'USD', + 'BTC', + 'ETH', + 'LTC', + ], + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + throw new ExchangeError ('Fetching order books is not supported by the API of ' + this.id); + } + + currencyCode (base, name) { + const currencies = { + 'ACChain': 'ACChain', + 'AdCoin': 'AdCoin', + 'BatCoin': 'BatCoin', + 'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE) + 'Bitgem': 'Bitgem', + 'BlazeCoin': 'BlazeCoin', + 'BlockCAT': 'BlockCAT', + 'Blocktrade Token': 'Blocktrade Token', + 'BOX Token': 'BOX Token', // conflict with BOX (ContentBox) + 'Catcoin': 'Catcoin', + 'CanYaCoin': 'CanYaCoin', // conflict with CAN (Content and AD Network) + 'CryptoBossCoin': 'CryptoBossCoin', // conflict with CBC (CashBet Coin) + 'Comet': 'Comet', // conflict with CMT (CyberMiles) + 'CPChain': 'CPChain', + 'CrowdCoin': 'CrowdCoin', // conflict with CRC CryCash + 'Cryptaur': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081 + 'Cubits': 'Cubits', // conflict with QBT (Qbao) + 'DAO.Casino': 'DAO.Casino', // conflict with BET (BetaCoin) + 'DefiBox': 'DefiBox', // conflict with BOX (ContentBox) + 'E-Dinar Coin': 'E-Dinar Coin', // conflict with EDR Endor Protocol and EDRCoin + 'EDRcoin': 'EDRcoin', // conflict with EDR Endor Protocol and E-Dinar Coin + 'ENTCash': 'ENTCash', // conflict with ENT (Eternity) + 'FairCoin': 'FairCoin', // conflict with FAIR (FairGame) https://github.com/ccxt/ccxt/pull/5865 + 'Fabric Token': 'Fabric Token', + // 'GET Protocol': 'GET Protocol', + 'GHOSTPRISM': 'GHOSTPRISM', // conflict with GHOST + 'Global Tour Coin': 'Global Tour Coin', // conflict with GTC (Game.com) + 'GuccioneCoin': 'GuccioneCoin', // conflict with GCC (Global Cryptocurrency) + 'HarmonyCoin': 'HarmonyCoin', // conflict with HMC (Hi Mutual Society) + 'Harvest Masternode Coin': 'Harvest Masternode Coin', // conflict with HC (HyperCash) + 'HOT Token': 'HOT Token', + 'Hydro Protocol': 'Hydro Protocol', // conflict with HOT (Holo) + 'Huncoin': 'Huncoin', // conflict with HNC (Helleniccoin) + 'iCoin': 'iCoin', + 'Infinity Economics': 'Infinity Economics', // conflict with XIN (Mixin) + 'IQ.cash': 'IQ.cash', // conflict with IQ (Everipedia) + 'KingN Coin': 'KingN Coin', // conflict with KNC (Kyber Network) + 'LiteBitcoin': 'LiteBitcoin', // conflict with LBTC (LightningBitcoin) + 'Maggie': 'Maggie', + 'Menlo One': 'Menlo One', // conflict with Harmony (ONE) + 'Mobilian Coin': 'Mobilian Coin', // conflict with Membrana (MBN) + 'Monarch': 'Monarch', // conflict with MyToken (MT) + 'MTC Mesh Network': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025 + 'IOTA': 'IOTA', // a special case, most exchanges list it as IOTA, therefore we change just the Coinmarketcap instead of changing them all + 'NetCoin': 'NetCoin', + 'PCHAIN': 'PCHAIN', // conflict with PAI (Project Pai) + 'Penta': 'Penta', // conflict with PNT (pNetwork) + 'Plair': 'Plair', // conflict with PLA (PLANET) + 'PlayChip': 'PlayChip', // conflict with PLA (PLANET) + 'Polcoin': 'Polcoin', + 'PutinCoin': 'PutinCoin', // conflict with PUT (Profile Utility Token) + 'Rcoin': 'Rcoin', // conflict with RCN (Ripio Credit Network) + // https://github.com/ccxt/ccxt/issues/6081 + // https://github.com/ccxt/ccxt/issues/3365 + // https://github.com/ccxt/ccxt/issues/2873 + 'TerraCredit': 'TerraCredit', // conflict with CREDIT (PROXI) + 'Themis': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol) + 'UNI COIN': 'UNI COIN', // conflict with UNI (Uniswap) + 'UNICORN Token': 'UNICORN Token', // conflict with UNI (Uniswap) + 'Universe': 'Universe', // conflict with UNI (Uniswap) + }; + return this.safeValue (currencies, name, base); + } + + async fetchMarkets (params = {}) { + const request = { + 'limit': 0, + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const currencies = this.currencyCodes; + for (let j = 0; j < currencies.length; j++) { + const quote = currencies[j]; + const quoteId = quote.toLowerCase (); + const baseId = market['id']; + const base = this.currencyCode (market['symbol'], market['name']); + const symbol = base + '/' + quote; + const id = baseId + '/' + quoteId; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + } + return result; + } + + async fetchGlobal (currency = 'USD') { + await this.loadMarkets (); + const request = {}; + if (currency) { + request['convert'] = currency; + } + return await this.publicGetGlobal (request); + } + + parseTicker (ticker, market = undefined) { + let timestamp = this.safeTimestamp (ticker, 'last_updated'); + if (timestamp === undefined) { + timestamp = this.milliseconds (); + } + const change = this.safeFloat (ticker, 'percent_change_24h'); + let last = undefined; + let symbol = undefined; + let volume = undefined; + if (market !== undefined) { + symbol = market['symbol']; + const priceKey = 'price_' + market['quoteId']; + last = this.safeFloat (ticker, priceKey); + const volumeKey = '24h_volume_' + market['quoteId']; + volume = this.safeFloat (ticker, volumeKey); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': change, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': volume, + 'info': ticker, + }; + } + + async fetchTickers (currency = 'USD', params = {}) { + await this.loadMarkets (); + const request = { + 'limit': 10000, + }; + if (currency) { + request['convert'] = currency; + } + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + for (let t = 0; t < response.length; t++) { + const ticker = response[t]; + const currencyId = currency.toLowerCase (); + const id = ticker['id'] + '/' + currencyId; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } + result[symbol] = this.parseTicker (ticker, market); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'convert': market['quote'], + 'id': market['baseId'], + }; + const response = await this.publicGetTickerId (this.extend (request, params)); + const ticker = response[0]; + return this.parseTicker (ticker, market); + } + + async fetchCurrencies (params = {}) { + const request = { + 'limit': 0, + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const name = this.safeString (currency, 'name'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const precision = 8; // default precision, todo: fix "magic constants" + const code = this.currencyCode (id, name); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': true, + 'fee': undefined, // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('error' in response) { + if (response['error']) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],70:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InvalidOrder, OrderNotFound, RateLimitExceeded, InsufficientFunds } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinmate extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinmate', + 'name': 'CoinMate', + 'countries': [ 'GB', 'CZ', 'EU' ], // UK, Czech Republic + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg', + 'api': 'https://coinmate.io/api', + 'www': 'https://coinmate.io', + 'fees': 'https://coinmate.io/fees', + 'doc': [ + 'https://coinmate.docs.apiary.io', + 'https://coinmate.io/developers', + ], + 'referral': 'https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'orderBook', + 'ticker', + 'transactions', + 'tradingPairs', + ], + }, + 'private': { + 'post': [ + 'balances', + 'bitcoinCashWithdrawal', + 'bitcoinCashDepositAddresses', + 'bitcoinDepositAddresses', + 'bitcoinWithdrawal', + 'bitcoinWithdrawalFees', + 'buyInstant', + 'buyLimit', + 'cancelOrder', + 'cancelOrderWithInfo', + 'createVoucher', + 'dashDepositAddresses', + 'dashWithdrawal', + 'ethereumWithdrawal', + 'ethereumDepositAddresses', + 'litecoinWithdrawal', + 'litecoinDepositAddresses', + 'openOrders', + 'order', + 'orderHistory', + 'orderById', + 'pusherAuth', + 'redeemVoucher', + 'replaceByBuyLimit', + 'replaceByBuyInstant', + 'replaceBySellLimit', + 'replaceBySellInstant', + 'rippleDepositAddresses', + 'rippleWithdrawal', + 'sellInstant', + 'sellLimit', + 'transactionHistory', + 'traderFees', + 'tradeHistory', + 'transfer', + 'transferHistory', + 'unconfirmedBitcoinDeposits', + 'unconfirmedBitcoinCashDeposits', + 'unconfirmedDashDeposits', + 'unconfirmedEthereumDeposits', + 'unconfirmedLitecoinDeposits', + 'unconfirmedRippleDeposits', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.12 / 100, + 'taker': 0.25 / 100, + 'tiers': { + 'taker': [ + [0, 0.25 / 100], + [10000, 0.23 / 100], + [100000, 0.21 / 100], + [250000, 0.20 / 100], + [500000, 0.15 / 100], + [1000000, 0.13 / 100], + [3000000, 0.10 / 100], + [15000000, 0.05 / 100], + ], + 'maker': [ + [0, 0.12 / 100], + [10000, 0.11 / 100], + [1000000, 0.10 / 100], + [250000, 0.08 / 100], + [500000, 0.05 / 100], + [1000000, 0.03 / 100], + [3000000, 0.02 / 100], + [15000000, 0], + ], + }, + }, + 'promotional': { + 'trading': { + 'maker': 0.05 / 100, + 'taker': 0.15 / 100, + 'tiers': { + 'taker': [ + [0, 0.15 / 100], + [10000, 0.14 / 100], + [100000, 0.13 / 100], + [250000, 0.12 / 100], + [500000, 0.11 / 100], + [1000000, 0.1 / 100], + [3000000, 0.08 / 100], + [15000000, 0.05 / 100], + ], + 'maker': [ + [0, 0.05 / 100], + [10000, 0.04 / 100], + [1000000, 0.03 / 100], + [250000, 0.02 / 100], + [500000, 0], + [1000000, 0], + [3000000, 0], + [15000000, 0], + ], + }, + }, + }, + }, + 'options': { + 'promotionalMarkets': ['ETH/EUR', 'ETH/CZK', 'ETH/BTC', 'XRP/EUR', 'XRP/CZK', 'XRP/BTC', 'DASH/EUR', 'DASH/CZK', 'DASH/BTC', 'BCH/EUR', 'BCH/CZK', 'BCH/BTC'], + }, + 'exceptions': { + 'exact': { + 'No order with given ID': OrderNotFound, + }, + 'broad': { + 'Not enough account balance available': InsufficientFunds, + 'Incorrect order ID': InvalidOrder, + 'Minimum Order Size ': InvalidOrder, + 'TOO MANY REQUESTS': RateLimitExceeded, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTradingPairs (params); + // + // { + // "error":false, + // "errorMessage":null, + // "data": [ + // { + // "name":"BTC_EUR", + // "firstCurrency":"BTC", + // "secondCurrency":"EUR", + // "priceDecimals":2, + // "lotDecimals":8, + // "minAmount":0.0002, + // "tradesWebSocketChannelId":"trades-BTC_EUR", + // "orderBookWebSocketChannelId":"order_book-BTC_EUR", + // "tradeStatisticsWebSocketChannelId":"statistics-BTC_EUR" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data'); + const result = []; + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'firstCurrency'); + const quoteId = this.safeString (market, 'secondCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const promotionalMarkets = this.safeValue (this.options, 'promotionalMarkets', []); + let fees = this.safeValue (this.fees, 'trading'); + if (this.inArray (symbol, promotionalMarkets)) { + const promotionalFees = this.safeValue (this.fees, 'promotional', {}); + fees = this.safeValue (promotionalFees, 'trading', fees); + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'maker': fees['maker'], + 'taker': fees['taker'], + 'info': market, + 'precision': { + 'price': this.safeInteger (market, 'priceDecimals'), + 'amount': this.safeInteger (market, 'lotDecimals'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalances (params); + const balances = this.safeValue (response, 'data'); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (balances, currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'reserved'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + 'groupByPriceLimit': 'False', + }; + const response = await this.publicGetOrderBook (this.extend (request, params)); + const orderbook = response['data']; + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'data'); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'vwap': undefined, + 'askVolume': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'amount'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'limit': 1000, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timestampFrom'] = since; + } + if (code !== undefined) { + request['currency'] = this.currencyId (code); + } + const response = await this.privatePostTransferHistory (this.extend (request, params)); + const items = response['data']; + return this.parseTransactions (items, undefined, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + // any other types ? + 'COMPLETED': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (item, currency = undefined) { + // + // deposits + // + // { + // transactionId: 1862815, + // timestamp: 1516803982388, + // amountCurrency: 'LTC', + // amount: 1, + // fee: 0, + // walletType: 'LTC', + // transferType: 'DEPOSIT', + // transferStatus: 'COMPLETED', + // txid: + // 'ccb9255dfa874e6c28f1a64179769164025329d65e5201849c2400abd6bce245', + // destination: 'LQrtSKA6LnhcwRrEuiborQJnjFF56xqsFn', + // destinationTag: null + // } + // + // withdrawals + // + // { + // transactionId: 2140966, + // timestamp: 1519314282976, + // amountCurrency: 'EUR', + // amount: 8421.7228, + // fee: 16.8772, + // walletType: 'BANK_WIRE', + // transferType: 'WITHDRAWAL', + // transferStatus: 'COMPLETED', + // txid: null, + // destination: null, + // destinationTag: null + // } + // + const timestamp = this.safeInteger (item, 'timestamp'); + const amount = this.safeFloat (item, 'amount'); + const fee = this.safeFloat (item, 'fee'); + const txid = this.safeString (item, 'txid'); + const address = this.safeString (item, 'destination'); + const tag = this.safeString (item, 'destinationTag'); + const currencyId = this.safeString (item, 'amountCurrency'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeStringLower (item, 'transferType'); + const status = this.parseTransactionStatus (this.safeString (item, 'transferStatus')); + const id = this.safeString (item, 'transactionId'); + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'currency': code, + 'amount': amount, + 'type': type, + 'txid': txid, + 'address': address, + 'tag': tag, + 'status': status, + 'fee': { + 'cost': fee, + 'currency': code, + }, + 'info': item, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 1000; + } + const request = { + 'limit': limit, + }; + if (symbol !== undefined) { + const market = this.market (symbol); + request['currencyPair'] = market['id']; + } + if (since !== undefined) { + request['timestampFrom'] = since; + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const items = response['data']; + return this.parseTrades (items, undefined, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchMyTrades (private) + // + // { + // transactionId: 2671819, + // createdTimestamp: 1529649127605, + // currencyPair: 'LTC_BTC', + // type: 'BUY', + // orderType: 'LIMIT', + // orderId: 101810227, + // amount: 0.01, + // price: 0.01406, + // fee: 0, + // feeType: 'MAKER' + // } + // + // fetchTrades (public) + // + // { + // "timestamp":1561598833416, + // "transactionId":"4156303", + // "price":10950.41, + // "amount":0.004, + // "currencyPair":"BTC_EUR", + // "tradeType":"BUY" + // } + // + const marketId = this.safeString (trade, 'currencyPair'); + market = this.safeMarket (marketId, market, '_'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const side = this.safeStringLower2 (trade, 'type', 'tradeType'); + const type = this.safeStringLower (trade, 'orderType'); + const orderId = this.safeString (trade, 'orderId'); + const id = this.safeString (trade, 'transactionId'); + const timestamp = this.safeInteger2 (trade, 'timestamp', 'createdTimestamp'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + let takerOrMaker = this.safeString (trade, 'feeType'); + takerOrMaker = (takerOrMaker === 'MAKER') ? 'maker' : 'taker'; + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'type': type, + 'side': side, + 'order': orderId, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'minutesIntoHistory': 10, + }; + const response = await this.publicGetTransactions (this.extend (request, params)); + // + // { + // "error":false, + // "errorMessage":null, + // "data":[ + // { + // "timestamp":1561598833416, + // "transactionId":"4156303", + // "price":10950.41, + // "amount":0.004, + // "currencyPair":"BTC_EUR", + // "tradeType":"BUY" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.privatePostOpenOrders (this.extend ({}, params)); + const extension = { 'status': 'open' }; + return this.parseOrders (response['data'], undefined, since, limit, extension); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + // offset param that appears in other parts of the API doesn't appear to be supported here + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostOrderHistory (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'FILLED': 'closed', + 'CANCELLED': 'canceled', + 'PARTIALLY_FILLED': 'open', + 'OPEN': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrderType (type) { + const types = { + 'LIMIT': 'limit', + 'MARKET': 'market', + }; + return this.safeString (types, type, type); + } + + parseOrder (order, market = undefined) { + // + // limit sell + // + // { + // id: 781246605, + // timestamp: 1584480015133, + // trailingUpdatedTimestamp: null, + // type: 'SELL', + // currencyPair: 'ETH_BTC', + // price: 0.0345, + // amount: 0.01, + // stopPrice: null, + // originalStopPrice: null, + // marketPriceAtLastUpdate: null, + // marketPriceAtOrderCreation: null, + // orderTradeType: 'LIMIT', + // hidden: false, + // trailing: false, + // clientOrderId: null + // } + // + // limit buy + // + // { + // id: 67527001, + // timestamp: 1517931722613, + // trailingUpdatedTimestamp: null, + // type: 'BUY', + // price: 5897.24, + // remainingAmount: 0.002367, + // originalAmount: 0.1, + // stopPrice: null, + // originalStopPrice: null, + // marketPriceAtLastUpdate: null, + // marketPriceAtOrderCreation: null, + // status: 'CANCELLED', + // orderTradeType: 'LIMIT', + // hidden: false, + // avgPrice: null, + // trailing: false, + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.safeInteger (order, 'timestamp'); + const side = this.safeStringLower (order, 'type'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'originalAmount'); + let remaining = this.safeFloat (order, 'remainingAmount'); + if (remaining === undefined) { + remaining = this.safeFloat (order, 'amount'); + } + let status = this.parseOrderStatus (this.safeString (order, 'status')); + const type = this.parseOrderType (this.safeString (order, 'orderTradeType')); + let filled = undefined; + let cost = undefined; + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (amount - remaining, 0); + if (remaining === 0) { + status = 'closed'; + } + if (price !== undefined) { + cost = filled * price; + } + } + const average = this.safeFloat (order, 'avgPrice'); + const marketId = this.safeString (order, 'currencyPair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'trades': undefined, + 'info': order, + 'fee': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let method = 'privatePost' + this.capitalize (side); + const request = { + 'currencyPair': this.marketId (symbol), + }; + if (type === 'market') { + if (side === 'buy') { + request['total'] = this.amountToPrecision (symbol, amount); // amount in fiat + } else { + request['amount'] = this.amountToPrecision (symbol, amount); // amount in fiat + } + method += 'Instant'; + } else { + request['amount'] = this.amountToPrecision (symbol, amount); // amount in crypto + request['price'] = this.priceToPrecision (symbol, price); + method += this.capitalize (type); + } + const response = await this[method] (this.extend (request, params)); + const id = this.safeString (response, 'data'); + return { + 'info': response, + 'id': id, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + let market = undefined; + if (symbol) { + market = this.market (symbol); + } + const response = await this.privatePostOrderById (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + // {"error":false,"errorMessage":null,"data":{"success":true,"remainingAmount":0.01}} + const request = { 'orderId': id }; + const response = await this.privatePostCancelOrderWithInfo (this.extend (request, params)); + return { + 'info': response, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.urlencode (this.extend ({ + 'clientId': this.uid, + 'nonce': nonce, + 'publicKey': this.apiKey, + 'signature': signature.toUpperCase (), + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response !== undefined) { + if ('error' in response) { + // {"error":true,"errorMessage":"Minimum Order Size 0.01 ETH","data":null} + if (response['error']) { + const message = this.safeString (response, 'errorMessage'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + } + if (code > 400) { + if (body) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], body, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); // unknown message + } + throw new ExchangeError (this.id + ' ' + body); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],71:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, BadRequest, ExchangeError, ArgumentsRequired, OrderNotFound, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinone extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinone', + 'name': 'CoinOne', + 'countries': [ 'KR' ], // Korea + // 'enableRateLimit': false, + 'rateLimit': 667, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + // https://github.com/ccxt/ccxt/pull/7067 + // the endpoint that should return closed orders actually returns trades + 'fetchClosedOrders': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/38003300-adc12fba-323f-11e8-8525-725f53c4a659.jpg', + 'api': 'https://api.coinone.co.kr', + 'www': 'https://coinone.co.kr', + 'doc': 'https://doc.coinone.co.kr', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'api': { + 'public': { + 'get': [ + 'orderbook/', + 'trades/', + 'ticker/', + ], + }, + 'private': { + 'post': [ + 'account/btc_deposit_address/', + 'account/balance/', + 'account/daily_balance/', + 'account/user_info/', + 'account/virtual_account/', + 'order/cancel_all/', + 'order/cancel/', + 'order/limit_buy/', + 'order/limit_sell/', + 'order/complete_orders/', + 'order/limit_orders/', + 'order/order_info/', + 'transaction/auth_number/', + 'transaction/history/', + 'transaction/krw/history/', + 'transaction/btc/', + 'transaction/coin/', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.002, + }, + }, + 'precision': { + 'price': 4, + 'amount': 4, + 'cost': 8, + }, + 'exceptions': { + '405': OnMaintenance, // {"errorCode":"405","status":"maintenance","result":"error"} + '104': OrderNotFound, // {"errorCode":"104","errorMsg":"Order id is not exist","result":"error"} + '108': BadSymbol, // {"errorCode":"108","errorMsg":"Unknown CryptoCurrency","result":"error"} + '107': BadRequest, // {"errorCode":"107","errorMsg":"Parameter error","result":"error"} + }, + }); + } + + async fetchMarkets (params = {}) { + const request = { + 'currency': 'all', + }; + const response = await this.publicGetTicker (request); + const result = []; + const quoteId = 'krw'; + const quote = this.safeCurrencyCode (quoteId); + const baseIds = Object.keys (response); + for (let i = 0; i < baseIds.length; i++) { + const baseId = baseIds[i]; + const ticker = this.safeValue (response, baseId, {}); + const currency = this.safeValue (ticker, 'currency'); + if (currency === undefined) { + continue; + } + const base = this.safeCurrencyCode (baseId); + result.push ({ + 'id': baseId, + 'symbol': base + '/' + quote, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostAccountBalance (params); + const result = { 'info': response }; + const balances = this.omit (response, [ + 'errorCode', + 'result', + 'normalWallets', + ]); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'avail'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'format': 'json', + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp, 'bid', 'ask', 'price', 'qty'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': 'all', + 'format': 'json', + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + const ids = Object.keys (response); + const timestamp = this.safeTimestamp (response, 'timestamp'); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + const ticker = response[id]; + result[symbol] = this.parseTicker (ticker, market); + result[symbol]['timestamp'] = timestamp; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'format': 'json', + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const first = this.safeFloat (ticker, 'first'); + const last = this.safeFloat (ticker, 'last'); + let average = undefined; + if (first !== undefined && last !== undefined) { + average = this.sum (first, last) / 2; + } + const previousClose = this.safeFloat (ticker, 'yesterday_last'); + let change = undefined; + let percentage = undefined; + if (last !== undefined && previousClose !== undefined) { + change = last - previousClose; + if (previousClose !== 0) { + percentage = change / previousClose * 100; + } + } + const symbol = (market !== undefined) ? market['symbol'] : undefined; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': first, + 'close': last, + 'last': last, + 'previousClose': previousClose, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "timestamp": "1416893212", + // "price": "420000.0", + // "qty": "0.1", + // "is_ask": "1" + // } + // + // fetchMyTrades (private) + // + // { + // "timestamp": "1416561032", + // "price": "419000.0", + // "type": "bid", + // "qty": "0.001", + // "feeRate": "-0.0015", + // "fee": "-0.0000015", + // "orderId": "E84A1AC2-8088-4FA0-B093-A3BCDB9B3C85" + // } + // + const timestamp = this.safeTimestamp (trade, 'timestamp'); + const symbol = (market !== undefined) ? market['symbol'] : undefined; + const is_ask = this.safeString (trade, 'is_ask'); + let side = this.safeString (trade, 'type'); + if (is_ask !== undefined) { + if (is_ask === '1') { + side = 'sell'; + } else if (is_ask === '0') { + side = 'buy'; + } + } else { + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'qty'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const orderId = this.safeString (trade, 'orderId'); + let feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + let feeRate = this.safeFloat (trade, 'feeRate'); + feeRate = Math.abs (feeRate); + let feeCurrencyCode = undefined; + if (market !== undefined) { + feeCurrencyCode = (side === 'sell') ? market['quote'] : market['base']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + } + return { + 'id': this.safeString (trade, 'id'), + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'format': 'json', + }; + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "timestamp": "1416895635", + // "currency": "btc", + // "completeOrders": [ + // { + // "timestamp": "1416893212", + // "price": "420000.0", + // "qty": "0.1", + // "is_ask": "1" + // } + // ] + // } + // + const completeOrders = this.safeValue (response, 'completeOrders', []); + return this.parseTrades (completeOrders, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const request = { + 'price': price, + 'currency': this.marketId (symbol), + 'qty': amount, + }; + const method = 'privatePostOrder' + this.capitalize (type) + this.capitalize (side); + const response = await this[method] (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "orderId": "8a82c561-40b4-4cb3-9bc0-9ac9ffc1d63b" + // } + // + return this.parseOrder (response); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'currency': market['id'], + }; + const response = await this.privatePostOrderOrderInfo (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "status": "live", + // "info": { + // "orderId": "32FF744B-D501-423A-8BA1-05BB6BE7814A", + // "currency": "BTC", + // "type": "bid", + // "price": "2922000.0", + // "qty": "115.4950", + // "remainQty": "45.4950", + // "feeRate": "0.0003", + // "fee": "0", + // "timestamp": "1499340941" + // } + // } + // + const info = this.safeValue (response, 'info', {}); + info['status'] = this.safeString (info, 'status'); + return this.parseOrder (info, market); + } + + parseOrderStatus (status) { + const statuses = { + 'live': 'open', + 'partially_filled': 'open', + 'filled': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "result": "success", + // "errorCode": "0", + // "orderId": "8a82c561-40b4-4cb3-9bc0-9ac9ffc1d63b" + // } + // + // fetchOrder + // + // { + // "status": "live", // injected in fetchOrder + // "orderId": "32FF744B-D501-423A-8BA1-05BB6BE7814A", + // "currency": "BTC", + // "type": "bid", + // "price": "2922000.0", + // "qty": "115.4950", + // "remainQty": "45.4950", + // "feeRate": "0.0003", + // "fee": "0", + // "timestamp": "1499340941" + // } + // + // fetchOpenOrders + // + // { + // "index": "0", + // "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489", + // "timestamp": "1449037367", + // "price": "444000.0", + // "qty": "0.3456", + // "type": "ask", + // "feeRate": "-0.0015" + // } + // + const id = this.safeString (order, 'orderId'); + const price = this.safeFloat (order, 'price'); + const timestamp = this.safeTimestamp (order, 'timestamp'); + let side = this.safeString (order, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const remaining = this.safeFloat (order, 'remainQty'); + let filled = undefined; + const amount = this.safeFloat (order, 'qty'); + let status = this.safeString (order, 'status'); + // https://github.com/ccxt/ccxt/pull/7067 + if (status === 'live') { + if ((remaining !== undefined) && (amount !== undefined)) { + if (remaining < amount) { + status = 'canceled'; + } + } + } + if ((remaining !== undefined) && (amount !== undefined)) { + filled = Math.max (amount - remaining); + } + status = this.parseOrderStatus (status); + let cost = undefined; + if ((price !== undefined) && (filled !== undefined)) { + cost = price * filled; + } + let symbol = undefined; + let base = undefined; + let quote = undefined; + const marketId = this.safeStringLower (order, 'currency'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + base = this.safeCurrencyCode (marketId); + quote = 'KRW'; + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = (side === 'sell') ? quote : base; + fee = { + 'cost': feeCost, + 'rate': this.safeFloat (order, 'feeRate'), + 'currency': feeCurrencyCode, + }; + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': amount, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // The returned amount might not be same as the ordered amount. If an order is partially filled, the returned amount means the remaining amount. + // For the same reason, the returned amount and remaining are always same, and the returned filled and cost are always zero. + if (symbol === undefined) { + throw new ExchangeError (this.id + ' allows fetching closed orders with a specific symbol'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.privatePostOrderLimitOrders (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "limitOrders": [ + // { + // "index": "0", + // "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489", + // "timestamp": "1449037367", + // "price": "444000.0", + // "qty": "0.3456", + // "type": "ask", + // "feeRate": "-0.0015" + // } + // ] + // } + // + const limitOrders = this.safeValue (response, 'limitOrders', []); + return this.parseOrders (limitOrders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.privatePostOrderCompleteOrders (this.extend (request, params)); + // + // despite the name of the endpoint it returns trades which may have a duplicate orderId + // https://github.com/ccxt/ccxt/pull/7067 + // + // { + // "result": "success", + // "errorCode": "0", + // "completeOrders": [ + // { + // "timestamp": "1416561032", + // "price": "419000.0", + // "type": "bid", + // "qty": "0.001", + // "feeRate": "-0.0015", + // "fee": "-0.0000015", + // "orderId": "E84A1AC2-8088-4FA0-B093-A3BCDB9B3C85" + // } + // ] + // } + // + const completeOrders = this.safeValue (response, 'completeOrders', []); + return this.parseTrades (completeOrders, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + // eslint-disable-next-line quotes + throw new ArgumentsRequired (this.id + " cancelOrder requires a symbol argument. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder."); + } + const price = this.safeFloat (params, 'price'); + const qty = this.safeFloat (params, 'qty'); + const isAsk = this.safeInteger (params, 'is_ask'); + if ((price === undefined) || (qty === undefined) || (isAsk === undefined)) { + // eslint-disable-next-line quotes + throw new ArgumentsRequired (this.id + " cancelOrder requires {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument."); + } + await this.loadMarkets (); + const request = { + 'order_id': id, + 'price': price, + 'qty': qty, + 'is_ask': isAsk, + 'currency': this.marketId (symbol), + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0" + // } + // + return response; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + '/'; + if (api === 'public') { + url += request; + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + url += this.version + '/' + request; + const nonce = this.nonce ().toString (); + const json = this.json (this.extend ({ + 'access_token': this.apiKey, + 'nonce': nonce, + }, params)); + const payload = this.stringToBase64 (json); + body = this.decode (payload); + const secret = this.secret.toUpperCase (); + const signature = this.hmac (payload, this.encode (secret), 'sha512'); + headers = { + 'Content-Type': 'application/json', + 'X-COINONE-PAYLOAD': payload, + 'X-COINONE-SIGNATURE': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if ('result' in response) { + const result = response['result']; + if (result !== 'success') { + // + // { "errorCode": "405", "status": "maintenance", "result": "error"} + // + const errorCode = this.safeString (response, 'errorCode'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + } else { + throw new ExchangeError (this.id + ' ' + body); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],72:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinspot extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinspot', + 'name': 'CoinSpot', + 'countries': [ 'AU' ], // Australia + 'rateLimit': 1000, + 'has': { + 'cancelOrder': false, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28208429-3cacdf9a-6896-11e7-854e-4c79a772a30f.jpg', + 'api': { + 'public': 'https://www.coinspot.com.au/pubapi', + 'private': 'https://www.coinspot.com.au/api', + }, + 'www': 'https://www.coinspot.com.au', + 'doc': 'https://www.coinspot.com.au/api', + 'referral': 'https://www.coinspot.com.au/register?code=PJURCU', + }, + 'api': { + 'public': { + 'get': [ + 'latest', + ], + }, + 'private': { + 'post': [ + 'orders', + 'orders/history', + 'my/coin/deposit', + 'my/coin/send', + 'quote/buy', + 'quote/sell', + 'my/balances', + 'my/orders', + 'my/buy', + 'my/sell', + 'my/buy/cancel', + 'my/sell/cancel', + 'ro/my/balances', + 'ro/my/balances/{cointype}', + 'ro/my/deposits', + 'ro/my/withdrawals', + 'ro/my/transactions', + 'ro/my/transactions/{cointype}', + 'ro/my/transactions/open', + 'ro/my/transactions/{cointype}/open', + 'ro/my/sendreceive', + 'ro/my/affiliatepayments', + 'ro/my/referralpayments', + ], + }, + }, + 'markets': { + 'BTC/AUD': { 'id': 'btc', 'symbol': 'BTC/AUD', 'base': 'BTC', 'quote': 'AUD', 'baseId': 'btc', 'quoteId': 'aud' }, + 'LTC/AUD': { 'id': 'ltc', 'symbol': 'LTC/AUD', 'base': 'LTC', 'quote': 'AUD', 'baseId': 'ltc', 'quoteId': 'aud' }, + 'DOGE/AUD': { 'id': 'doge', 'symbol': 'DOGE/AUD', 'base': 'DOGE', 'quote': 'AUD', 'baseId': 'doge', 'quoteId': 'aud' }, + }, + 'commonCurrencies': { + 'DRK': 'DASH', + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostMyBalances (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'balance', {}); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balances, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cointype': market['id'], + }; + const orderbook = await this.privatePostOrders (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'buyorders', 'sellorders', 'rate', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetLatest (params); + let id = this.marketId (symbol); + id = id.toLowerCase (); + const ticker = response['prices'][id]; + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cointype': market['id'], + }; + const response = await this.privatePostOrdersHistory (this.extend (request, params)); + // + // { + // "status":"ok", + // "orders":[ + // {"amount":0.00102091,"rate":21549.09999991,"total":21.99969168,"coin":"BTC","solddate":1604890646143,"market":"BTC/AUD"}, + // ], + // } + // + const trades = this.safeValue (response, 'orders', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "amount":0.00102091, + // "rate":21549.09999991, + // "total":21.99969168, + // "coin":"BTC", + // "solddate":1604890646143, + // "market":"BTC/AUD" + // } + // + const price = this.safeFloat (trade, 'rate'); + const amount = this.safeFloat (trade, 'amount'); + let cost = this.safeFloat (trade, 'total'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const timestamp = this.safeInteger (trade, 'solddate'); + const marketId = this.safeString (trade, 'market'); + const symbol = this.safeSymbol (marketId, market, '/'); + return { + 'info': trade, + 'id': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': undefined, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostMy' + this.capitalize (side); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'cointype': this.marketId (symbol), + 'amount': amount, + 'rate': price, + }; + return await this[method] (this.extend (request, params)); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' cancelOrder () is not fully implemented yet'); + // let method = 'privatePostMyBuy'; + // return await this[method] ({ 'id': id }); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (!this.apiKey) { + throw new AuthenticationError (this.id + ' requires apiKey for all requests'); + } + const url = this.urls['api'][api] + '/' + path; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.json (this.extend ({ 'nonce': nonce }, params)); + headers = { + 'Content-Type': 'application/json', + 'key': this.apiKey, + 'sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],73:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadRequest, InvalidNonce, RequestTimeout, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, AuthenticationError, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class crex24 extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'crex24', + 'name': 'CREX24', + 'countries': [ 'EE' ], // Estonia + 'rateLimit': 500, + 'version': 'v2', + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': false, // actually, true, but will be implemented later + 'fetchTradingFees': false, // actually, true, but will be implemented later + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '1d': '1d', + '1w': '1w', + '1M': '1mo', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/47813922-6f12cc00-dd5d-11e8-97c6-70f957712d47.jpg', + 'api': 'https://api.crex24.com', + 'www': 'https://crex24.com', + 'referral': 'https://crex24.com/?refid=slxsjsjtil8xexl9hksr', + 'doc': 'https://docs.crex24.com/trade-api/v2', + 'fees': 'https://crex24.com/fees', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'instruments', + 'tickers', + 'recentTrades', + 'orderBook', + 'ohlcv', + ], + }, + 'trading': { + 'get': [ + 'orderStatus', + 'orderTrades', + 'activeOrders', + 'orderHistory', + 'tradeHistory', + 'tradeFee', + // this is in trading API according to their docs, but most likely a typo in their docs + 'moneyTransferStatus', + ], + 'post': [ + 'placeOrder', + 'modifyOrder', + 'cancelOrdersById', + 'cancelOrdersByInstrument', + 'cancelAllOrders', + ], + }, + 'account': { + 'get': [ + 'balance', + 'depositAddress', + 'moneyTransfers', + // this is in trading API according to their docs, but most likely a typo in their docs + 'moneyTransferStatus', + 'previewWithdrawal', + ], + 'post': [ + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.001, + 'maker': -0.0001, + }, + // should be deleted, these are outdated and inaccurate + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'commonCurrencies': { + 'BCC': 'BCH', + 'BIT': 'BitMoney', + 'BULL': 'BuySell', + 'CREDIT': 'TerraCredit', + 'GHOST': 'GHOSTPRISM', + 'IQ': 'IQ.Cash', + 'PUT': 'PutinCoin', + 'UNI': 'Universe', + 'YOYO': 'YOYOW', + }, + // exchange-specific options + 'options': { + 'fetchOrdersMethod': 'tradingGetOrderHistory', // or 'tradingGetActiveOrders' + 'fetchClosedOrdersMethod': 'tradingGetOrderHistory', // or 'tradingGetActiveOrders' + 'fetchTickersMethod': 'publicGetTicker24hr', + 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel + 'hasAlreadyAuthenticatedSuccessfully': false, + 'warnOnFetchOpenOrdersWithoutSymbol': true, + 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision + 'newOrderRespType': 'RESULT', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + }, + 'exceptions': { + 'exact': { + "Parameter 'filter' contains invalid value.": BadRequest, // eslint-disable-quotes + "Mandatory parameter 'instrument' is missing.": BadRequest, // eslint-disable-quotes + "The value of parameter 'till' must be greater than or equal to the value of parameter 'from'.": BadRequest, // eslint-disable-quotes + 'Failed to verify request signature.': AuthenticationError, // eslint-disable-quotes + "Nonce error. Make sure that the value passed in the 'X-CREX24-API-NONCE' header is greater in each consecutive request than in the previous one for the corresponding API-Key provided in 'X-CREX24-API-KEY' header.": InvalidNonce, + 'Market orders are not supported by the instrument currently.': InvalidOrder, + "Parameter 'instrument' contains invalid value.": BadSymbol, + }, + 'broad': { + 'API Key': AuthenticationError, // "API Key '9edc48de-d5b0-4248-8e7e-f59ffcd1c7f1' doesn't exist." + 'Insufficient funds': InsufficientFunds, // "Insufficient funds: new order requires 10 ETH which is more than the available balance." + 'has been delisted.': BadSymbol, // {"errorDescription":"Instrument '$PAC-BTC' has been delisted."} + 'Mandatory parameter': BadRequest, // {"errorDescription":"Mandatory parameter 'feeCurrency' is missing."} + }, + }, + }); + } + + nonce () { + return this.milliseconds (); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInstruments (params); + // + // [ { symbol: "$PAC-BTC", + // baseCurrency: "$PAC", + // quoteCurrency: "BTC", + // feeCurrency: "BTC", + // tickSize: 1e-8, + // minPrice: 1e-8, + // minVolume: 1, + // supportedOrderTypes: ["limit"], + // state: "active" }, + // { symbol: "ZZC-USD", + // baseCurrency: "ZZC", + // quoteCurrency: "USD", + // feeCurrency: "USD", + // tickSize: 0.0001, + // minPrice: 0.0001, + // minVolume: 1, + // supportedOrderTypes: ["limit"], + // state: "active" } ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const tickSize = this.safeValue (market, 'tickSize'); + const minPrice = this.safeValue (market, 'minPrice'); + const minAmount = this.safeFloat (market, 'minVolume'); + const precision = { + 'amount': this.precisionFromString (this.numberToString (minAmount)), + 'price': this.precisionFromString (this.numberToString (tickSize)), + }; + const active = (market['state'] === 'active'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': minPrice, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ { symbol: "$PAC", + // name: "PACCoin", + // isFiat: false, + // depositsAllowed: true, + // depositConfirmationCount: 8, + // minDeposit: 0, + // withdrawalsAllowed: true, + // withdrawalPrecision: 8, + // minWithdrawal: 4, + // maxWithdrawal: 1000000000, + // flatWithdrawalFee: 2, + // isDelisted: false }, + // { symbol: "ZZC", + // name: "Zozo", + // isFiat: false, + // depositsAllowed: false, + // depositConfirmationCount: 8, + // minDeposit: 0, + // withdrawalsAllowed: false, + // withdrawalPrecision: 8, + // minWithdrawal: 0.2, + // maxWithdrawal: 1000000000, + // flatWithdrawalFee: 0.1, + // isDelisted: false } ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (currency, 'withdrawalPrecision'); + const address = this.safeValue (currency, 'BaseAddress'); + const active = (currency['depositsAllowed'] && currency['withdrawalsAllowed'] && !currency['isDelisted']); + const type = currency['isFiat'] ? 'fiat' : 'crypto'; + result[code] = { + 'id': id, + 'code': code, + 'address': address, + 'info': currency, + 'type': type, + 'name': this.safeString (currency, 'name'), + 'active': active, + 'fee': this.safeFloat (currency, 'flatWithdrawalFee'), // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'deposit': { + 'min': this.safeFloat (currency, 'minDeposit'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawal'), + 'max': this.safeFloat (currency, 'maxWithdrawal'), + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': 'ETH', // comma-separated list of currency ids + // 'nonZeroOnly': 'false', // true by default + }; + const response = await this.accountGetBalance (this.extend (request, params)); + // + // [ + // { + // "currency": "ETH", + // "available": 0.0, + // "reserved": 0.0 + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = maximum = 100 + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { buyLevels: [ { price: 0.03099, volume: 0.00610063 }, + // { price: 0.03097, volume: 1.33455158 }, + // { price: 0.03096, volume: 0.0830889 }, + // { price: 0.03095, volume: 0.0820356 }, + // { price: 0.03093, volume: 0.5499419 }, + // { price: 0.03092, volume: 0.23317494 }, + // { price: 0.03091, volume: 0.62105322 }, + // { price: 0.00620041, volume: 0.003 } ], + // sellLevels: [ { price: 0.03117, volume: 5.47492315 }, + // { price: 0.03118, volume: 1.97744139 }, + // { price: 0.03119, volume: 0.012 }, + // { price: 0.03121, volume: 0.741242 }, + // { price: 0.03122, volume: 0.96178089 }, + // { price: 0.03123, volume: 0.152326 }, + // { price: 0.03124, volume: 2.63462933 }, + // { price: 0.069, volume: 0.004 } ] } + // + return this.parseOrderBook (response, undefined, 'buyLevels', 'sellLevels', 'price', 'volume'); + } + + parseTicker (ticker, market = undefined) { + // + // { instrument: "ZZC-USD", + // last: 0.065, + // percentChange: 0, + // low: 0.065, + // high: 0.065, + // baseVolume: 0, + // quoteVolume: 0, + // volumeInBtc: 0, + // volumeInUsd: 0, + // ask: 0.5, + // bid: 0.0007, + // timestamp: "2018-10-31T09:21:25Z" } ] + // + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const marketId = this.safeString (ticker, 'instrument'); + const symbol = this.safeSymbol (marketId, market, '-'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'percentChange'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ { instrument: "$PAC-BTC", + // last: 3.3e-7, + // percentChange: 3.125, + // low: 2.7e-7, + // high: 3.3e-7, + // baseVolume: 191700.79823187, + // quoteVolume: 0.0587930939346704, + // volumeInBtc: 0.0587930939346704, + // volumeInUsd: 376.2006339435353, + // ask: 3.3e-7, + // bid: 3.1e-7, + // timestamp: "2018-10-31T09:21:25Z" } ] + // + const numTickers = response.length; + if (numTickers < 1) { + throw new ExchangeError (this.id + ' fetchTicker could not load quotes for symbol ' + symbol); + } + return this.parseTicker (response[0], market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['instrument'] = ids.join (','); + } + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ { instrument: "$PAC-BTC", + // last: 3.3e-7, + // percentChange: 3.125, + // low: 2.7e-7, + // high: 3.3e-7, + // baseVolume: 191700.79823187, + // quoteVolume: 0.0587930939346704, + // volumeInBtc: 0.0587930939346704, + // volumeInUsd: 376.2006339435353, + // ask: 3.3e-7, + // bid: 3.1e-7, + // timestamp: "2018-10-31T09:21:25Z" }, + // { instrument: "ZZC-USD", + // last: 0.065, + // percentChange: 0, + // low: 0.065, + // high: 0.065, + // baseVolume: 0, + // quoteVolume: 0, + // volumeInBtc: 0, + // volumeInUsd: 0, + // ask: 0.5, + // bid: 0.0007, + // timestamp: "2018-10-31T09:21:25Z" } ] + // + return this.parseTickers (response, symbols); + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { price: 0.03105, + // volume: 0.11, + // side: "sell", + // timestamp: "2018-10-31T04:19:35Z" } ] + // + // private fetchMyTrades + // + // { + // "id": 3005866, + // "orderId": 468533093, + // "timestamp": "2018-06-02T16:26:27Z", + // "instrument": "BCH-ETH", + // "side": "buy", + // "price": 1.78882, + // "volume": 0.027, + // "fee": 0.0000483, + // "feeCurrency": "ETH" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const id = this.safeString (trade, 'id'); + const side = this.safeString (trade, 'side'); + const orderId = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'instrument'); + const symbol = this.safeSymbol (marketId, market, '-'); + let fee = undefined; + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + const feeCode = this.safeCurrencyCode (feeCurrencyId); + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCode, + }; + } + const takerOrMaker = undefined; + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'cost': cost, + 'amount': amount, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // min 1, max 1000, default 100 + } + const response = await this.publicGetRecentTrades (this.extend (request, params)); + // + // [ { price: 0.03117, + // volume: 0.02597403, + // side: "buy", + // timestamp: "2018-10-31T09:37:46Z" }, + // { price: 0.03105, + // volume: 0.11, + // side: "sell", + // timestamp: "2018-10-31T04:19:35Z" } ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // timestamp: '2019-09-21T10:36:00Z', + // open: 0.02152, + // high: 0.02156, + // low: 0.02152, + // close: 0.02156, + // volume: 0.01741259 + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'granularity': this.timeframes[timeframe], + 'instrument': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // Accepted values: 1 - 1000. If the parameter is not specified, the number of results is limited to 100 + } + const response = await this.publicGetOhlcv (this.extend (request, params)); + // + // [ + // { + // "timestamp": "2020-06-06T17:36:00Z", + // "open": 0.025, + // "high": 0.025, + // "low": 0.02499, + // "close": 0.02499, + // "volume": 0.00643127 + // } + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'submitting': 'open', // A newly created limit order has a status "submitting" until it has been processed. + // This status changes during the lifetime of an order and can have different values depending on the value of the parameter Time In Force. + 'unfilledActive': 'open', // order is active, no trades have been made + 'partiallyFilledActive': 'open', // part of the order has been filled, the other part is active + 'filled': 'closed', // order has been filled entirely + 'partiallyFilledCancelled': 'canceled', // part of the order has been filled, the other part has been cancelled either by the trader or by the system (see the value of cancellationReason of an Order for more details on the reason of cancellation) + 'unfilledCancelled': 'canceled', // order has been cancelled, no trades have taken place (see the value of cancellationReason of an Order for more details on the reason of cancellation) + }; + return (status in statuses) ? statuses[status] : status; + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": 469594855, + // "timestamp": "2018-06-08T16:59:44Z", + // "instrument": "BTS-BTC", + // "side": "buy", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 4.0, + // "price": 0.000025, + // "stopPrice": null, + // "remainingVolume": 4.0, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'instrument'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + const remaining = this.safeFloat (order, 'remainingVolume'); + let filled = undefined; + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'lastUpdate')); + let cost = undefined; + if (remaining !== undefined) { + if (amount !== undefined) { + filled = amount - remaining; + if (this.options['parseOrderToPrecision']) { + filled = parseFloat (this.amountToPrecision (symbol, filled)); + } + filled = Math.max (filled, 0.0); + if (price !== undefined) { + cost = price * filled; + } + } + } + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + const side = this.safeString (order, 'side'); + const fee = undefined; + const trades = undefined; + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + if (this.options['parseOrderToPrecision']) { + cost = parseFloat (this.costToPrecision (symbol, cost)); + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + 'volume': this.amountToPrecision (symbol, amount), + // The value must comply with the list of order types supported by the instrument (see the value of parameter supportedOrderTypes of the Instrument) + // If the parameter is not specified, the default value "limit" is used + // More about order types in the corresponding section of documentation + 'type': type, // 'limit', 'market', 'stopLimit', in fact as of 2018-10-31, only 'limit' orders are supported for all markets + 'side': side, // 'buy' or 'sell' + // "GTC" - Good-Til-Cancelled + // "IOC" - Immediate-Or-Cancel (currently not supported by the exchange API, reserved for future use) + // "FOK" - Fill-Or-Kill (currently not supported by the exchange API, reserved for future use) + // 'timeInForce': 'GTC', // IOC', 'FOK' + // 'strictValidation': false, // false - prices will be rounded to meet the requirement, true - execution of the method will be aborted and an error message will be returned + }; + let priceIsRequired = false; + let stopPriceIsRequired = false; + if (type === 'limit') { + priceIsRequired = true; + } else if (type === 'stopLimit') { + priceIsRequired = true; + stopPriceIsRequired = true; + } + if (priceIsRequired) { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a price argument for a ' + type + ' order'); + } + request['price'] = this.priceToPrecision (symbol, price); + } + if (stopPriceIsRequired) { + const stopPrice = this.safeFloat (params, 'stopPrice'); + if (stopPrice === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order'); + } else { + request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); + } + params = this.omit (params, 'stopPrice'); + } + const response = await this.tradingPostPlaceOrder (this.extend (request, params)); + // + // { + // "id": 469594855, + // "timestamp": "2018-06-08T16:59:44Z", + // "instrument": "BTS-BTC", + // "side": "buy", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 4.0, + // "price": 0.000025, + // "stopPrice": null, + // "remainingVolume": 4.0, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // + return this.parseOrder (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.tradingGetOrderStatus (this.extend (request, params)); + // + // [ + // { + // "id": 466747915, + // "timestamp": "2018-05-26T06:43:49Z", + // "instrument": "UNI-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 5700.0, + // "price": 0.000005, + // "stopPrice": null, + // "remainingVolume": 1.948051948052, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // ] + // + const numOrders = response.length; + if (numOrders < 1) { + throw new OrderNotFound (this.id + ' fetchOrder could not fetch order id ' + id); + } + return this.parseOrder (response[0]); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['from'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (symbol !== undefined) { + const market = this.market (symbol); + request['instrument'] = market['id']; + } + const method = this.safeString (this.options, 'fetchOrdersMethod', 'tradingGetOrderHistory'); + const response = await this[method] (this.extend (request, params)); + // + // [ + // { + // "id": 468535711, + // "timestamp": "2018-06-02T16:42:40Z", + // "instrument": "BTC-EUR", + // "side": "sell", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 0.00770733, + // "price": 6724.9, + // "stopPrice": null, + // "remainingVolume": 0.00770733, + // "lastUpdate": "2018-06-02T16:42:40Z", + // "parentOrderId": null, + // "childOrderId": null + // } + // ] + // + return this.parseOrders (response); + } + + async fetchOrdersByIds (ids = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': ids.join (','), + }; + const response = await this.tradingGetOrderStatus (this.extend (request, params)); + // + // [ + // { + // "id": 466747915, + // "timestamp": "2018-05-26T06:43:49Z", + // "instrument": "UNI-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 5700.0, + // "price": 0.000005, + // "stopPrice": null, + // "remainingVolume": 1.948051948052, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // ] + // + return this.parseOrders (response, undefined, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument'] = market['id']; + } + const response = await this.tradingGetActiveOrders (this.extend (request, params)); + // + // [ + // { + // "id": 466747915, + // "timestamp": "2018-05-26T06:43:49Z", + // "instrument": "UNI-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 5700.0, + // "price": 0.000005, + // "stopPrice": null, + // "remainingVolume": 1.948051948052, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // { + // "id": 466748077, + // "timestamp": "2018-05-26T06:45:29Z", + // "instrument": "PRJ-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 10000.0, + // "price": 0.0000007, + // "stopPrice": null, + // "remainingVolume": 9975.0, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // ... + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['limit'] = limit; // min 1, max 1000, default 100 + } + const method = this.safeString (this.options, 'fetchClosedOrdersMethod', 'tradingGetOrderHistory'); + const response = await this[method] (this.extend (request, params)); + // [ + // { + // "id": 468535711, + // "timestamp": "2018-06-02T16:42:40Z", + // "instrument": "BTC-EUR", + // "side": "sell", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 0.00770733, + // "price": 6724.9, + // "stopPrice": null, + // "remainingVolume": 0.00770733, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // { + // "id": 468535707, + // "timestamp": "2018-06-02T16:42:37Z", + // "instrument": "BTG-BTC", + // "side": "buy", + // "type": "limit", + // "status": "unfilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 0.0173737, + // "price": 0.00589027, + // "stopPrice": null, + // "remainingVolume": 0.0173737, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // ... + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'ids': [ + parseInt (id), + ], + }; + const response = await this.tradingPostCancelOrdersById (this.extend (request, params)); + // + // [ + // 465448358, + // 468364313 + // ] + // + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const response = await this.tradingPostCancelAllOrders (params); + // + // [ + // 465448358, + // 468364313 + // ] + // + return response; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['limit'] = limit; // min 1, max 1000, default 100 + } + const response = await this.tradingGetTradeHistory (this.extend (request, params)); + // + // [ + // { + // "id": 3005866, + // "orderId": 468533093, + // "timestamp": "2018-06-02T16:26:27Z", + // "instrument": "BCH-ETH", + // "side": "buy", + // "price": 1.78882, + // "volume": 0.027, + // "fee": 0.0000483, + // "feeCurrency": "ETH" + // }, + // { + // "id": 3005812, + // "orderId": 468515771, + // "timestamp": "2018-06-02T16:16:05Z", + // "instrument": "ETC-BTC", + // "side": "sell", + // "price": 0.00210958, + // "volume": 0.05994006, + // "fee": -0.000000063224, + // "feeCurrency": "BTC" + // }, + // ... + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['from'] = this.ymd (since, 'T'); + } + const response = await this.accountGetMoneyTransfers (this.extend (request, params)); + // + // [ + // { + // "id": 756446, + // "type": "deposit", + // "currency": "ETH", + // "address": "0x451d5a1b7519aa75164f440df78c74aac96023fe", + // "paymentId": null, + // "amount": 0.142, + // "fee": null, + // "txId": "0x2b49098749840a9482c4894be94f94864b498a1306b6874687a5640cc9871918", + // "createdAt": "2018-06-02T19:30:28Z", + // "processedAt": "2018-06-02T21:10:41Z", + // "confirmationsRequired": 12, + // "confirmationCount": 12, + // "status": "success", + // "errorDescription": null + // }, + // { + // "id": 754618, + // "type": "deposit", + // "currency": "BTC", + // "address": "1IgNfmERVcier4IhfGEfutkLfu4AcmeiUC", + // "paymentId": null, + // "amount": 0.09, + // "fee": null, + // "txId": "6876541687a9187e987c9187654f7198b9718af974641687b19a87987f91874f", + // "createdAt": "2018-06-02T16:19:44Z", + // "processedAt": "2018-06-02T16:20:50Z", + // "confirmationsRequired": 1, + // "confirmationCount": 1, + // "status": "success", + // "errorDescription": null + // }, + // ... + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'deposit', + }; + return this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'withdrawal', + }; + return this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', // transfer is in progress + 'success': 'ok', // completed successfully + 'failed': 'failed', // aborted at some point (money will be credited back to the account of origin) + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": 756446, + // "type": "deposit", + // "currency": "ETH", + // "address": "0x451d5a1b7519aa75164f440df78c74aac96023fe", + // "paymentId": null, + // "amount": 0.142, + // "fee": null, + // "txId": "0x2b49098749840a9482c4894be94f94864b498a1306b6874687a5640cc9871918", + // "createdAt": "2018-06-02T19:30:28Z", + // "processedAt": "2018-06-02T21:10:41Z", + // "confirmationsRequired": 12, + // "confirmationCount": 12, + // "status": "success", + // "errorDescription": null, + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'paymentId'); + const txid = this.safeValue (transaction, 'txId'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeString (transaction, 'type'); + const timestamp = this.parse8601 (this.safeString (transaction, 'createdAt')); + const updated = this.parse8601 (this.safeString (transaction, 'processedAt')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const feeCost = this.safeFloat (transaction, 'fee'); + const fee = { + 'cost': feeCost, + 'currency': code, + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.accountGetDepositAddress (this.extend (request, params)); + // + // { + // "currency": "BTS", + // "address": "crex24", + // "paymentId": "0fg4da4186741579" + // } + // + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'paymentId'); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'address': address, + 'amount': parseFloat (this.currencyToPrecision (code, amount)), + // sets whether the specified amount includes fee, can have either of the two values + // true - balance will be decreased by amount, whereas [amount - fee] will be transferred to the specified address + // false - amount will be deposited to the specified address, whereas the balance will be decreased by [amount + fee] + // 'includeFee': false, // the default value is false + 'feeCurrency': currency['id'], // https://github.com/ccxt/ccxt/issues/7544 + }; + if (tag !== undefined) { + request['paymentId'] = tag; + } + const response = await this.accountPostWithdraw (this.extend (request, params)); + return this.parseTransaction (response); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.version + '/' + api + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + request; + if ((api === 'trading') || (api === 'account')) { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const secret = this.base64ToBinary (this.secret); + let auth = request + nonce; + headers = { + 'X-CREX24-API-KEY': this.apiKey, + 'X-CREX24-API-NONCE': nonce, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + body = this.json (params); + auth += body; + } + headers['X-CREX24-API-SIGN'] = this.hmac (this.encode (auth), secret, 'sha512', 'base64'); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!this.isJsonEncodedObject (body)) { + return; // fallback to default error handler + } + if ((code >= 200) && (code < 300)) { + return; // no error + } + const message = this.safeString (response, 'errorDescription'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + if (code === 400) { + throw new BadRequest (feedback); + } else if (code === 401) { + throw new AuthenticationError (feedback); + } else if (code === 403) { + throw new AuthenticationError (feedback); + } else if (code === 429) { + throw new DDoSProtection (feedback); + } else if (code === 500) { + throw new ExchangeError (feedback); + } else if (code === 503) { + throw new ExchangeNotAvailable (feedback); + } else if (code === 504) { + throw new RequestTimeout (feedback); + } + throw new ExchangeError (feedback); // unknown message + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],74:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, BadRequest } = require ('./base/errors'); +const { ROUND, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class currencycom extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'currencycom', + 'name': 'Currency.com', + 'countries': [ 'BY' ], // Belarus + 'rateLimit': 500, + 'certified': true, + 'pro': true, + 'version': 'v1', + // new metainfo interface + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTradingFees': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '1d': '1d', + '1w': '1w', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg', + 'api': { + 'public': 'https://api-adapter.backend.currency.com/api', + 'private': 'https://api-adapter.backend.currency.com/api', + }, + 'www': 'https://www.currency.com', + 'referral': 'https://currency.com/trading/signup?c=362jaimv&pid=referral', + 'doc': [ + 'https://currency.com/api', + ], + 'fees': 'https://currency.com/fees-charges', + }, + 'api': { + 'public': { + 'get': [ + 'time', + 'exchangeInfo', + 'depth', + 'aggTrades', + 'klines', + 'ticker/24hr', + ], + }, + 'private': { + 'get': [ + 'leverageSettings', + 'openOrders', + 'tradingPositions', + 'account', + 'myTrades', + ], + 'post': [ + 'order', + 'updateTradingPosition', + 'updateTradingOrder', + 'closeTradingPosition', + ], + 'delete': [ + 'order', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.002, + }, + }, + 'precisionMode': TICK_SIZE, + // exchange-specific options + 'options': { + 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel, 'FOK' = Fill Or Kill + 'warnOnFetchOpenOrdersWithoutSymbol': true, + 'recvWindow': 5 * 1000, // 5 sec, default + 'timeDifference': 0, // the difference between system clock and Binance clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision + 'newOrderRespType': { + 'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + 'limit': 'RESULT', // we change it from 'ACK' by default to 'RESULT' + 'stop': 'RESULT', + }, + }, + 'exceptions': { + 'broad': { + 'FIELD_VALIDATION_ERROR Cancel is available only for LIMIT order': InvalidOrder, + 'API key does not exist': AuthenticationError, + 'Order would trigger immediately.': InvalidOrder, + 'Account has insufficient balance for requested action.': InsufficientFunds, + 'Rest API trading is not enabled.': ExchangeNotAvailable, + }, + 'exact': { + '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} + '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL + '-1021': InvalidNonce, // 'your time is ahead of server' + '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."} + '-1100': InvalidOrder, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price' + '-1104': ExchangeError, // Not all sent parameters were read, read 8 parameters but was sent 9 + '-1025': AuthenticationError, // {"code":-1025,"msg":"Invalid API-key, IP, or permissions for action"} + '-1128': BadRequest, // {"code":-1128,"msg":"Combination of optional parameters invalid."} + '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc... + '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER' + '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist' + '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." } + '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action." + }, + }, + 'commonCurrencies': { + 'IQ': 'iQIYI', + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "serverTime": 1590998366609 + // } + // + return this.safeInteger (response, 'serverTime'); + } + + async loadTimeDifference (params = {}) { + const response = await this.publicGetTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = parseInt (after - response['serverTime']); + return this.options['timeDifference']; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeInfo (params); + // + // { + // "timezone":"UTC", + // "serverTime":1603252990096, + // "rateLimits":[ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, + // {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":1,"limit":10}, + // {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":864000}, + // ], + // "exchangeFilters":[], + // "symbols":[ + // { + // "symbol":"EVK", + // "name":"Evonik", + // "status":"BREAK", + // "baseAsset":"EVK", + // "baseAssetPrecision":3, + // "quoteAsset":"EUR", + // "quoteAssetId":"EUR", + // "quotePrecision":3, + // "orderTypes":["LIMIT","MARKET"], + // "filters":[ + // {"filterType":"LOT_SIZE","minQty":"1","maxQty":"27000","stepSize":"1"}, + // {"filterType":"MIN_NOTIONAL","minNotional":"23"} + // ], + // "marketType":"SPOT", + // "country":"DE", + // "sector":"Basic Materials", + // "industry":"Diversified Chemicals", + // "tradingHours":"UTC; Mon 07:02 - 15:30; Tue 07:02 - 15:30; Wed 07:02 - 15:30; Thu 07:02 - 15:30; Fri 07:02 - 15:30", + // "tickSize":0.005, + // "tickValue":0.11125, + // "exchangeFee":0.05 + // }, + // { + // "symbol":"BTC/USD_LEVERAGE", + // "name":"Bitcoin / USD", + // "status":"TRADING", + // "baseAsset":"BTC", + // "baseAssetPrecision":3, + // "quoteAsset":"USD", + // "quoteAssetId":"USD_LEVERAGE", + // "quotePrecision":3, + // "orderTypes":["LIMIT","MARKET","STOP"], + // "filters":[ + // {"filterType":"LOT_SIZE","minQty":"0.001","maxQty":"100","stepSize":"0.001"}, + // {"filterType":"MIN_NOTIONAL","minNotional":"13"} + // ], + // "marketType":"LEVERAGE", + // "longRate":-0.01, + // "shortRate":0.01, + // "swapChargeInterval":480, + // "country":"", + // "sector":"", + // "industry":"", + // "tradingHours":"UTC; Mon - 21:00, 21:05 -; Tue - 21:00, 21:05 -; Wed - 21:00, 21:05 -; Thu - 21:00, 21:05 -; Fri - 21:00, 22:01 -; Sat - 21:00, 21:05 -; Sun - 20:00, 21:05 -", + // "tickSize":0.05, + // "tickValue":610.20875, + // "makerFee":-0.025, + // "takerFee":0.075 + // }, + // ] + // } + // + if (this.options['adjustForTimeDifference']) { + await this.loadTimeDifference (); + } + const markets = this.safeValue (response, 'symbols'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + if (id.indexOf ('/') >= 0) { + symbol = id; + } + const filters = this.safeValue (market, 'filters', []); + const filtersByType = this.indexBy (filters, 'filterType'); + const precision = { + 'amount': 1 / Math.pow (1, this.safeInteger (market, 'baseAssetPrecision')), + 'price': this.safeFloat (market, 'tickSize'), + }; + const status = this.safeString (market, 'status'); + const active = (status === 'TRADING'); + let type = this.safeStringLower (market, 'marketType'); + if (type === 'leverage') { + type = 'margin'; + } + const spot = (type === 'spot'); + const margin = (type === 'margin'); + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': type, + 'spot': spot, + 'margin': margin, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': -1 * Math.log10 (precision['amount']), + 'max': undefined, + }, + }, + }; + const exchangeFee = this.safeFloat2 (market, 'exchangeFee', 'tradingFee'); + const makerFee = this.safeFloat (market, 'makerFee', exchangeFee); + const takerFee = this.safeFloat (market, 'takerFee', exchangeFee); + if (makerFee !== undefined) { + entry['maker'] = makerFee / 100; + } + if (takerFee !== undefined) { + entry['taker'] = takerFee / 100; + } + if ('PRICE_FILTER' in filtersByType) { + const filter = this.safeValue (filtersByType, 'PRICE_FILTER', {}); + entry['precision']['price'] = this.safeFloat (filter, 'tickSize'); + // PRICE_FILTER reports zero values for maxPrice + // since they updated filter types in November 2018 + // https://github.com/ccxt/ccxt/issues/4286 + // therefore limits['price']['max'] doesn't have any meaningful value except undefined + entry['limits']['price'] = { + 'min': this.safeFloat (filter, 'minPrice'), + 'max': undefined, + }; + const maxPrice = this.safeFloat (filter, 'maxPrice'); + if ((maxPrice !== undefined) && (maxPrice > 0)) { + entry['limits']['price']['max'] = maxPrice; + } + } + if ('LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'LOT_SIZE', {}); + entry['precision']['amount'] = this.safeFloat (filter, 'stepSize'); + entry['limits']['amount'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MARKET_LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MARKET_LOT_SIZE', {}); + entry['limits']['market'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MIN_NOTIONAL' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MIN_NOTIONAL', {}); + entry['limits']['cost']['min'] = this.safeFloat (filter, 'minNotional'); + } + result.push (entry); + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccount (params); + // + // { + // "makerCommission":0.20, + // "takerCommission":0.20, + // "buyerCommission":0.20, + // "sellerCommission":0.20, + // "canTrade":true, + // "canWithdraw":true, + // "canDeposit":true, + // "updateTime":1591056268, + // "balances":[ + // { + // "accountId":5470306579272968, + // "collateralCurrency":true, + // "asset":"ETH", + // "free":0.0, + // "locked":0.0, + // "default":false, + // }, + // ] + // } + // + const accounts = this.safeValue (response, 'balances', []); + const result = []; + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + const accountId = this.safeInteger (account, 'accountId'); + const currencyId = this.safeString (account, 'asset'); + const currencyCode = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': accountId, + 'type': undefined, + 'currency': currencyCode, + 'info': response, + }); + } + return result; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + return { + 'info': response, + 'maker': this.safeFloat (response, 'makerCommission'), + 'taker': this.safeFloat (response, 'takerCommission'), + }; + } + + parseBalanceResponse (response) { + // + // { + // "makerCommission":0.20, + // "takerCommission":0.20, + // "buyerCommission":0.20, + // "sellerCommission":0.20, + // "canTrade":true, + // "canWithdraw":true, + // "canDeposit":true, + // "updateTime":1591056268, + // "balances":[ + // { + // "accountId":5470306579272968, + // "collateralCurrency":true, + // "asset":"ETH", + // "free":0.0, + // "locked":0.0, + // "default":false, + // }, + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'balances', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + // + // { + // "makerCommission":0.20, + // "takerCommission":0.20, + // "buyerCommission":0.20, + // "sellerCommission":0.20, + // "canTrade":true, + // "canWithdraw":true, + // "canDeposit":true, + // "updateTime":1591056268, + // "balances":[ + // { + // "accountId":5470306579272968, + // "collateralCurrency":true, + // "asset":"ETH", + // "free":0.0, + // "locked":0.0, + // "default":false, + // }, + // ] + // } + // + return this.parseBalanceResponse (response); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000, 5000 + } + const response = await this.publicGetDepth (this.extend (request, params)); + // + // { + // "lastUpdateId":1590999849037, + // "asks":[ + // [0.02495,60.0000], + // [0.02496,120.0000], + // [0.02497,240.0000], + // ], + // "bids":[ + // [0.02487,60.0000], + // [0.02486,120.0000], + // [0.02485,240.0000], + // ] + // } + // + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "symbol":"ETH/BTC", + // "priceChange":"0.00030", + // "priceChangePercent":"1.21", + // "weightedAvgPrice":"0.02481", + // "prevClosePrice":"0.02447", + // "lastPrice":"0.02477", + // "lastQty":"60.0", + // "bidPrice":"0.02477", + // "askPrice":"0.02484", + // "openPrice":"0.02447", + // "highPrice":"0.02524", + // "lowPrice":"0.02438", + // "volume":"11.97", + // "quoteVolume":"0.298053", + // "openTime":1590969600000, + // "closeTime":1591000072693 + // } + // + // fetchTickers + // + // { + // "symbol":"EVK", + // "highPrice":"22.57", + // "lowPrice":"22.16", + // "volume":"1", + // "quoteVolume":"22.2", + // "openTime":1590699364000, + // "closeTime":1590785764000 + // } + // + const timestamp = this.safeInteger (ticker, 'closeTime'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'lastPrice'); + const open = this.safeFloat (ticker, 'openPrice'); + let average = undefined; + if ((open !== undefined) && (last !== undefined)) { + average = this.sum (open, last) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), + 'open': open, + 'close': last, + 'last': last, + 'previousClose': this.safeFloat (ticker, 'prevClosePrice'), // previous day close + 'change': this.safeFloat (ticker, 'priceChange'), + 'percentage': this.safeFloat (ticker, 'priceChangePercent'), + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker24hr (this.extend (request, params)); + // + // { + // "symbol":"ETH/BTC", + // "priceChange":"0.00030", + // "priceChangePercent":"1.21", + // "weightedAvgPrice":"0.02481", + // "prevClosePrice":"0.02447", + // "lastPrice":"0.02477", + // "lastQty":"60.0", + // "bidPrice":"0.02477", + // "askPrice":"0.02484", + // "openPrice":"0.02447", + // "highPrice":"0.02524", + // "lowPrice":"0.02438", + // "volume":"11.97", + // "quoteVolume":"0.298053", + // "openTime":1590969600000, + // "closeTime":1591000072693 + // } + // + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker24hr (params); + // + // [ + // { + // "symbol":"EVK", + // "highPrice":"22.57", + // "lowPrice":"22.16", + // "volume":"1", + // "quoteVolume":"22.2", + // "openTime":1590699364000, + // "closeTime":1590785764000 + // } + // ] + // + return this.parseTickers (response, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1590971040000, + // "0.02454", + // "0.02456", + // "0.02452", + // "0.02456", + // 249 + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.publicGetKlines (this.extend (request, params)); + // + // [ + // [1590971040000,"0.02454","0.02456","0.02452","0.02456",249], + // [1590971100000,"0.02455","0.02457","0.02452","0.02456",300], + // [1590971160000,"0.02455","0.02456","0.02453","0.02454",286], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public aggregate trades) + // + // { + // "a":1658318071, + // "p":"0.02476", + // "q":"0.0", + // "T":1591001423382, + // "m":false + // } + // + // createOrder fills (private) + // + // { + // "price": "9807.05", + // "qty": "0.01", + // "commission": "0", + // "commissionAsset": "dUSD" + // } + // + // fetchMyTrades + // + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true + // } + // + const timestamp = this.safeInteger2 (trade, 'T', 'time'); + const price = this.safeFloat2 (trade, 'p', 'price'); + const amount = this.safeFloat2 (trade, 'q', 'qty'); + const id = this.safeString2 (trade, 'a', 'id'); + let side = undefined; + const orderId = this.safeString (trade, 'orderId'); + if ('m' in trade) { + side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally + } else if ('isBuyerMaker' in trade) { + side = trade['isBuyerMaker'] ? 'sell' : 'buy'; + } else { + if ('isBuyer' in trade) { + side = (trade['isBuyer']) ? 'buy' : 'sell'; // this is a true side + } + } + let fee = undefined; + if ('commission' in trade) { + fee = { + 'cost': this.safeFloat (trade, 'commission'), + 'currency': this.safeCurrencyCode (this.safeString (trade, 'commissionAsset')), + }; + } + let takerOrMaker = undefined; + if ('isMaker' in trade) { + takerOrMaker = trade['isMaker'] ? 'maker' : 'taker'; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': price * amount, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'limit': 500, // default 500, max 1000 + }; + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.publicGetAggTrades (this.extend (request, params)); + // + // [ + // { + // "a":1658318071, + // "p":"0.02476", + // "q":"0.0", + // "T":1591001423382, + // "m":false + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'PARTIALLY_FILLED': 'open', + 'FILLED': 'closed', + 'CANCELED': 'canceled', + 'PENDING_CANCEL': 'canceling', // currently unused + 'REJECTED': 'rejected', + 'EXPIRED': 'expired', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "symbol": "BTC/USD", + // "orderId": "00000000-0000-0000-0000-0000000c0263", + // "clientOrderId": "00000000-0000-0000-0000-0000000c0263", + // "transactTime": 1589878206426, + // "price": "9825.66210000", + // "origQty": "0.01", + // "executedQty": "0.01", + // "status": "FILLED", + // "timeInForce": "FOK", + // "type": "MARKET", + // "side": "BUY", + // "fills": [ + // { + // "price": "9807.05", + // "qty": "0.01", + // "commission": "0", + // "commissionAsset": "dUSD" + // } + // ] + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '/'); + let timestamp = undefined; + if ('time' in order) { + timestamp = this.safeInteger (order, 'time'); + } else if ('transactTime' in order) { + timestamp = this.safeInteger (order, 'transactTime'); + } + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'origQty'); + const filled = this.safeFloat (order, 'executedQty'); + let remaining = undefined; + let cost = this.safeFloat (order, 'cummulativeQuoteQty'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + if (this.options['parseOrderToPrecision']) { + remaining = parseFloat (this.amountToPrecision (symbol, remaining)); + } + remaining = Math.max (remaining, 0.0); + } + if (price !== undefined) { + if (cost === undefined) { + cost = price * filled; + } + } + } + const id = this.safeString (order, 'orderId'); + const type = this.safeStringLower (order, 'type'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + const side = this.safeStringLower (order, 'side'); + let fee = undefined; + let trades = undefined; + const fills = this.safeValue (order, 'fills'); + if (fills !== undefined) { + trades = this.parseTrades (fills, market); + const numTrades = trades.length; + if (numTrades > 0) { + cost = trades[0]['cost']; + fee = { + 'cost': trades[0]['fee']['cost'], + 'currency': trades[0]['fee']['currency'], + }; + for (let i = 1; i < trades.length; i++) { + cost = this.sum (cost, trades[i]['cost']); + fee['cost'] = this.sum (fee['cost'], trades[i]['fee']['cost']); + } + } + } + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + if (this.options['parseOrderToPrecision']) { + cost = parseFloat (this.costToPrecision (symbol, cost)); + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'info': order, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let accountId = undefined; + if (market['margin']) { + accountId = this.safeInteger (params, 'accountId'); + if (accountId === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires an accountId parameter for ' + market['type'] + ' market ' + symbol); + } + } + const uppercaseType = type.toUpperCase (); + const newOrderRespType = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); + const request = { + 'symbol': market['id'], + 'quantity': this.amountToPrecision (symbol, amount), + 'type': uppercaseType, + 'side': side.toUpperCase (), + 'newOrderRespType': newOrderRespType, // 'RESULT' for full order or 'FULL' for order with fills + // 'leverage': 1, + // 'accountId': 5470306579272968, // required for leverage markets + // 'takeProfit': '123.45', + // 'stopLoss': '54.321' + // 'guaranteedStopLoss': '54.321', + }; + if (uppercaseType === 'LIMIT') { + request['price'] = this.priceToPrecision (symbol, price); + request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel, 'FOK' = Fill Or Kill + } else if (uppercaseType === 'STOP') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrder (this.extend (request, params)); + // + // { + // "symbol": "BTC/USD", + // "orderId": "00000000-0000-0000-0000-0000000c0263", + // "clientOrderId": "00000000-0000-0000-0000-0000000c0263", + // "transactTime": 1589878206426, + // "price": "9825.66210000", + // "origQty": "0.01", + // "executedQty": "0.01", + // "status": "FILLED", + // "timeInForce": "FOK", + // "type": "MARKET", + // "side": "BUY", + // "fills": [ + // { + // "price": "9807.05", + // "qty": "0.01", + // "commission": "0", + // "commissionAsset": "dUSD" + // } + // ] + // } + // + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) { + const symbols = this.symbols; + const numSymbols = symbols.length; + const fetchOpenOrdersRateLimit = parseInt (numSymbols / 2); + throw new ExchangeError (this.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.'); + } + const response = await this.privateGetOpenOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const origClientOrderId = this.safeValue (params, 'origClientOrderId'); + const request = { + 'symbol': market['id'], + // 'orderId': parseInt (id), + // 'origClientOrderId': id, + }; + if (origClientOrderId === undefined) { + request['orderId'] = id; + } else { + request['origClientOrderId'] = origClientOrderId; + } + const response = await this.privateDeleteOrder (this.extend (request, params)); + // + // { + // "symbol":"ETH/USD", + // "orderId":"00000000-0000-0000-0000-00000024383b", + // "clientOrderId":"00000000-0000-0000-0000-00000024383b", + // "price":"150", + // "origQty":"0.1", + // "executedQty":"0.0", + // "status":"CANCELED", + // "timeInForce":"GTC", + // "type":"LIMIT", + // "side":"BUY" + // } + // + return this.parseOrder (response, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetMyTrades (this.extend (request, params)); + // + // [ + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + path; + if (path === 'historicalTrades') { + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + } + if (api === 'private') { + this.checkRequiredCredentials (); + let query = this.urlencode (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': this.options['recvWindow'], + }, params)); + const signature = this.hmac (this.encode (query), this.encode (this.secret)); + query += '&' + 'signature=' + signature; + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + if ((method === 'GET') || (method === 'DELETE')) { + url += '?' + query; + } else { + body = query; + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + } else { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((httpCode === 418) || (httpCode === 429)) { + throw new DDoSProtection (this.id + ' ' + httpCode.toString () + ' ' + reason + ' ' + body); + } + // error response in a form: { "code": -1013, "msg": "Invalid quantity." } + // following block cointains legacy checks against message patterns in "msg" property + // will switch "code" checks eventually, when we know all of them + if (httpCode >= 400) { + if (body.indexOf ('Price * QTY is zero or less') >= 0) { + throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body); + } + if (body.indexOf ('LOT_SIZE') >= 0) { + throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size ' + body); + } + if (body.indexOf ('PRICE_FILTER') >= 0) { + throw new InvalidOrder (this.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use this.priceToPrecision (symbol, amount) ' + body); + } + } + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"code":-1128,"msg":"Combination of optional parameters invalid."} + // + const errorCode = this.safeString (response, 'code'); + if ((errorCode !== undefined) && (errorCode !== '0')) { + const feedback = this.id + ' ' + this.json (response); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + const message = this.safeString (response, 'msg'); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],75:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, DDoSProtection, NotSupported, ExchangeNotAvailable, InsufficientFunds, BadRequest, InvalidAddress, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class deribit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'deribit', + 'name': 'Deribit', + 'countries': [ 'NL' ], // Netherlands + 'version': 'v2', + 'userAgent': undefined, + 'rateLimit': 500, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '3m': '3', + '5m': '5', + '10m': '10', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '3h': '180', + '6h': '360', + '12h': '720', + '1d': '1D', + }, + 'urls': { + 'test': 'https://test.deribit.com', + 'logo': 'https://user-images.githubusercontent.com/1294454/41933112-9e2dd65a-798b-11e8-8440-5bab2959fcb8.jpg', + 'api': 'https://www.deribit.com', + 'www': 'https://www.deribit.com', + 'doc': [ + 'https://docs.deribit.com/v2', + 'https://github.com/deribit', + ], + 'fees': 'https://www.deribit.com/pages/information/fees', + 'referral': 'https://www.deribit.com/reg-1189.4038', + }, + 'api': { + 'public': { + 'get': [ + // Authentication + 'auth', + 'exchange_token', + 'fork_token', + // Session management + 'set_heartbeat', + 'disable_heartbeat', + // Supporting + 'get_time', + 'hello', + 'test', + // Subscription management + 'subscribe', + 'unsubscribe', + // Account management + 'get_announcements', + // Market data + 'get_book_summary_by_currency', + 'get_book_summary_by_instrument', + 'get_contract_size', + 'get_currencies', + 'get_funding_chart_data', + 'get_funding_rate_history', + 'get_funding_rate_value', + 'get_historical_volatility', + 'get_index', + 'get_instruments', + 'get_last_settlements_by_currency', + 'get_last_settlements_by_instrument', + 'get_last_trades_by_currency', + 'get_last_trades_by_currency_and_time', + 'get_last_trades_by_instrument', + 'get_last_trades_by_instrument_and_time', + 'get_order_book', + 'get_trade_volumes', + 'get_tradingview_chart_data', + 'ticker', + ], + }, + 'private': { + 'get': [ + // Authentication + 'logout', + // Session management + 'enable_cancel_on_disconnect', + 'disable_cancel_on_disconnect', + 'get_cancel_on_disconnect', + // Subscription management + 'subscribe', + 'unsubscribe', + // Account management + 'change_api_key_name', + 'change_scope_in_api_key', + 'change_subaccount_name', + 'create_api_key', + 'create_subaccount', + 'disable_api_key', + 'disable_tfa_for_subaccount', + 'enable_api_key', + 'get_account_summary', + 'get_email_language', + 'get_new_announcements', + 'get_position', + 'get_positions', + 'get_subaccounts', + 'list_api_keys', + 'remove_api_key', + 'reset_api_key', + 'set_announcement_as_read', + 'set_api_key_as_default', + 'set_email_for_subaccount', + 'set_email_language', + 'set_password_for_subaccount', + 'toggle_notifications_from_subaccount', + 'toggle_subaccount_login', + // Block Trade + 'execute_block_trade', + 'get_block_trade', + 'get_last_block_trades_by_currency', + 'invalidate_block_trade_signature', + 'verify_block_trade', + // Trading + 'buy', + 'sell', + 'edit', + 'cancel', + 'cancel_all', + 'cancel_all_by_currency', + 'cancel_all_by_instrument', + 'cancel_by_label', + 'close_position', + 'get_margins', + 'get_open_orders_by_currency', + 'get_open_orders_by_instrument', + 'get_order_history_by_currency', + 'get_order_history_by_instrument', + 'get_order_margin_by_ids', + 'get_order_state', + 'get_stop_order_history', + 'get_user_trades_by_currency', + 'get_user_trades_by_currency_and_time', + 'get_user_trades_by_instrument', + 'get_user_trades_by_instrument_and_time', + 'get_user_trades_by_order', + 'get_settlement_history_by_instrument', + 'get_settlement_history_by_currency', + // Wallet + 'cancel_transfer_by_id', + 'cancel_withdrawal', + 'create_deposit_address', + 'get_current_deposit_address', + 'get_deposits', + 'get_transfers', + 'get_withdrawals', + 'submit_transfer_to_subaccount', + 'submit_transfer_to_user', + 'withdraw', + ], + }, + }, + 'exceptions': { + // 0 or absent Success, No error. + '9999': PermissionDenied, // 'api_not_enabled' User didn't enable API for the Account. + '10000': AuthenticationError, // 'authorization_required' Authorization issue, invalid or absent signature etc. + '10001': ExchangeError, // 'error' Some general failure, no public information available. + '10002': InvalidOrder, // 'qty_too_low' Order quantity is too low. + '10003': InvalidOrder, // 'order_overlap' Rejection, order overlap is found and self-trading is not enabled. + '10004': OrderNotFound, // 'order_not_found' Attempt to operate with order that can't be found by specified id. + '10005': InvalidOrder, // 'price_too_low ' Price is too low, defines current limit for the operation. + '10006': InvalidOrder, // 'price_too_low4idx ' Price is too low for current index, defines current bottom limit for the operation. + '10007': InvalidOrder, // 'price_too_high ' Price is too high, defines current up limit for the operation. + '10008': InvalidOrder, // 'price_too_high4idx ' Price is too high for current index, defines current up limit for the operation. + '10009': InsufficientFunds, // 'not_enough_funds' Account has not enough funds for the operation. + '10010': OrderNotFound, // 'already_closed' Attempt of doing something with closed order. + '10011': InvalidOrder, // 'price_not_allowed' This price is not allowed for some reason. + '10012': InvalidOrder, // 'book_closed' Operation for instrument which order book had been closed. + '10013': PermissionDenied, // 'pme_max_total_open_orders ' Total limit of open orders has been exceeded, it is applicable for PME users. + '10014': PermissionDenied, // 'pme_max_future_open_orders ' Limit of count of futures' open orders has been exceeded, it is applicable for PME users. + '10015': PermissionDenied, // 'pme_max_option_open_orders ' Limit of count of options' open orders has been exceeded, it is applicable for PME users. + '10016': PermissionDenied, // 'pme_max_future_open_orders_size ' Limit of size for futures has been exceeded, it is applicable for PME users. + '10017': PermissionDenied, // 'pme_max_option_open_orders_size ' Limit of size for options has been exceeded, it is applicable for PME users. + '10018': PermissionDenied, // 'non_pme_max_future_position_size ' Limit of size for futures has been exceeded, it is applicable for non-PME users. + '10019': PermissionDenied, // 'locked_by_admin' Trading is temporary locked by admin. + '10020': ExchangeError, // 'invalid_or_unsupported_instrument' Instrument name is not valid. + '10021': InvalidOrder, // 'invalid_amount' Amount is not valid. + '10022': InvalidOrder, // 'invalid_quantity' quantity was not recognized as a valid number (for API v1). + '10023': InvalidOrder, // 'invalid_price' price was not recognized as a valid number. + '10024': InvalidOrder, // 'invalid_max_show' max_show parameter was not recognized as a valid number. + '10025': InvalidOrder, // 'invalid_order_id' Order id is missing or its format was not recognized as valid. + '10026': InvalidOrder, // 'price_precision_exceeded' Extra precision of the price is not supported. + '10027': InvalidOrder, // 'non_integer_contract_amount' Futures contract amount was not recognized as integer. + '10028': DDoSProtection, // 'too_many_requests' Allowed request rate has been exceeded. + '10029': OrderNotFound, // 'not_owner_of_order' Attempt to operate with not own order. + '10030': ExchangeError, // 'must_be_websocket_request' REST request where Websocket is expected. + '10031': ExchangeError, // 'invalid_args_for_instrument' Some of arguments are not recognized as valid. + '10032': InvalidOrder, // 'whole_cost_too_low' Total cost is too low. + '10033': NotSupported, // 'not_implemented' Method is not implemented yet. + '10034': InvalidOrder, // 'stop_price_too_high' Stop price is too high. + '10035': InvalidOrder, // 'stop_price_too_low' Stop price is too low. + '10036': InvalidOrder, // 'invalid_max_show_amount' Max Show Amount is not valid. + '10040': ExchangeNotAvailable, // 'retry' Request can't be processed right now and should be retried. + '10041': OnMaintenance, // 'settlement_in_progress' Settlement is in progress. Every day at settlement time for several seconds, the system calculates user profits and updates balances. That time trading is paused for several seconds till the calculation is completed. + '10043': InvalidOrder, // 'price_wrong_tick' Price has to be rounded to a certain tick size. + '10044': InvalidOrder, // 'stop_price_wrong_tick' Stop Price has to be rounded to a certain tick size. + '10045': InvalidOrder, // 'can_not_cancel_liquidation_order' Liquidation order can't be canceled. + '10046': InvalidOrder, // 'can_not_edit_liquidation_order' Liquidation order can't be edited. + '10047': DDoSProtection, // 'matching_engine_queue_full' Reached limit of pending Matching Engine requests for user. + '10048': ExchangeError, // 'not_on_this_server' The requested operation is not available on this server. + '11008': InvalidOrder, // 'already_filled' This request is not allowed in regards to the filled order. + '11029': BadRequest, // 'invalid_arguments' Some invalid input has been detected. + '11030': ExchangeError, // 'other_reject ' Some rejects which are not considered as very often, more info may be specified in . + '11031': ExchangeError, // 'other_error ' Some errors which are not considered as very often, more info may be specified in . + '11035': DDoSProtection, // 'no_more_stops ' Allowed amount of stop orders has been exceeded. + '11036': InvalidOrder, // 'invalid_stoppx_for_index_or_last' Invalid StopPx (too high or too low) as to current index or market. + '11037': BadRequest, // 'outdated_instrument_for_IV_order' Instrument already not available for trading. + '11038': InvalidOrder, // 'no_adv_for_futures' Advanced orders are not available for futures. + '11039': InvalidOrder, // 'no_adv_postonly' Advanced post-only orders are not supported yet. + '11041': InvalidOrder, // 'not_adv_order' Advanced order properties can't be set if the order is not advanced. + '11042': PermissionDenied, // 'permission_denied' Permission for the operation has been denied. + '11043': BadRequest, // 'bad_argument' Bad argument has been passed. + '11044': InvalidOrder, // 'not_open_order' Attempt to do open order operations with the not open order. + '11045': BadRequest, // 'invalid_event' Event name has not been recognized. + '11046': BadRequest, // 'outdated_instrument' At several minutes to instrument expiration, corresponding advanced implied volatility orders are not allowed. + '11047': BadRequest, // 'unsupported_arg_combination' The specified combination of arguments is not supported. + '11048': ExchangeError, // 'wrong_max_show_for_option' Wrong Max Show for options. + '11049': BadRequest, // 'bad_arguments' Several bad arguments have been passed. + '11050': BadRequest, // 'bad_request' Request has not been parsed properly. + '11051': OnMaintenance, // 'system_maintenance' System is under maintenance. + '11052': ExchangeError, // 'subscribe_error_unsubscribed' Subscription error. However, subscription may fail without this error, please check list of subscribed channels returned, as some channels can be not subscribed due to wrong input or lack of permissions. + '11053': ExchangeError, // 'transfer_not_found' Specified transfer is not found. + '11090': InvalidAddress, // 'invalid_addr' Invalid address. + '11091': InvalidAddress, // 'invalid_transfer_address' Invalid addres for the transfer. + '11092': InvalidAddress, // 'address_already_exist' The address already exists. + '11093': DDoSProtection, // 'max_addr_count_exceeded' Limit of allowed addresses has been reached. + '11094': ExchangeError, // 'internal_server_error' Some unhandled error on server. Please report to admin. The details of the request will help to locate the problem. + '11095': ExchangeError, // 'disabled_deposit_address_creation' Deposit address creation has been disabled by admin. + '11096': ExchangeError, // 'address_belongs_to_user' Withdrawal instead of transfer. + '12000': AuthenticationError, // 'bad_tfa' Wrong TFA code + '12001': DDoSProtection, // 'too_many_subaccounts' Limit of subbacounts is reached. + '12002': ExchangeError, // 'wrong_subaccount_name' The input is not allowed as name of subaccount. + '12998': AuthenticationError, // 'tfa_over_limit' The number of failed TFA attempts is limited. + '12003': AuthenticationError, // 'login_over_limit' The number of failed login attempts is limited. + '12004': AuthenticationError, // 'registration_over_limit' The number of registration requests is limited. + '12005': AuthenticationError, // 'country_is_banned' The country is banned (possibly via IP check). + '12100': ExchangeError, // 'transfer_not_allowed' Transfer is not allowed. Possible wrong direction or other mistake. + '12999': AuthenticationError, // 'tfa_used' TFA code is correct but it is already used. Please, use next code. + '13000': AuthenticationError, // 'invalid_login' Login name is invalid (not allowed or it contains wrong characters). + '13001': AuthenticationError, // 'account_not_activated' Account must be activated. + '13002': PermissionDenied, // 'account_blocked' Account is blocked by admin. + '13003': AuthenticationError, // 'tfa_required' This action requires TFA authentication. + '13004': AuthenticationError, // 'invalid_credentials' Invalid credentials has been used. + '13005': AuthenticationError, // 'pwd_match_error' Password confirmation error. + '13006': AuthenticationError, // 'security_error' Invalid Security Code. + '13007': AuthenticationError, // 'user_not_found' User's security code has been changed or wrong. + '13008': ExchangeError, // 'request_failed' Request failed because of invalid input or internal failure. + '13009': AuthenticationError, // 'unauthorized' Wrong or expired authorization token or bad signature. For example, please check scope of the token, 'connection' scope can't be reused for other connections. + '13010': BadRequest, // 'value_required' Invalid input, missing value. + '13011': BadRequest, // 'value_too_short' Input is too short. + '13012': PermissionDenied, // 'unavailable_in_subaccount' Subaccount restrictions. + '13013': BadRequest, // 'invalid_phone_number' Unsupported or invalid phone number. + '13014': BadRequest, // 'cannot_send_sms' SMS sending failed -- phone number is wrong. + '13015': BadRequest, // 'invalid_sms_code' Invalid SMS code. + '13016': BadRequest, // 'invalid_input' Invalid input. + '13017': ExchangeError, // 'subscription_failed' Subscription hailed, invalid subscription parameters. + '13018': ExchangeError, // 'invalid_content_type' Invalid content type of the request. + '13019': ExchangeError, // 'orderbook_closed' Closed, expired order book. + '13020': ExchangeError, // 'not_found' Instrument is not found, invalid instrument name. + '13021': PermissionDenied, // 'forbidden' Not enough permissions to execute the request, forbidden. + '13025': ExchangeError, // 'method_switched_off_by_admin' API method temporarily switched off by administrator. + '-32602': BadRequest, // 'Invalid params' see JSON-RPC spec. + '-32601': BadRequest, // 'Method not found' see JSON-RPC spec. + '-32700': BadRequest, // 'Parse error' see JSON-RPC spec. + '-32000': BadRequest, // 'Missing params' see JSON-RPC spec. + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'code': 'BTC', + 'fetchBalance': { + 'code': 'BTC', + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetGetTime (params); + // + // { + // jsonrpc: '2.0', + // result: 1583922446019, + // usIn: 1583922446019955, + // usOut: 1583922446019956, + // usDiff: 1, + // testnet: false + // } + // + return this.safeInteger (response, 'result'); + } + + codeFromOptions (methodName) { + const defaultCode = this.safeValue (this.options, 'code', 'BTC'); + const options = this.safeValue (this.options, methodName, {}); + return this.safeValue (options, 'code', defaultCode); + } + + async fetchStatus (params = {}) { + const request = { + // 'expected_result': false, // true will trigger an error for testing purposes + }; + await this.publicGetTest (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { version: '1.2.26' }, + // usIn: 1583922623964485, + // usOut: 1583922623964487, + // usDiff: 2, + // testnet: false + // } + // + this.status = this.extend (this.status, { + 'status': 'ok', + 'updated': this.milliseconds (), + }); + return this.status; + } + + async fetchMarkets (params = {}) { + const currenciesResponse = await this.publicGetGetCurrencies (params); + // + // { + // jsonrpc: '2.0', + // result: [ + // { + // withdrawal_priorities: [ + // { value: 0.15, name: 'very_low' }, + // { value: 1.5, name: 'very_high' }, + // ], + // withdrawal_fee: 0.0005, + // min_withdrawal_fee: 0.0005, + // min_confirmations: 1, + // fee_precision: 4, + // currency_long: 'Bitcoin', + // currency: 'BTC', + // coin_type: 'BITCOIN' + // } + // ], + // usIn: 1583761588590479, + // usOut: 1583761588590544, + // usDiff: 65, + // testnet: false + // } + // + const currenciesResult = this.safeValue (currenciesResponse, 'result', []); + const result = []; + for (let i = 0; i < currenciesResult.length; i++) { + const currencyId = this.safeString (currenciesResult[i], 'currency'); + const request = { + 'currency': currencyId, + }; + const instrumentsResponse = await this.publicGetGetInstruments (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: [ + // { + // tick_size: 0.0005, + // taker_commission: 0.0004, + // strike: 300, + // settlement_period: 'week', + // quote_currency: 'USD', + // option_type: 'call', + // min_trade_amount: 1, + // maker_commission: 0.0004, + // kind: 'option', + // is_active: true, + // instrument_name: 'ETH-13MAR20-300-C', + // expiration_timestamp: 1584086400000, + // creation_timestamp: 1582790403000, + // contract_size: 1, + // base_currency: 'ETH' + // }, + // ], + // usIn: 1583761889500586, + // usOut: 1583761889505066, + // usDiff: 4480, + // testnet: false + // } + // + const instrumentsResult = this.safeValue (instrumentsResponse, 'result', []); + for (let k = 0; k < instrumentsResult.length; k++) { + const market = instrumentsResult[k]; + const id = this.safeString (market, 'instrument_name'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const type = this.safeString (market, 'kind'); + const future = (type === 'future'); + const option = (type === 'option'); + const active = this.safeValue (market, 'is_active'); + const minTradeAmount = this.safeFloat (market, 'min_trade_amount'); + const tickSize = this.safeFloat (market, 'tick_size'); + const precision = { + 'amount': minTradeAmount, + 'price': tickSize, + }; + result.push ({ + 'id': id, + 'symbol': id, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'taker': this.safeFloat (market, 'taker_commission'), + 'maker': this.safeFloat (market, 'maker_commission'), + 'limits': { + 'amount': { + 'min': minTradeAmount, + 'max': undefined, + }, + 'price': { + 'min': tickSize, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'type': type, + 'spot': false, + 'future': future, + 'option': option, + 'info': market, + }); + } + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const code = this.codeFromOptions ('fetchBalance'); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetGetAccountSummary (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // total_pl: 0, + // session_upl: 0, + // session_rpl: 0, + // session_funding: 0, + // portfolio_margining_enabled: false, + // options_vega: 0, + // options_theta: 0, + // options_session_upl: 0, + // options_session_rpl: 0, + // options_pl: 0, + // options_gamma: 0, + // options_delta: 0, + // margin_balance: 0.00062359, + // maintenance_margin: 0, + // limits: { + // non_matching_engine_burst: 300, + // non_matching_engine: 200, + // matching_engine_burst: 20, + // matching_engine: 2 + // }, + // initial_margin: 0, + // futures_session_upl: 0, + // futures_session_rpl: 0, + // futures_pl: 0, + // equity: 0.00062359, + // deposit_address: '13tUtNsJSZa1F5GeCmwBywVrymHpZispzw', + // delta_total: 0, + // currency: 'BTC', + // balance: 0.00062359, + // available_withdrawal_funds: 0.00062359, + // available_funds: 0.00062359 + // }, + // usIn: 1583775838115975, + // usOut: 1583775838116520, + // usDiff: 545, + // testnet: false + // } + // + const result = { + 'info': response, + }; + const balance = this.safeValue (response, 'result', {}); + const currencyId = this.safeString (balance, 'currency'); + const currencyCode = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableFunds'); + account['used'] = this.safeFloat (balance, 'maintenanceMargin'); + account['total'] = this.safeFloat (balance, 'equity'); + result[currencyCode] = account; + return this.parseBalance (result); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetCreateDepositAddress (this.extend (request, params)); + // + // { + // 'jsonrpc': '2.0', + // 'id': 7538, + // 'result': { + // 'address': '2N8udZGBc1hLRCFsU9kGwMPpmYUwMFTuCwB', + // 'creation_timestamp': 1550575165170, + // 'currency': 'BTC', + // 'type': 'deposit' + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const address = this.safeString (result, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetGetCurrentDepositAddress (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // type: 'deposit', + // status: 'ready', + // requires_confirmation: true, + // currency: 'BTC', + // creation_timestamp: 1514694684651, + // address: '13tUtNsJSZa1F5GeCmwBywVrymHpZispzw' + // }, + // usIn: 1583785137274288, + // usOut: 1583785137274454, + // usDiff: 166, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', {}); + const address = this.safeString (result, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker /public/ticker + // + // { + // timestamp: 1583778859480, + // stats: { volume: 60627.57263769, low: 7631.5, high: 8311.5 }, + // state: 'open', + // settlement_price: 7903.21, + // open_interest: 111543850, + // min_price: 7634, + // max_price: 7866.51, + // mark_price: 7750.02, + // last_price: 7750.5, + // instrument_name: 'BTC-PERPETUAL', + // index_price: 7748.01, + // funding_8h: 0.0000026, + // current_funding: 0, + // best_bid_price: 7750, + // best_bid_amount: 19470, + // best_ask_price: 7750.5, + // best_ask_amount: 343280 + // } + // + // fetchTicker /public/get_book_summary_by_instrument + // fetchTickers /public/get_book_summary_by_currency + // + // { + // volume: 124.1, + // underlying_price: 7856.445926872601, + // underlying_index: 'SYN.BTC-10MAR20', + // quote_currency: 'USD', + // open_interest: 121.8, + // mid_price: 0.01975, + // mark_price: 0.01984559, + // low: 0.0095, + // last: 0.0205, + // interest_rate: 0, + // instrument_name: 'BTC-10MAR20-7750-C', + // high: 0.0295, + // estimated_delivery_price: 7856.29, + // creation_timestamp: 1583783678366, + // bid_price: 0.0185, + // base_currency: 'BTC', + // ask_price: 0.021 + // }, + // + const timestamp = this.safeInteger2 (ticker, 'timestamp', 'creation_timestamp'); + const marketId = this.safeString (ticker, 'instrument_name'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat2 (ticker, 'last_price', 'last'); + const stats = this.safeValue (ticker, 'stats', ticker); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (stats, 'high', 'max_price'), + 'low': this.safeFloat2 (stats, 'low', 'min_price'), + 'bid': this.safeFloat2 (ticker, 'best_bid_price', 'bid_price'), + 'bidVolume': this.safeFloat (ticker, 'best_bid_amount'), + 'ask': this.safeFloat2 (ticker, 'best_ask_price', 'ask_price'), + 'askVolume': this.safeFloat (ticker, 'best_ask_amount'), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (stats, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // timestamp: 1583778859480, + // stats: { volume: 60627.57263769, low: 7631.5, high: 8311.5 }, + // state: 'open', + // settlement_price: 7903.21, + // open_interest: 111543850, + // min_price: 7634, + // max_price: 7866.51, + // mark_price: 7750.02, + // last_price: 7750.5, + // instrument_name: 'BTC-PERPETUAL', + // index_price: 7748.01, + // funding_8h: 0.0000026, + // current_funding: 0, + // best_bid_price: 7750, + // best_bid_amount: 19470, + // best_ask_price: 7750.5, + // best_ask_amount: 343280 + // }, + // usIn: 1583778859483941, + // usOut: 1583778859484075, + // usDiff: 134, + // testnet: false + // } + // + const result = this.safeValue (response, 'result'); + return this.parseTicker (result, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const code = this.codeFromOptions ('fetchTickers'); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.publicGetGetBookSummaryByCurrency (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: [ + // { + // volume: 124.1, + // underlying_price: 7856.445926872601, + // underlying_index: 'SYN.BTC-10MAR20', + // quote_currency: 'USD', + // open_interest: 121.8, + // mid_price: 0.01975, + // mark_price: 0.01984559, + // low: 0.0095, + // last: 0.0205, + // interest_rate: 0, + // instrument_name: 'BTC-10MAR20-7750-C', + // high: 0.0295, + // estimated_delivery_price: 7856.29, + // creation_timestamp: 1583783678366, + // bid_price: 0.0185, + // base_currency: 'BTC', + // ask_price: 0.021 + // }, + // ], + // usIn: 1583783678361966, + // usOut: 1583783678372069, + // usDiff: 10103, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', []); + const tickers = {}; + for (let i = 0; i < result.length; i++) { + const ticker = this.parseTicker (result[i]); + const symbol = ticker['symbol']; + tickers[symbol] = ticker; + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + 'resolution': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + const now = this.milliseconds (); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + request['start_timestamp'] = now - (limit - 1) * duration * 1000; + request['end_timestamp'] = now; + } + } else { + request['start_timestamp'] = since; + if (limit === undefined) { + request['end_timestamp'] = now; + } else { + request['end_timestamp'] = this.sum (since, limit * duration * 1000); + } + } + const response = await this.publicGetGetTradingviewChartData (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // volume: [ 3.6680847969999992, 22.682721123, 3.011587939, 0 ], + // ticks: [ 1583916960000, 1583917020000, 1583917080000, 1583917140000 ], + // status: 'ok', + // open: [ 7834, 7839, 7833.5, 7833 ], + // low: [ 7834, 7833.5, 7832.5, 7833 ], + // high: [ 7839.5, 7839, 7833.5, 7833 ], + // cost: [ 28740, 177740, 23590, 0 ], + // close: [ 7839.5, 7833.5, 7833, 7833 ] + // }, + // usIn: 1583917166709801, + // usOut: 1583917166710175, + // usDiff: 374, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', {}); + const ohlcvs = this.convertTradingViewToOHLCV (result, 'ticks', 'open', 'high', 'low', 'close', 'volume', true); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // 'trade_seq': 39201926, + // 'trade_id':' 64135724', + // 'timestamp': 1583174775400, + // 'tick_direction': 1, + // 'price': 8865.0, + // 'instrument_name': 'BTC-PERPETUAL', + // 'index_price': 8863.31, + // 'direction': 'buy', + // 'amount': 10.0 + // } + // + // fetchMyTrades, fetchOrderTrades (private) + // + // { + // "trade_seq": 3, + // "trade_id": "ETH-34066", + // "timestamp": 1550219814585, + // "tick_direction": 1, + // "state": "open", + // "self_trade": false, + // "reduce_only": false, + // "price": 0.04, + // "post_only": false, + // "order_type": "limit", + // "order_id": "ETH-334607", + // "matching_id": null, + // "liquidity": "M", + // "iv": 56.83, + // "instrument_name": "ETH-22FEB19-120-C", + // "index_price": 121.37, + // "fee_currency": "ETH", + // "fee": 0.0011, + // "direction": "buy", + // "amount": 11 + // } + // + const id = this.safeString (trade, 'trade_id'); + const marketId = this.safeString (trade, 'instrument_name'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (trade, 'timestamp'); + const side = this.safeString (trade, 'direction'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const liquidity = this.safeString (trade, 'liquidity'); + let takerOrMaker = undefined; + if (liquidity !== undefined) { + // M = maker, T = taker, MT = both + takerOrMaker = (liquidity === 'M') ? 'maker' : 'taker'; + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': this.safeString (trade, 'order_id'), + 'type': this.safeString (trade, 'order_type'), + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + 'include_old': true, + }; + const method = (since === undefined) ? 'publicGetGetLastTradesByInstrument' : 'publicGetGetLastTradesByInstrumentAndTime'; + if (since !== undefined) { + request['start_timestamp'] = since; + } + if (limit !== undefined) { + request['count'] = limit; // default 10 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // 'jsonrpc': '2.0', + // 'result': { + // 'trades': [ + // { + // 'trade_seq': 39201926, + // 'trade_id':' 64135724', + // 'timestamp': 1583174775400, + // 'tick_direction': 1, + // 'price': 8865.0, + // 'instrument_name': 'BTC-PERPETUAL', + // 'index_price': 8863.31, + // 'direction': 'buy', + // 'amount': 10.0 + // }, + // ], + // 'has_more': true, + // }, + // 'usIn': 1583779594843931, + // 'usOut': 1583779594844446, + // 'usDiff': 515, + // 'testnet': false + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetGetOrderBook (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // timestamp: 1583781354740, + // stats: { volume: 61249.66735634, low: 7631.5, high: 8311.5 }, + // state: 'open', + // settlement_price: 7903.21, + // open_interest: 111536690, + // min_price: 7695.13, + // max_price: 7929.49, + // mark_price: 7813.06, + // last_price: 7814.5, + // instrument_name: 'BTC-PERPETUAL', + // index_price: 7810.12, + // funding_8h: 0.0000031, + // current_funding: 0, + // change_id: 17538025952, + // bids: [ + // [7814, 351820], + // [7813.5, 207490], + // [7813, 32160], + // ], + // best_bid_price: 7814, + // best_bid_amount: 351820, + // best_ask_price: 7814.5, + // best_ask_amount: 11880, + // asks: [ + // [7814.5, 11880], + // [7815, 18100], + // [7815.5, 2640], + // ], + // }, + // usIn: 1583781354745804, + // usOut: 1583781354745932, + // usDiff: 128, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', {}); + const timestamp = this.safeInteger (result, 'timestamp'); + const nonce = this.safeInteger (result, 'change_id'); + const orderbook = this.parseOrderBook (result, timestamp); + orderbook['nonce'] = nonce; + return orderbook; + } + + parseOrderStatus (status) { + const statuses = { + 'open': 'open', + 'cancelled': 'canceled', + 'filled': 'closed', + 'rejected': 'rejected', + // 'untriggered': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'good_til_cancelled': 'GTC', + 'fill_or_kill': 'FOK', + 'immediate_or_cancel': 'IOC', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "time_in_force": "good_til_cancelled", + // "reduce_only": false, + // "profit_loss": 0, + // "price": "market_price", + // "post_only": false, + // "order_type": "market", + // "order_state": "filled", + // "order_id": "ETH-349249", + // "max_show": 40, + // "last_update_timestamp": 1550657341322, + // "label": "market0000234", + // "is_liquidation": false, + // "instrument_name": "ETH-PERPETUAL", + // "filled_amount": 40, + // "direction": "buy", + // "creation_timestamp": 1550657341322, + // "commission": 0.000139, + // "average_price": 143.81, + // "api": true, + // "amount": 40, + // "trades": [], // injected by createOrder + // } + // + const timestamp = this.safeInteger (order, 'creation_timestamp'); + const lastUpdate = this.safeInteger (order, 'last_update_timestamp'); + const id = this.safeString (order, 'order_id'); + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'average_price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled_amount'); + let lastTradeTimestamp = undefined; + if (filled !== undefined) { + if (filled > 0) { + lastTradeTimestamp = lastUpdate; + } + } + let remaining = undefined; + let cost = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (price !== undefined) { + cost = price * filled; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'order_state')); + const marketId = this.safeString (order, 'instrument_name'); + market = this.safeMarket (marketId, market); + const side = this.safeStringLower (order, 'direction'); + let feeCost = this.safeFloat (order, 'commission'); + let fee = undefined; + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + fee = { + 'cost': feeCost, + 'currency': market['base'], + }; + } + const type = this.safeString (order, 'order_type'); + // injected in createOrder + let trades = this.safeValue (order, 'trades'); + if (trades !== undefined) { + trades = this.parseTrades (trades, market); + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force')); + const stopPrice = undefined; + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetGetOrderState (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 4316, + // "result": { + // "time_in_force": "good_til_cancelled", + // "reduce_only": false, + // "profit_loss": 0.051134, + // "price": 118.94, + // "post_only": false, + // "order_type": "limit", + // "order_state": "filled", + // "order_id": "ETH-331562", + // "max_show": 37, + // "last_update_timestamp": 1550219810944, + // "label": "", + // "is_liquidation": false, + // "instrument_name": "ETH-PERPETUAL", + // "filled_amount": 37, + // "direction": "sell", + // "creation_timestamp": 1550219749176, + // "commission": 0.000031, + // "average_price": 118.94, + // "api": false, + // "amount": 37 + // } + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + // for perpetual and futures the amount is in USD + // for options it is in corresponding cryptocurrency contracts, e.g., BTC or ETH + 'amount': this.amountToPrecision (symbol, amount), + 'type': type, // limit, stop_limit, market, stop_market, default is limit + // 'label': 'string', // user-defined label for the order (maximum 64 characters) + // 'price': this.priceToPrecision (symbol, 123.45), // only for limit and stop_limit orders + // 'time_in_force' : 'good_til_cancelled', // fill_or_kill, immediate_or_cancel + // 'max_show': 123.45, // max amount within an order to be shown to other customers, 0 for invisible order + // 'post_only': false, // if the new price would cause the order to be filled immediately (as taker), the price will be changed to be just below the spread. + // 'reject_post_only': false, // if true the order is put to order book unmodified or request is rejected + // 'reduce_only': false, // if true, the order is intended to only reduce a current position + // 'stop_price': false, // stop price, required for stop_limit orders + // 'trigger': 'index_price', // mark_price, last_price, required for stop_limit orders + // 'advanced': 'usd', // 'implv', advanced option order type, options only + }; + let priceIsRequired = false; + let stopPriceIsRequired = false; + if (type === 'limit') { + priceIsRequired = true; + } else if (type === 'stop_limit') { + priceIsRequired = true; + stopPriceIsRequired = true; + } + if (priceIsRequired) { + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } else { + throw new ArgumentsRequired (this.id + ' createOrder requires a price argument for a ' + type + ' order'); + } + } + if (stopPriceIsRequired) { + const stopPrice = this.safeFloat2 (params, 'stop_price', 'stopPrice'); + if (stopPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a stop_price or stopPrice param for a ' + type + ' order'); + } else { + request['stop_price'] = this.priceToPrecision (symbol, stopPrice); + } + params = this.omit (params, [ 'stop_price', 'stopPrice' ]); + } + const method = 'privateGet' + this.capitalize (side); + const response = await this[method] (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 5275, + // "result": { + // "trades": [ + // { + // "trade_seq": 14151, + // "trade_id": "ETH-37435", + // "timestamp": 1550657341322, + // "tick_direction": 2, + // "state": "closed", + // "self_trade": false, + // "price": 143.81, + // "order_type": "market", + // "order_id": "ETH-349249", + // "matching_id": null, + // "liquidity": "T", + // "label": "market0000234", + // "instrument_name": "ETH-PERPETUAL", + // "index_price": 143.73, + // "fee_currency": "ETH", + // "fee": 0.000139, + // "direction": "buy", + // "amount": 40 + // } + // ], + // "order": { + // "time_in_force": "good_til_cancelled", + // "reduce_only": false, + // "profit_loss": 0, + // "price": "market_price", + // "post_only": false, + // "order_type": "market", + // "order_state": "filled", + // "order_id": "ETH-349249", + // "max_show": 40, + // "last_update_timestamp": 1550657341322, + // "label": "market0000234", + // "is_liquidation": false, + // "instrument_name": "ETH-PERPETUAL", + // "filled_amount": 40, + // "direction": "buy", + // "creation_timestamp": 1550657341322, + // "commission": 0.000139, + // "average_price": 143.81, + // "api": true, + // "amount": 40 + // } + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const order = this.safeValue (result, 'order'); + const trades = this.safeValue (result, 'trades', []); + order['trades'] = trades; + return this.parseOrder (order, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (amount === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires an amount argument'); + } + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires a price argument'); + } + await this.loadMarkets (); + const request = { + 'order_id': id, + // for perpetual and futures the amount is in USD + // for options it is in corresponding cryptocurrency contracts, e.g., BTC or ETH + 'amount': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), // required + // 'post_only': false, // if the new price would cause the order to be filled immediately (as taker), the price will be changed to be just below the spread. + // 'reject_post_only': false, // if true the order is put to order book unmodified or request is rejected + // 'reduce_only': false, // if true, the order is intended to only reduce a current position + // 'stop_price': false, // stop price, required for stop_limit orders + // 'advanced': 'usd', // 'implv', advanced option order type, options only + }; + const response = await this.privateGetEdit (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + const order = this.safeValue (result, 'order'); + const trades = this.safeValue (result, 'trades', []); + order['trades'] = trades; + return this.parseOrder (order); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetCancel (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = undefined; + if (symbol === undefined) { + method = 'privateGetCancelAll'; + } else { + method = 'privateGetCancelAllByInstrument'; + const market = this.market (symbol); + request['instrument_name'] = market['id']; + } + const response = await this[method] (this.extend (request, params)); + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + let method = undefined; + if (symbol === undefined) { + const code = this.codeFromOptions ('fetchOpenOrders'); + const currency = this.currency (code); + request['currency'] = currency['id']; + method = 'privateGetGetOpenOrdersByCurrency'; + } else { + market = this.market (symbol); + request['instrument_name'] = market['id']; + method = 'privateGetGetOpenOrdersByInstrument'; + } + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + let method = undefined; + if (symbol === undefined) { + const code = this.codeFromOptions ('fetchClosedOrders'); + const currency = this.currency (code); + request['currency'] = currency['id']; + method = 'privateGetGetOrderHistoryByCurrency'; + } else { + market = this.market (symbol); + request['instrument_name'] = market['id']; + method = 'privateGetGetOrderHistoryByInstrument'; + } + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetGetUserTradesByOrder (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 9367, + // "result": { + // "trades": [ + // { + // "trade_seq": 3, + // "trade_id": "ETH-34066", + // "timestamp": 1550219814585, + // "tick_direction": 1, + // "state": "open", + // "self_trade": false, + // "reduce_only": false, + // "price": 0.04, + // "post_only": false, + // "order_type": "limit", + // "order_id": "ETH-334607", + // "matching_id": null, + // "liquidity": "M", + // "iv": 56.83, + // "instrument_name": "ETH-22FEB19-120-C", + // "index_price": 121.37, + // "fee_currency": "ETH", + // "fee": 0.0011, + // "direction": "buy", + // "amount": 11 + // }, + // ], + // "has_more": true + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, undefined, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'include_old': true, + }; + let market = undefined; + let method = undefined; + if (symbol === undefined) { + const code = this.codeFromOptions ('fetchMyTrades'); + const currency = this.currency (code); + request['currency'] = currency['id']; + if (since === undefined) { + method = 'privateGetGetUserTradesByCurrency'; + } else { + method = 'privateGetGetUserTradesByCurrencyAndTime'; + request['start_timestamp'] = since; + } + } else { + market = this.market (symbol); + request['instrument_name'] = market['id']; + if (since === undefined) { + method = 'privateGetGetUserTradesByInstrument'; + } else { + method = 'privateGetGetUserTradesByInstrumentAndTime'; + request['start_timestamp'] = since; + } + } + if (limit !== undefined) { + request['count'] = limit; // default 10 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 9367, + // "result": { + // "trades": [ + // { + // "trade_seq": 3, + // "trade_id": "ETH-34066", + // "timestamp": 1550219814585, + // "tick_direction": 1, + // "state": "open", + // "self_trade": false, + // "reduce_only": false, + // "price": 0.04, + // "post_only": false, + // "order_type": "limit", + // "order_id": "ETH-334607", + // "matching_id": null, + // "liquidity": "M", + // "iv": 56.83, + // "instrument_name": "ETH-22FEB19-120-C", + // "index_price": 121.37, + // "fee_currency": "ETH", + // "fee": 0.0011, + // "direction": "buy", + // "amount": 11 + // }, + // ], + // "has_more": true + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetDeposits (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 5611, + // "result": { + // "count": 1, + // "data": [ + // { + // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", + // "amount": 5, + // "currency": "BTC", + // "received_timestamp": 1549295017670, + // "state": "completed", + // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", + // "updated_timestamp": 1549295130159 + // } + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetWithdrawals (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 2745, + // "result": { + // "count": 1, + // "data": [ + // { + // "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", + // "amount": 0.5, + // "confirmed_timestamp": null, + // "created_timestamp": 1550571443070, + // "currency": "BTC", + // "fee": 0.0001, + // "id": 1, + // "priority": 0.15, + // "state": "unconfirmed", + // "transaction_id": null, + // "updated_timestamp": 1550571443070 + // } + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + 'completed': 'ok', + 'unconfirmed': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchWithdrawals + // + // { + // "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", + // "amount": 0.5, + // "confirmed_timestamp": null, + // "created_timestamp": 1550571443070, + // "currency": "BTC", + // "fee": 0.0001, + // "id": 1, + // "priority": 0.15, + // "state": "unconfirmed", + // "transaction_id": null, + // "updated_timestamp": 1550571443070 + // } + // + // fetchDeposits + // + // { + // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", + // "amount": 5, + // "currency": "BTC", + // "received_timestamp": 1549295017670, + // "state": "completed", + // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", + // "updated_timestamp": 1549295130159 + // } + // + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeInteger2 (transaction, 'created_timestamp', 'received_timestamp'); + const updated = this.safeInteger (transaction, 'updated_timestamp'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const address = this.safeString (transaction, 'address'); + const feeCost = this.safeFloat (transaction, 'fee'); + let type = 'deposit'; + let fee = undefined; + if (feeCost !== undefined) { + type = 'withdrawal'; + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'transaction_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'tagFrom': undefined, + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + }; + const response = await this.privateGetPosition (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 404, + // "result": { + // "average_price": 0, + // "delta": 0, + // "direction": "buy", + // "estimated_liquidation_price": 0, + // "floating_profit_loss": 0, + // "index_price": 3555.86, + // "initial_margin": 0, + // "instrument_name": "BTC-PERPETUAL", + // "leverage": 100, + // "kind": "future", + // "maintenance_margin": 0, + // "mark_price": 3556.62, + // "open_orders_margin": 0.000165889, + // "realized_profit_loss": 0, + // "settlement_price": 3555.44, + // "size": 0, + // "size_currency": 0, + // "total_profit_loss": 0 + // } + // } + // + // todo unify parsePosition/parsePositions + const result = this.safeValue (response, 'result'); + return result; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const code = this.codeFromOptions ('fetchPositions'); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetPositions (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 2236, + // "result": [ + // { + // "average_price": 7440.18, + // "delta": 0.006687487, + // "direction": "buy", + // "estimated_liquidation_price": 1.74, + // "floating_profit_loss": 0, + // "index_price": 7466.79, + // "initial_margin": 0.000197283, + // "instrument_name": "BTC-PERPETUAL", + // "kind": "future", + // "leverage": 34, + // "maintenance_margin": 0.000143783, + // "mark_price": 7476.65, + // "open_orders_margin": 0.000197288, + // "realized_funding": -1e-8, + // "realized_profit_loss": -9e-9, + // "settlement_price": 7476.65, + // "size": 50, + // "size_currency": 0.006687487, + // "total_profit_loss": 0.000032781 + // } + // ] + // } + // + // todo unify parsePosition/parsePositions + const result = this.safeValue (response, 'result', []); + return result; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'address': address, // must be in the address book + 'amount': amount, + // 'priority': 'high', // low, mid, high, very_high, extreme_high, insane + // 'tfa': '123456', // if enabled + }; + if (this.twofa !== undefined) { + request['tfa'] = this.oath (); + } + const response = await this.privateGetWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + 'api/' + this.version + '/' + api + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + request += '?' + this.urlencode (params); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const timestamp = this.milliseconds ().toString (); + const requestBody = ''; + if (Object.keys (params).length) { + request += '?' + this.urlencode (params); + } + const requestData = method + "\n" + request + "\n" + requestBody + "\n"; // eslint-disable-line quotes + const auth = timestamp + "\n" + nonce + "\n" + requestData; // eslint-disable-line quotes + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + headers = { + 'Authorization': 'deri-hmac-sha256 id=' + this.apiKey + ',ts=' + timestamp + ',sig=' + signature + ',' + 'nonce=' + nonce, + }; + } + const url = this.urls['api'] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + // + // { + // jsonrpc: '2.0', + // error: { + // message: 'Invalid params', + // data: { reason: 'invalid currency', param: 'currency' }, + // code: -32602 + // }, + // testnet: false, + // usIn: 1583763842150374, + // usOut: 1583763842150410, + // usDiff: 36 + // } + // + const error = this.safeValue (response, 'error'); + if (error !== undefined) { + const errorCode = this.safeString (error, 'code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],76:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AccountSuspended, BadRequest, BadResponse, NetworkError, DDoSProtection, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, InsufficientFunds, InvalidOrder, InvalidNonce, OrderNotFound, InvalidAddress, RateLimitExceeded, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class digifinex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'digifinex', + 'name': 'DigiFinex', + 'countries': [ 'SG' ], + 'version': 'v3', + 'rateLimit': 900, // 300 for posts + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '4h': '240', + '12h': '720', + '1d': '1D', + '1w': '1W', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443315-01283a00-c5fe-11ea-8628-c2a0feaf07ac.jpg', + 'api': 'https://openapi.digifinex.com', + 'www': 'https://www.digifinex.com', + 'doc': [ + 'https://docs.digifinex.com', + ], + 'fees': 'https://digifinex.zendesk.com/hc/en-us/articles/360000328422-Fee-Structure-on-DigiFinex', + 'referral': 'https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp', + }, + 'api': { + 'v2': { + 'get': [ + 'ticker', + ], + }, + 'public': { + 'get': [ + '{market}/symbols', + 'kline', + 'margin/currencies', + 'margin/symbols', + 'markets', + 'order_book', + 'ping', + 'spot/symbols', + 'time', + 'trades', + 'trades/symbols', + 'ticker', + 'currencies', // todo add fetchCurrencies + ], + }, + 'private': { + 'get': [ + '{market}/financelog', + '{market}/mytrades', + '{market}/order', + '{market}​/order​/detail', // todo add fetchOrder + '{market}/order/current', + '{market}/order/history', + 'margin/assets', + 'margin/financelog', + 'margin/mytrades', + 'margin/order', + 'margin/order/current', + 'margin/order/history', + 'margin/positions', + 'otc/financelog', + 'spot/assets', + 'spot/financelog', + 'spot/mytrades', + 'spot/order', + 'spot/order/current', + 'spot/order/history', + 'deposit/address', // todo add fetchDepositAddress + 'deposit/history', // todo add fetchDeposits + 'withdraw/history', // todo add fetchWithdrawals + ], + 'post': [ + '{market}/order/cancel', + '{market}/order/new', + '{market}​/order​/batch_new', + 'margin/order/cancel', + 'margin/order/new', + 'margin/position/close', + 'spot/order/cancel', + 'spot/order/new', + 'transfer', + 'withdraw/new', // todo add withdraw() + 'withdraw/cancel', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.002, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + '10001': [ BadRequest, "Wrong request method, please check it's a GET ot POST request" ], + '10002': [ AuthenticationError, 'Invalid ApiKey' ], + '10003': [ AuthenticationError, "Sign doesn't match" ], + '10004': [ BadRequest, 'Illegal request parameters' ], + '10005': [ DDoSProtection, 'Request frequency exceeds the limit' ], + '10006': [ PermissionDenied, 'Unauthorized to execute this request' ], + '10007': [ PermissionDenied, 'IP address Unauthorized' ], + '10008': [ InvalidNonce, 'Timestamp for this request is invalid, timestamp must within 1 minute' ], + '10009': [ NetworkError, 'Unexist endpoint, please check endpoint URL' ], + '10011': [ AccountSuspended, 'ApiKey expired. Please go to client side to re-create an ApiKey' ], + '20001': [ PermissionDenied, 'Trade is not open for this trading pair' ], + '20002': [ PermissionDenied, 'Trade of this trading pair is suspended' ], + '20003': [ InvalidOrder, 'Invalid price or amount' ], + '20007': [ InvalidOrder, 'Price precision error' ], + '20008': [ InvalidOrder, 'Amount precision error' ], + '20009': [ InvalidOrder, 'Amount is less than the minimum requirement' ], + '20010': [ InvalidOrder, 'Cash Amount is less than the minimum requirement' ], + '20011': [ InsufficientFunds, 'Insufficient balance' ], + '20012': [ BadRequest, 'Invalid trade type, valid value: buy/sell)' ], + '20013': [ InvalidOrder, 'No order info found' ], + '20014': [ BadRequest, 'Invalid date, Valid format: 2018-07-25)' ], + '20015': [ BadRequest, 'Date exceeds the limit' ], + '20018': [ PermissionDenied, 'Your trading rights have been banned by the system' ], + '20019': [ BadRequest, 'Wrong trading pair symbol. Correct format:"usdt_btc". Quote asset is in the front' ], + '20020': [ DDoSProtection, "You have violated the API operation trading rules and temporarily forbid trading. At present, we have certain restrictions on the user's transaction rate and withdrawal rate." ], + '50000': [ ExchangeError, 'Exception error' ], + '20021': [ BadRequest, 'Invalid currency' ], + '20022': [ BadRequest, 'The ending timestamp must be larger than the starting timestamp' ], + '20023': [ BadRequest, 'Invalid transfer type' ], + '20024': [ BadRequest, 'Invalid amount' ], + '20025': [ BadRequest, 'This currency is not transferable at the moment' ], + '20026': [ InsufficientFunds, 'Transfer amount exceed your balance' ], + '20027': [ PermissionDenied, 'Abnormal account status' ], + '20028': [ PermissionDenied, 'Blacklist for transfer' ], + '20029': [ PermissionDenied, 'Transfer amount exceed your daily limit' ], + '20030': [ BadRequest, 'You have no position on this trading pair' ], + '20032': [ PermissionDenied, 'Withdrawal limited' ], + '20033': [ BadRequest, 'Wrong Withdrawal ID' ], + '20034': [ PermissionDenied, 'Withdrawal service of this crypto has been closed' ], + '20035': [ PermissionDenied, 'Withdrawal limit' ], + '20036': [ ExchangeError, 'Withdrawal cancellation failed' ], + '20037': [ InvalidAddress, 'The withdrawal address, Tag or chain type is not included in the withdrawal management list' ], + '20038': [ InvalidAddress, 'The withdrawal address is not on the white list' ], + '20039': [ ExchangeError, "Can't be canceled in current status" ], + '20040': [ RateLimitExceeded, 'Withdraw too frequently; limitation: 3 times a minute, 100 times a day' ], + '20041': [ PermissionDenied, 'Beyond the daily withdrawal limit' ], + '20042': [ BadSymbol, 'Current trading pair does not support API trading' ], + }, + 'broad': { + }, + }, + 'options': { + 'defaultType': 'spot', + 'types': [ 'spot', 'margin', 'otc' ], + }, + 'commonCurrencies': { + 'BHT': 'Black House Test', + 'MBN': 'Mobilian Coin', + 'TEL': 'TEL666', + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "data":[ + // { + // "deposit_status":1, + // "min_deposit_amount":10, + // "withdraw_fee_rate":0, + // "min_withdraw_amount":10, + // "min_withdraw_fee":5, + // "currency":"USDT", + // "withdraw_status":0, + // "chain":"OMNI" + // }, + // { + // "deposit_status":1, + // "min_deposit_amount":10, + // "withdraw_fee_rate":0, + // "min_withdraw_amount":10, + // "min_withdraw_fee":3, + // "currency":"USDT", + // "withdraw_status":1, + // "chain":"ERC20" + // }, + // { + // "deposit_status":0, + // "min_deposit_amount":0, + // "withdraw_fee_rate":0, + // "min_withdraw_amount":0, + // "min_withdraw_fee":0, + // "currency":"DGF13", + // "withdraw_status":0, + // "chain":"" + // }, + // ], + // "code":200 + // } + // + const data = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < data.length; i++) { + const currency = data[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const depositStatus = this.safeValue (currency, 'deposit_status', 1); + const withdrawStatus = this.safeValue (currency, 'withdraw_status', 1); + const active = depositStatus && withdrawStatus; + const fee = this.safeFloat (currency, 'withdraw_fee_rate'); + if (code in result) { + if (Array.isArray (result[code]['info'])) { + result[code]['info'].push (currency); + } else { + result[code]['info'] = [ result[code]['info'], currency ]; + } + } else { + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': undefined, + 'active': active, + 'fee': fee, + 'precision': 8, // todo fix hardcoded value + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'min_withdraw_amount'), + 'max': undefined, + }, + }, + }; + } + } + return result; + } + + async fetchMarkets (params = {}) { + const options = this.safeValue (this.options, 'fetchMarkets', {}); + const method = this.safeString (options, 'method', 'fetch_markets_v2'); + return await this[method] (params); + } + + async fetchMarketsV2 (params = {}) { + const response = await this.publicGetTradesSymbols (params); + // + // { + // "symbol_list":[ + // { + // "order_types":["LIMIT","MARKET"], + // "quote_asset":"USDT", + // "minimum_value":2, + // "amount_precision":4, + // "status":"TRADING", + // "minimum_amount":0.0001, + // "symbol":"BTC_USDT", + // "is_allow":1, + // "zone":"MAIN", + // "base_asset":"BTC", + // "price_precision":2 + // } + // ], + // "code":0 + // } + // + const markets = this.safeValue (response, 'symbol_list', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'base_asset'); + const quoteId = this.safeString (market, 'quote_asset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amount_precision'), + 'price': this.safeInteger (market, 'price_precision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_amount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minimum_value'), + 'max': undefined, + }, + }; + // + // The status is documented in the exchange API docs as follows: + // TRADING, HALT (delisted), BREAK (trading paused) + // https://docs.digifinex.vip/en-ww/v3/#/public/spot/symbols + // However, all spot markets actually have status === 'HALT' + // despite that they appear to be active on the exchange website. + // Apparently, we can't trust this status. + // const status = this.safeString (market, 'status'); + // const active = (status === 'TRADING'); + // + const isAllowed = this.safeValue (market, 'is_allow', 1); + const active = isAllowed ? true : false; + const type = 'spot'; + const spot = (type === 'spot'); + const margin = (type === 'margin'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'type': type, + 'spot': spot, + 'margin': margin, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchMarketsV1 (params = {}) { + const response = await this.publicGetMarkets (params); + // + // { + // "data": [ + // { + // "volume_precision":4, + // "price_precision":2, + // "market":"btc_usdt", + // "min_amount":2, + // "min_volume":0.0001 + // }, + // ], + // "date":1564507456, + // "code":0 + // } + // + const markets = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'market'); + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'volume_precision'), + 'price': this.safeInteger (market, 'price_precision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'min_volume'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': undefined, + }, + }; + const active = undefined; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const method = 'privateGet' + this.capitalize (type) + 'Assets'; + const response = await this[method] (params); + // + // { + // "code": 0, + // "list": [ + // { + // "currency": "BTC", + // "free": 4723846.89208129, + // "total": 0 + // } + // ] + // } + const balances = this.safeValue (response, 'list', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'frozen'); + account['free'] = this.safeFloat (balance, 'free'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 150 + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { + // "bids": [ + // [9605.77,0.0016], + // [9605.46,0.0003], + // [9602.04,0.0127], + // ], + // "asks": [ + // [9627.22,0.025803], + // [9627.12,0.168543], + // [9626.52,0.0011529], + // ], + // "date":1564509499, + // "code":0 + // } + // + const timestamp = this.safeTimestamp (response, 'date'); + return this.parseOrderBook (response, timestamp); + } + + async fetchTickers (symbols = undefined, params = {}) { + const apiKey = this.safeValue (params, 'apiKey', this.apiKey); + if (!apiKey) { + throw new ArgumentsRequired (this.id + ' fetchTicker is a private v2 endpoint that requires an `exchange.apiKey` credential or an `apiKey` extra parameter'); + } + await this.loadMarkets (); + const request = { + 'apiKey': apiKey, + }; + const response = await this.v2GetTicker (this.extend (request, params)); + // + // { + // "ticker":{ + // "btc_eth":{ + // "last":0.021957, + // "base_vol":2249.3521732227, + // "change":-0.6, + // "vol":102443.5111, + // "sell":0.021978, + // "low":0.021791, + // "buy":0.021946, + // "high":0.022266 + // } + // }, + // "date":1564518452, + // "code":0 + // } + // + const result = {}; + const tickers = this.safeValue (response, 'ticker', {}); + const date = this.safeInteger (response, 'date'); + const reversedMarketIds = Object.keys (tickers); + for (let i = 0; i < reversedMarketIds.length; i++) { + const reversedMarketId = reversedMarketIds[i]; + const ticker = this.extend ({ + 'date': date, + }, tickers[reversedMarketId]); + const [ quoteId, baseId ] = reversedMarketId.split ('_'); + const marketId = baseId.toUpperCase () + '_' + quoteId.toUpperCase (); + const market = this.safeMarket (marketId, undefined, '_'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const apiKey = this.safeValue (params, 'apiKey', this.apiKey); + if (!apiKey) { + throw new ArgumentsRequired (this.id + ' fetchTicker is a private v2 endpoint that requires an `exchange.apiKey` credential or an `apiKey` extra parameter'); + } + await this.loadMarkets (); + const market = this.market (symbol); + // reversed base/quote in v2 + const marketId = market['quoteId'].toLowerCase () + '_' + market['baseId'].toLowerCase (); + const request = { + 'symbol': marketId, + 'apiKey': apiKey, + }; + const response = await this.v2GetTicker (this.extend (request, params)); + // + // { + // "ticker":{ + // "btc_eth":{ + // "last":0.021957, + // "base_vol":2249.3521732227, + // "change":-0.6, + // "vol":102443.5111, + // "sell":0.021978, + // "low":0.021791, + // "buy":0.021946, + // "high":0.022266 + // } + // }, + // "date":1564518452, + // "code":0 + // } + // + const date = this.safeInteger (response, 'date'); + const ticker = this.safeValue (response, 'ticker', {}); + let result = this.safeValue (ticker, marketId, {}); + result = this.extend ({ 'date': date }, result); + return this.parseTicker (result, market); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "last":0.021957, + // "base_vol":2249.3521732227, + // "change":-0.6, + // "vol":102443.5111, + // "sell":0.021978, + // "low":0.021791, + // "buy":0.021946, + // "high":0.022266, + // "date"1564518452, // injected from fetchTicker/fetchTickers + // } + // + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (ticker, 'date'); + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'change'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'base_vol'), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "date":1564520003, + // "id":1596149203, + // "amount":0.7073, + // "type":"buy", + // "price":0.02193, + // } + // + // fetchMyTrades (private) + // + // { + // "symbol": "BTC_USDT", + // "order_id": "6707cbdcda0edfaa7f4ab509e4cbf966", + // "id": 28457, + // "price": 0.1, + // "amount": 0, + // "fee": 0.096, + // "fee_currency": "USDT", + // "timestamp": 1499865549, + // "side": "buy", + // "is_maker": true + // } + // + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'order_id'); + const timestamp = this.safeTimestamp2 (trade, 'date', 'timestamp'); + const side = this.safeString2 (trade, 'type', 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const takerOrMaker = this.safeValue (trade, 'is_maker'); + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': orderId, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "server_time": 1589873762, + // "code": 0 + // } + // + return this.safeTimestamp (response, 'server_time'); + } + + async fetchStatus (params = {}) { + await this.publicGetPing (params); + // + // { + // "msg": "pong", + // "code": 0 + // } + // + this.status = this.extend (this.status, { + 'status': 'ok', + 'updated': this.milliseconds (), + }); + return this.status; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 500 + } + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "data":[ + // { + // "date":1564520003, + // "id":1596149203, + // "amount":0.7073, + // "type":"buy", + // "price":0.02193, + // }, + // { + // "date":1564520002, + // "id":1596149165, + // "amount":0.3232, + // "type":"sell", + // "price":0.021927, + // }, + // ], + // "code": 0, + // "date": 1564520003, + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1556712900, + // 2205.899, + // 0.029967, + // 0.02997, + // 0.029871, + // 0.029927 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 5), // open + this.safeFloat (ohlcv, 3), // high + this.safeFloat (ohlcv, 4), // low + this.safeFloat (ohlcv, 2), // close + this.safeFloat (ohlcv, 1), // volume + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + // 'start_time': 1564520003, // starting timestamp, 200 candles before end_time by default + // 'end_time': 1564520003, // ending timestamp, current timestamp by default + }; + if (since !== undefined) { + const startTime = parseInt (since / 1000); + request['start_time'] = startTime; + if (limit !== undefined) { + const duration = this.parseTimeframe (timeframe); + request['end_time'] = this.sum (startTime, limit * duration); + } + } else if (limit !== undefined) { + const endTime = this.seconds (); + const duration = this.parseTimeframe (timeframe); + request['startTime'] = this.sum (endTime, -limit * duration); + } + const response = await this.publicGetKline (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // [1556712900,2205.899,0.029967,0.02997,0.029871,0.029927], + // [1556713800,1912.9174,0.029992,0.030014,0.029955,0.02996], + // [1556714700,1556.4795,0.029974,0.030019,0.029969,0.02999], + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const request = { + 'market': orderType, + 'symbol': market['id'], + 'amount': this.amountToPrecision (symbol, amount), + // 'post_only': 0, // 0 by default, if set to 1 the order will be canceled if it can be executed immediately, making sure there will be no market taking + }; + let suffix = ''; + if (type === 'market') { + suffix = '_market'; + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + request['type'] = side + suffix; + const response = await this.privatePostMarketOrderNew (this.extend (request, params)); + // + // { + // "code": 0, + // "order_id": "198361cecdc65f9c8c9bb2fa68faec40" + // } + // + const result = this.parseOrder (response, market); + return this.extend (result, { + 'symbol': symbol, + 'side': side, + 'type': type, + 'amount': amount, + 'price': price, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const request = { + 'market': orderType, + 'order_id': id, + }; + const response = await this.privatePostMarketOrderCancel (this.extend (request, params)); + // + // { + // "code": 0, + // "success": [ + // "198361cecdc65f9c8c9bb2fa68faec40", + // "3fb0d98e51c18954f10d439a9cf57de0" + // ], + // "error": [ + // "78a7104e3c65cc0c5a212a53e76d0205" + // ] + // } + // + const canceledOrders = this.safeValue (response, 'success', []); + const numCanceledOrders = canceledOrders.length; + if (numCanceledOrders !== 1) { + throw new OrderNotFound (this.id + ' cancelOrder ' + id + ' not found'); + } + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const request = { + 'market': orderType, + 'order_id': ids.join (','), + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "success": [ + // "198361cecdc65f9c8c9bb2fa68faec40", + // "3fb0d98e51c18954f10d439a9cf57de0" + // ], + // "error": [ + // "78a7104e3c65cc0c5a212a53e76d0205" + // ] + // } + // + const canceledOrders = this.safeValue (response, 'success', []); + const numCanceledOrders = canceledOrders.length; + if (numCanceledOrders < 1) { + throw new OrderNotFound (this.id + ' cancelOrders error'); + } + return response; + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', // partially filled + '2': 'closed', + '3': 'canceled', + '4': 'canceled', // partially filled and canceled + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "code": 0, + // "order_id": "198361cecdc65f9c8c9bb2fa68faec40" + // } + // + // fetchOrder, fetchOpenOrders, fetchOrders + // + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // + const id = this.safeString (order, 'order_id'); + const timestamp = this.safeTimestamp (order, 'created_date'); + const lastTradeTimestamp = this.safeTimestamp (order, 'finished_date'); + let side = this.safeString (order, 'type'); + let type = undefined; + if (side !== undefined) { + const parts = side.split ('_'); + const numParts = parts.length; + if (numParts > 1) { + side = parts[0]; + type = parts[1]; + } else { + type = 'limit'; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'executed_amount'); + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'avg_price'); + let remaining = undefined; + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = filled * average; + } + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'average': average, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + const request = { + 'market': orderType, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetMarketOrderCurrent (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + const request = { + 'market': orderType, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); // default 3 days from now, max 30 days + } + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 100 + } + const response = await this.privateGetMarketOrderHistory (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'market': orderType, + 'order_id': id, + }; + const response = await this.privateGetMarketOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const order = this.safeValue (data, 0); + if (order === undefined) { + throw new OrderNotFound (this.id + ' fetchOrder() order ' + id + ' not found'); + } + return this.parseOrder (order, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + const request = { + 'market': orderType, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); // default 3 days from now, max 30 days + } + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 100 + } + const response = await this.privateGetMarketMytrades (this.extend (request, params)); + // + // { + // "code": 0, + // "list": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "6707cbdcda0edfaa7f4ab509e4cbf966", + // "id": 28457, + // "price": 0.1, + // "amount": 0, + // "fee": 0.096, + // "fee_currency": "USDT", + // "timestamp": 1499865549, + // "side": "buy", + // "is_maker": true + // } + // ] + // } + // + const data = this.safeValue (response, 'list', []); + return this.parseTrades (data, market, since, limit); + } + + parseLedgerEntryType (type) { + const types = {}; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "currency_mark": "BTC", + // "type": 100234, + // "num": 28457, + // "balance": 0.1, + // "time": 1546272000 + // } + // + const id = this.safeString (item, 'num'); + const account = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const code = this.safeCurrencyCode (this.safeString (item, 'currency_mark'), currency); + const timestamp = this.safeTimestamp (item, 'time'); + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': undefined, + 'account': account, + 'referenceId': undefined, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': undefined, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + const request = { + 'market': orderType, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency_mark'] = currency['id']; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + } + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 1000 + } + const response = await this.privateGetMarketFinancelog (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "total": 521, + // "finance": [ + // { + // "currency_mark": "BTC", + // "type": 100234, + // "num": 28457, + // "balance": 0.1, + // "time": 1546272000 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const items = this.safeValue (data, 'finance', []); + return this.parseLedger (items, currency, since, limit); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // "addressTag":"", + // "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373", + // "currency":"USDT", + // "chain":"ERC20" + // } + // + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'addressTag'); + const currencyId = this.safeStringUpper (depositAddress, 'currency'); + const code = this.safeCurrencCode (currencyId); + return { + 'info': depositAddress, + 'code': code, + 'address': address, + 'tag': tag, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + // + // { + // "data":[ + // { + // "addressTag":"", + // "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373", + // "currency":"USDT", + // "chain":"ERC20" + // } + // ], + // "code":200 + // } + // + const data = this.safeValue (response, 'data', []); + const addresses = this.parseDepositAddresses (data); + const address = this.safeValue (addresses, code); + if (address === undefined) { + throw new InvalidAddress (this.id + ' fetchDepositAddress did not return an address for ' + code + ' - create the deposit address in the user settings on the exchange website first.'); + } + return address; + } + + async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + // 'currency': currency['id'], + // 'from': 'fromId', // When direct is' prev ', from is 1, returning from old to new ascending, when direct is' next ', from is the ID of the most recent record, returned from the old descending order + // 'size': 100, // default 100, max 500 + // 'direct': 'prev', // "prev" ascending, "next" descending + }; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = Math.min (500, limit); + } + const method = (type === 'deposit') ? 'privateGetDepositHistory' : 'privateGetWithdrawHistory'; + const response = await this[method] (this.extend (request, params)); + // + // { + // "code": 200, + // "data": [ + // { + // "id": 1171, + // "currency": "xrp", + // "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "chain": "", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "memo": "100040", + // "fee": 0, + // "state": "safe", + // "created_date": "2020-04-20 11:23:00", + // "finished_date": "2020-04-20 13:23:00" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit, { 'type': type }); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposit', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdrawal', code, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + '0': 'pending', // Email Sent + '1': 'canceled', // Cancelled (different from 1 = ok in deposits) + '2': 'pending', // Awaiting Approval + '3': 'failed', // Rejected + '4': 'pending', // Processing + '5': 'failed', // Failure + '6': 'ok', // Completed + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchWithdrawals + // + // { + // "id": 1171, + // "currency": "xrp", + // "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "chain": "", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "memo": "100040", + // "fee": 0, + // "state": "safe", + // "created_date": "2020-04-20 11:23:00", + // "finished_date": "2020-04-20 13:23:00" + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString (transaction, 'memo'); // set but unused + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const txid = this.safeString (transaction, 'hash'); + const currencyId = this.safeStringUpper (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_date')); + const updated = this.parse8601 (this.safeString (transaction, 'finished_date')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const amount = this.safeFloat (transaction, 'amount'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { 'currency': code, 'cost': feeCost }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': undefined, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const version = (api === 'v2') ? api : this.version; + let url = this.urls['api'] + '/' + version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const urlencoded = this.urlencode (this.keysort (query)); + if (api === 'private') { + const nonce = this.nonce ().toString (); + const auth = urlencoded; + // the signature is not time-limited :\ + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + if (method === 'GET') { + if (urlencoded) { + url += '?' + urlencoded; + } + } else if (method === 'POST') { + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + if (urlencoded) { + body = urlencoded; + } + } + headers = { + 'ACCESS-KEY': this.apiKey, + 'ACCESS-SIGN': signature, + 'ACCESS-TIMESTAMP': nonce, + }; + } else { + if (urlencoded) { + url += '?' + urlencoded; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + if (!response) { + return; // fall back to default error handler + } + const code = this.safeString (response, 'code'); + if ((code === '0') || (code === '200')) { + return; // no error + } + const feedback = this.id + ' ' + responseBody; + if (code === undefined) { + throw new BadResponse (feedback); + } + const unknownError = [ ExchangeError, feedback ]; + const [ ExceptionClass, message ] = this.safeValue (this.exceptions['exact'], code, unknownError); + throw new ExceptionClass (message); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],77:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InvalidNonce, BadRequest, InsufficientFunds, PermissionDenied, DDoSProtection, InvalidOrder, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class dsx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'dsx', + 'name': 'DSX', + 'countries': [ 'UK' ], + 'rateLimit': 1500, + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTransactions': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/76909626-cb2bb100-68bc-11ea-99e0-28ba54f04792.jpg', + 'api': { + 'public': 'https://dsxglobal.com/mapi', // market data + 'private': 'https://dsxglobal.com/tapi', // trading + 'dwapi': 'https://dsxglobal.com/dwapi', // deposit/withdraw + }, + 'www': 'https://dsxglobal.com', + 'doc': [ + 'https://dsxglobal.com/developers/publicApi', + ], + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.15 / 100, + 'taker': 0.25 / 100, + }, + }, + 'timeframes': { + '1m': 'm', + '1h': 'h', + '1d': 'd', + }, + 'api': { + // market data (public) + 'public': { + 'get': [ + 'barsFromMoment/{pair}/{period}/{start}', + 'depth/{pair}', + 'info', + 'lastBars/{pair}/{period}/{amount}', // period is 'm', 'h' or 'd' + 'periodBars/{pair}/{period}/{start}/{end}', + 'ticker/{pair}', + 'trades/{pair}', + ], + }, + // trading (private) + 'private': { + 'post': [ + 'info/account', + 'history/transactions', + 'history/trades', + 'history/orders', + 'orders', + 'order/cancel', + // 'order/cancel/all', + 'order/status', + 'order/new', + 'volume', + 'fees', // trading fee schedule + ], + }, + // deposit / withdraw (private) + 'dwapi': { + 'post': [ + 'deposit/cryptoaddress', + 'withdraw/crypto', + 'withdraw/fiat', + 'withdraw/submit', + // 'withdraw/cancel', + 'transaction/status', // see 'history/transactions' in private tapi above + ], + }, + }, + 'exceptions': { + 'exact': { + 'Sign is invalid': AuthenticationError, // {"success":0,"error":"Sign is invalid"} + 'Order was rejected. Incorrect price.': InvalidOrder, // {"success":0,"error":"Order was rejected. Incorrect price."} + "Order was rejected. You don't have enough money.": InsufficientFunds, // {"success":0,"error":"Order was rejected. You don't have enough money."} + 'This method is blocked for your pair of keys': PermissionDenied, // {"success":0,"error":"This method is blocked for your pair of keys"} + }, + 'broad': { + 'INVALID_PARAMETER': BadRequest, + 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} + 'invalid api key': AuthenticationError, + 'invalid sign': AuthenticationError, + 'api key dont have trade permission': AuthenticationError, + 'invalid parameter': InvalidOrder, + 'invalid order': InvalidOrder, + 'Requests too often': DDoSProtection, + 'not available': ExchangeNotAvailable, + 'data unavailable': ExchangeNotAvailable, + 'external service unavailable': ExchangeNotAvailable, + 'nonce is invalid': InvalidNonce, // {"success":0,"error":"Parameter: nonce is invalid"} + 'Incorrect volume': InvalidOrder, // {"success": 0,"error":"Order was rejected. Incorrect volume."} + }, + }, + 'options': { + 'fetchTickersMaxLength': 250, + }, + 'commonCurrencies': { + 'DSH': 'DASH', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInfo (params); + // + // { + // "server_time": 1522057909, + // "pairs": { + // "ethusd": { + // "decimal_places": 5, + // "min_price": 100, + // "max_price": 1500, + // "min_amount": 0.01, + // "hidden": 0, + // "fee": 0, + // "amount_decimal_places": 4, + // "quoted_currency": "USD", + // "base_currency": "ETH" + // } + // } + // } + // + const markets = this.safeValue (response, 'pairs'); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quoted_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'decimal_places'), + 'price': this.safeInteger (market, 'decimal_places'), + }; + const amountLimits = { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }; + const priceLimits = { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }; + const costLimits = { + 'min': this.safeFloat (market, 'min_total'), + }; + const limits = { + 'amount': amountLimits, + 'price': priceLimits, + 'cost': costLimits, + }; + const hidden = this.safeInteger (market, 'hidden'); + const active = (hidden === 0); + // see parseMarket below + // https://github.com/ccxt/ccxt/pull/5786 + const otherId = base.toLowerCase () + quote.toLowerCase (); + result.push ({ + 'id': id, + 'otherId': otherId, // https://github.com/ccxt/ccxt/pull/5786 + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostInfoAccount (); + // + // { + // "success" : 1, + // "return" : { + // "funds" : { + // "BTC" : { + // "total" : 0, + // "available" : 0 + // }, + // "USD" : { + // "total" : 0, + // "available" : 0 + // }, + // "USDT" : { + // "total" : 0, + // "available" : 0 + // } + // }, + // "rights" : { + // "info" : 1, + // "trade" : 1 + // }, + // "transactionCount" : 0, + // "openOrders" : 0, + // "serverTime" : 1537451465 + // } + // } + // + const balances = this.safeValue (response, 'return'); + const result = { 'info': response }; + const funds = this.safeValue (balances, 'funds'); + const currencyIds = Object.keys (funds); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (funds, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTicker (ticker, market = undefined) { + // + // { high: 0.03492, + // low: 0.03245, + // avg: 29.46133, + // vol: 500.8661, + // vol_cur: 17.000797104, + // last: 0.03364, + // buy: 0.03362, + // sell: 0.03381, + // updated: 1537521993, + // pair: "ethbtc" } + // + const timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + const marketId = this.safeString (ticker, 'pair'); + market = this.parseMarket (marketId); + if (market !== undefined) { + symbol = market['symbol']; + } + // dsx average is inverted, liqui average is not + let average = this.safeFloat (ticker, 'avg'); + if (average !== undefined) { + if (average > 0) { + average = 1 / average; + } + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'vol_cur'), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "amount" : 0.0128, + // "price" : 6483.99000, + // "timestamp" : 1540334614, + // "tid" : 35684364, + // "type" : "ask" + // } + // + // fetchMyTrades (private) + // + // { + // "number": "36635882", // <-- this is present if the trade has come from the '/order/status' call + // "id": "36635882", // <-- this may have been artifically added by the parseTrades method + // "pair": "btcusd", + // "type": "buy", + // "volume": 0.0595, + // "rate": 9750, + // "orderId": 77149299, + // "timestamp": 1519612317, + // "commission": 0.00020825, + // "commissionCurrency": "btc" + // } + // + const timestamp = this.safeTimestamp (trade, 'timestamp'); + let side = this.safeString (trade, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const price = this.safeFloat2 (trade, 'rate', 'price'); + const id = this.safeString2 (trade, 'number', 'id'); + const orderId = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'pair'); + market = this.parseMarket (marketId); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const amount = this.safeFloat2 (trade, 'amount', 'volume'); + const type = 'limit'; // all trades are still limit trades + let takerOrMaker = undefined; + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const isYourOrder = this.safeValue (trade, 'is_your_order'); + if (isYourOrder !== undefined) { + takerOrMaker = 'taker'; + if (isYourOrder) { + takerOrMaker = 'maker'; + } + if (fee === undefined) { + fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker); + } + } + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + parseTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { + let result = []; + if (Array.isArray (trades)) { + for (let i = 0; i < trades.length; i++) { + result.push (this.parseTrade (trades[i], market)); + } + } else { + const ids = Object.keys (trades); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const trade = this.parseTrade (trades[id], market); + result.push (this.extend (trade, { 'id': id }, params)); + } + } + result = this.sortBy (result, 'timestamp'); + const symbol = (market !== undefined) ? market['symbol'] : undefined; + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const market_id_in_reponse = (market['id'] in response); + if (!market_id_in_reponse) { + throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); + } + const orderbook = response[market['id']]; + return this.parseOrderBook (orderbook); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join ('-'); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const result = {}; + ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = id; + if (id in this.markets_by_id) { + const market = this.markets_by_id[id]; + symbol = market['symbol']; + } + result[symbol] = this.parseOrderBook (response[id]); + } + return result; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = this.ids; + if (symbols === undefined) { + const numIds = ids.length; + ids = ids.join ('-'); + const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); + // max URL length is 2048 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + const tickers = await this.publicGetTickerPair (this.extend (request, params)); + // + // { + // "bchbtc" : { + // "high" : 0.02989, + // "low" : 0.02736, + // "avg" : 33.90585, + // "vol" : 0.65982205, + // "vol_cur" : 0.0194604180960, + // "last" : 0.03000, + // "buy" : 0.02980, + // "sell" : 0.03001, + // "updated" : 1568104614, + // "pair" : "bchbtc" + // }, + // "ethbtc" : { + // "high" : 0.01772, + // "low" : 0.01742, + // "avg" : 56.89082, + // "vol" : 229.247115044, + // "vol_cur" : 4.02959737298943, + // "last" : 0.01769, + // "buy" : 0.01768, + // "sell" : 0.01776, + // "updated" : 1568104614, + // "pair" : "ethbtc" + // } + // } + // + const result = {}; + const keys = Object.keys (tickers); + for (let k = 0; k < keys.length; k++) { + const id = keys[k]; + const ticker = tickers[id]; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return tickers[symbol]; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTradesPair (this.extend (request, params)); + if (Array.isArray (response)) { + const numElements = response.length; + if (numElements === 0) { + return []; + } + } + return this.parseTrades (response[market['id']], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "high" : 0.01955, + // "open" : 0.01955, + // "low" : 0.01955, + // "close" : 0.01955, + // "amount" : 2.5, + // "timestamp" : 1565155740000 + // } + // + return [ + this.safeInteger (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'amount'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'period': this.timeframes[timeframe], + }; + let method = 'publicGetLastBarsPairPeriodAmount'; + if (since === undefined) { + if (limit === undefined) { + limit = 100; // required, max 2000 + } + request['amount'] = limit; + } else { + method = 'publicGetPeriodBarsPairPeriodStartEnd'; + // in their docs they expect milliseconds + // but it returns empty arrays with milliseconds + // however, it does work properly with seconds + request['start'] = parseInt (since / 1000); + if (limit === undefined) { + request['end'] = this.seconds (); + } else { + const duration = this.parseTimeframe (timeframe) * 1000; + const end = this.sum (since, duration * limit); + request['end'] = parseInt (end / 1000); + } + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ethbtc": [ + // { + // "high" : 0.01955, + // "open" : 0.01955, + // "low" : 0.01955, + // "close" : 0.01955, + // "amount" : 2.5, + // "timestamp" : 1565155740000 + // }, + // { + // "high" : 0.01967, + // "open" : 0.01967, + // "low" : 0.01967, + // "close" : 0.01967, + // "amount" : 0, + // "timestamp" : 1565155680000 + // } + // ] + // } + // + const candles = this.safeValue (response, market['id'], []); + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (type === 'market' && price === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a price argument even for market orders, that is the worst price that you agree to fill your order for'); + } + const request = { + 'pair': market['id'], + 'type': side, + 'volume': this.amountToPrecision (symbol, amount), + 'rate': this.priceToPrecision (symbol, price), + 'orderType': type, + }; + price = parseFloat (price); + amount = parseFloat (amount); + const response = await this.privatePostOrderNew (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "received": 0, + // "remains": 10, + // "funds": { + // "BTC": { + // "total": 100, + // "available": 95 + // }, + // "USD": { + // "total": 10000, + // "available": 9995 + // }, + // "EUR": { + // "total": 1000, + // "available": 995 + // }, + // "LTC": { + // "total": 1000, + // "available": 995 + // } + // }, + // "orderId": 0, // https://github.com/ccxt/ccxt/issues/3677 + // } + // } + // + let status = 'open'; + let filled = 0.0; + let remaining = amount; + const responseReturn = this.safeValue (response, 'return'); + let id = this.safeString2 (responseReturn, 'orderId', 'order_id'); + if (id === '0') { + id = this.safeString (responseReturn, 'initOrderId', 'init_order_id'); + status = 'closed'; + } + filled = this.safeFloat (responseReturn, 'received', 0.0); + remaining = this.safeFloat (responseReturn, 'remains', amount); + const timestamp = this.milliseconds (); + return { + 'info': response, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * filled, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + // 'trades': this.parseTrades (order['trades'], market), + }; + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', // Active + '1': 'closed', // Filled + '2': 'canceled', // Killed + '3': 'canceling', // Killing + '7': 'canceled', // Rejected + }; + return this.safeString (statuses, status, status); + } + + parseMarket (id) { + if (id in this.markets_by_id) { + return this.markets_by_id[id]; + } else { + // the following is a fix for + // https://github.com/ccxt/ccxt/pull/5786 + // https://github.com/ccxt/ccxt/issues/5770 + let markets_by_other_id = this.safeValue (this.options, 'markets_by_other_id'); + if (markets_by_other_id === undefined) { + this.options['markets_by_other_id'] = this.indexBy (this.markets, 'otherId'); + markets_by_other_id = this.options['markets_by_other_id']; + } + if (id in markets_by_other_id) { + return markets_by_other_id[id]; + } + } + return undefined; + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // "number": 36635882, + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit", + // "deals": [ + // { + // "pair": "btcusd", + // "type": "buy", + // "amount": 1, + // "rate": 1000.0, + // "orderId": 1, + // "timestamp": 1496672724, + // "commission": 0.001, + // "commissionCurrency": "btc" + // } + // ] + // } + // + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'timestampCreated'); + const marketId = this.safeString (order, 'pair'); + market = this.parseMarket (marketId); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const remaining = this.safeFloat (order, 'remainingVolume'); + const amount = this.safeFloat (order, 'volume'); + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + cost = price * filled; + } + } + const orderType = this.safeString (order, 'orderType'); + const side = this.safeString (order, 'type'); + let fee = undefined; + const deals = this.safeValue (order, 'deals', []); + const numDeals = deals.length; + let trades = undefined; + let lastTradeTimestamp = undefined; + if (numDeals > 0) { + trades = this.parseTrades (deals); + let feeCost = undefined; + let feeCurrency = undefined; + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + if (feeCost === undefined) { + feeCost = 0; + } + feeCost = this.sum (feeCost, trade['fee']['cost']); + feeCurrency = trade['fee']['currency']; + lastTradeTimestamp = trade['timestamp']; + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': parseInt (id), + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit", + // "deals": [ + // { + // "pair": "btcusd", + // "type": "buy", + // "amount": 1, + // "rate": 1000.0, + // "orderId": 1, + // "timestamp": 1496672724, + // "commission": 0.001, + // "commissionCurrency": "btc" + // } + // ] + // } + // } + // + return this.parseOrder (this.extend ({ + 'id': id, + }, response['return'])); + } + + parseOrdersById (orders, symbol = undefined, since = undefined, limit = undefined) { + const ids = Object.keys (orders); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const order = this.parseOrder (this.extend ({ + 'id': id.toString (), + }, orders[id])); + result.push (order); + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'count': 10, // Decimal, The maximum number of orders to return + // 'fromId': 123, // Decimal, ID of the first order of the selection + // 'endId': 321, // Decimal, ID of the last order of the selection + // 'order': 'ASC', // String, Order in which orders shown. Possible values are "ASC" — from first to last, "DESC" — from last to first. + }; + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "0": { + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit" + // } + // } + // } + // + return this.parseOrdersById (this.safeValue (response, 'return', {}), symbol, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'count': 10, // Decimal, The maximum number of orders to return + // 'fromId': 123, // Decimal, ID of the first order of the selection + // 'endId': 321, // Decimal, ID of the last order of the selection + // 'order': 'ASC', // String, Order in which orders shown. Possible values are "ASC" — from first to last, "DESC" — from last to first. + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privatePostHistoryOrders (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "0": { + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit" + // } + // } + // } + // + return this.parseOrdersById (this.safeValue (response, 'return', {}), symbol, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + return response; + } + + parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { + const result = []; + const ids = Object.keys (orders); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const order = this.extend ({ 'id': id }, orders[id]); + result.push (this.extend (this.parseOrder (order, market), params)); + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + // some derived classes use camelcase notation for request fields + const request = { + // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) + // 'count': 1000, // the number of trades for display numerical, default = 1000 + // 'from_id': trade ID, from which the display starts numerical 0 + // 'end_id': trade ID on which the display ends numerical ∞ + // 'order': 'ASC', // sorting, default = DESC (test result: liqui ignores this field, most recent trade always goes last) + // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) + // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) + // 'pair': 'eth_btc', // default = all markets + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['count'] = parseInt (limit); + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privatePostHistoryTrades (this.extend (request, params)); + let trades = []; + if ('return' in response) { + trades = response['return']; + } + return this.parseTrades (trades, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privatePostHistoryTransactions (this.extend (request, params)); + // + // { + // "success": 1, + // "return": [ + // { + // "id": 1, + // "timestamp": 11, + // "type": "Withdraw", + // "amount": 1, + // "currency": "btc", + // "confirmationsCount": 6, + // "address": "address", + // "status": 2, + // "commission": 0.0001 + // } + // ] + // } + // + const transactions = this.safeValue (response, 'return', []); + return this.parseTransactions (transactions, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + '1': 'failed', + '2': 'ok', + '3': 'pending', + '4': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": 1, + // "timestamp": 11, // 11 in their docs ( + // "type": "Withdraw", + // "amount": 1, + // "currency": "btc", + // "confirmationsCount": 6, + // "address": "address", + // "status": 2, + // "commission": 0.0001 + // } + // + const timestamp = this.safeTimestamp (transaction, 'timestamp'); + let type = this.safeString (transaction, 'type'); + if (type !== undefined) { + if (type === 'Incoming') { + type = 'deposit'; + } else if (type === 'Withdraw') { + type = 'withdrawal'; + } + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + return { + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'txid'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'address'), + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'fee': { + 'currency': code, + 'cost': this.safeFloat (transaction, 'commission'), + 'rate': undefined, + }, + 'info': transaction, + }; + } + + async createDepositAddress (code, params = {}) { + const request = { + 'new': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + return response; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.dwapiPostDepositCryptoaddress (this.extend (request, params)); + const result = this.safeValue (response, 'return', {}); + const address = this.safeString (result, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, // not documented in DSX API + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const commission = this.safeValue (params, 'commission'); + if (commission === undefined) { + throw new ArgumentsRequired (this.id + ' withdraw() requires a `commission` (withdrawal fee) parameter (string)'); + } + params = this.omit (params, commission); + const request = { + 'currency': currency['id'], + 'amount': parseFloat (amount), + 'address': address, + 'commission': commission, + }; + if (tag !== undefined) { + request['address'] += ':' + tag; + } + const response = await this.dwapiPostWithdrawCrypto (this.extend (request, params)); + // + // [ + // { + // "success": 1, + // "return": { + // "transactionId": 2863073 + // } + // } + // ] + // + const data = this.safeValue (response, 'return', {}); + const id = this.safeString (data, 'transactionId'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.omit (params, this.extractParams (path)); + if (api === 'private' || api === 'dwapi') { + url += '/' + this.version + '/' + this.implodeParams (path, params); + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + }, query)); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512', 'base64'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } else if (api === 'public') { + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + url += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + if (Object.keys (query).length) { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // 1 - Liqui only returns the integer 'success' key from their private API + // + // { "success": 1, ... } httpCode === 200 + // { "success": 0, ... } httpCode === 200 + // + // 2 - However, exchanges derived from Liqui, can return non-integers + // + // It can be a numeric string + // { "sucesss": "1", ... } + // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) + // + // Or just a string + // { "success": "true", ... } + // { "success": "false", ... }, httpCode >= 200 + // + // Or a boolean + // { "success": true, ... } + // { "success": false, ... }, httpCode >= 200 + // + // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types + // + // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui + // + // To cover points 1, 2, 3 and 4 combined this handler should work like this: + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],78:[function(require,module,exports){ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, InvalidOrder, ExchangeError, BadRequest, BadSymbol } = require ('./base/errors'); +const { TRUNCATE, SIGNIFICANT_DIGITS } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class eterbase extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'eterbase', + 'name': 'Eterbase', + 'countries': [ 'SK' ], // Slovakia + 'rateLimit': 500, + 'version': 'v1', + 'certified': true, + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelOrder': true, + 'createOrder': true, + 'deposit': false, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '1h': '60', + '4h': '240', + '1d': '1440', + '1w': '10080', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg', + 'api': 'https://api.eterbase.exchange', + 'www': 'https://www.eterbase.com', + 'doc': 'https://developers.eterbase.exchange', + 'fees': 'https://www.eterbase.com/exchange/fees', + 'referral': 'https://eterbase.exchange/invite/1wjjh4Pe', + }, + 'api': { + 'markets': { + 'get': [ + '{id}/order-book', + ], + }, + 'public': { + 'get': [ + 'ping', + 'assets', + 'markets', + 'tickers', + 'tickers/{id}/ticker', + 'markets/{id}/trades', + 'markets/{id}/ohlcv', + 'wstoken', + ], + }, + 'private': { + 'get': [ + 'accounts/{id}/balances', + 'accounts/{id}/orders', + 'accounts/{id}/fills', + 'orders/{id}/fills', + 'orders/{id}', + ], + 'post': [ + 'orders', + 'accounts/{id}/withdrawals', + ], + 'delete': [ + 'orders/{id}', + ], + }, + 'feed': { + 'get': [ + 'feed', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.35 / 100, + 'maker': 0.35 / 100, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'precisionMode': SIGNIFICANT_DIGITS, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + }, + 'exceptions': { + 'exact': { + 'Invalid cost': InvalidOrder, // {"message":"Invalid cost","_links":{"self":{"href":"/orders","templated":false}}} + 'Invalid order ID': InvalidOrder, // {"message":"Invalid order ID","_links":{"self":{"href":"/orders/4a151805-d594-4a96-9d64-e3984f2441f7","templated":false}}} + 'Invalid market !': BadSymbol, // {"message":"Invalid market !","_links":{"self":{"href":"/markets/300/order-book","templated":false}}} + }, + 'broad': { + 'Failed to convert argument': BadRequest, + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { "pong": 1556354416582 } + // + return this.safeInteger (response, 'pong'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "id":33, + // "symbol":"ETHUSDT", + // "base":"ETH", + // "quote":"USDT", + // "priceSigDigs":5, + // "qtySigDigs":8, + // "costSigDigs":8, + // "verificationLevelUser":1, + // "verificationLevelCorporate":11, + // "group":"USD", + // "tradingRules":[ + // {"attribute":"Qty","condition":"Min","value":0.006}, + // {"attribute":"Qty","condition":"Max","value":1000}, + // {"attribute":"Cost","condition":"Min","value":1}, + // {"attribute":"Cost","condition":"Max","value":210000} + // ], + // "allowedOrderTypes":[1,2,3,4], + // "state":"Trading" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = this.parseMarket (response[i]); + result.push (market); + } + return result; + } + + parseMarket (market) { + // + // { + // "id":33, + // "symbol":"ETHUSDT", + // "base":"ETH", + // "quote":"USDT", + // "priceSigDigs":5, + // "qtySigDigs":8, + // "costSigDigs":8, + // "verificationLevelUser":1, + // "verificationLevelCorporate":11, + // "group":"USD", + // "tradingRules":[ + // {"attribute":"Qty","condition":"Min","value":0.006}, + // {"attribute":"Qty","condition":"Max","value":1000}, + // {"attribute":"Cost","condition":"Min","value":1}, + // {"attribute":"Cost","condition":"Max","value":210000} + // ], + // "allowedOrderTypes":[1,2,3,4], + // "state":"Trading" + // } + // + const id = this.safeString (market, 'id'); + // const numericId = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const state = this.safeString (market, 'state'); + const active = (state === 'Trading'); + const precision = { + 'price': this.safeInteger (market, 'priceSigDigs'), + 'amount': this.safeInteger (market, 'qtySigDigs'), + 'cost': this.safeInteger (market, 'costSigDigs'), + }; + const rules = this.safeValue (market, 'tradingRules', []); + let minAmount = undefined; + let maxAmount = undefined; + let minCost = undefined; + let maxCost = undefined; + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + const attribute = this.safeString (rule, 'attribute'); + const condition = this.safeString (rule, 'condition'); + const value = this.safeFloat (rule, 'value'); + if ((attribute === 'Qty') && (condition === 'Min')) { + minAmount = value; + } else if ((attribute === 'Qty') && (condition === 'Max')) { + maxAmount = value; + } else if ((attribute === 'Cost') && (condition === 'Min')) { + minCost = value; + } else if ((attribute === 'Cost') && (condition === 'Max')) { + maxCost = value; + } + } + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': maxCost, + }, + }, + }; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetAssets (params); + // + // [ + // { + // "id":"LINK", + // "name":"ChainLink Token", + // "precisionDisplay":8, + // "precisionMax":18, + // "precisionBasis":1000000000000000000, + // "precisionStep":1, + // "verificationLevelMin":"null", + // "cmcId":"LINK", + // "txnUrl":"https://etherscan.io/tx/{txnId}", + // "state":"Active", + // "type":"Crypto", + // "isReference":false, + // "withdrawalMin":"0", + // "withdrawalMax":"50587", + // "withdrawalFee":"0.55", + // "depositEnabled":true, + // "withdrawalEnabled":true, + // "description":"", + // "coingeckoUrl":"https://www.coingecko.com/en/coins/chainlink", + // "coinmarketcapUrl":"https://coinmarketcap.com/currencies/chainlink", + // "eterbaseUrl":"https://www.eterbase.com/system-status/LINK", + // "explorerUrl":"https://etherscan.io/token/0x514910771af9ca656af840dff83e8264ecf986ca", + // "withdrawalMemoAllowed":false, + // "countries":[], + // "networks":[] + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'id'); + const precision = this.safeInteger (currency, 'precisionDisplay'); + const code = this.safeCurrencyCode (id); + const depositEnabled = this.safeValue (currency, 'depositEnabled'); + const withdrawalEnabled = this.safeValue (currency, 'withdrawalEnabled'); + const state = this.safeString (currency, 'state'); + const active = depositEnabled && withdrawalEnabled && (state === 'Active'); + const type = this.safeStringLower (currency, 'type'); + const name = this.safeString (currency, 'name'); + result[code] = { + 'id': id, + 'info': currency, + 'code': code, + 'type': type, + 'name': name, + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawalFee'), + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'withdrawalMin'), + 'max': this.safeFloat (currency, 'withdrawalMax'), + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "time":1588778516608, + // "marketId":250, + // "symbol": "ETHUSDT", + // "price":0.0, + // "change":0.0, + // "volumeBase":0.0, + // "volume":0.0, + // "low":0.0, + // "high":0.0, + // } + // + const marketId = this.safeString (ticker, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (ticker, 'time'); + const last = this.safeFloat (ticker, 'price'); + const baseVolume = this.safeFloat (ticker, 'volumeBase'); + const quoteVolume = this.safeFloat (ticker, 'volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + const percentage = this.safeFloat (ticker, 'change'); + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': undefined, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetTickersIdTicker (this.extend (request, params)); + // + // { + // "time":1588778516608, + // "marketId":250, + // "price":0.0, + // "change":0.0, + // "volumeBase":0.0, + // "volume":0.0, + // "low":0.0, + // "high":0.0, + // } + // + return this.parseTicker (response, market); + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'quote': 'USDT', // identifier of a quote asset to filter the markets + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ + // { + // "time":1588831771698, + // "marketId":33, + // "price":204.54, + // "change":-1.03, + // "volumeBase":544.9801776699998, + // "volume":111550.433735, + // "low":200.33, + // "high":209.51 + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + parseTrade (trade, market) { + // + // fetchTrades (public) + // + // { + // "id":251199246, + // "side":2, + // "price":0.022044, + // "executedAt":1588830682664, + // "qty":0.13545846, + // "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c", + // "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3" + // } + // + // fetchMyTrades, fetchOrderTrades (private) + // + // { + // "id": 123, + // "marketId": 123, + // "side": 1, + // "qty": "1.23456", + // "price": "1.23456", + // "cost": "1.23456", + // "fee": "1.23456", + // "feeAsset": "XBASE", + // "liquidity": 1, + // "orderId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "tradeId": 123, + // "filledAt": 1556355722341 + // } + // + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'qty'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeAsset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + let cost = this.safeFloat (trade, 'qty'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const timestamp = this.safeInteger2 (trade, 'executedAt', 'filledAt'); + const tradeSide = this.safeString (trade, 'side'); + const side = (tradeSide === '1') ? 'buy' : 'sell'; + const liquidity = this.safeString (trade, 'liquidity'); + let takerOrMaker = undefined; + if (liquidity !== undefined) { + takerOrMaker = (liquidity === '1') ? 'maker' : 'taker'; + } + const orderId = this.safeString (trade, 'orderId'); + const id = this.safeString (trade, 'id'); + const marketId = this.safeString (trade, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + return { + 'info': trade, + 'id': id, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + // 'offset': 0 // the number of records to skip + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketsIdTrades (this.extend (request, params)); + // + // [ + // { + // "id":251199246, + // "side":2, + // "price":0.022044, + // "executedAt":1588830682664, + // "qty":0.13545846, + // "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c", + // "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.marketsGetIdOrderBook (this.extend (request, params)); + // + // { + // "type":"ob_snapshot", + // "marketId":3, + // "timestamp":1588836429847, + // "bids":[ + // [0.021694,8.8793688,1], // price, amount, count + // [0.01937,7.1340473,1], + // [0.020774,3.314881,1], + // ], + // "asks":[ + // [0.02305,8.8793688,1], + // [0.028022,3.314881,1], + // [0.022598,3.314881,1], + // ] + // } + // + const timestamp = this.safeInteger (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":1588807500000, + // "open":0.022077, + // "high":0.022077, + // "low":0.022051, + // "close":0.022051, + // "volume":10.532025119999997 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + const request = { + // 'id': market['id'], + 'interval': this.timeframes[timeframe], + // 'start': 1588830682664, // milliseconds + // 'end': 1588830682664, // milliseconds + }; + const duration = this.parseTimeframe (timeframe); + const now = this.milliseconds (); + if (since !== undefined) { + request['start'] = since; + if (limit === undefined) { + request['end'] = now; + } else { + request['end'] = this.sum (since, duration * limit * 1000); + } + } else if (limit !== undefined) { + request['start'] = now - duration * limit * 1000; + request['end'] = now; + } else { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument, or a limit argument, or both'); + } + await this.loadMarkets (); + const market = this.market (symbol); + request['id'] = market['id']; + const response = await this.publicGetMarketsIdOhlcv (this.extend (request, params)); + // + // [ + // {"time":1588807500000,"open":0.022077,"high":0.022077,"low":0.022051,"close":0.022051,"volume":10.532025119999997}, + // {"time":1588807800000,"open":0.022051,"high":0.022051,"low":0.022044,"close":0.022044,"volume":0.655987}, + // {"time":1588808400000,"open":0.022044,"high":0.022044,"low":0.022044,"close":0.022044,"volume":3.9615545499999993}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.uid, + }; + const response = await this.privateGetAccountsIdBalances (this.extend (request, params)); + // + // [ + // { + // "assetId":"USDT", + // "available":"25", + // "balance":"25", + // "reserved":"0", + // "balanceBtc":"0.0", + // "balanceRef":"0.0", + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'assetId'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'reserved'), + 'total': this.safeFloat (balance, 'balance'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + // + // { + // "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "marketId": 123, + // "type": 1, + // "side": 1, + // "qty": "1.23456", + // "cost": "1.23456", + // "remainingQty": "1.23456", + // "remainingCost": "1.23456", + // "limitPrice": "1.23456", + // "stopPrice": "1.23456", + // "postOnly": false, + // "timeInForce": "GTC", + // "state": 1, + // "closeReason": "FILLED", + // "placedAt": 1556355722341, + // "closedAt": 1556355722341 + // } + // + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + '1': undefined, // pending + '2': 'open', // open + '3': 'open', // partially filled + '4': 'closed', // closed + 'FILLED': 'closed', + 'USER_REQUESTED_CANCEL': 'canceled', + 'ADMINISTRATIVE_CANCEL': 'canceled', + 'NOT_ENOUGH_LIQUIDITY': 'canceled', + 'EXPIRED': 'expired', + 'ONE_CANCELS_OTHER': 'canceled', + }; + return this.safeString (statuses, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "marketId": 123, + // "type": 1, + // "side": 1, + // "qty": "1.23456", + // "cost": "1.23456", + // "remainingQty": "1.23456", + // "remainingCost": "1.23456", + // "limitPrice": "1.23456", + // "stopPrice": "1.23456", + // "postOnly": false, + // "timeInForce": "GTC", + // "state": 1, + // "closeReason": "FILLED", + // "placedAt": 1556355722341, + // "closedAt": 1556355722341 + // } + // + // createOrder + // + // market buy + // + // { + // "id":"ff81127c-8fd5-4846-b683-110639dcd322", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":1, + // "side":1, + // "cost":"25", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589510846735 + // } + // + // market sell, limit buy, limit sell + // + // { + // "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":2, + // "side":1, + // "qty":"1000", + // "limitPrice":"100", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589403938682, + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.safeInteger (order, 'placedAt'); + const marketId = this.safeInteger (order, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + let status = this.parseOrderStatus (this.safeString (order, 'state')); + if (status === 'closed') { + status = this.parseOrderStatus (this.safeString (order, 'closeReason')); + } + const orderSide = this.safeString (order, 'side'); + const side = (orderSide === '1') ? 'buy' : 'sell'; + const orderType = this.safeString (order, 'type'); + let type = undefined; + if (orderType === '1') { + type = 'market'; + } else if (orderType === '2') { + type = 'limit'; + } else if (orderType === '3') { + type = 'stopmarket'; + } else { + type = 'stoplimit'; + } + let price = this.safeFloat (order, 'limitPrice'); + const amount = this.safeFloat (order, 'qty'); + let remaining = this.safeFloat (order, 'remainingQty'); + let filled = undefined; + const remainingCost = this.safeFloat (order, 'remainingCost'); + if ((remainingCost !== undefined) && (remainingCost === 0.0)) { + remaining = 0; + } + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + const cost = this.safeFloat (order, 'cost'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'timeInForce': timeInForce, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const now = this.milliseconds (); + const ninetyDays = 90 * 24 * 60 * 60 * 1000; // 90 days timerange max + const request = { + 'id': this.uid, + 'state': state, + // 'side': Integer, // 1 = buy, 2 = sell + // 'offset': 0, // the number of records to skip + }; + if (since === undefined) { + request['from'] = now - ninetyDays; + request['to'] = now; + } else { + request['from'] = since; + request['to'] = this.sum (since, ninetyDays); + } + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50 + } + const response = await this.privateGetAccountsIdOrders (this.extend (request, params)); + // + // [ + // { + // "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "marketId": 123, + // "type": 1, + // "side": 1, + // "qty": "1.23456", + // "cost": "1.23456", + // "remainingQty": "1.23456", + // "remainingCost": "1.23456", + // "limitPrice": "1.23456", + // "stopPrice": "1.23456", + // "postOnly": false, + // "timeInForce": "GTC", + // "state": 1, + // "closeReason": "FILLED", + // "placedAt": 1556355722341, + // "closedAt": 1556355722341 + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('INACTIVE', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('ACTIVE', symbol, since, limit, params); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const now = this.milliseconds (); + const ninetyDays = 90 * 24 * 60 * 60 * 1000; // 90 days timerange max + const request = { + 'id': this.uid, + // 'side': Integer, // 1 = buy, 2 = sell + // 'offset': 0, // the number of records to skip + }; + if (since === undefined) { + request['from'] = now - ninetyDays; + request['to'] = now; + } else { + request['from'] = since; + request['to'] = this.sum (since, ninetyDays); + } + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 200 + } + const response = await this.privateGetAccountsIdFills (this.extend (request, params)); + // + // [ + // { + // "id": 123, + // "marketId": 123, + // "side": 1, + // "qty": "1.23456", + // "price": "1.23456", + // "cost": "1.23456", + // "fee": "1.23456", + // "feeAsset": "XBASE", + // "liquidity": 1, + // "orderId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "tradeId": 123, + // "filledAt": 1556355722341 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const trades = await this.privateGetOrdersIdFills (this.extend (request, params)); + return this.parseTrades (trades); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + if (uppercaseType === 'MARKET') { + type = 1; + } else if (uppercaseType === 'LIMIT') { + type = 2; + } else if (uppercaseType === 'STOPMARKET') { + type = 3; + } else if (uppercaseType === 'STOPLIMIT') { + type = 4; + } + const uppercaseSide = side.toUpperCase (); + side = uppercaseSide === 'BUY' ? 1 : 2; + const request = { + 'accountId': this.uid, + 'marketId': market['id'], + 'type': type, + 'side': side, + // 'postOnly': false, + // 'timeInForce': 'GTC', + }; + const clientOrderId = this.safeValue2 (params, 'refId', 'clientOrderId'); + let query = params; + if (clientOrderId !== undefined) { + request['refId'] = clientOrderId; + query = this.omit (params, [ 'refId', 'clientOrderId' ]); + } + if ((uppercaseType === 'MARKET') && (uppercaseSide === 'BUY')) { + // for market buy it requires the amount of quote currency to spend + let cost = this.safeFloat (params, 'cost'); + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (cost === undefined) { + if (price !== undefined) { + cost = amount * price; + } else { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } + } + } else { + cost = (cost === undefined) ? amount : cost; + } + const precision = market['precision']['price']; + request['cost'] = this.decimalToPrecision (cost, TRUNCATE, precision, this.precisionMode); + } else { + request['qty'] = this.amountToPrecision (symbol, amount); + } + if (uppercaseType === 'LIMIT') { + request['limitPrice'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, query)); + // + // market buy + // + // { + // "id":"ff81127c-8fd5-4846-b683-110639dcd322", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":1, + // "side":1, + // "cost":"25", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589510846735 + // } + // + // market sell, limit buy, limit sell + // + // { + // "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":2, + // "side":1, + // "qty":"1000", + // "limitPrice":"100", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589403938682, + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privateDeleteOrdersId (this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'id': this.uid, + 'accountId': this.uid, + 'assetId': currency['id'], + 'amount': amount, + // 'cryptoAddress': address, + // 'accountNumber': 'IBAN', // IBAN account number + // 'networkId': 'XBASE', // underlying network + }; + if (address !== undefined) { + request['cryptoAddress'] = address; + if (tag !== undefined) { + request['memo'] = tag; + } + } + const response = await this.privatePostAccountsIdWithdrawals (this.extend (request, params)); + // + // { + // "id": "98b62dde-a87f-45f0-8db8-80ae2d312fa6" + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, httpHeaders = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let request = '/'; + if (api === 'public') { + request += 'api/' + this.version; + } else if (api === 'private') { + request += 'api/' + this.version; + } else if (api === 'markets') { + request += 'api/' + api; + } + request += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + request; + if (api === 'private') { + this.checkRequiredCredentials (); + let payload = ''; + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + // construct signature + const hasBody = (method === 'POST') || (method === 'PUT') || (method === 'PATCH'); + // const date = 'Mon, 30 Sep 2019 13:57:23 GMT'; + const date = this.rfc2616 (this.milliseconds ()); + let headersCSV = 'date' + ' ' + 'request-line'; + let message = 'date' + ':' + ' ' + date + "\n" + method + ' ' + request + ' HTTP/1.1'; // eslint-disable-line quotes + let digest = ''; + if (hasBody) { + digest = 'SHA-256=' + this.hash (payload, 'sha256', 'base64'); + message += "\ndigest" + ':' + ' ' + digest; // eslint-disable-line quotes + headersCSV += ' ' + 'digest'; + } + const signature = this.hmac (this.encode (message), this.encode (this.secret), 'sha256', 'base64'); + const authorizationHeader = 'hmac username="' + this.apiKey + '",algorithm="hmac-sha256",headers="' + headersCSV + '",' + 'signature="' + signature + '"'; + httpHeaders = { + 'Date': date, + 'Authorization': authorizationHeader, + 'Content-Type': 'application/json', + }; + if (hasBody) { + httpHeaders['Digest'] = digest; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': httpHeaders }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"message":"Invalid cost","_links":{"self":{"href":"/orders","templated":false}}} + // + const message = this.safeString (response, 'message'); + if (message !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],79:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, OrderNotFound, AuthenticationError, InsufficientFunds, InvalidOrder, InvalidNonce, NotSupported, OnMaintenance, RateLimitExceeded, BadRequest, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class exmo extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'exmo', + 'name': 'EXMO', + 'countries': [ 'ES', 'RU' ], // Spain, Russia + 'rateLimit': 350, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second + 'version': 'v1.1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': 'emulated', + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '45m': '45', + '1h': '60', + '2h': '120', + '3h': '180', + '4h': '240', + '1d': 'D', + '1w': 'W', + '1M': 'M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg', + 'api': { + 'public': 'https://api.exmo.com', + 'private': 'https://api.exmo.com', + 'web': 'https://exmo.me', + }, + 'www': 'https://exmo.me', + 'referral': 'https://exmo.me/?ref=131685', + 'doc': [ + 'https://exmo.me/en/api_doc?ref=131685', + 'https://github.com/exmo-dev/exmo_api_lib/tree/master/nodejs', + ], + 'fees': 'https://exmo.com/en/docs/fees', + }, + 'api': { + 'web': { + 'get': [ + 'ctrl/feesAndLimits', + 'en/docs/fees', + ], + }, + 'public': { + 'get': [ + 'currency', + 'currency/list/extended', + 'order_book', + 'pair_settings', + 'ticker', + 'trades', + 'candles_history', + 'required_amount', + 'payments/providers/crypto/list', + ], + }, + 'private': { + 'post': [ + 'user_info', + 'order_create', + 'order_cancel', + 'stop_market_order_create', + 'stop_market_order_cancel', + 'user_open_orders', + 'user_trades', + 'user_cancelled_orders', + 'order_trades', + 'deposit_address', + 'withdraw_crypt', + 'withdraw_get_txid', + 'excode_create', + 'excode_load', + 'code_check', + 'wallet_history', + 'wallet_operations', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, // fixed funding fees for crypto, see fetchFundingFees below + }, + }, + 'options': { + 'useWebapiForFetchingFees': false, // TODO: figure why Exmo bans us when we try to fetch() their web urls + 'feesAndLimits': { + 'success': 1, + 'ctlr': 'feesAndLimits', + 'error': '', + 'data': { + 'limits': [ + { 'pair': 'BTC/USD', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/RUB', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '2000000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/EUR', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/GBP', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/UAH', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '15000000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/PLN', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '20000000', 'min_a': '50', 'max_a': '2000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/TRY', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '800000', 'min_a': '40', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/KZT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1000', 'max_p': '12000000', 'min_a': '1000', 'max_a': '100000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/USDT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.01', 'max_p': '30000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'ETH/BTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/USD', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/RUB', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/USD', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/RUB', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ADA/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ADA/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ADA/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '50', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/USDT', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'ALGO/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/EUR', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/EUR', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/USD', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '30000', 'min_a': '0.0001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/RUB', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '2000000', 'min_a': '0.0001', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/EUR', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/UAH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.1', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/BTC', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '5', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/ETH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.0000001', 'max_p': '200', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/USDT', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'BTG/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTG/BTC', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTG/ETH', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTT/RUB', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTT/UAH', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTT/BTC', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'CRON/BTC', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'CRON/ETH', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'CRON/USDT', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'CRON/EXM', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '0.5', 'max_a': '30000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '200000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/USDT', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'DCR/RUB', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '3000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DCR/UAH', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.25', 'max_a': '1000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DCR/BTC', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DOGE/USD', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DOGE/BTC', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EOS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EOS/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EOS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETC/USD', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETC/RUB', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETC/BTC', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '0.5', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/EUR', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/GBP', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/UAH', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '1000000', 'min_a': '90', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/PLN', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '2000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/TRY', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.1', 'max_p': '80000', 'min_a': '10', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/KZT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '4', 'max_p': '40000000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/USDT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'ETH/LTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '100000', 'min_a': '0.05', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETZ/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETZ/ETH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETZ/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '1000', 'taker': '0', 'maker': '0' }, + { 'pair': 'EXM/USDT', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0', 'maker': '0' }, + { 'pair': 'EXM/ETH', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GAS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GAS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GNT/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GNT/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GUSD/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GUSD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GUSD/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'HP/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'HB/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LSK/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LSK/RUB', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LSK/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/USD', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/RUB', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/EUR', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/UAH', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '5', 'max_a': '18000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/BTC', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MKR/BTC', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MKR/DAI', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.5', 'max_p': '500000', 'min_a': '0.005', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MNC/USD', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MNC/BTC', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MNC/ETH', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'NEO/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'NEO/RUB', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1500000', 'min_a': '50', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'NEO/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'OMG/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'OMG/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'OMG/ETH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '250000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/UAH', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/UAH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '250000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '600000', 'min_a': '10', 'max_a': '600000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/EOS', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0000001', 'max_p': '5000', 'min_a': '0.01', 'max_a': '20000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0', 'maker': '0' }, + { 'pair': 'QTUM/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'QTUM/BTC', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'QTUM/ETH', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ROOBEE/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/USD', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/RUB', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.0001', 'max_p': '100000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/EUR', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/USD', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/EUR', 'min_q': '0.01', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/UAH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/USDT', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'USDT/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/GBP', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/UAH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '1', 'max_p': '3000', 'min_a': '2', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/KZT', 'min_q': '1', 'max_q': '500000', 'min_p': '200', 'max_p': '4000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'VLX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/USD', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.001', 'max_p': '3500', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/RUB', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/BTC', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/ETH', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.00001', 'max_p': '30', 'min_a': '0.0035', 'max_a': '3500', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/USD', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/EUR', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/UAH', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/USD', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/RUB', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/TRY', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '600000', 'min_a': '10', 'max_a': '16000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '300000', 'min_a': '5', 'max_a': '16000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/ETH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/EUR', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/GBP', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/TRY', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/UAH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/USDT', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'XRP/ETH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/USD', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/RUB', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/ETH', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZRX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZRX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZRX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZAG/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EXM/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0000001', 'max_a': '1', 'taker': '0.4', 'maker': '0.4' }, + ], + 'fees': [ + { + 'group': 'crypto', + 'title': 'Cryptocurrency', + 'items': [ + { 'prov': 'EXM', 'dep': '0%', 'wd': '1 EXM' }, + { 'prov': 'BTC', 'dep': '0%', 'wd': '0.0004 BTC' }, + { 'prov': 'LTC', 'dep': '0%', 'wd': '0.01 LTC' }, + { 'prov': 'DOGE', 'dep': '0%', 'wd': '1 Doge' }, + { 'prov': 'DASH', 'dep': '0%', 'wd': '0.002 DASH' }, + { 'prov': 'ETH', 'dep': '0%', 'wd': '0.003 ETH' }, + { 'prov': 'WAVES', 'dep': '0%', 'wd': '0.001 WAVES' }, + { 'prov': 'ZEC', 'dep': '0%', 'wd': '0.001 ZEC' }, + { 'prov': 'USDT', 'dep': '0%', 'wd': '' }, + { 'prov': 'XMR', 'dep': '0%', 'wd': '0.001 XMR' }, + { 'prov': 'XRP', 'dep': '0%', 'wd': '0.02 XRP' }, + { 'prov': 'ETC', 'dep': '0%', 'wd': '0.01 ETC' }, + { 'prov': 'BCH', 'dep': '0%', 'wd': '0.001 BCH' }, + { 'prov': 'BTG', 'dep': '0%', 'wd': '0.001 BTG' }, + { 'prov': 'EOS', 'dep': '0%', 'wd': '0.05 EOS' }, + { 'prov': 'XLM', 'dep': '0%', 'wd': '0.01 XLM' }, + { 'prov': 'OMG', 'dep': '0.1 OMG', 'wd': '0.5 OMG' }, + { 'prov': 'TRX', 'dep': '0%', 'wd': '1 TRX' }, + { 'prov': 'ADA', 'dep': '0%', 'wd': '1 ADA' }, + { 'prov': 'NEO', 'dep': '0%', 'wd': '0%' }, + { 'prov': 'GAS', 'dep': '0%', 'wd': '0%' }, + { 'prov': 'ZRX', 'dep': '0%', 'wd': '1 ZRX' }, + { 'prov': 'GNT', 'dep': '0%', 'wd': '1 GNT' }, + { 'prov': 'GUSD', 'dep': '0%', 'wd': '0.5 GUSD' }, + { 'prov': 'LSK', 'dep': '0%', 'wd': '0.1 LSK' }, + { 'prov': 'XEM', 'dep': '0%', 'wd': '5 XEM' }, + { 'prov': 'SMART', 'dep': '0%', 'wd': '0.5 SMART' }, + { 'prov': 'QTUM', 'dep': '0%', 'wd': '0.01 QTUM' }, + { 'prov': 'HB', 'dep': '0%', 'wd': '10 HB' }, + { 'prov': 'DAI', 'dep': '0%', 'wd': '1 DAI' }, + { 'prov': 'MKR', 'dep': '0%', 'wd': '0.005 MKR' }, + { 'prov': 'MNC', 'dep': '0%', 'wd': '15 MNC' }, + { 'prov': 'PTI', 'dep': '-', 'wd': '10 PTI' }, + { 'prov': 'ETZ', 'dep': '0%', 'wd': '1 ETZ' }, + { 'prov': 'USDC', 'dep': '0%', 'wd': '0.5 USDC' }, + { 'prov': 'ROOBEE', 'dep': '0%', 'wd': '200 ROOBEE' }, + { 'prov': 'DCR', 'dep': '0%', 'wd': '0.01 DCR' }, + { 'prov': 'ZAG', 'dep': '0%', 'wd': '0%' }, + { 'prov': 'BTT', 'dep': '0 BTT', 'wd': '100 BTT' }, + { 'prov': 'VLX', 'dep': '0%', 'wd': '1 VLX' }, + { 'prov': 'CRON', 'dep': '0%', 'wd': '5 CRON' }, + { 'prov': 'ONT', 'dep': '0%', 'wd': '1 ONT' }, + { 'prov': 'ONG', 'dep': '0%', 'wd': '5 ONG' }, + { 'prov': 'ALGO', 'dep': '0%', 'wd': '0.01 ALGO' }, + { 'prov': 'ATOM', 'dep': '0%', 'wd': '0.05 ATOM' }, + ], + }, + { + 'group': 'usd', + 'title': 'USD', + 'items': [ + { 'prov': 'Payeer', 'dep': '3.95%', 'wd': '-' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'AdvCash', 'dep': '0%', 'wd': '2.49%' }, + { 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5% + 0.5 USD', 'wd': '-' }, + { 'prov': 'Visa', 'dep': '3.45%', 'wd': '-' }, + { 'prov': 'Frick Bank', 'dep': '0 USD', 'wd': '-' }, + ], + }, + { + 'group': 'eur', + 'title': 'EUR', + 'items': [ + { 'prov': 'Visa/MasterCard', 'dep': '4.5% + 0.5 EUR', 'wd': '-' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Visa', 'dep': '2.95%', 'wd': '-' }, + { 'prov': 'Frick Internal Transfer', 'dep': '0 EUR', 'wd': '-' }, + { 'prov': 'SEPA Frick Bank', 'dep': '0 EUR', 'wd': '1 EUR' }, + { 'prov': 'WIRE Frick Bank', 'dep': '0%', 'wd': '20 EUR' }, + { 'prov': 'SEPA Weg Ag', 'dep': '-', 'wd': '1 EUR' }, + ], + }, + { + 'group': 'gbp', + 'title': 'GBP', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'WIRE Frick Bank', 'dep': '10 GBP', 'wd': '-' }, + ], + }, + { + 'group': 'rub', + 'title': 'RUB', + 'items': [ + { 'prov': 'Payeer', 'dep': '2.49%', 'wd': '3.49%' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Qiwi', 'dep': '1.49%', 'wd': '2.49%' }, + { 'prov': 'Yandex Money', 'dep': '1.49%', 'wd': '1.95 %' }, + { 'prov': 'AdvCash', 'dep': '0.99%', 'wd': '0.99%' }, + { 'prov': 'Visa/MasterCard', 'dep': '2.99%', 'wd': '3.99% + 60 RUB' }, + ], + }, + { + 'group': 'pln', + 'title': 'PLN', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + ], + }, + { + 'group': 'try', + 'title': 'TRY', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Visa', 'dep': '3.05%', 'wd': '-' }, + { 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5% + 2 TRY', 'wd': '-' }, + { 'prov': 'AdvCash', 'dep': '0%', 'wd': '-' }, + ], + }, + { + 'group': 'uah', + 'title': 'UAH', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Terminal', 'dep': '2.6%', 'wd': '-' }, + { 'prov': 'Visa/MasterCard EasyTransfer', 'dep': '-', 'wd': '2.99%' }, + { 'prov': 'Visa/MasterCard', 'dep': '1% + 5 UAH', 'wd': '-' }, + ], + }, + { + 'group': 'kzt', + 'title': 'KZT', + 'items': [ + { 'prov': 'Visa/MasterCard', 'dep': '3.5%', 'wd': '2.99% + 450 KZT' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'AdvCash', 'dep': '0%', 'wd': '-' }, + ], + }, + ], + }, + }, + }, + 'exceptions': { + 'exact': { + '40005': AuthenticationError, // Authorization error, incorrect signature + '40009': InvalidNonce, // + '40015': ExchangeError, // API function do not exist + '40016': OnMaintenance, // {"result":false,"error":"Error 40016: Maintenance work in progress"} + '40017': AuthenticationError, // Wrong API Key + '40032': PermissionDenied, // {"result":false,"error":"Error 40032: Access is denied for this API key"} + '40034': RateLimitExceeded, // {"result":false,"error":"Error 40034: Access is denied, rate limit is exceeded"} + '50052': InsufficientFunds, + '50054': InsufficientFunds, + '50304': OrderNotFound, // "Order was not found '123456789'" (fetching order trades for an order that does not have trades yet) + '50173': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) + '50277': InvalidOrder, + '50319': InvalidOrder, // Price by order is less than permissible minimum for this pair + '50321': InvalidOrder, // Price by order is more than permissible maximum for this pair + }, + 'broad': { + 'range period is too long': BadRequest, + 'invalid syntax': BadRequest, + 'API rate limit exceeded': RateLimitExceeded, // {"result":false,"error":"API rate limit exceeded for 99.33.55.224. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400} + }, + }, + 'orders': {}, // orders cache / emulation + }); + } + + async fetchTradingFees (params = {}) { + if (this.options['useWebapiForFetchingFees']) { + const response = await this.webGetEnDocsFees (params); + let parts = response.split (''); + let numParts = parts.length; + if (numParts !== 2) { + throw new NotSupported (this.id + ' fetchTradingFees format has changed'); + } + const rest = parts[1]; + parts = rest.split (''); + numParts = parts.length; + if (numParts < 2) { + throw new NotSupported (this.id + ' fetchTradingFees format has changed'); + } + const fee = parseFloat (parts[0].replace ('%', '')) * 0.01; + const taker = fee; + const maker = fee; + return { + // 'info': response, + 'maker': maker, + 'taker': taker, + }; + } else { + return { + 'maker': this.fees['trading']['maker'], + 'taker': this.fees['trading']['taker'], + }; + } + } + + parseFixedFloatValue (input) { + if ((input === undefined) || (input === '-')) { + return undefined; + } + if (input === '') { + return 0; + } + const isPercentage = (input.indexOf ('%') >= 0); + const parts = input.split (' '); + const value = parts[0].replace ('%', ''); + const result = parseFloat (value); + if ((result > 0) && isPercentage) { + throw new ExchangeError (this.id + ' parseFixedFloatValue detected an unsupported non-zero percentage-based fee ' + input); + } + return result; + } + + async fetchFundingFees (params = {}) { + let response = undefined; + if (this.options['useWebapiForFetchingFees']) { + response = await this.webGetCtrlFeesAndLimits (params); + } else { + response = this.options['feesAndLimits']; + } + // the code below assumes all non-zero crypto fees are fixed (for now) + const withdraw = {}; + const deposit = {}; + const groups = this.safeValue (response['data'], 'fees'); + const groupsByGroup = this.indexBy (groups, 'group'); + const items = groupsByGroup['crypto']['items']; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const code = this.safeCurrencyCode (this.safeString (item, 'prov')); + const withdrawalFee = this.safeString (item, 'wd'); + const depositFee = this.safeString (item, 'dep'); + if (withdrawalFee !== undefined) { + withdraw[code] = this.parseFixedFloatValue (withdrawalFee); + } + if (depositFee !== undefined) { + deposit[code] = this.parseFixedFloatValue (depositFee); + } + } + // sets fiat fees to undefined + const fiatGroups = this.toArray (this.omit (groupsByGroup, 'crypto')); + for (let i = 0; i < fiatGroups.length; i++) { + const code = this.safeCurrencyCode (this.safeString (fiatGroups[i], 'title')); + withdraw[code] = undefined; + deposit[code] = undefined; + } + const result = { + 'info': response, + 'withdraw': withdraw, + 'deposit': deposit, + }; + // cache them for later use + this.options['fundingFees'] = result; + return result; + } + + async fetchCurrencies (params = {}) { + const fees = await this.fetchFundingFees (params); + // todo redesign the 'fee' property in currencies + const ids = Object.keys (fees['withdraw']); + const limitsByMarketId = this.indexBy (fees['info']['data']['limits'], 'pair'); + const marketIds = Object.keys (limitsByMarketId); + const minAmounts = {}; + const minPrices = {}; + const minCosts = {}; + const maxAmounts = {}; + const maxPrices = {}; + const maxCosts = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const limit = limitsByMarketId[marketId]; + const [ baseId, quoteId ] = marketId.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const maxAmount = this.safeFloat (limit, 'max_q'); + const maxPrice = this.safeFloat (limit, 'max_p'); + const maxCost = this.safeFloat (limit, 'max_a'); + const minAmount = this.safeFloat (limit, 'min_q'); + const minPrice = this.safeFloat (limit, 'min_p'); + const minCost = this.safeFloat (limit, 'min_a'); + minAmounts[base] = Math.min (this.safeFloat (minAmounts, base, minAmount), minAmount); + maxAmounts[base] = Math.max (this.safeFloat (maxAmounts, base, maxAmount), maxAmount); + minPrices[quote] = Math.min (this.safeFloat (minPrices, quote, minPrice), minPrice); + minCosts[quote] = Math.min (this.safeFloat (minCosts, quote, minCost), minCost); + maxPrices[quote] = Math.max (this.safeFloat (maxPrices, quote, maxPrice), maxPrice); + maxCosts[quote] = Math.max (this.safeFloat (maxCosts, quote, maxCost), maxCost); + } + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + const fee = this.safeValue (fees['withdraw'], code); + const active = true; + result[code] = { + 'id': id, + 'code': code, + 'name': code, + 'active': active, + 'fee': fee, + 'precision': 8, + 'limits': { + 'amount': { + 'min': this.safeFloat (minAmounts, code), + 'max': this.safeFloat (maxAmounts, code), + }, + 'price': { + 'min': this.safeFloat (minPrices, code), + 'max': this.safeFloat (maxPrices, code), + }, + 'cost': { + 'min': this.safeFloat (minCosts, code), + 'max': this.safeFloat (maxCosts, code), + }, + }, + 'info': id, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPairSettings (params); + // + // { + // "BTC_USD":{ + // "min_quantity":"0.0001", + // "max_quantity":"1000", + // "min_price":"1", + // "max_price":"30000", + // "max_amount":"500000", + // "min_amount":"1", + // "price_precision":8, + // "commission_taker_percent":"0.4", + // "commission_maker_percent":"0.4" + // }, + // } + // + const keys = Object.keys (response); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = response[id]; + const symbol = id.replace ('_', '/'); + const [ baseId, quoteId ] = symbol.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const taker = this.safeFloat (market, 'commission_taker_percent'); + const maker = this.safeFloat (market, 'commission_maker_percent'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'taker': taker / 100, + 'maker': maker / 100, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_quantity'), + 'max': this.safeFloat (market, 'max_quantity'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }, + }, + 'precision': { + 'amount': 8, + 'price': this.safeInteger (market, 'price_precision'), + }, + 'info': market, + }); + } + return result; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + }; + const options = this.safeValue (this.options, 'fetchOHLCV'); + const maxLimit = this.safeInteger (options, 'maxLimit', 3000); + const duration = this.parseTimeframe (timeframe); + const now = this.milliseconds (); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + if (limit > maxLimit) { + throw new BadRequest (this.id + ' fetchOHLCV will serve ' + maxLimit.toString () + ' candles at most'); + } + request['from'] = parseInt (now / 1000) - limit * duration - 1; + request['to'] = parseInt (now / 1000); + } + } else { + request['from'] = parseInt (since / 1000) - 1; + if (limit === undefined) { + request['to'] = parseInt (now / 1000); + } else { + if (limit > maxLimit) { + throw new BadRequest (this.id + ' fetchOHLCV will serve ' + maxLimit.toString () + ' candles at most'); + } + const to = this.sum (since, limit * duration * 1000); + request['to'] = parseInt (to / 1000); + } + } + const response = await this.publicGetCandlesHistory (this.extend (request, params)); + // + // { + // "candles":[ + // {"t":1584057600000,"o":0.02235144,"c":0.02400233,"h":0.025171,"l":0.02221,"v":5988.34031761}, + // {"t":1584144000000,"o":0.0240373,"c":0.02367413,"h":0.024399,"l":0.0235,"v":2027.82522329}, + // {"t":1584230400000,"o":0.02363458,"c":0.02319242,"h":0.0237948,"l":0.02223196,"v":1707.96944997}, + // ] + // } + // + const candles = this.safeValue (response, 'candles', []); + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "t":1584057600000, + // "o":0.02235144, + // "c":0.02400233, + // "h":0.025171, + // "l":0.02221, + // "v":5988.34031761 + // } + // + return [ + this.safeInteger (ohlcv, 't'), + this.safeFloat (ohlcv, 'o'), + this.safeFloat (ohlcv, 'h'), + this.safeFloat (ohlcv, 'l'), + this.safeFloat (ohlcv, 'c'), + this.safeFloat (ohlcv, 'v'), + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostUserInfo (params); + const result = { 'info': response }; + const free = this.safeValue (response, 'balances', {}); + const used = this.safeValue (response, 'reserved', {}); + const codes = Object.keys (free); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currencyId = this.currencyId (code); + const account = this.account (); + if (currencyId in free) { + account['free'] = this.safeFloat (free, currencyId); + } + if (currencyId in used) { + account['used'] = this.safeFloat (used, currencyId); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + const result = this.safeValue (response, market['id']); + return this.parseOrderBook (result, undefined, 'bid', 'ask'); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join (','); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join (','); + } + const request = { + 'pair': ids, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + const result = {}; + const marketIds = Object.keys (response); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let symbol = marketId; + if (marketId in this.markets_by_id) { + const market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } + result[symbol] = this.parseOrderBook (response[marketId], undefined, 'bid', 'ask'); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last_trade'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy_price'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell_price'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'vol_curr'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const result = {}; + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.markets_by_id[id]; + const symbol = market['symbol']; + const ticker = response[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const market = this.market (symbol); + return this.parseTicker (response[market['id']], market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "trade_id":165087520, + // "date":1587470005, + // "type":"buy", + // "quantity":"1.004", + // "price":"0.02491461", + // "amount":"0.02501426" + // }, + // + // fetchMyTrades, fetchOrderTrades + // + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100, + // "exec_type": "taker", + // "commission_amount": "0.02", + // "commission_currency": "BTC", + // "commission_percent": "0.2" + // } + // + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + const id = this.safeString (trade, 'trade_id'); + const orderId = this.safeString (trade, 'order_id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const cost = this.safeFloat (trade, 'amount'); + const side = this.safeString (trade, 'type'); + const type = undefined; + const marketId = this.safeString (trade, 'pair'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const takerOrMaker = this.safeString (trade, 'exec_type'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission_amount'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commission_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + let feeRate = this.safeFloat (trade, 'commission_percent'); + if (feeRate !== undefined) { + feeRate /= 1000; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "ETH_BTC":[ + // { + // "trade_id":165087520, + // "date":1587470005, + // "type":"buy", + // "quantity":"1.004", + // "price":"0.02491461", + // "amount":"0.02501426" + // }, + // { + // "trade_id":165087369, + // "date":1587469938, + // "type":"buy", + // "quantity":"0.94", + // "price":"0.02492348", + // "amount":"0.02342807" + // } + // ] + // } + // + const data = this.safeValue (response, market['id'], []); + return this.parseTrades (data, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // a symbol is required but it can be a single string, or a non-empty array + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument (a single symbol or an array)'); + } + await this.loadMarkets (); + let pair = undefined; + let market = undefined; + if (Array.isArray (symbol)) { + const numSymbols = symbol.length; + if (numSymbols < 1) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a non-empty symbol array'); + } + const marketIds = this.marketIds (symbol); + pair = marketIds.join (','); + } else { + market = this.market (symbol); + pair = market['id']; + } + const request = { + 'pair': pair, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostUserTrades (this.extend (request, params)); + let result = []; + const marketIds = Object.keys (response); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let symbol = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + const [ baseId, quoteId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const items = response[marketId]; + const trades = this.parseTrades (items, market, since, limit, { + 'symbol': symbol, + }); + result = this.arrayConcat (result, trades); + } + return this.filterBySinceLimit (result, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const prefix = (type === 'market') ? (type + '_') : ''; + const market = this.market (symbol); + if ((type === 'market') && (price === undefined)) { + price = 0; + } + const request = { + 'pair': market['id'], + 'quantity': this.amountToPrecision (symbol, amount), + 'type': prefix + side, + 'price': this.priceToPrecision (symbol, price), + }; + const response = await this.privatePostOrderCreate (this.extend (request, params)); + const id = this.safeString (response, 'order_id'); + const timestamp = this.milliseconds (); + amount = parseFloat (amount); + price = parseFloat (price); + const status = 'open'; + return { + 'id': id, + 'info': response, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * amount, + 'amount': amount, + 'remaining': amount, + 'filled': 0.0, + 'fee': undefined, + 'trades': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'order_id': id }; + return await this.privatePostOrderCancel (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id.toString (), + }; + const response = await this.privatePostOrderTrades (this.extend (request, params)); + // + // { + // "type": "buy", + // "in_currency": "BTC", + // "in_amount": "1", + // "out_currency": "USD", + // "out_amount": "100", + // "trades": [ + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100 + // } + // ] + // } + // + const order = this.parseOrder (response); + return this.extend (order, { + 'id': id.toString (), + }); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'order_id': id.toString (), + }; + const response = await this.privatePostOrderTrades (this.extend (request, params)); + // + // { + // "type": "buy", + // "in_currency": "BTC", + // "in_amount": "1", + // "out_currency": "USD", + // "out_amount": "100", + // "trades": [ + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100, + // "exec_type": "taker", + // "commission_amount": "0.02", + // "commission_currency": "BTC", + // "commission_percent": "0.2" + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades'); + return this.parseTrades (trades, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostUserOpenOrders (params); + const marketIds = Object.keys (response); + let orders = []; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let market = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + const parsedOrders = this.parseOrders (response[marketId], market); + orders = this.arrayConcat (orders, parsedOrders); + } + return this.filterBySymbolSinceLimit (orders, symbol, since, limit); + } + + parseOrder (order, market = undefined) { + // + // fetchOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "order_id": "14", + // "created": "1435517311", + // "type": "buy", + // "pair": "BTC_USD", + // "price": "100", + // "quantity": "1", + // "amount": "100" + // } + // + // fetchOrder + // + // { + // "type": "buy", + // "in_currency": "BTC", + // "in_amount": "1", + // "out_currency": "USD", + // "out_amount": "100", + // "trades": [ + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100 + // } + // ] + // } + // + let id = this.safeString (order, 'order_id'); + let timestamp = this.safeTimestamp (order, 'created'); + let symbol = undefined; + const side = this.safeString (order, 'type'); + if (market === undefined) { + let marketId = undefined; + if ('pair' in order) { + marketId = order['pair']; + } else if (('in_currency' in order) && ('out_currency' in order)) { + if (side === 'buy') { + marketId = order['in_currency'] + '_' + order['out_currency']; + } else { + marketId = order['out_currency'] + '_' + order['in_currency']; + } + } + if ((marketId !== undefined) && (marketId in this.markets_by_id)) { + market = this.markets_by_id[marketId]; + } + } + let amount = this.safeFloat (order, 'quantity'); + if (amount === undefined) { + const amountField = (side === 'buy') ? 'in_amount' : 'out_amount'; + amount = this.safeFloat (order, amountField); + } + let price = this.safeFloat (order, 'price'); + let cost = this.safeFloat (order, 'amount'); + let filled = 0.0; + const trades = []; + const transactions = this.safeValue (order, 'trades', []); + let feeCost = undefined; + let lastTradeTimestamp = undefined; + let average = undefined; + const numTransactions = transactions.length; + if (numTransactions > 0) { + feeCost = 0; + for (let i = 0; i < numTransactions; i++) { + const trade = this.parseTrade (transactions[i], market); + if (id === undefined) { + id = trade['order']; + } + if (timestamp === undefined) { + timestamp = trade['timestamp']; + } + if (timestamp > trade['timestamp']) { + timestamp = trade['timestamp']; + } + filled = this.sum (filled, trade['amount']); + feeCost = this.sum (feeCost, trade['fee']['cost']); + trades.push (trade); + } + lastTradeTimestamp = trades[numTransactions - 1]['timestamp']; + } + let status = this.safeString (order, 'status'); // in case we need to redefine it for canceled orders + let remaining = undefined; + if (amount !== undefined) { + remaining = amount - filled; + if (filled >= amount) { + status = 'closed'; + } else { + status = 'open'; + } + } + if (market === undefined) { + market = this.getMarketFromTrades (trades); + } + let feeCurrency = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } else { + if (filled > 0) { + if (average === undefined) { + average = cost / filled; + } + if (price === undefined) { + price = cost / filled; + } + } + } + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': average, + 'trades': trades, + 'fee': fee, + 'info': order, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostDepositAddress (params); + const depositAddress = this.safeString (response, code); + let address = undefined; + let tag = undefined; + if (depositAddress) { + const addressAndTag = depositAddress.split (','); + address = addressAndTag[0]; + const numParts = addressAndTag.length; + if (numParts > 1) { + tag = addressAndTag[1]; + } + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + getMarketFromTrades (trades) { + const tradesBySymbol = this.indexBy (trades, 'pair'); + const symbols = Object.keys (tradesBySymbol); + const numSymbols = symbols.length; + if (numSymbols === 1) { + return this.markets[symbols[0]]; + } + return undefined; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + let key = 'quote'; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, cost)), + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'amount': amount, + 'currency': currency['id'], + 'address': address, + }; + if (tag !== undefined) { + request['invoice'] = tag; + } + const response = await this.privatePostWithdrawCrypt (this.extend (request, params)); + return { + 'info': response, + 'id': response['task_id'], + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'transferred': 'ok', + 'paid': 'ok', + 'pending': 'pending', + 'processing': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchTransactions + // + // { + // "dt": 1461841192, + // "type": "deposit", + // "curr": "RUB", + // "status": "processing", + // "provider": "Qiwi (LA) [12345]", + // "amount": "1", + // "account": "", + // "txid": "ec46f784ad976fd7f7539089d1a129fe46...", + // } + // + const timestamp = this.safeTimestamp (transaction, 'dt'); + let amount = this.safeFloat (transaction, 'amount'); + if (amount !== undefined) { + amount = Math.abs (amount); + } + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const txid = this.safeString (transaction, 'txid'); + const type = this.safeString (transaction, 'type'); + const currencyId = this.safeString (transaction, 'curr'); + const code = this.safeCurrencyCode (currencyId, currency); + let address = this.safeString (transaction, 'account'); + if (address !== undefined) { + const parts = address.split (':'); + const numParts = parts.length; + if (numParts === 2) { + address = parts[1].replace (' ', ''); + } + } + let fee = undefined; + // fixed funding fees only (for now) + if (!this.fees['funding']['percentage']) { + const key = (type === 'withdrawal') ? 'withdraw' : 'deposit'; + let feeCost = this.safeFloat (this.options['fundingFees'][key], code); + // users don't pay for cashbacks, no fees for that + const provider = this.safeString (transaction, 'provider'); + if (provider === 'cashback') { + feeCost = 0; + } + if (feeCost !== undefined) { + // withdrawal amount includes the fee + if (type === 'withdrawal') { + amount = amount - feeCost; + } + fee = { + 'cost': feeCost, + 'currency': code, + 'rate': undefined, + }; + } + } + return { + 'info': transaction, + 'id': undefined, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, // refix it properly + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['date'] = parseInt (since / 1000); + } + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privatePostWalletHistory (this.extend (request, params)); + // + // { + // "result": true, + // "error": "", + // "begin": "1493942400", + // "end": "1494028800", + // "history": [ + // { + // "dt": 1461841192, + // "type": "deposit", + // "curr": "RUB", + // "status": "processing", + // "provider": "Qiwi (LA) [12345]", + // "amount": "1", + // "account": "", + // "txid": "ec46f784ad976fd7f7539089d1a129fe46...", + // }, + // { + // "dt": 1463414785, + // "type": "withdrawal", + // "curr": "USD", + // "status": "paid", + // "provider": "EXCODE", + // "amount": "-1", + // "account": "EX-CODE_19371_USDda...", + // "txid": "", + // }, + // ], + // } + // + return this.parseTransactions (response['history'], currency, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + if (api !== 'web') { + url += this.version + '/'; + } + url += path; + if ((api === 'public') || (api === 'web')) { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + nonce () { + return this.milliseconds (); + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (('result' in response) || ('errmsg' in response)) { + // + // {"result":false,"error":"Error 50052: Insufficient funds"} + // {"s":"error","errmsg":"strconv.ParseInt: parsing \"\": invalid syntax"} + // + let success = this.safeValue (response, 'result', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + let code = undefined; + const message = this.safeString2 (response, 'error', 'errmsg'); + const errorParts = message.split (':'); + const numParts = errorParts.length; + if (numParts > 1) { + const errorSubParts = errorParts[0].split (' '); + const numSubParts = errorSubParts.length; + code = (numSubParts > 1) ? errorSubParts[1] : errorSubParts[0]; + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],80:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ExchangeNotAvailable, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class exx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'exx', + 'name': 'EXX', + 'countries': [ 'CN' ], + 'rateLimit': 1000 / 10, + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg', + 'api': { + 'public': 'https://api.exx.com/data/v1', + 'private': 'https://trade.exx.com/api', + }, + 'www': 'https://www.exx.com/', + 'doc': 'https://www.exx.com/help/restApi', + 'fees': 'https://www.exx.com/help/rate', + 'referral': 'https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'tickers', + 'ticker', + 'depth', + 'trades', + ], + }, + 'private': { + 'get': [ + 'order', + 'cancel', + 'getOrder', + 'getOpenOrders', + 'getBalance', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BCC': 0.0003, + 'BCD': 0.0, + 'BOT': 10.0, + 'BTC': 0.001, + 'BTG': 0.0, + 'BTM': 25.0, + 'BTS': 3.0, + 'EOS': 1.0, + 'ETC': 0.01, + 'ETH': 0.01, + 'ETP': 0.012, + 'HPY': 0.0, + 'HSR': 0.001, + 'INK': 20.0, + 'LTC': 0.005, + 'MCO': 0.6, + 'MONA': 0.01, + 'QASH': 5.0, + 'QCASH': 5.0, + 'QTUM': 0.01, + 'USDT': 5.0, + }, + }, + }, + 'commonCurrencies': { + 'DOS': 'DEMOS', + 'TV': 'TIV', // Ti-Value + }, + 'exceptions': { + '103': AuthenticationError, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const ids = Object.keys (response); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = response[id]; + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = market['isOpen'] === true; + const precision = { + 'amount': parseInt (market['amountScale']), + 'price': parseInt (market['priceScale']), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const symbol = market['symbol']; + const timestamp = this.safeInteger (ticker, 'date'); + ticker = ticker['ticker']; + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'riseRate'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const result = {}; + const timestamp = this.milliseconds (); + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (!(id in this.marketsById)) { + continue; + } + const market = this.marketsById[id]; + const symbol = market['symbol']; + const ticker = { + 'date': timestamp, + 'ticker': response[id], + }; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': this.marketId (symbol), + }; + const response = await this.publicGetDepth (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, 'tid'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetGetBalance (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'funds'); + const currencies = Object.keys (balances); + for (let i = 0; i < currencies.length; i++) { + const currencyId = currencies[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'balance'), + 'used': this.safeFloat (balance, 'freeze'), + 'total': this.safeFloat (balance, 'total'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // { + // "fees": 0, + // "total_amount": 1, + // "trade_amount": 0, + // "price": 30, + // "currency": “eth_hsr", + // "id": "13878", + // "trade_money": 0, + // "type": "buy", + // "trade_date": 1509728897772, + // "status": 0 + // } + // + const symbol = market['symbol']; + const timestamp = parseInt (order['trade_date']); + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'trade_money'); + const amount = this.safeFloat (order, 'total_amount'); + const filled = this.safeFloat (order, 'trade_amount', 0.0); + const remaining = parseFloat (this.amountToPrecision (symbol, amount - filled)); + let status = this.safeInteger (order, 'status'); + if (status === 1) { + status = 'canceled'; + } else if (status === 2) { + status = 'closed'; + } else { + status = 'open'; + } + let fee = undefined; + if ('fees' in order) { + fee = { + 'cost': this.safeFloat (order, 'fees'), + 'currency': market['quote'], + }; + } + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': order['type'], + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'type': side, + 'price': price, + 'amount': amount, + }; + const response = await this.privateGetOrder (this.extend (request, params)); + const id = this.safeString (response, 'id'); + const order = this.parseOrder ({ + 'id': id, + 'trade_date': this.milliseconds (), + 'total_amount': amount, + 'price': price, + 'type': side, + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'currency': market['id'], + }; + const response = await this.privateGetCancel (this.extend (request, params)); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'currency': market['id'], + }; + const response = await this.privateGetGetOrder (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.privateGetGetOpenOrders (this.extend (request, params)); + if (!Array.isArray (response)) { + return []; + } + return this.parseOrders (response, market, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const query = this.urlencode (this.keysort (this.extend ({ + 'accesskey': this.apiKey, + 'nonce': this.nonce (), + }, params))); + const signed = this.hmac (this.encode (query), this.encode (this.secret), 'sha512'); + url += '?' + query + '&signature=' + signed; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"result":false,"message":"服务端忙碌"} + // ... and other formats + // + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + if (code === '100') { + return; + } + if (code !== undefined) { + this.throwExactlyMatchedException (this.exceptions, code, feedback); + if (code === '308') { + // this is returned by the exchange when there are no open orders + // {"code":308,"message":"Not Found Transaction Record"} + return; + } else { + throw new ExchangeError (feedback); + } + } + const result = this.safeValue (response, 'result'); + if (result !== undefined) { + if (!result) { + if (message === '服务端忙碌') { + throw new ExchangeNotAvailable (feedback); + } else { + throw new ExchangeError (feedback); + } + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],81:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError, ExchangeNotAvailable, ArgumentsRequired, InsufficientFunds, InvalidOrder, RateLimitExceeded, InvalidNonce, AuthenticationError, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class fcoin extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'fcoin', + 'name': 'FCoin', + 'countries': [ 'CN' ], + 'rateLimit': 2000, + 'userAgent': this.userAgents['chrome39'], + 'version': 'v2', + 'accounts': undefined, + 'accountsById': undefined, + 'hostname': 'fcoin.com', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': false, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingLimits': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': 'M1', + '3m': 'M3', + '5m': 'M5', + '15m': 'M15', + '30m': 'M30', + '1h': 'H1', + '1d': 'D1', + '1w': 'W1', + '1M': 'MN', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/42244210-c8c42e1e-7f1c-11e8-8710-a5fb63b165c4.jpg', + 'api': { + 'public': 'https://api.{hostname}', + 'private': 'https://api.{hostname}', + 'market': 'https://api.{hostname}', + 'openapi': 'https://www.{hostname}', + }, + 'www': 'https://www.fcoin.com', + 'referral': 'https://www.fcoin.com/i/Z5P7V', + 'doc': 'https://developer.fcoin.com', + 'fees': 'https://fcoinjp.zendesk.com/hc/en-us/articles/360018727371', + }, + 'api': { + 'openapi': { + 'get': [ + 'symbols', + ], + }, + 'market': { + 'get': [ + 'ticker/{symbol}', + 'depth/{level}/{symbol}', + 'trades/{symbol}', + 'candles/{timeframe}/{symbol}', + ], + }, + 'public': { + 'get': [ + 'symbols', + 'currencies', + 'server-time', + ], + }, + 'private': { + 'get': [ + 'accounts/balance', + 'assets/accounts/balance', + 'broker/otc/suborders', + 'broker/otc/suborders/{id}', + 'broker/otc/suborders/{id}/payments', + 'broker/otc/users', + 'broker/otc/users/me/balances', + 'broker/otc/users/me/balance', + 'broker/leveraged_accounts/account', + 'broker/leveraged_accounts', + 'orders', + 'orders/{order_id}', + 'orders/{order_id}/match-results', // check order result + ], + 'post': [ + 'assets/accounts/assets-to-spot', + 'accounts/spot-to-assets', + 'broker/otc/assets/transfer/in', + 'broker/otc/assets/transfer/out', + 'broker/otc/suborders', + 'broker/otc/suborders/{id}/pay_confirm', + 'broker/otc/suborders/{id}/cancel', + 'broker/leveraged/assets/transfer/in', + 'broker/leveraged/assets/transfer/out', + 'orders', + 'orders/{order_id}/submit-cancel', // cancel order + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': -0.0002, + 'taker': 0.0003, + }, + }, + 'limits': { + 'amount': { 'min': 0.01, 'max': 100000 }, + }, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarketsMethod': 'fetch_markets_from_open_api', // or 'fetch_markets_from_api' + 'limits': { + 'BTM/USDT': { 'amount': { 'min': 0.1, 'max': 10000000 }}, + 'ETC/USDT': { 'amount': { 'min': 0.001, 'max': 400000 }}, + 'ETH/USDT': { 'amount': { 'min': 0.001, 'max': 10000 }}, + 'LTC/USDT': { 'amount': { 'min': 0.001, 'max': 40000 }}, + 'BCH/USDT': { 'amount': { 'min': 0.001, 'max': 5000 }}, + 'BTC/USDT': { 'amount': { 'min': 0.001, 'max': 1000 }}, + 'ICX/ETH': { 'amount': { 'min': 0.01, 'max': 3000000 }}, + 'OMG/ETH': { 'amount': { 'min': 0.01, 'max': 500000 }}, + 'FT/USDT': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'ZIL/ETH': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'ZIP/ETH': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'FT/BTC': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'FT/ETH': { 'amount': { 'min': 1, 'max': 10000000 }}, + }, + }, + 'exceptions': { + '400': NotSupported, // Bad Request + '401': AuthenticationError, + '405': NotSupported, + '429': RateLimitExceeded, // Too Many Requests, exceed api request limit + '1002': ExchangeNotAvailable, // System busy + '1016': InsufficientFunds, + '2136': AuthenticationError, // The API key is expired + '3008': InvalidOrder, + '6004': InvalidNonce, + '6005': AuthenticationError, // Illegal API Signature + '40003': BadSymbol, + }, + 'commonCurrencies': { + 'DAG': 'DAGX', + 'PAI': 'PCHAIN', + 'MT': 'Mariana Token', + }, + }); + } + + async fetchMarkets (params = {}) { + const method = this.safeString (this.options, 'fetchMarketsMethod', 'fetch_markets_from_open_api'); + return await this[method] (params); + } + + async fetchMarketsFromOpenAPI (params = {}) { + // https://github.com/ccxt/ccxt/issues/5648 + const response = await this.openapiGetSymbols (params); + // + // { + // "status":"ok", + // "data":{ + // "categories":[ "fone::coinforce", ... ], + // "symbols":{ + // "mdaeth":{ + // "price_decimal":8, + // "amount_decimal":2, + // "base_currency":"mda", + // "quote_currency":"eth", + // "symbol":"mdaeth", + // "category":"fone::bitangel", + // "leveraged_multiple":null, + // "tradeable":false, + // "market_order_enabled":false, + // "limit_amount_min":"1", + // "limit_amount_max":"10000000", + // "main_tag":"", + // "daily_open_at":"", + // "daily_close_at":"" + // }, + // } + // "category_ref":{ + // "fone::coinforce":[ "btcusdt", ... ], + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const markets = this.safeValue (data, 'symbols', {}); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const market = markets[key]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'price_decimal'), + 'amount': this.safeInteger (market, 'amount_decimal'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'limit_amount_min'), + 'max': this.safeFloat (market, 'limit_amount_max'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + const active = this.safeValue (market, 'tradeable', false); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchMarketsFromAPI (params = {}) { + const response = await this.publicGetSymbols (params); + // + // { + // "status":0, + // "data":[ + // { + // "name":"dapusdt", + // "base_currency":"dap", + // "quote_currency":"usdt", + // "price_decimal":6, + // "amount_decimal":2, + // "tradable":true + // }, + // ] + // } + // + const result = []; + const markets = this.safeValue (response, 'data'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': market['price_decimal'], + 'amount': market['amount_decimal'], + }; + let limits = { + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + }; + const active = this.safeValue (market, 'tradable', false); + if (symbol in this.options['limits']) { + limits = this.extend (this.options['limits'][symbol], limits); + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountsBalance (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'frozen'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseBidsAsks (orders, priceKey = 0, amountKey = 1) { + const result = []; + const length = orders.length; + const halfLength = parseInt (length / 2); + // += 2 in the for loop below won't transpile + for (let i = 0; i < halfLength; i++) { + const index = i * 2; + const priceField = this.sum (index, priceKey); + const amountField = this.sum (index, amountKey); + result.push ([ + this.safeFloat (orders, priceField), + this.safeFloat (orders, amountField), + ]); + } + return result; + } + + async fetchOrderBook (symbol = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit !== undefined) { + if ((limit === 20) || (limit === 150)) { + limit = 'L' + limit.toString (); + } else { + throw new ExchangeError (this.id + ' fetchOrderBook supports limit of 20 or 150. Other values are not accepted'); + } + } else { + limit = 'L20'; + } + const request = { + 'symbol': this.marketId (symbol), + 'level': limit, // L20, L150 + }; + const response = await this.marketGetDepthLevelSymbol (this.extend (request, params)); + const orderbook = this.safeValue (response, 'data'); + return this.parseOrderBook (orderbook, orderbook['ts'], 'bids', 'asks', 0, 1); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const ticker = await this.marketGetTickerSymbol (this.extend (request, params)); + return this.parseTicker (ticker['data'], market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = undefined; + let symbol = undefined; + if (market === undefined) { + const tickerType = this.safeString (ticker, 'type'); + if (tickerType !== undefined) { + const parts = tickerType.split ('.'); + const id = parts[1]; + symbol = this.safeSymbol (id, market); + } + } + const values = ticker['ticker']; + const last = this.safeFloat (values, 0); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (values, 7), + 'low': this.safeFloat (values, 8), + 'bid': this.safeFloat (values, 2), + 'bidVolume': this.safeFloat (values, 3), + 'ask': this.safeFloat (values, 4), + 'askVolume': this.safeFloat (values, 5), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (values, 9), + 'quoteVolume': this.safeFloat (values, 10), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (trade, 'ts'); + const side = this.safeStringLower (trade, 'side'); + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const fee = undefined; + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetServerTime (params); + // + // { + // "status": 0, + // "data": 1523430502977 + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchTrades (symbol, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'limit': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.marketGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'side': side, + 'type': type, + }; + // for market buy it requires the amount of quote currency to spend + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + request['amount'] = this.costToPrecision (symbol, amount * price); + } + } else { + request['amount'] = this.costToPrecision (symbol, amount); + } + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if ((type === 'limit') || (type === 'ioc') || (type === 'fok')) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + return { + 'info': response, + 'id': response['data'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privatePostOrdersOrderIdSubmitCancel (this.extend (request, params)); + const order = this.parseOrder (response); + return this.extend (order, { + 'id': id, + 'status': 'canceled', + }); + } + + parseOrderStatus (status) { + const statuses = { + 'submitted': 'open', + 'canceled': 'canceled', + 'partial_filled': 'open', + 'partial_canceled': 'canceled', + 'filled': 'closed', + 'pending_cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": "string", + // "symbol": "string", + // "type": "limit", + // "side": "buy", + // "price": "string", + // "amount": "string", + // "state": "submitted", + // "executed_value": "string", + // "fill_fees": "string", + // "filled_amount": "string", + // "created_at": 0, + // "source": "web" + // } + // + const id = this.safeString (order, 'id'); + const side = this.safeString (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const orderType = this.safeString (order, 'type'); + const timestamp = this.safeInteger (order, 'created_at'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled_amount'); + let remaining = undefined; + let price = this.safeFloat (order, 'price'); + let cost = this.safeFloat (order, 'executed_value'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } else if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + let feeCurrency = undefined; + let feeCost = undefined; + const feeRebate = this.safeFloat (order, 'fees_income'); + if ((feeRebate !== undefined) && (feeRebate > 0)) { + if (market !== undefined) { + feeCurrency = (side === 'buy') ? market['quote'] : market['base']; + } + feeCost = -feeRebate; + } else { + feeCost = this.safeFloat (order, 'fill_fees'); + if (market !== undefined) { + feeCurrency = (side === 'buy') ? market['base'] : market['quote']; + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'average': undefined, + 'status': status, + 'fee': { + 'cost': feeCost, + 'currency': feeCurrency, + }, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetOrdersOrderId (this.extend (request, params)); + return this.parseOrder (response['data']); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'states': 'submitted,partial_filled' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'states': 'partial_canceled,filled' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'states': 'submitted,partial_filled,partial_canceled,filled,canceled', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeTimestamp (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'base_vol'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 20; // default is 20 + } + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + const sinceInSeconds = parseInt (since / 1000); + const timerange = limit * this.parseTimeframe (timeframe); + request['before'] = this.sum (sinceInSeconds, timerange) - 1; + } + const response = await this.marketGetCandlesTimeframeSymbol (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/'; + const openAPI = (api === 'openapi'); + const privateAPI = (api === 'private'); + request += openAPI ? (api + '/') : ''; + request += this.version + '/'; + request += (privateAPI || openAPI) ? '' : (api + '/'); + request += this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }); + url += request; + if (privateAPI) { + this.checkRequiredCredentials (); + const timestamp = this.nonce ().toString (); + query = this.keysort (query); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.rawencode (query); + } + } + // HTTP_METHOD + HTTP_REQUEST_URI + TIMESTAMP + POST_BODY + let auth = method + url + timestamp; + if (method === 'POST') { + if (Object.keys (query).length) { + body = this.json (query); + auth += this.urlencode (query); + } + } + const payload = this.stringToBase64 (auth); + let signature = this.hmac (payload, this.encode (this.secret), 'sha1', 'binary'); + signature = this.decode (this.stringToBase64 (signature)); + headers = { + 'FC-ACCESS-KEY': this.apiKey, + 'FC-ACCESS-SIGNATURE': signature, + 'FC-ACCESS-TIMESTAMP': timestamp, + 'Content-Type': 'application/json', + }; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const status = this.safeString (response, 'status'); + if (status !== '0' && status !== 'ok') { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, status, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],82:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const fcoin = require ('./fcoin.js'); + +// --------------------------------------------------------------------------- + +module.exports = class fcoinjp extends fcoin { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'fcoinjp', + 'name': 'FCoinJP', + 'countries': [ 'JP' ], + 'hostname': 'fcoinjp.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/54219174-08b66b00-4500-11e9-862d-f522d0fe08c6.jpg', + 'fees': 'https://fcoinjp.zendesk.com/hc/en-us/articles/360018727371', + 'www': 'https://www.fcoinjp.com', + 'referral': undefined, + }, + }); + } +}; + +},{"./fcoin.js":81}],83:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class flowbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'flowbtc', + 'name': 'flowBTC', + 'countries': [ 'BR' ], // Brazil + 'version': 'v1', + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443317-01c0d080-c5fe-11ea-95c2-9ebe1a8fafd9.jpg', + 'api': 'https://publicapi.flowbtc.com.br', + 'www': 'https://www.flowbtc.com.br', + 'doc': 'https://www.flowbtc.com.br/api.html', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'post': [ + 'GetTicker', + 'GetTrades', + 'GetTradesByDate', + 'GetOrderBook', + 'GetProductPairs', + 'GetProducts', + ], + }, + 'private': { + 'post': [ + 'CreateAccount', + 'GetUserInfo', + 'SetUserInfo', + 'GetAccountInfo', + 'GetAccountTrades', + 'GetDepositAddresses', + 'Withdraw', + 'CreateOrder', + 'ModifyOrder', + 'CancelOrder', + 'CancelAllOrders', + 'GetAccountOpenOrders', + 'GetOrderFee', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0025, + 'taker': 0.005, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicPostGetProductPairs (params); + const markets = this.safeValue (response, 'productPairs'); + const result = {}; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'product1Label'); + const quoteId = this.safeString (market, 'product2Label'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const precision = { + 'amount': this.safeInteger (market, 'product1DecimalPlaces'), + 'price': this.safeInteger (market, 'product2DecimalPlaces'), + }; + const symbol = base + '/' + quote; + result[symbol] = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + 'active': undefined, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetAccountInfo (params); + const balances = this.safeValue (response, 'currencies'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = balance['name']; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['total'] = this.safeFloat (balance, 'hold'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'productPair': market['id'], + }; + const response = await this.publicPostGetOrderBook (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'px', 'qty'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'productPair': market['id'], + }; + const ticker = await this.publicPostGetTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume24hr'), + 'quoteVolume': this.safeFloat (ticker, 'volume24hrProduct2'), + 'info': ticker, + }; + } + + parseTrade (trade, market) { + const timestamp = this.safeTimestamp (trade, 'unixtime'); + const side = (trade['incomingOrderSide'] === 0) ? 'buy' : 'sell'; + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'px'); + const amount = this.safeFloat (trade, 'qty'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'id': id, + 'order': undefined, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': undefined, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'ins': market['id'], + 'startIndex': -1, + }; + const response = await this.publicPostGetTrades (this.extend (request, params)); + return this.parseTrades (response['trades'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const orderType = (type === 'market') ? 1 : 0; + const request = { + 'ins': this.marketId (symbol), + 'side': side, + 'orderType': orderType, + 'qty': amount, + 'px': this.priceToPrecision (symbol, price), + }; + const response = await this.privatePostCreateOrder (this.extend (request, params)); + return { + 'info': response, + 'id': response['serverOrderId'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + if ('ins' in params) { + const request = { + 'serverOrderId': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + throw new ExchangeError (this.id + ' requires `ins` symbol parameter for cancelling an order'); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + body = this.json (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const auth = nonce.toString () + this.uid + this.apiKey; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.json (this.extend ({ + 'apiKey': this.apiKey, + 'apiNonce': nonce, + 'apiSig': signature.toUpperCase (), + }, params)); + headers = { + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('isAccepted' in response) { + if (response['isAccepted']) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],84:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class foxbit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'foxbit', + 'name': 'FoxBit', + 'countries': [ 'BR' ], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'rateLimit': 1000, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443320-01c0d080-c5fe-11ea-92e2-4ef56d32b026.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://foxbit.com.br/exchange', + 'doc': 'https://foxbit.com.br/api/', + }, + 'comment': 'Blinktrade API', + 'api': { + 'public': { + 'get': [ + '{currency}/ticker', // ?crypto_currency=BTC + '{currency}/orderbook', // ?crypto_currency=BTC + '{currency}/trades', // ?crypto_currency=BTC&since=&limit= + ], + }, + 'private': { + 'post': [ + 'D', // order + 'F', // cancel order + 'U2', // balance + 'U4', // my orders + 'U6', // withdraw + 'U18', // deposit + 'U24', // confirm withdrawal + 'U26', // list withdrawals + 'U30', // list deposits + 'U34', // ledger + 'U70', // cancel withdrawal + ], + }, + }, + 'markets': { + 'BTC/VEF': { 'id': 'BTCVEF', 'symbol': 'BTC/VEF', 'base': 'BTC', 'quote': 'VEF', 'brokerId': 1, 'broker': 'SurBitcoin' }, + 'BTC/VND': { 'id': 'BTCVND', 'symbol': 'BTC/VND', 'base': 'BTC', 'quote': 'VND', 'brokerId': 3, 'broker': 'VBTC' }, + 'BTC/BRL': { 'id': 'BTCBRL', 'symbol': 'BTC/BRL', 'base': 'BTC', 'quote': 'BRL', 'brokerId': 4, 'broker': 'FoxBit' }, + 'BTC/PKR': { 'id': 'BTCPKR', 'symbol': 'BTC/PKR', 'base': 'BTC', 'quote': 'PKR', 'brokerId': 8, 'broker': 'UrduBit' }, + 'BTC/CLP': { 'id': 'BTCCLP', 'symbol': 'BTC/CLP', 'base': 'BTC', 'quote': 'CLP', 'brokerId': 9, 'broker': 'ChileBit' }, + }, + 'options': { + 'brokerId': '4', // https://blinktrade.com/docs/#brokers + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'BalanceReqID': this.nonce (), + }; + const response = await this.privatePostU2 (this.extend (request, params)); + const balances = this.safeValue (response['Responses'], this.options['brokerId']); + const result = { 'info': response }; + if (balances !== undefined) { + const currencyIds = Object.keys (this.currencies_by_id); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + // we only set the balance for the currency if that currency is present in response + // otherwise we will lose the info if the currency balance has been funded or traded or not + if (currencyId in balances) { + const account = this.account (); + let used = this.safeFloat (balances, currencyId + '_locked'); + if (used !== undefined) { + used *= 1e-8; + } + let total = this.safeFloat (balances, currencyId); + if (total !== undefined) { + total *= 1e-8; + } + account['used'] = used; + account['total'] = total; + result[code] = account; + } + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['quote'], + 'crypto_currency': market['base'], + }; + const response = await this.publicGetCurrencyOrderbook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['quote'], + 'crypto_currency': market['base'], + }; + const ticker = await this.publicGetCurrencyTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const lowercaseQuote = market['quote'].toLowerCase (); + const quoteVolume = 'vol_' + lowercaseQuote; + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, quoteVolume), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['quote'], + 'crypto_currency': market['base'], + }; + const response = await this.publicGetCurrencyTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const market = this.market (symbol); + const orderSide = (side === 'buy') ? '1' : '2'; + const request = { + 'ClOrdID': this.nonce (), + 'Symbol': market['id'], + 'Side': orderSide, + 'OrdType': '2', + 'Price': price, + 'OrderQty': amount, + 'BrokerID': market['brokerId'], + }; + const response = await this.privatePostD (this.extend (request, params)); + const indexed = this.indexBy (response['Responses'], 'MsgType'); + const execution = indexed['8']; + return { + 'info': response, + 'id': execution['OrderID'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + return await this.privatePostF (this.extend ({ + 'ClOrdID': id, + }, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const request = this.extend ({ 'MsgType': path }, query); + body = this.json (request); + headers = { + 'APIKey': this.apiKey, + 'Nonce': nonce, + 'Signature': this.hmac (this.encode (nonce), this.encode (this.secret)), + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('Status' in response) { + if (response['Status'] !== 200) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],85:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { ExchangeError, InvalidOrder, BadRequest, InsufficientFunds, OrderNotFound, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class ftx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'ftx', + 'name': 'FTX', + 'countries': [ 'HK' ], + 'rateLimit': 100, + 'certified': true, + 'pro': true, + 'hostname': 'ftx.com', // or ftx.us + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg', + 'www': 'https://ftx.com', + 'api': { + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + }, + 'doc': 'https://github.com/ftexchange/ftx', + 'fees': 'https://ftexchange.zendesk.com/hc/en-us/articles/360024479432-Fees', + 'referral': 'https://ftx.com/#a=1623029', + }, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFees': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '15s': '15', + '1m': '60', + '5m': '300', + '15m': '900', + '1h': '3600', + '4h': '14400', + '1d': '86400', + }, + 'api': { + 'public': { + 'get': [ + 'coins', + 'markets', + 'markets/{market_name}', + 'markets/{market_name}/orderbook', // ?depth={depth} + 'markets/{market_name}/trades', // ?limit={limit}&start_time={start_time}&end_time={end_time} + 'markets/{market_name}/candles', // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time} + // futures + 'futures', + 'futures/{future_name}', + 'futures/{future_name}/stats', + 'funding_rates', + 'indexes/{index_name}/weights', + 'expired_futures', + 'indexes/{market_name}/candles', // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time} + // leverage tokens + 'lt/tokens', + 'lt/{token_name}', + // options + 'options/requests', + 'options/trades', + 'stats/24h_options_volume', + 'options/historical_volumes/BTC', + 'options/open_interest/BTC', + 'options/historical_open_interest/BTC', + ], + }, + 'private': { + 'get': [ + 'account', + 'positions', + 'wallet/coins', + 'wallet/balances', + 'wallet/all_balances', + 'wallet/deposit_address/{coin}', + 'wallet/deposits', + 'wallet/withdrawals', + 'orders', // ?market={market} + 'orders/history', // ?market={market} + 'orders/{order_id}', + 'orders/by_client_id/{client_order_id}', + 'conditional_orders', // ?market={market} + 'conditional_orders/{conditional_order_id}/triggers', + 'conditional_orders/history', // ?market={market} + 'fills', // ?market={market} + 'funding_payments', + // leverage tokens + 'lt/balances', + 'lt/creations', + 'lt/redemptions', + // subaccounts + 'subaccounts', + 'subaccounts/{nickname}/balances', + // otc + 'otc/quotes/{quoteId}', + // options + 'options/my_requests', + 'options/requests/{request_id}/quotes', + 'options/my_quotes', + 'options/account_info', + 'options/positions', + 'options/fills', + ], + 'post': [ + 'account/leverage', + 'wallet/withdrawals', + 'orders', + 'conditional_orders', + 'orders/{order_id}/modify', + 'orders/by_client_id/{client_order_id}/modify', + 'conditional_orders/{order_id}/modify', + // leverage tokens + 'lt/{token_name}/create', + 'lt/{token_name}/redeem', + // subaccounts + 'subaccounts', + 'subaccounts/update_name', + 'subaccounts/transfer', + // otc + 'otc/quotes/{quote_id}/accept', + 'otc/quotes', + // options + 'options/requests', + 'options/requests/{request_id}/quotes', + 'options/quotes/{quote_id}/accept', + ], + 'delete': [ + 'orders/{order_id}', + 'orders/by_client_id/{client_order_id}', + 'orders', + 'conditional_orders/{order_id}', + // subaccounts + 'subaccounts', + // options + 'options/requests/{request_id}', + 'options/quotes/{quote_id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.02 / 100, + 'taker': 0.07 / 100, + 'tiers': { + 'taker': [ + [0, 0.07 / 100], + [1000000, 0.06 / 100], + [5000000, 0.055 / 100], + [10000000, 0.05 / 100], + [15000000, 0.045 / 100], + [35000000, 0.04 / 100], + ], + 'maker': [ + [0, 0.02 / 100], + [1000000, 0.02 / 100], + [5000000, 0.015 / 100], + [10000000, 0.015 / 100], + [15000000, 0.01 / 100], + [35000000, 0.01 / 100], + ], + }, + }, + 'funding': { + 'withdraw': {}, + }, + }, + 'exceptions': { + 'exact': { + 'Not logged in': AuthenticationError, // {"error":"Not logged in","success":false} + 'Not enough balances': InsufficientFunds, // {"error":"Not enough balances","success":false} + 'InvalidPrice': InvalidOrder, // {"error":"Invalid price","success":false} + 'Size too small': InvalidOrder, // {"error":"Size too small","success":false} + 'Missing parameter price': InvalidOrder, // {"error":"Missing parameter price","success":false} + 'Order not found': OrderNotFound, // {"error":"Order not found","success":false} + 'Order already closed': InvalidOrder, // {"error":"Order already closed","success":false} + }, + 'broad': { + 'Invalid parameter': BadRequest, // {"error":"Invalid parameter start_time","success":false} + 'The requested URL was not found on the server': BadRequest, + 'No such coin': BadRequest, + 'No such market': BadRequest, + 'An unexpected error occurred': ExchangeError, // {"error":"An unexpected error occurred, please try again later (58BC21C795).","success":false} + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + 'cancelOrder': { + 'method': 'privateDeleteOrdersOrderId', // privateDeleteConditionalOrdersOrderId + }, + 'fetchOpenOrders': { + 'method': 'privateGetOrders', // privateGetConditionalOrders + }, + 'fetchOrders': { + 'method': 'privateGetOrdersHistory', // privateGetConditionalOrdersHistory + }, + 'sign': { + 'ftx.com': 'FTX', + 'ftx.us': 'FTXUS', + }, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCoins (params); + const currencies = this.safeValue (response, 'result', []); + // + // { + // "success":true, + // "result": [ + // {"id":"BTC","name":"Bitcoin"}, + // {"id":"ETH","name":"Ethereum"}, + // {"id":"ETHMOON","name":"10X Long Ethereum Token","underlying":"ETH"}, + // {"id":"EOSBULL","name":"3X Long EOS Token","underlying":"EOS"}, + // ], + // } + // + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'id'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': name, + 'active': undefined, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'withdraw': { 'min': undefined, 'max': undefined }, + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // { + // 'success': true, + // "result": [ + // { + // "ask":170.37, + // "baseCurrency":null, + // "bid":170.31, + // "change1h":-0.019001554672655036, + // "change24h":-0.024841165359738997, + // "changeBod":-0.03816406029469881, + // "enabled":true, + // "last":170.37, + // "name":"ETH-PERP", + // "price":170.37, + // "priceIncrement":0.01, + // "quoteCurrency":null, + // "quoteVolume24h":7742164.59889, + // "sizeIncrement":0.001, + // "type":"future", + // "underlying":"ETH", + // "volumeUsd24h":7742164.59889 + // }, + // { + // "ask":170.44, + // "baseCurrency":"ETH", + // "bid":170.41, + // "change1h":-0.018485459257126403, + // "change24h":-0.023825887743413515, + // "changeBod":-0.037605872388481086, + // "enabled":true, + // "last":172.72, + // "name":"ETH/USD", + // "price":170.44, + // "priceIncrement":0.01, + // "quoteCurrency":"USD", + // "quoteVolume24h":382802.0252, + // "sizeIncrement":0.001, + // "type":"spot", + // "underlying":null, + // "volumeUsd24h":382802.0252 + // }, + // ], + // } + // + const result = []; + const markets = this.safeValue (response, 'result', []); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString2 (market, 'baseCurrency', 'underlying'); + const quoteId = this.safeString (market, 'quoteCurrency', 'USD'); + const type = this.safeString (market, 'type'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + // check if a market is a spot or future market + const symbol = (type === 'future') ? this.safeString (market, 'name') : (base + '/' + quote); + const active = this.safeValue (market, 'enabled'); + const sizeIncrement = this.safeFloat (market, 'sizeIncrement'); + const priceIncrement = this.safeFloat (market, 'priceIncrement'); + const precision = { + 'amount': sizeIncrement, + 'price': priceIncrement, + }; + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': type, + 'future': (type === 'future'), + 'spot': (type === 'spot'), + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': sizeIncrement, + 'max': undefined, + }, + 'price': { + 'min': priceIncrement, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }; + result.push (entry); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { + // "ask":171.29, + // "baseCurrency":null, // base currency for spot markets + // "bid":171.24, + // "change1h":-0.0012244897959183673, + // "change24h":-0.031603346901854366, + // "changeBod":-0.03297013492914808, + // "enabled":true, + // "last":171.44, + // "name":"ETH-PERP", + // "price":171.29, + // "priceIncrement":0.01, + // "quoteCurrency":null, // quote currency for spot markets + // "quoteVolume24h":8570651.12113, + // "sizeIncrement":0.001, + // "type":"future", + // "underlying":"ETH", // null for spot markets + // "volumeUsd24h":8570651.12113, + // } + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'name'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const type = this.safeString (ticker, 'type'); + if (type === 'future') { + symbol = marketId; + } else { + const base = this.safeCurrencyCode (this.safeString (ticker, 'baseCurrency')); + const quote = this.safeCurrencyCode (this.safeString (ticker, 'quoteCurrency')); + if ((base !== undefined) && (quote !== undefined)) { + symbol = base + '/' + quote; + } + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const timestamp = this.safeTimestamp (ticker, 'time', this.milliseconds ()); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': this.safeFloat (ticker, 'bidSize'), + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': this.safeFloat (ticker, 'askSize'), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'change24h'), + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume24h'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_name': market['id'], + }; + const response = await this.publicGetMarketsMarketName (this.extend (request, params)); + // + // { + // "success":true, + // "result":{ + // "ask":171.29, + // "baseCurrency":null, // base currency for spot markets + // "bid":171.24, + // "change1h":-0.0012244897959183673, + // "change24h":-0.031603346901854366, + // "changeBod":-0.03297013492914808, + // "enabled":true, + // "last":171.44, + // "name":"ETH-PERP", + // "price":171.29, + // "priceIncrement":0.01, + // "quoteCurrency":null, // quote currency for spot markets + // "quoteVolume24h":8570651.12113, + // "sizeIncrement":0.001, + // "type":"future", + // "underlying":"ETH", // null for spot markets + // "volumeUsd24h":8570651.12113, + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTicker (result, market); + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarkets (params); + // + // { + // 'success': true, + // "result": [ + // { + // "ask":170.44, + // "baseCurrency":"ETH", + // "bid":170.41, + // "change1h":-0.018485459257126403, + // "change24h":-0.023825887743413515, + // "changeBod":-0.037605872388481086, + // "enabled":true, + // "last":172.72, + // "name":"ETH/USD", + // "price":170.44, + // "priceIncrement":0.01, + // "quoteCurrency":"USD", + // "quoteVolume24h":382802.0252, + // "sizeIncrement":0.001, + // "type":"spot", + // "underlying":null, + // "volumeUsd24h":382802.0252 + // }, + // ], + // } + // + const tickers = this.safeValue (response, 'result', []); + return this.parseTickers (tickers, symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_name': market['id'], + }; + if (limit !== undefined) { + request['depth'] = limit; // max 100, default 20 + } + const response = await this.publicGetMarketsMarketNameOrderbook (this.extend (request, params)); + // + // { + // "success":true, + // "result":{ + // "asks":[ + // [171.95,279.865], + // [171.98,102.42], + // [171.99,124.11], + // ], + // "bids":[ + // [171.93,69.749], + // [171.9,288.325], + // [171.88,87.47], + // ], + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOrderBook (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "close":177.23, + // "high":177.45, + // "low":177.2, + // "open":177.43, + // "startTime":"2019-10-17T13:27:00+00:00", + // "time":1571318820000.0, + // "volume":0.0 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + getMarketId (symbol, key, params = {}) { + const parts = this.getMarketParams (symbol, key, params); + return this.safeString (parts, 1, symbol); + } + + getMarketParams (symbol, key, params = {}) { + let market = undefined; + let marketId = undefined; + if (symbol in this.markets) { + market = this.market (symbol); + marketId = market['id']; + } else { + marketId = this.safeString (params, key, symbol); + } + return [ market, marketId ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params); + const request = { + 'resolution': this.timeframes[timeframe], + 'market_name': marketId, + }; + // max 1501 candles, including the current candle when since is not specified + limit = (limit === undefined) ? 1501 : limit; + if (since === undefined) { + request['end_time'] = this.seconds (); + request['limit'] = limit; + request['start_time'] = request['end_time'] - limit * this.parseTimeframe (timeframe); + } else { + request['start_time'] = parseInt (since / 1000); + request['limit'] = limit; + request['end_time'] = this.sum (request['start_time'], limit * this.parseTimeframe (timeframe)); + } + const response = await this.publicGetMarketsMarketNameCandles (this.extend (request, params)); + // + // { + // "success": true, + // "result":[ + // { + // "close":177.23, + // "high":177.45, + // "low":177.2, + // "open":177.43, + // "startTime":"2019-10-17T13:27:00+00:00", + // "time":1571318820000.0, + // "volume":0.0 + // }, + // { + // "close":177.26, + // "high":177.33, + // "low":177.23, + // "open":177.23, + // "startTime":"2019-10-17T13:28:00+00:00", + // "time":1571318880000.0, + // "volume":0.0 + // }, + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":1715826, + // "liquidation":false, + // "price":171.62, + // "side":"buy", + // "size":2.095, + // "time":"2019-10-18T12:59:54.288166+00:00" + // } + // + // fetchMyTrades (private) + // + // { + // "fee": 20.1374935, + // "feeRate": 0.0005, + // "feeCurrency": "USD", + // "future": "EOS-0329", + // "id": 11215, + // "liquidity": "taker", + // "market": "EOS-0329", + // "baseCurrency": null, + // "quoteCurrency": null, + // "orderId": 8436981, + // "price": 4.201, + // "side": "buy", + // "size": 9587, + // "time": "2019-03-27T19:15:10.204619+00:00", + // "type": "order" + // } + // + const id = this.safeString (trade, 'id'); + const takerOrMaker = this.safeString (trade, 'liquidity'); + const marketId = this.safeString (trade, 'market'); + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + const base = this.safeCurrencyCode (this.safeString (trade, 'baseCurrency')); + const quote = this.safeCurrencyCode (this.safeString (trade, 'quoteCurrency')); + if ((base !== undefined) && (quote !== undefined)) { + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + } + const timestamp = this.parse8601 (this.safeString (trade, 'time')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'side'); + let cost = undefined; + if (price !== undefined && amount !== undefined) { + cost = price * amount; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': this.safeFloat (trade, 'feeRate'), + }; + } + const orderId = this.safeString (trade, 'orderId'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params); + const request = { + 'market_name': marketId, + }; + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + // start_time doesn't work without end_time + request['end_time'] = this.seconds (); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketsMarketNameTrades (this.extend (request, params)); + // + // { + // "success":true, + // "result":[ + // { + // "id":1715826, + // "liquidation":false, + // "price":171.62, + // "side":"buy", + // "size":2.095, + // "time":"2019-10-18T12:59:54.288166+00:00" + // }, + // { + // "id":1715763, + // "liquidation":false, + // "price":171.89, + // "side":"sell", + // "size":1.477, + // "time":"2019-10-18T12:58:38.443734+00:00" + // }, + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseTrades (result, market, since, limit); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + // + // { + // "success": true, + // "result": { + // "backstopProvider": true, + // "collateral": 3568181.02691129, + // "freeCollateral": 1786071.456884368, + // "initialMarginRequirement": 0.12222384240257728, + // "liquidating": false, + // "maintenanceMarginRequirement": 0.07177992558058484, + // "makerFee": 0.0002, + // "marginFraction": 0.5588433331419503, + // "openMarginFraction": 0.2447194090423075, + // "takerFee": 0.0005, + // "totalAccountValue": 3568180.98341129, + // "totalPositionSize": 6384939.6992, + // "username": "user@domain.com", + // "positions": [ + // { + // "cost": -31.7906, + // "entryPrice": 138.22, + // "future": "ETH-PERP", + // "initialMarginRequirement": 0.1, + // "longOrderSize": 1744.55, + // "maintenanceMarginRequirement": 0.04, + // "netSize": -0.23, + // "openSize": 1744.32, + // "realizedPnl": 3.39441714, + // "shortOrderSize": 1732.09, + // "side": "sell", + // "size": 0.23, + // "unrealizedPnl": 0, + // }, + // ], + // }, + // } + // + const result = this.safeValue (response, 'result', {}); + return { + 'info': response, + 'maker': this.safeFloat (result, 'makerFee'), + 'taker': this.safeFloat (result, 'takerFee'), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWalletBalances (params); + // + // { + // "success": true, + // "result": [ + // { + // "coin": "USDTBEAR", + // "free": 2320.2, + // "total": 2340.2 + // }, + // ], + // } + // + const result = { + 'info': response, + }; + const balances = this.safeValue (response, 'result', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const code = this.safeCurrencyCode (this.safeString (balance, 'coin')); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'new': 'open', + 'open': 'open', + 'closed': 'closed', // filled or canceled + 'triggered': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // limit orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder, editOrder + // + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 0, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "remainingSize": 31431, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null, + // } + // + // market orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder + // + // { + // "avgFillPrice": 2666.0, + // "clientId": None, + // "createdAt": "2020-02-12T00: 53: 49.009726+00: 00", + // "filledSize": 0.0007, + // "future": None, + // "id": 3109208514, + // "ioc": True, + // "market": "BNBBULL/USD", + // "postOnly": False, + // "price": None, + // "reduceOnly": False, + // "remainingSize": 0.0, + // "side": "buy", + // "size": 0.0007, + // "status": "closed", + // "type": "market" + // } + // + // createOrder (conditional, "stop", "trailingStop", or "takeProfit") + // + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306525, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false + // } + // + // editOrder (conditional, stop, trailing stop, take profit) + // + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306225, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false, + // "orderType": "market", + // "filledSize": 0, + // "avgFillPrice": null, + // "retryUntilFilled": false + // } + // + // canceled order with a closed status + // + // { + // "avgFillPrice":null, + // "clientId":null, + // "createdAt":"2020-09-01T13:45:57.119695+00:00", + // "filledSize":0.0, + // "future":null, + // "id":8553541288, + // "ioc":false, + // "liquidation":false, + // "market":"XRP/USDT", + // "postOnly":false, + // "price":0.5, + // "reduceOnly":false, + // "remainingSize":0.0, + // "side":"sell", + // "size":46.0, + // "status":"closed", + // "type":"limit" + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'createdAt')); + let status = this.parseOrderStatus (this.safeString (order, 'status')); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'filledSize'); + let remaining = this.safeFloat (order, 'remainingSize'); + if ((remaining === 0.0) && (amount !== undefined) && (filled !== undefined)) { + remaining = Math.max (amount - filled, 0); + if (remaining > 0) { + status = 'canceled'; + } + } + let symbol = undefined; + const marketId = this.safeString (order, 'market'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + // support for delisted market ids + // https://github.com/ccxt/ccxt/issues/7113 + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'type'); + const average = this.safeFloat (order, 'avgFillPrice'); + const price = this.safeFloat2 (order, 'price', 'triggerPrice', average); + let cost = undefined; + if (filled !== undefined && price !== undefined) { + cost = filled * price; + } + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'triggeredAt')); + const clientOrderId = this.safeString (order, 'clientId'); + const stopPrice = this.safeFloat (order, 'triggerPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': side, // "buy" or "sell" + // 'price': 0.306525, // send null for market orders + 'type': type, // "limit", "market", "stop", "trailingStop", or "takeProfit" + 'size': parseFloat (this.amountToPrecision (symbol, amount)), + // 'reduceOnly': false, // optional, default is false + // 'ioc': false, // optional, default is false, limit or market orders only + // 'postOnly': false, // optional, default is false, limit or market orders only + // 'clientId': 'abcdef0123456789', // string, optional, client order id, limit or market orders only + }; + const clientOrderId = this.safeString2 (params, 'clientId', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clientId'] = clientOrderId; + params = this.omit (params, [ 'clientId', 'clientOrderId' ]); + } + let priceToPrecision = undefined; + if (price !== undefined) { + priceToPrecision = parseFloat (this.priceToPrecision (symbol, price)); + } + let method = 'privatePostConditionalOrders'; + if (type === 'limit') { + method = 'privatePostOrders'; + request['price'] = priceToPrecision; + } else if (type === 'market') { + method = 'privatePostOrders'; + request['price'] = null; + } else if ((type === 'stop') || (type === 'takeProfit')) { + request['triggerPrice'] = priceToPrecision; + // request['orderPrice'] = number; // optional, order type is limit if this is specified, otherwise market + } else if (type === 'trailingStop') { + request['trailValue'] = priceToPrecision; // negative for "sell", positive for "buy" + } else { + throw new InvalidOrder (this.id + ' createOrder () does not support order type ' + type + ', only limit, market, stop, trailingStop, or takeProfit orders are supported'); + } + const response = await this[method] (this.extend (request, params)); + // + // orders + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 0, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "remainingSize": 31431, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null, + // } + // ] + // } + // + // conditional orders + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306525, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false + // } + // ] + // } + // + // + const result = this.safeValue (response, 'result', []); + return this.parseOrder (result, market); + } + + async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + const clientOrderId = this.safeString2 (params, 'client_order_id', 'clientOrderId'); + const triggerPrice = this.safeFloat (params, 'triggerPrice'); + const orderPrice = this.safeFloat (params, 'orderPrice'); + const trailValue = this.safeFloat (params, 'trailValue'); + params = this.omit (params, [ 'client_order_id', 'clientOrderId', 'triggerPrice', 'orderPrice', 'trailValue' ]); + const triggerPriceIsDefined = (triggerPrice !== undefined); + const orderPriceIsDefined = (orderPrice !== undefined); + const trailValueIsDefined = (trailValue !== undefined); + if (triggerPriceIsDefined || orderPriceIsDefined || trailValueIsDefined) { + method = 'privatePostConditionalOrdersOrderIdModify'; + request['order_id'] = id; + if (triggerPriceIsDefined) { + request['triggerPrice'] = parseFloat (this.priceToPrecision (symbol, triggerPrice)); + } + if (orderPriceIsDefined) { + // only for stop limit or take profit limit orders + request['orderPrice'] = parseFloat (this.priceToPrecision (symbol, orderPrice)); + } + if (trailValueIsDefined) { + // negative for sell orders, positive for buy orders + request['trailValue'] = parseFloat (this.priceToPrecision (symbol, trailValue)); + } + } else { + if (clientOrderId === undefined) { + method = 'privatePostOrdersByClientIdClientOrderIdModify'; + request['client_order_id'] = clientOrderId; + // request['clientId'] = clientOrderId; + } else { + method = 'privatePostOrdersOrderIdModify'; + request['order_id'] = id; + } + if (price !== undefined) { + request['price'] = parseFloat (this.priceToPrecision (symbol, price)); + } + } + if (amount !== undefined) { + request['size'] = parseFloat (this.amountToPrecision (symbol, amount)); + } + const response = await this[method] (this.extend (request, params)); + // + // regular order + // + // { + // "success": true, + // "result": { + // "createdAt": "2019-03-05T11:56:55.728933+00:00", + // "filledSize": 0, + // "future": "XRP-PERP", + // "id": 9596932, + // "market": "XRP-PERP", + // "price": 0.326525, + // "remainingSize": 31431, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null, + // } + // } + // + // conditional trigger order + // + // { + // "success": true, + // "result": { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306225, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false, + // "orderType": "market", + // "filledSize": 0, + // "avgFillPrice": null, + // "retryUntilFilled": false + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + const options = this.safeValue (this.options, 'cancelOrder', {}); + const defaultMethod = this.safeString (options, 'method', 'privateDeleteOrdersOrderId'); + let method = this.safeString (params, 'method', defaultMethod); + const type = this.safeValue (params, 'type'); + const clientOrderId = this.safeValue2 (params, 'client_order_id', 'clientOrderId'); + if (clientOrderId === undefined) { + request['order_id'] = parseInt (id); + if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) { + method = 'privateDeleteConditionalOrdersOrderId'; + } + } else { + request['client_order_id'] = clientOrderId; + method = 'privateDeleteOrdersByClientIdClientOrderId'; + } + const query = this.omit (params, [ 'method', 'type', 'client_order_id', 'clientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + // + // { + // "success": true, + // "result": "Order queued for cancelation" + // } + // + const result = this.safeValue (response, 'result', {}); + return result; + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'market': market['id'], // optional + 'conditionalOrdersOnly': false, // cancel conditional orders only + 'limitOrdersOnly': false, // cancel existing limit orders (non-conditional orders) only + }; + const marketId = this.getMarketId (symbol, 'market', params); + if (marketId !== undefined) { + request['market'] = marketId; + } + const response = await this.privateDeleteOrders (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + // + // { + // "success": true, + // "result": "Orders queued for cancelation" + // } + // + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const clientOrderId = this.safeValue2 (params, 'client_order_id', 'clientOrderId'); + let method = 'privateGetOrdersOrderId'; + if (clientOrderId === undefined) { + request['order_id'] = id; + } else { + request['client_order_id'] = clientOrderId; + params = this.omit (params, [ 'client_order_id', 'clientOrderId']); + method = 'privateGetOrdersByClientIdClientOrderId'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "success": true, + // "result": { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 10, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "avgFillPrice": 0.306526, + // "remainingSize": 31421, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const [ market, marketId ] = this.getMarketParams (symbol, 'market', params); + if (marketId !== undefined) { + request['market'] = marketId; + } + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + const defaultMethod = this.safeString (options, 'method', 'privateGetOrders'); + let method = this.safeString (params, 'method', defaultMethod); + const type = this.safeValue (params, 'type'); + if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) { + method = 'privateGetConditionalOrders'; + } + const query = this.omit (params, [ 'method', 'type' ]); + const response = await this[method] (this.extend (request, query)); + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 10, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "avgFillPrice": 0.306526, + // "remainingSize": 31421, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + // } + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const [ market, marketId ] = this.getMarketParams (symbol, 'market', params); + if (marketId !== undefined) { + request['market'] = marketId; + } + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 100 + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + } + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + const options = this.safeValue (this.options, 'fetchOrders', {}); + const defaultMethod = this.safeString (options, 'method', 'privateGetOrdersHistory'); + let method = this.safeString (params, 'method', defaultMethod); + const type = this.safeValue (params, 'type'); + if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) { + method = 'privateGetConditionalOrdersHistory'; + } + const query = this.omit (params, [ 'method', 'type' ]); + const response = await this[method] (this.extend (request, query)); + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 10, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "avgFillPrice": 0.306526, + // "remainingSize": 31421, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + // } + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const [ market, marketId ] = this.getMarketParams (symbol, 'market', params); + const request = {}; + if (marketId !== undefined) { + request['market'] = marketId; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + } + const response = await this.privateGetFills (this.extend (request, params)); + // + // { + // "success": true, + // "result": [ + // { + // "fee": 20.1374935, + // "feeRate": 0.0005, + // "future": "EOS-0329", + // "id": 11215, + // "liquidity": "taker", + // "market": "EOS-0329", + // "baseCurrency": null, + // "quoteCurrency": null, + // "orderId": 8436981, + // "price": 4.201, + // "side": "buy", + // "size": 9587, + // "time": "2019-03-27T19:15:10.204619+00:00", + // "type": "order" + // } + // ] + // } + // + const trades = this.safeValue (response, 'result', []); + return this.parseTrades (trades, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'size': amount, + 'address': address, + // 'password': 'string', // optional withdrawal password if it is required for your account + // 'code': '192837', // optional 2fa code if it is required for your account + }; + if (this.password !== undefined) { + request['password'] = this.password; + } + if (tag !== undefined) { + request['tag'] = tag; + } + const response = await this.privatePostWalletWithdrawals (this.extend (request, params)); + // + // { + // "success": true, + // "result": { + // "coin": "USDTBEAR", + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null", + // "fee": 0, + // "id": 1, + // "size": "20.2", + // "status": "requested", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "null" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTransaction (result, currency); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + // + // { + // "result":{ + // "backstopProvider":false, + // "chargeInterestOnNegativeUsd":false, + // "collateral":2830.2567913677476, + // "freeCollateral":2829.670741867416, + // "initialMarginRequirement":0.05, + // "leverage":20.0, + // "liquidating":false, + // "maintenanceMarginRequirement":0.03, + // "makerFee":0.0, + // "marginFraction":null, + // "openMarginFraction":null, + // "positionLimit":null, + // "positionLimitUsed":null, + // "positions":[ + // { + // "collateralUsed":0.0, + // "cost":0.0, + // "entryPrice":null, + // "estimatedLiquidationPrice":null, + // "future":"XRP-PERP", + // "initialMarginRequirement":0.05, + // "longOrderSize":0.0, + // "maintenanceMarginRequirement":0.03, + // "netSize":0.0, + // "openSize":0.0, + // "realizedPnl":0.016, + // "shortOrderSize":0.0, + // "side":"buy", + // "size":0.0, + // "unrealizedPnl":0.0, + // } + // ], + // "spotLendingEnabled":false, + // "spotMarginEnabled":false, + // "takerFee":0.0007, + // "totalAccountValue":2830.2567913677476, + // "totalPositionSize":0.0, + // "useFttCollateral":true, + // "username":"igor.kroitor@gmail.com" + // }, + // "success":true + // } + // + const result = this.safeValue (response, 'result', {}); + // todo unify parsePosition/parsePositions + return this.safeValue (result, 'positions', []); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + }; + const response = await this.privateGetWalletDepositAddressCoin (this.extend (request, params)); + // + // { + // "success": true, + // "result": { + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const address = this.safeString (result, 'address'); + const tag = this.safeString (result, 'tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + // what are other statuses here? + 'confirmed': 'ok', // deposits + 'complete': 'ok', // withdrawals + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "coin": "TUSD", + // "confirmations": 64, + // "confirmedTime": "2019-03-05T09:56:55.728933+00:00", + // "fee": 0, + // "id": 1, + // "sentTime": "2019-03-05T09:56:55.735929+00:00", + // "size": "99.0", + // "status": "confirmed", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // + // fetchWithdrawals + // + // { + // "coin": "TUSD", + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null", + // "fee": 0, + // "id": 1, + // "size": "99.0", + // "status": "complete", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // + const code = this.safeCurrencyCode (this.safeString (transaction, 'coin')); + const id = this.safeString (transaction, 'id'); + const amount = this.safeFloat (transaction, 'size'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const timestamp = this.parse8601 (this.safeString (transaction, 'time')); + const txid = this.safeString (transaction, 'txid'); + let tag = undefined; + let address = this.safeValue (transaction, 'address'); + if (typeof address !== 'string') { + tag = this.safeString (address, 'tag'); + address = this.safeString (address, 'address'); + } + const fee = this.safeFloat (transaction, 'fee'); + const type = ('destinationName' in transaction) ? 'withdrawal' : 'deposit'; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': { + 'currency': code, + 'cost': fee, + 'rate': undefined, + }, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWalletDeposits (params); + // + // { + // "success": true, + // "result": { + // "coin": "TUSD", + // "confirmations": 64, + // "confirmedTime": "2019-03-05T09:56:55.728933+00:00", + // "fee": 0, + // "id": 1, + // "sentTime": "2019-03-05T09:56:55.735929+00:00", + // "size": "99.0", + // "status": "confirmed", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // } + // + const result = this.safeValue (response, 'result', []); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (result, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWalletWithdrawals (params); + // + // { + // "success": true, + // "result": { + // "coin": "TUSD", + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null", + // "fee": 0, + // "id": 1, + // "size": "99.0", + // "status": "complete", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // } + // + const result = this.safeValue (response, 'result', []); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (result, currency, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/api/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const baseUrl = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }); + let url = baseUrl + request; + if (method !== 'POST') { + if (Object.keys (query).length) { + const suffix = '?' + this.urlencode (query); + url += suffix; + request += suffix; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + let auth = timestamp + method + request; + headers = {}; + if ((method === 'POST') || (method === 'DELETE')) { + body = this.json (query); + auth += body; + headers['Content-Type'] = 'application/json'; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + const options = this.safeValue (this.options, 'sign', {}); + const headerPrefix = this.safeString (options, this.hostname, 'FTX'); + const keyField = headerPrefix + '-KEY'; + const tsField = headerPrefix + '-TS'; + const signField = headerPrefix + '-SIGN'; + headers[keyField] = this.apiKey; + headers[tsField] = timestamp; + headers[signField] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to the default error handler + } + // + // {"error":"Invalid parameter start_time","success":false} + // {"error":"Not enough balances","success":false} + // + const success = this.safeValue (response, 'success'); + if (!success) { + const feedback = this.id + ' ' + body; + const error = this.safeString (response, 'error'); + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],86:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, ArgumentsRequired, InvalidAddress, OrderNotFound, NotSupported, DDoSProtection, InsufficientFunds, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class gateio extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'gateio', + 'name': 'Gate.io', + 'countries': [ 'CN' ], + 'version': '2', + 'rateLimit': 1000, + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 60, + '5m': 300, + '10m': 600, + '15m': 900, + '30m': 1800, + '1h': 3600, + '2h': 7200, + '4h': 14400, + '6h': 21600, + '12h': 43200, + '1d': 86400, + '1w': 604800, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg', + 'api': { + 'public': 'https://data.gate.io/api', + 'private': 'https://data.gate.io/api', + }, + 'www': 'https://gate.io/', + 'doc': 'https://gate.io/api2', + 'fees': [ + 'https://gate.io/fee', + 'https://support.gate.io/hc/en-us/articles/115003577673', + ], + 'referral': 'https://www.gate.io/signup/2436035', + }, + 'api': { + 'public': { + 'get': [ + 'candlestick2/{id}', + 'pairs', + 'coininfo', + 'marketinfo', + 'marketlist', + 'coininfo', + 'tickers', + 'ticker/{id}', + 'orderBook/{id}', + 'trade/{id}', + 'tradeHistory/{id}', + 'tradeHistory/{id}/{tid}', + ], + }, + 'private': { + 'post': [ + 'balances', + 'depositAddress', + 'newAddress', + 'depositsWithdrawals', + 'buy', + 'sell', + 'cancelOrder', + 'cancelAllOrders', + 'getOrder', + 'openOrders', + 'tradeHistory', + 'feelist', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.002, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + '4': DDoSProtection, + '5': AuthenticationError, // { result: "false", code: 5, message: "Error: invalid key or sign, please re-generate it from your account" } + '6': AuthenticationError, // { result: 'false', code: 6, message: 'Error: invalid data ' } + '7': NotSupported, + '8': NotSupported, + '9': NotSupported, + '15': DDoSProtection, + '16': OrderNotFound, + '17': OrderNotFound, + '20': InvalidOrder, + '21': InsufficientFunds, + }, + // https://gate.io/api2#errCode + 'errorCodeNames': { + '1': 'Invalid request', + '2': 'Invalid version', + '3': 'Invalid request', + '4': 'Too many attempts', + '5': 'Invalid sign', + '6': 'Invalid sign', + '7': 'Currency is not supported', + '8': 'Currency is not supported', + '9': 'Currency is not supported', + '10': 'Verified failed', + '11': 'Obtaining address failed', + '12': 'Empty params', + '13': 'Internal error, please report to administrator', + '14': 'Invalid user', + '15': 'Cancel order too fast, please wait 1 min and try again', + '16': 'Invalid order id or order is already closed', + '17': 'Invalid orderid', + '18': 'Invalid amount', + '19': 'Not permitted or trade is disabled', + '20': 'Your order size is too small', + '21': 'You don\'t have enough fund', + }, + }, + 'options': { + 'limits': { + 'cost': { + 'min': { + 'BTC': 0.0001, + 'ETH': 0.001, + 'USDT': 1, + }, + }, + }, + }, + 'commonCurrencies': { + 'BOX': 'DefiBox', + 'BTCBEAR': 'BEAR', + 'BTCBULL': 'BULL', + 'TNC': 'Trinity Network Credit', + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCoininfo (params); + // + // { + // "result":"true", + // "coins":[ + // { + // "CNYX":{ + // "delisted":0, + // "withdraw_disabled":1, + // "withdraw_delayed":0, + // "deposit_disabled":0, + // "trade_disabled":0 + // } + // }, + // { + // "USDT_ETH":{ + // "delisted":0, + // "withdraw_disabled":1, + // "withdraw_delayed":0, + // "deposit_disabled":0, + // "trade_disabled":1 + // } + // } + // ] + // } + // + const coins = this.safeValue (response, 'coins'); + if (!coins) { + throw new ExchangeError (this.id + ' fetchCurrencies got an unrecognized response'); + } + const result = {}; + for (let i = 0; i < coins.length; i++) { + const coin = coins[i]; + const ids = Object.keys (coin); + for (let j = 0; j < ids.length; j++) { + const id = ids[j]; + const currency = coin[id]; + const code = this.safeCurrencyCode (id); + const delisted = this.safeValue (currency, 'delisted', 0); + const withdrawDisabled = this.safeValue (currency, 'withdraw_disabled', 0); + const depositDisabled = this.safeValue (currency, 'deposit_disabled', 0); + const tradeDisabled = this.safeValue (currency, 'trade_disabled', 0); + const listed = (delisted === 0); + const withdrawEnabled = (withdrawDisabled === 0); + const depositEnabled = (depositDisabled === 0); + const tradeEnabled = (tradeDisabled === 0); + const active = listed && withdrawEnabled && depositEnabled && tradeEnabled; + result[code] = { + 'id': id, + 'code': code, + 'active': active, + 'info': currency, + 'name': undefined, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarketinfo (params); + // + // { + // "result":"true", + // "pairs":[ + // { + // "usdt_cnyx":{ + // "decimal_places":3, + // "amount_decimal_places":3, + // "min_amount":1, + // "min_amount_a":1, + // "min_amount_b":3, + // "fee":0.02, + // "trade_disabled":0, + // "buy_disabled":0, + // "sell_disabled":0 + // } + // }, + // ] + // } + // + const markets = this.safeValue (response, 'pairs'); + if (!markets) { + throw new ExchangeError (this.id + ' fetchMarkets got an unrecognized response'); + } + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const keys = Object.keys (market); + const id = this.safeString (keys, 0); + const details = market[id]; + // all of their symbols are separated with an underscore + // but not boe_eth_eth (BOE_ETH/ETH) which has two underscores + // https://github.com/ccxt/ccxt/issues/4894 + const parts = id.split ('_'); + const numParts = parts.length; + let baseId = parts[0]; + let quoteId = parts[1]; + if (numParts > 2) { + baseId = parts[0] + '_' + parts[1]; + quoteId = parts[2]; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (details, 'amount_decimal_places'), + 'price': this.safeInteger (details, 'decimal_places'), + }; + const amountLimits = { + 'min': this.safeFloat (details, 'min_amount'), + 'max': undefined, + }; + const priceLimits = { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }; + const defaultCost = amountLimits['min'] * priceLimits['min']; + const minCost = this.safeFloat (this.options['limits']['cost']['min'], quote, defaultCost); + const costLimits = { + 'min': minCost, + 'max': undefined, + }; + const limits = { + 'amount': amountLimits, + 'price': priceLimits, + 'cost': costLimits, + }; + const disabled = this.safeValue (details, 'trade_disabled'); + const active = !disabled; + const uppercaseId = id.toUpperCase (); + const fee = this.safeFloat (details, 'fee'); + result.push ({ + 'id': id, + 'uppercaseId': uppercaseId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'maker': fee / 100, + 'taker': fee / 100, + 'precision': precision, + 'limits': limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalances (params); + const result = { 'info': response }; + let available = this.safeValue (response, 'available', {}); + if (Array.isArray (available)) { + available = {}; + } + const locked = this.safeValue (response, 'locked', {}); + const currencyIds = Object.keys (available); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (available, currencyId); + account['used'] = this.safeFloat (locked, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.publicGetOrderBookId (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseOHLCV (ohlcv, market = undefined) { + // they return [ Timestamp, Volume, Close, High, Low, Open ] + return [ + this.safeInteger (ohlcv, 0), // t + this.safeFloat (ohlcv, 5), // o + this.safeFloat (ohlcv, 3), // h + this.safeFloat (ohlcv, 4), // l + this.safeFloat (ohlcv, 2), // c + this.safeFloat (ohlcv, 1), // v + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + 'group_sec': this.timeframes[timeframe], + }; + // max limit = 1001 + if (limit !== undefined) { + const periodDurationInSeconds = this.parseTimeframe (timeframe); + const hours = parseInt ((periodDurationInSeconds * limit) / 3600); + request['range_hour'] = Math.max (0, hours - 1); + } + const response = await this.publicGetCandlestick2Id (this.extend (request, params)); + // + // { + // "elapsed": "15ms", + // "result": "true", + // "data": [ + // [ "1553930820000", "1.005299", "4081.05", "4086.18", "4081.05", "4086.18" ], + // [ "1553930880000", "0.110923277", "4095.2", "4095.23", "4091.15", "4091.15" ], + // ... + // [ "1553934420000", "0", "4089.42", "4089.42", "4089.42", "4089.42" ], + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'percentChange'); + let open = undefined; + let change = undefined; + let average = undefined; + if ((last !== undefined) && (percentage !== undefined)) { + const relativeChange = percentage / 100; + open = last / this.sum (1, relativeChange); + change = last - open; + average = this.sum (last, open) / 2; + } + open = this.safeFloat (ticker, 'open', open); + change = this.safeFloat (ticker, 'change', change); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (ticker, 'high24hr', 'high'), + 'low': this.safeFloat2 (ticker, 'low24hr', 'low'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'quoteVolume'), // gateio has them reversed + 'quoteVolume': this.safeFloat (ticker, 'baseVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const result = {}; + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id, undefined, '_'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (response[id], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const ticker = await this.publicGetTickerId (this.extend ({ + 'id': market['id'], + }, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // { + // "tradeID": 3175762, + // "date": "2017-08-25 07:24:28", + // "type": "sell", + // "rate": 29011, + // "amount": 0.0019, + // "total": 55.1209, + // "fee": "0", + // "fee_coin": "btc", + // "gt_fee":"0", + // "point_fee":"0.1213", + // }, + let timestamp = this.safeTimestamp2 (trade, 'timestamp', 'time_unix'); + timestamp = this.safeTimestamp (trade, 'time', timestamp); + const id = this.safeString2 (trade, 'tradeID', 'id'); + // take either of orderid or orderId + const orderId = this.safeString2 (trade, 'orderid', 'orderNumber'); + const price = this.safeFloat2 (trade, 'rate', 'price'); + const amount = this.safeFloat (trade, 'amount'); + const type = this.safeString (trade, 'type'); + const takerOrMaker = this.safeString (trade, 'role'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let fee = undefined; + let feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee_coin')); + let feeCost = this.safeFloat (trade, 'point_fee'); + if ((feeCost === undefined) || (feeCost === 0)) { + feeCost = this.safeFloat (trade, 'gt_fee'); + if ((feeCost === undefined) || (feeCost === 0)) { + feeCost = this.safeFloat (trade, 'fee'); + } else { + feeCurrency = this.safeCurrencyCode ('GT'); + } + } else { + feeCurrency = this.safeCurrencyCode ('POINT'); + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': type, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + let method = undefined; + if ('tid' in params) { + method = 'publicGetTradeHistoryIdTid'; + } else { + method = 'publicGetTradeHistoryId'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.privatePostOpenOrders (params); + return this.parseOrders (response['orders'], undefined, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderNumber': id, + 'currencyPair': this.marketId (symbol), + }; + const response = await this.privatePostGetOrder (this.extend (request, params)); + return this.parseOrder (response['order']); + } + + parseOrderStatus (status) { + const statuses = { + 'cancelled': 'canceled', + // 'closed': 'closed', // these two statuses aren't actually needed + // 'open': 'open', // as they are mapped one-to-one + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "fee": "0 ZEC", + // "code": 0, + // "rate": "0.0055", + // "side": 2, + // "type": "buy", + // "ctime": 1586460839.138, + // "market": "ZEC_BTC", + // "result": "true", + // "status": "open", + // "iceberg": "0", + // "message": "Success", + // "feeValue": "0", + // "filledRate": "0.005500000", + // "leftAmount": "0.60607456", + // "feeCurrency": "ZEC", + // "orderNumber": 10755887009, + // "filledAmount": "0", + // "feePercentage": 0.002, + // "initialAmount": "0.60607456" + // } + // + // { + // 'amount': '0.00000000', + // 'currencyPair': 'xlm_usdt', + // 'fee': '0.0113766632239302 USDT', + // 'feeCurrency': 'USDT', + // 'feePercentage': 0.18, + // 'feeValue': '0.0113766632239302', + // 'filledAmount': '30.14004987', + // 'filledRate': 0.2097, + // 'initialAmount': '30.14004987', + // 'initialRate': '0.2097', + // 'left': 0, + // 'orderNumber': '998307286', + // 'rate': '0.2097', + // 'status': 'closed', + // 'timestamp': 1531158583, + // 'type': 'sell' + // } + // + // { + // "orderNumber": 10802237760, + // "orderType": 1, + // "type": "buy", + // "rate": "0.54250000", + // "amount": "45.55638518", + // "total": "24.71433896", + // "initialRate": "0.54250000", + // "initialAmount": "45.55638518", + // "filledRate": "0.54250000", + // "filledAmount": "0", + // "currencyPair": "nano_usdt", + // "timestamp": 1586556143, + // "status": "open" + // } + // + const id = this.safeString2 (order, 'orderNumber', 'id'); + const marketId = this.safeString (order, 'currencyPair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const timestamp = this.safeTimestamp2 (order, 'timestamp', 'ctime'); + const lastTradeTimestamp = this.safeTimestamp (order, 'mtime'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let side = this.safeString (order, 'type'); + // handling for order.update messages + if (side === '1') { + side = 'sell'; + } else if (side === '2') { + side = 'buy'; + } + const price = this.safeFloat2 (order, 'initialRate', 'rate'); + const average = this.safeFloat (order, 'filledRate'); + const amount = this.safeFloat2 (order, 'initialAmount', 'amount'); + const filled = this.safeFloat (order, 'filledAmount'); + // In the order status response, this field has a different name. + let remaining = this.safeFloat2 (order, 'leftAmount', 'left'); + if (remaining === undefined) { + remaining = amount - filled; + } + const feeCost = this.safeFloat (order, 'feeValue'); + const feeCurrencyId = this.safeString (order, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + let feeRate = this.safeFloat (order, 'feePercentage'); + if (feeRate !== undefined) { + feeRate = feeRate / 100; + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': average, + 'trades': undefined, + 'fee': { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }, + 'info': order, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const method = 'privatePost' + this.capitalize (side); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'rate': price, + 'amount': amount, + }; + const response = await this[method] (this.extend (request, params)); + return this.parseOrder (this.extend ({ + 'status': 'open', + 'type': side, + 'initialAmount': amount, + }, response), market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires symbol argument'); + } + await this.loadMarkets (); + const request = { + 'orderNumber': id, + 'currencyPair': this.marketId (symbol), + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async queryDepositAddress (method, code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + method = 'privatePost' + method + 'Address'; + const request = { + 'currency': currency['id'], + }; + const response = await this[method] (this.extend (request, params)); + let address = this.safeString (response, 'addr'); + let tag = undefined; + if ((address !== undefined) && (address.indexOf ('address') >= 0)) { + throw new InvalidAddress (this.id + ' queryDepositAddress ' + address); + } + if (code === 'XRP') { + const parts = address.split (' '); + address = parts[0]; + tag = parts[1]; + } + return { + 'currency': currency, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async createDepositAddress (code, params = {}) { + return await this.queryDepositAddress ('New', code, params); + } + + async fetchDepositAddress (code, params = {}) { + return await this.queryDepositAddress ('Deposit', code, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privatePostOpenOrders (params); + return this.parseOrders (response['orders'], market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'orderNumber': id, + }; + const response = await this.privatePostTradeHistory (this.extend (request, params)); + return this.parseTrades (response['trades'], market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const response = await this.privatePostTradeHistory (this.extend (request, params)); + return this.parseTrades (response['trades'], market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, // Address must exist in you AddressBook in security settings + }; + if (tag !== undefined) { + request['address'] += ' ' + tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + async fetchTransactionsByType (type = undefined, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['start'] = since; + } + const response = await this.privatePostDepositsWithdrawals (this.extend (request, params)); + let transactions = undefined; + if (type === undefined) { + const deposits = this.safeValue (response, 'deposits', []); + const withdrawals = this.safeValue (response, 'withdraws', []); + transactions = this.arrayConcat (deposits, withdrawals); + } else { + transactions = this.safeValue (response, type, []); + } + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (transactions, currency, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType (undefined, code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposits', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdraws', code, since, limit, params); + } + + parseTransaction (transaction, currency = undefined) { + // + // deposit + // + // { + // 'id': 'd16520849', + // 'currency': 'NEO', + // 'address': False, + // 'amount': '1', + // 'txid': '01acf6b8ce4d24a....', + // 'timestamp': '1553125968', + // 'status': 'DONE', + // 'type': 'deposit' + // } + // + // withdrawal + // + // { + // 'id': 'w5864259', + // 'currency': 'ETH', + // 'address': '0x72632f462....', + // 'amount': '0.4947', + // 'txid': '0x111167d120f736....', + // 'timestamp': '1553123688', + // 'status': 'DONE', + // 'type': 'withdrawal' + // } + // + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const id = this.safeString (transaction, 'id'); + const txid = this.safeString (transaction, 'txid'); + const amount = this.safeFloat (transaction, 'amount'); + let address = this.safeString (transaction, 'address'); + if (address === 'false') { + address = undefined; + } + const timestamp = this.safeTimestamp (transaction, 'timestamp'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const type = this.parseTransactionType (id[0]); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'PEND': 'pending', + 'REQUEST': 'pending', + 'DMOVE': 'pending', + 'CANCEL': 'failed', + 'DONE': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const types = { + 'd': 'deposit', + 'w': 'withdrawal', + }; + return this.safeString (types, type, type); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + const resultString = this.safeString (response, 'result', ''); + if (resultString !== 'false') { + return; + } + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message', body); + if (errorCode !== undefined) { + const feedback = this.safeString (this.exceptions['errorCodeNames'], errorCode, message); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const prefix = (api === 'private') ? (api + '/') : ''; + let url = this.urls['api'][api] + this.version + '/1/' + prefix + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = { 'nonce': nonce }; + body = this.rawencode (this.extend (request, query)); + // gateio does not like the plus sign in the URL query + // https://github.com/ccxt/ccxt/issues/4529 + body = body.replace ('+', ' '); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Key': this.apiKey, + 'Sign': signature, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],87:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, OrderNotFound, InvalidOrder, InvalidNonce, InsufficientFunds, AuthenticationError, PermissionDenied, NotSupported, OnMaintenance, RateLimitExceeded, ExchangeNotAvailable } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class gemini extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'gemini', + 'name': 'Gemini', + 'countries': [ 'US' ], + 'rateLimit': 1500, // 200 for private API + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': false, + 'fetchClosedOrders': false, + 'fetchDepositAddress': false, + 'fetchDeposits': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg', + 'api': { + 'public': 'https://api.gemini.com', + 'private': 'https://api.gemini.com', + 'web': 'https://docs.gemini.com', + }, + 'www': 'https://gemini.com/', + 'doc': [ + 'https://docs.gemini.com/rest-api', + 'https://docs.sandbox.gemini.com', + ], + 'test': { + 'public': 'https://api.sandbox.gemini.com', + 'private': 'https://api.sandbox.gemini.com', + // use the true doc instead of the sandbox doc + // since they differ in parsing + // https://github.com/ccxt/ccxt/issues/7874 + // https://github.com/ccxt/ccxt/issues/7894 + 'web': 'https://docs.gemini.com', + }, + 'fees': [ + 'https://gemini.com/api-fee-schedule', + 'https://gemini.com/trading-fees', + 'https://gemini.com/transfer-fees', + ], + }, + 'api': { + 'web': { + 'get': [ + 'rest-api', + ], + }, + 'public': { + 'get': [ + 'v1/symbols', + 'v1/pricefeed', + 'v1/pubticker/{symbol}', + 'v1/book/{symbol}', + 'v1/trades/{symbol}', + 'v1/auction/{symbol}', + 'v1/auction/{symbol}/history', + 'v2/candles/{symbol}/{timeframe}', + 'v2/ticker/{symbol}', + ], + }, + 'private': { + 'post': [ + 'v1/order/new', + 'v1/order/cancel', + 'v1/order/cancel/session', + 'v1/order/cancel/all', + 'v1/order/status', + 'v1/orders', + 'v1/mytrades', + 'v1/notionalvolume', + 'v1/tradevolume', + 'v1/transfers', + 'v1/balances', + 'v1/deposit/{currency}/newAddress', + 'v1/withdraw/{currency}', + 'v1/heartbeat', + 'v1/transfers', + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'taker': 0.0035, + 'maker': 0.001, + }, + }, + 'httpExceptions': { + '400': BadRequest, // Auction not open or paused, ineligible timing, market not open, or the request was malformed, in the case of a private API request, missing or malformed Gemini private API authentication headers + '403': PermissionDenied, // The API key is missing the role necessary to access this private API endpoint + '404': OrderNotFound, // Unknown API entry point or Order not found + '406': InsufficientFunds, // Insufficient Funds + '429': RateLimitExceeded, // Rate Limiting was applied + '500': ExchangeError, // The server encountered an error + '502': ExchangeNotAvailable, // Technical issues are preventing the request from being satisfied + '503': OnMaintenance, // The exchange is down for maintenance + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1hr', + '6h': '6hr', + '1d': '1day', + }, + 'exceptions': { + 'exact': { + 'AuctionNotOpen': BadRequest, // Failed to place an auction-only order because there is no current auction open for this symbol + 'ClientOrderIdTooLong': BadRequest, // The Client Order ID must be under 100 characters + 'ClientOrderIdMustBeString': BadRequest, // The Client Order ID must be a string + 'ConflictingOptions': BadRequest, // New orders using a combination of order execution options are not supported + 'EndpointMismatch': BadRequest, // The request was submitted to an endpoint different than the one in the payload + 'EndpointNotFound': BadRequest, // No endpoint was specified + 'IneligibleTiming': BadRequest, // Failed to place an auction order for the current auction on this symbol because the timing is not eligible, new orders may only be placed before the auction begins. + 'InsufficientFunds': InsufficientFunds, // The order was rejected because of insufficient funds + 'InvalidJson': BadRequest, // The JSON provided is invalid + 'InvalidNonce': InvalidNonce, // The nonce was not greater than the previously used nonce, or was not present + 'InvalidOrderType': InvalidOrder, // An unknown order type was provided + 'InvalidPrice': InvalidOrder, // For new orders, the price was invalid + 'InvalidQuantity': InvalidOrder, // A negative or otherwise invalid quantity was specified + 'InvalidSide': InvalidOrder, // For new orders, and invalid side was specified + 'InvalidSignature': AuthenticationError, // The signature did not match the expected signature + 'InvalidSymbol': BadRequest, // An invalid symbol was specified + 'InvalidTimestampInPayload': BadRequest, // The JSON payload contained a timestamp parameter with an unsupported value. + 'Maintenance': OnMaintenance, // The system is down for maintenance + 'MarketNotOpen': InvalidOrder, // The order was rejected because the market is not accepting new orders + 'MissingApikeyHeader': AuthenticationError, // The X-GEMINI-APIKEY header was missing + 'MissingOrderField': InvalidOrder, // A required order_id field was not specified + 'MissingRole': AuthenticationError, // The API key used to access this endpoint does not have the required role assigned to it + 'MissingPayloadHeader': AuthenticationError, // The X-GEMINI-PAYLOAD header was missing + 'MissingSignatureHeader': AuthenticationError, // The X-GEMINI-SIGNATURE header was missing + 'NoSSL': AuthenticationError, // You must use HTTPS to access the API + 'OptionsMustBeArray': BadRequest, // The options parameter must be an array. + 'OrderNotFound': OrderNotFound, // The order specified was not found + 'RateLimit': RateLimitExceeded, // Requests were made too frequently. See Rate Limits below. + 'System': ExchangeError, // We are experiencing technical issues + 'UnsupportedOption': BadRequest, // This order execution option is not supported. + }, + 'broad': { + 'The Gemini Exchange is currently undergoing maintenance.': OnMaintenance, // The Gemini Exchange is currently undergoing maintenance. Please check https://status.gemini.com/ for more information. + 'We are investigating technical issues with the Gemini Exchange.': ExchangeNotAvailable, // We are investigating technical issues with the Gemini Exchange. Please check https://status.gemini.com/ for more information. + }, + }, + 'options': { + 'fetchMarketsMethod': 'fetch_markets_from_web', + 'fetchTickerMethod': 'fetchTickerV1', // fetchTickerV1, fetchTickerV2, fetchTickerV1AndV2 + }, + }); + } + + async fetchMarkets (params = {}) { + const method = this.safeValue (this.options, 'fetchMarketsMethod', 'fetch_markets_from_api'); + return await this[method] (params); + } + + async fetchMarketsFromWeb (params = {}) { + const response = await this.webGetRestApi (params); + const sections = response.split ('

Symbols and minimums

'); + const numSections = sections.length; + const error = this.id + ' the ' + this.name + ' API doc HTML markup has changed, breaking the parser of order limits and precision info for ' + this.name + ' markets.'; + if (numSections !== 2) { + throw new NotSupported (error); + } + const tables = sections[1].split ('tbody>'); + const numTables = tables.length; + if (numTables < 2) { + throw new NotSupported (error); + } + const rows = tables[1].split ("\n\n"); // eslint-disable-line quotes + const numRows = rows.length; + if (numRows < 2) { + throw new NotSupported (error); + } + const result = []; + // skip the first element (empty string) + for (let i = 1; i < numRows; i++) { + const row = rows[i]; + const cells = row.split ("\n"); // eslint-disable-line quotes + const numCells = cells.length; + if (numCells < 5) { + throw new NotSupported (error); + } + // [ + // 'btcusd', // currency + // '0.00001 BTC (1e-5)', // min order size + // '0.00000001 BTC (1e-8)', // tick size + // '0.01 USD', // quote currency price increment + // '' + // ] + const marketId = cells[0].replace ('', ''); + // const base = this.safeCurrencyCode (baseId); + const minAmountString = cells[1].replace ('', ''); + const minAmountParts = minAmountString.split (' '); + const minAmount = this.safeFloat (minAmountParts, 0); + const amountPrecisionString = cells[2].replace ('', ''); + const amountPrecisionParts = amountPrecisionString.split (' '); + const amountPrecision = this.safeFloat (amountPrecisionParts, 0); + const idLength = marketId.length - 0; + const quoteId = marketId.slice (idLength - 3, idLength); + const quote = this.safeCurrencyCode (quoteId); + const pricePrecisionString = cells[3].replace ('', ''); + const pricePrecisionParts = pricePrecisionString.split (' '); + const pricePrecision = this.safeFloat (pricePrecisionParts, 0); + const baseId = marketId.replace (quoteId, ''); + const base = this.safeCurrencyCode (baseId); + const symbol = base + '/' + quote; + const active = undefined; + result.push ({ + 'id': marketId, + 'info': row, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': { + 'amount': amountPrecision, + 'price': pricePrecision, + }, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchMarketsFromAPI (params = {}) { + const response = await this.publicGetV1Symbols (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const marketId = response[i]; + const market = marketId; + const idLength = marketId.length - 0; + const baseId = marketId.slice (0, idLength - 3); + const quoteId = marketId.slice (idLength - 3, idLength); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': undefined, + 'price': undefined, + }; + result.push ({ + 'id': marketId, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'active': undefined, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit_bids'] = limit; + request['limit_asks'] = limit; + } + const response = await this.publicGetV1BookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTickerV1 (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetV1PubtickerSymbol (this.extend (request, params)); + // + // { + // "bid":"9117.95", + // "ask":"9117.96", + // "volume":{ + // "BTC":"1615.46861748", + // "USD":"14727307.57545006088", + // "timestamp":1594982700000 + // }, + // "last":"9115.23" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickerV2 (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetV2TickerSymbol (this.extend (request, params)); + // + // { + // "symbol":"BTCUSD", + // "open":"9080.58", + // "high":"9184.53", + // "low":"9063.56", + // "close":"9116.08", + // // Hourly prices descending for past 24 hours + // "changes":["9117.33","9105.69","9106.23","9120.35","9098.57","9114.53","9113.55","9128.01","9113.63","9133.49","9133.49","9137.75","9126.73","9103.91","9119.33","9123.04","9124.44","9117.57","9114.22","9102.33","9076.67","9074.72","9074.97","9092.05"], + // "bid":"9115.86", + // "ask":"9115.87" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickerV1AndV2 (symbol, params = {}) { + const tickerA = await this.fetchTickerV1 (symbol, params); + const tickerB = await this.fetchTickerV2 (symbol, params); + return this.deepExtend (tickerA, { + 'open': tickerB['open'], + 'high': tickerB['high'], + 'low': tickerB['low'], + 'change': tickerB['change'], + 'percentage': tickerB['percentage'], + 'average': tickerB['average'], + 'info': tickerB['info'], + }); + } + + async fetchTicker (symbol, params = {}) { + const method = this.safeValue (this.options, 'fetchTickerMethod', 'fetchTickerV1'); + return await this[method] (symbol, params); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTickers + // + // { + // "pair": "BATUSD", + // "price": "0.20687", + // "percentChange24h": "0.0146" + // } + // + // fetchTickerV1 + // + // { + // "bid":"9117.95", + // "ask":"9117.96", + // "volume":{ + // "BTC":"1615.46861748", + // "USD":"14727307.57545006088", + // "timestamp":1594982700000 + // }, + // "last":"9115.23" + // } + // + // fetchTickerV2 + // + // { + // "symbol":"BTCUSD", + // "open":"9080.58", + // "high":"9184.53", + // "low":"9063.56", + // "close":"9116.08", + // // Hourly prices descending for past 24 hours + // "changes":["9117.33","9105.69","9106.23","9120.35","9098.57","9114.53","9113.55","9128.01","9113.63","9133.49","9133.49","9137.75","9126.73","9103.91","9119.33","9123.04","9124.44","9117.57","9114.22","9102.33","9076.67","9074.72","9074.97","9092.05"], + // "bid":"9115.86", + // "ask":"9115.87" + // } + // + const volume = this.safeValue (ticker, 'volume', {}); + const timestamp = this.safeInteger (volume, 'timestamp'); + let symbol = undefined; + const marketId = this.safeString (ticker, 'pair'); + let baseId = undefined; + let quoteId = undefined; + let base = undefined; + let quote = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const idLength = marketId.length - 0; + if (idLength === 7) { + baseId = marketId.slice (0, 4); + quoteId = marketId.slice (4, 7); + } else { + baseId = marketId.slice (0, 3); + quoteId = marketId.slice (3, 6); + } + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + baseId = market['baseId'].toUpperCase (); + quoteId = market['quoteId'].toUpperCase (); + base = market['base']; + quote = market['quote']; + } + const price = this.safeFloat (ticker, 'price'); + const last = this.safeFloat2 (ticker, 'last', 'close', price); + let percentage = this.safeFloat (ticker, 'percentChange24h'); + let change = undefined; + let open = this.safeFloat (ticker, 'open'); + let average = undefined; + if (last !== undefined) { + if (open !== undefined) { + change = last - open; + if (open !== 0) { + percentage = change / open * 100; + } + average = this.sum (last, open) / 2; + } else if (percentage !== undefined) { + change = last * percentage; + if (open === undefined) { + open = last - change; + } + average = this.sum (last, open) / 2; + } + } + const baseVolume = this.safeFloat (volume, baseId); + const quoteVolume = this.safeFloat (volume, quoteId); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetV1Pricefeed (params); + // + // [ + // { + // "pair": "BATUSD", + // "price": "0.20687", + // "percentChange24h": "0.0146" + // }, + // { + // "pair": "LINKETH", + // "price": "0.018", + // "percentChange24h": "0.0000" + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "timestamp":1601617445, + // "timestampms":1601617445144, + // "tid":14122489752, + // "price":"0.46476", + // "amount":"28.407209", + // "exchange":"gemini", + // "type":"buy" + // } + // + const timestamp = this.safeInteger (trade, 'timestampms'); + const id = this.safeString (trade, 'tid'); + const orderId = this.safeString (trade, 'order_id'); + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + const fee = { + 'cost': this.safeFloat (trade, 'fee_amount'), + 'currency': feeCurrencyCode, + }; + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const type = undefined; + const side = this.safeStringLower (trade, 'type'); + const symbol = this.safeSymbol (undefined, market); + return { + 'id': id, + 'order': orderId, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'cost': cost, + 'amount': amount, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetV1TradesSymbol (this.extend (request, params)); + // + // [ + // { + // "timestamp":1601617445, + // "timestampms":1601617445144, + // "tid":14122489752, + // "price":"0.46476", + // "amount":"28.407209", + // "exchange":"gemini", + // "type":"buy" + // }, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostV1Balances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'amount'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + const timestamp = this.safeInteger (order, 'timestampms'); + const amount = this.safeFloat (order, 'original_amount'); + const remaining = this.safeFloat (order, 'remaining_amount'); + const filled = this.safeFloat (order, 'executed_amount'); + let status = 'closed'; + if (order['is_live']) { + status = 'open'; + } + if (order['is_cancelled']) { + status = 'canceled'; + } + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'avg_execution_price'); + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = filled * average; + } + } + let type = this.safeString (order, 'type'); + if (type === 'exchange limit') { + type = 'limit'; + } else if (type === 'market buy' || type === 'market sell') { + type = 'market'; + } else { + type = order['type']; + } + const fee = undefined; + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const id = this.safeString (order, 'order_id'); + const side = this.safeStringLower (order, 'side'); + const clientOrderId = this.safeString (order, 'client_order_id'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privatePostV1OrderStatus (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostV1Orders (params); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); // throws on non-existent symbol + } + return this.parseOrders (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const nonce = this.nonce (); + const request = { + 'client_order_id': nonce.toString (), + 'symbol': this.marketId (symbol), + 'amount': amount.toString (), + 'price': price.toString (), + 'side': side, + 'type': 'exchange limit', // gemini allows limit orders only + }; + const response = await this.privatePostV1OrderNew (this.extend (request, params)); + return { + 'info': response, + 'id': response['order_id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + return await this.privatePostV1OrderCancel (this.extend (request, params)); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit_trades'] = limit; + } + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.privatePostV1Mytrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + }; + const response = await this.privatePostV1WithdrawCurrency (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'txHash'), + }; + } + + nonce () { + return this.milliseconds (); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit_transfers'] = limit; + } + if (since !== undefined) { + request['timestamp'] = since; + } + const response = await this.privatePostV1Transfers (this.extend (request, params)); + return this.parseTransactions (response); + } + + parseTransaction (transaction, currency = undefined) { + const timestamp = this.safeInteger (transaction, 'timestampms'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const address = this.safeString (transaction, 'destination'); + const type = this.safeStringLower (transaction, 'type'); + let status = 'pending'; + // When deposits show as Advanced or Complete they are available for trading. + if (transaction['status']) { + status = 'ok'; + } + let fee = undefined; + const feeAmount = this.safeFloat (transaction, 'feeAmount'); + if (feeAmount !== undefined) { + fee = { + 'cost': feeAmount, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'eid'), + 'txid': this.safeString (transaction, 'txHash'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': undefined, // or is it defined? + 'type': type, // direction of the transaction, ('deposit' | 'withdraw') + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = this.extend ({ + 'request': url, + 'nonce': nonce, + }, query); + let payload = this.json (request); + payload = this.stringToBase64 (payload); + const signature = this.hmac (payload, this.encode (this.secret), 'sha384'); + headers = { + 'Content-Type': 'text/plain', + 'X-GEMINI-APIKEY': this.apiKey, + 'X-GEMINI-PAYLOAD': this.decode (payload), + 'X-GEMINI-SIGNATURE': signature, + }; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + if (typeof body === 'string') { + const feedback = this.id + ' ' + body; + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + } + return; // fallback to default error handler + } + // + // { + // "result": "error", + // "reason": "BadNonce", + // "message": "Out-of-sequence nonce <1234> precedes previously used nonce <2345>" + // } + // + const result = this.safeString (response, 'result'); + if (result === 'error') { + const reason = this.safeString (response, 'reason'); + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions['exact'], reason, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostV1DepositCurrencyNewAddress (this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'timeframe': this.timeframes[timeframe], + 'symbol': market['id'], + }; + const response = await this.publicGetV2CandlesSymbolTimeframe (this.extend (request, params)); + // + // [ + // [1591515000000,0.02509,0.02509,0.02509,0.02509,0], + // [1591514700000,0.02503,0.02509,0.02503,0.02509,44.6405], + // [1591514400000,0.02503,0.02503,0.02503,0.02503,0], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],88:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, OrderNotFound, ArgumentsRequired, BadSymbol, BadRequest, NullResponse, InvalidOrder, BadResponse, NotSupported, ExchangeNotAvailable, RequestTimeout, RateLimitExceeded, PermissionDenied, InsufficientFunds, InvalidAddress } = require ('./base/errors'); +const { TICK_SIZE, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class hbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'hbtc', + 'name': 'HBTC', + 'countries': [ 'CN' ], + 'rateLimit': 2000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchBidAsk': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingLimits': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/80134449-70663300-85a7-11ea-8942-e204cdeaab5d.jpg', // 交易所LOGO + 'api': { + 'quote': 'https://api.hbtc.com/openapi/quote', // 市场API数据端点 + 'contract': 'https://api.hbtc.com/openapi/contract', // 合约API数据端点 + 'option': 'https://api.hbtc.com/openapi/option', // 合约API数据端点 + 'public': 'https://api.hbtc.com/openapi', // 公共API数据端点 + 'private': 'https://api.hbtc.com/openapi', // 私有API数据端点 + 'zendesk': 'https://hbtc.zendesk.com/hc/en-us', + }, + 'www': 'https://www.hbtc.com', // 公司主页 + 'referral': 'https://www.hbtc.com/register/O2S8NS', // 邀请链接 + 'doc': 'https://github.com/bhexopen/BHEX-OpenApi/tree/master/doc', // openapi文档地址 + 'fees': 'https://hbtc.zendesk.com/hc/zh-cn/articles/360009274694', // 费率介绍 + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'time', + 'brokerInfo', // 查询当前broker交易规则和symbol信息 + 'getOptions', + ], + }, + 'quote': { + 'get': [ + 'depth', // 获取深度 + 'depth/merged', + 'trades', // 获取当前最新成交 + 'klines', // 获取K线数据 + 'ticker/24hr', // 获取24小时价格变化数据 + 'ticker/price', + 'ticker/bookTicker', + 'contract/index', // 获取合约标的指数价格 + 'contract/depth', // 获取合约深度 + 'contract/depth/merged', + 'contract/trades', // 获取合约最近成交, + 'contract/klines', // 获取合约的K线数据 + 'contract/ticker/24hr', + 'option/index', + 'option/depth', + 'option/depth/merged', + 'option/trades', + 'option/klines', + 'option/ticker/24hr', + ], + }, + 'contract': { + 'get': [ + // public + 'insurance', + 'fundingRate', // 获取资金费率信息 + // private + 'openOrders', // 查询合约当前委托 + 'historyOrders', // 查询合约历史委托 + 'getOrder', // 查询合约订单详情 + 'myTrades', // 查询合约历史成交 + 'positions', // 查询合约当前持仓 + 'account', // 查询合约账户信息 + ], + 'post': [ + 'order', // 创建合约订单 + 'modifyMargin', // 修改保证金 + ], + 'delete': [ + 'order/cancel', // 取消合约订单 + 'order/batchCancel', + ], + }, + 'option': { + 'get': [ + 'openOrders', + 'positions', + 'historyOrders', + // 'getOrder', + 'myTrades', + 'settlements', + 'account', + ], + 'post': [ + 'order', + ], + 'delete': [ + 'order/cancel', + ], + }, + 'private': { + 'get': [ + 'order', // 查询订单 + 'openOrders', // 查询当前委托 + 'historyOrders', // 查询历史委托 + 'account', // 获取当前账户信息 + 'myTrades', // 查询历史成交 + 'depositOrders', + 'withdrawalOrders', + 'withdraw/detail', + 'balance_flow', + ], + 'post': [ + 'order', // 创建新订单 + 'order/test', + 'userDataStream', + 'subAccount/query', + 'transfer', + 'user/transfer', + 'withdraw', + ], + 'put': [ + 'userDataStream', + ], + 'delete': [ + 'order', // 取消订单 + 'userDataStream', + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.001, + 'taker': 0.001, + }, + }, + 'exceptions': { + 'exact': { + // general server or network errors + '-1000': ExchangeError, // An unknown error occured while processing the request + '-1001': ExchangeError, // Internal error, unable to process your request. Please try again + '-1002': AuthenticationError, // You are not authorized to execute this request. Request need API Key included in. We suggest that API Key be included in any request + '-1003': RateLimitExceeded, // Too many requests, please use the websocket for live updates + '-1004': BadRequest, + '-1005': PermissionDenied, + '-1006': BadResponse, // An unexpected response was received from the message bus. Execution status unknown. OPEN API server find some exception in execute request.Please report to Customer service + '-1007': RequestTimeout, // Timeout waiting for response from backend server. Send status unknown, execution status unknown + '-1014': InvalidOrder, // Unsupported order combination + '-1015': RateLimitExceeded, // Reach the rate limit.Please slow down your request speed + '-1016': ExchangeNotAvailable, // This service is no longer available + '-1020': NotSupported, // This operation is not supported + '-1021': BadRequest, // Timestamp for this request is outside of the recvWindow + '-1022': AuthenticationError, // Signature for this request is not valid + // request issues + '-1100': BadRequest, // Illegal characters found in a parameter + '-1101': BadRequest, // Too many parameters sent for this endpoint + '-1102': BadRequest, // A mandatory parameter was not sent, was empty/null, or malformed + '-1103': BadRequest, // An unknown parameter was sent + '-1104': BadRequest, // Not all sent parameters were read + '-1105': BadRequest, // A parameter was empty + '-1106': BadRequest, // A parameter was sent when not required + '-1111': BadRequest, // Precision is over the maximum defined for this asset + '-1112': NullResponse, // No orders on book for symbol + '-1114': InvalidOrder, // TimeInForce parameter sent when not required + '-1115': InvalidOrder, // Invalid timeInForce + '-1116': InvalidOrder, // Invalid orderType + '-1117': InvalidOrder, // Invalid side + '-1118': InvalidOrder, // New client order ID was empty + '-1119': InvalidOrder, // Original client order ID was empty + '-1120': BadRequest, // Invalid interval + '-1121': BadSymbol, // Invalid symbol + '-1125': AuthenticationError, // This listenKey does not exist + '-1127': BadRequest, // Lookup interval is too big + '-1128': BadRequest, // Combination of optional parameters invalid + '-1130': BadRequest, // Invalid data sent for a parameter + '-1131': InsufficientFunds, + '-1132': InvalidOrder, // Order price too high + '-1133': InvalidOrder, // Order price lower than the minimum,please check general broker info + '-1134': InvalidOrder, // Order price decimal too long,please check general broker info + '-1135': InvalidOrder, // Order quantity too large + '-1136': InvalidOrder, // Order quantity lower than the minimum + '-1137': InvalidOrder, // Order quantity decimal too long + '-1138': InvalidOrder, // Order price exceeds permissible range + '-1139': InvalidOrder, // Order has been filled + '-1140': InvalidOrder, // Transaction amount lower than the minimum + '-1141': InvalidOrder, // Duplicate clientOrderId + '-1142': InvalidOrder, // Order has been canceled + '-1143': OrderNotFound, // Cannot be found on order book + '-1144': InvalidOrder, // Order has been locked + '-1145': InvalidOrder, // This order type does not support cancellation + '-1146': RequestTimeout, // Order creation timeout + '-1147': RequestTimeout, // Order cancellation timeout + '-1149': InvalidOrder, // Create order failed + '-1187': InvalidAddress, // Withdrawal address not in whitelist + '-2010': InvalidOrder, // NEW_ORDER_REJECTED + '-2011': InvalidOrder, // CANCEL_REJECTED + '-2013': OrderNotFound, // Order does not exist + '-2014': AuthenticationError, // API-key format invalid + '-2015': AuthenticationError, // Invalid API-key, IP, or permissions for action + '-2016': ExchangeError, // No trading window could be found for the symbol. Try ticker/24hrs instead + }, + }, + // exchange-specific options + 'options': { + 'fetchTickers': { + 'method': 'quoteGetTicker24hr', + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "serverTime": 1527777538000 + // } + // + return this.safeInteger (response, 'serverTime'); + } + + parseMarket (market, type = 'spot') { + const filters = this.safeValue (market, 'filters', []); + const id = this.safeString (market, 'symbol'); + let baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + let base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + let spot = true; + let future = false; + let option = false; + let inverse = false; + if (type === 'future') { + symbol = id; + spot = false; + future = true; + inverse = this.safeValue (market, 'inverse', false); + baseId = this.safeString (market, 'underlying'); + base = this.safeCurrencyCode (baseId); + } else if (type === 'option') { + symbol = id; + spot = false; + option = true; + } + let amountMin = undefined; + let amountMax = undefined; + let priceMin = undefined; + let priceMax = undefined; + let costMin = undefined; + for (let j = 0; j < filters.length; j++) { + const filter = filters[j]; + const filterType = this.safeString (filter, 'filterType'); + if (filterType === 'LOT_SIZE') { + amountMin = this.safeFloat (filter, 'minQty'); + amountMax = this.safeFloat (filter, 'maxQty'); + } + if (filterType === 'PRICE_FILTER') { + priceMin = this.safeFloat (filter, 'minPrice'); + priceMax = this.safeFloat (filter, 'maxPrice'); + } + if (filterType === 'MIN_NOTIONAL') { + costMin = this.safeFloat (filter, 'minNotional'); + } + } + if ((costMin === undefined) && (amountMin !== undefined) && (priceMin !== undefined)) { + costMin = amountMin * priceMin; + } + const precision = { + 'price': this.safeFloat2 (market, 'quotePrecision', 'quoteAssetPrecision'), + 'amount': this.safeFloat (market, 'baseAssetPrecision'), + }; + const limits = { + 'amount': { + 'min': amountMin, + 'max': amountMax, + }, + 'price': { + 'min': priceMin, + 'max': priceMax, + }, + 'cost': { + 'min': costMin, + 'max': undefined, + }, + }; + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'type': type, + 'spot': spot, + 'future': future, + 'option': option, + 'inverse': inverse, + 'precision': precision, + 'limits': limits, + 'info': market, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetBrokerInfo (params); + // + // { + // "timezone":"UTC", + // "serverTime":"1588015885118", + // "brokerFilters":[], + // "symbols":[ + // { + // "filters":[ + // {"minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"}, + // {"minQty":"0.0005","maxQty":"100000.00000000","stepSize":"0.000001","filterType":"LOT_SIZE"}, + // {"minNotional":"5","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTCUSDT", + // "symbolName":"BTCUSDT", + // "status":"TRADING", + // "baseAsset":"BTC", + // "baseAssetPrecision":"0.000001", + // "quoteAsset":"USDT", + // "quotePrecision":"0.01", + // "icebergAllowed":false + // }, + // ], + // "options":[ + // { + // "filters":[ + // {"minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"}, + // {"minQty":"0.01","maxQty":"100000.00000000","stepSize":"0.001","filterType":"LOT_SIZE"}, + // {"minNotional":"1","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC0501CS8500", + // "symbolName":"BTC0501CS8500", + // "status":"TRADING", + // "baseAsset":"BTC0501CS8500", + // "baseAssetPrecision":"0.001", + // "quoteAsset":"BUSDT", + // "quotePrecision":"0.01", + // "icebergAllowed":false + // }, + // ], + // "contracts":[ + // { + // "filters":[ + // {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"}, + // {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"}, + // {"minNotional":"0.000001","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC-PERP-REV", + // "symbolName":"BTC-PERP-REV", + // "status":"TRADING", + // "baseAsset":"BTC-PERP-REV", + // "baseAssetPrecision":"1", + // "quoteAsset":"USDT", + // "quoteAssetPrecision":"0.1", + // "icebergAllowed":false, + // "inverse":true, + // "index":"BTCUSDT", + // "marginToken":"TBTC", + // "marginPrecision":"0.00000001", + // "contractMultiplier":"1.0", + // "underlying":"TBTC", + // "riskLimits":[ + // {"riskLimitId":"200000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"}, + // {"riskLimitId":"200000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"}, + // {"riskLimitId":"200000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"}, + // {"riskLimitId":"200000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"} + // ] + // }, + // { + // "filters":[ + // {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"}, + // {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"}, + // {"minNotional":"0.000001","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC-SWAP", + // "symbolName":"BTC-SWAP", + // "status":"TRADING", + // "baseAsset":"BTC-SWAP", + // "baseAssetPrecision":"1", + // "quoteAsset":"USDT", + // "quoteAssetPrecision":"0.1", + // "icebergAllowed":false, + // "inverse":true, + // "index":"BTCUSDT", + // "marginToken":"BTC", + // "marginPrecision":"0.00000001", + // "contractMultiplier":"1.0", + // "underlying":"BTC", + // "riskLimits":[ + // {"riskLimitId":"500000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"}, + // {"riskLimitId":"500000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"}, + // {"riskLimitId":"500000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"}, + // {"riskLimitId":"500000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"} + // ] + // }, + // { + // "filters":[ + // {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"}, + // {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"}, + // {"minNotional":"0.000000001","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC-PERP-BUSDT", + // "symbolName":"BTC-PERP-BUSDT", + // "status":"TRADING", + // "baseAsset":"BTC-PERP-BUSDT", + // "baseAssetPrecision":"1", + // "quoteAsset":"BUSDT", + // "quoteAssetPrecision":"0.1", + // "icebergAllowed":false, + // "inverse":false, + // "index":"BTCUSDT", + // "marginToken":"BUSDT", + // "marginPrecision":"0.0001", + // "contractMultiplier":"0.0001", + // "underlying":"TBTC", + // "riskLimits":[ + // {"riskLimitId":"600000132","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"}, + // {"riskLimitId":"600000133","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"}, + // {"riskLimitId":"600000134","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"}, + // {"riskLimitId":"600000135","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"} + // ] + // }, + // ] + // } + // + const result = []; + const symbols = this.safeValue (response, 'symbols', []); + for (let i = 0; i < symbols.length; i++) { + const market = this.parseMarket (symbols[i], 'spot'); + result.push (market); + } + const options = this.safeValue (response, 'options', []); + for (let i = 0; i < options.length; i++) { + const market = this.parseMarket (options[i], 'option'); + result.push (market); + } + const contracts = this.safeValue (response, 'contracts', []); + for (let i = 0; i < contracts.length; i++) { + const market = this.parseMarket (contracts[i], 'future'); + result.push (market); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 40, max 40 + } + const response = await this.quoteGetDepth (this.extend (request, params)); + // + // { + // "time":1588068913453, + // "bids":[ + // ["0.025278","0.0202"], + // ["0.025277","16.1132"], + // ["0.025276","7.9056"], + // ] + // "asks":[ + // ["0.025302","5.9999"], + // ["0.025303","34.9151"], + // ["0.025304","92.391"], + // ] + // } + // + const timestamp = this.safeInteger (response, 'time'); + return this.parseOrderBook (response, timestamp); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.quoteGetTicker24hr (this.extend (request, params)); + // + // { + // "time":1588069860794, + // "symbol":"BNB0501PS16", + // "bestBidPrice":"0.2129", + // "bestAskPrice":"0.3163", + // "volume":"33547", + // "quoteVolume":"10801.987", + // "lastPrice":"0.2625", + // "highPrice":"0.3918", + // "lowPrice":"0.2625", + // "openPrice":"0.362", + // } + // + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchBidAsk (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.quoteGetTickerBookTicker (this.extend (request, params)); + // + // { + // "symbol": "LTCBTC", + // "bidPrice": "4.00000000", + // "bidQty": "431.00000000", + // "askPrice": "4.00000200", + // "askQty": "9.00000000" + // } + // + return this.parseTicker (response, market); + } + + async fetchBidsAsks (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.quoteGetTickerBookTicker (params); + // + // [ + // { + // "symbol": "LTCBTC", + // "bidPrice": "4.00000000", + // "bidQty": "431.00000000", + // "askPrice": "4.00000200", + // "askQty": "9.00000000" + // }, + // { + // "symbol": "ETHBTC", + // "bidPrice": "0.07946700", + // "bidQty": "9.00000000", + // "askPrice": "100000.00000000", + // "askQty": "1000.00000000" + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchTickers', {}); + const defaultMethod = this.safeString (options, 'method', 'quoteGetTicker24hr'); + const defaultType = this.safeString (options, 'type', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = defaultMethod; + if (type === 'future') { + method = 'quoteGetContractTicker24hr'; + } else if (type === 'option') { + method = 'quoteGetOptionTicker24hr'; + } + const response = await this[method] (query); + // + // [ + // { + // "time": 1538725500422, + // "symbol": "ETHBTC", + // "lastPrice": "4.00000200", + // "openPrice": "99.00000000", + // "highPrice": "100.00000000", + // "lowPrice": "0.10000000", + // "volume": "8913.30000000" + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchBalance', {}); + const defaultType = this.safeString (options, 'type', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = 'privateGetAccount'; + if (type === 'future') { + method = 'contractGetAccount'; + } else if (type === 'option') { + method = 'optionGetAccount'; + } + const response = await this[method] (query); + // + // spot + // + // { + // 'balances': [ + // { + // 'asset': 'ALGO', + // 'free': '0', + // 'locked': '0' + // }, + // { + // 'asset': 'BHT', + // 'free': '0', + // 'locked': '0' + // } + // ] + // } + // + // contract + // + // { + // "BUSDT":{ + // "total":"1000", + // "availableMargin":"1000", + // "positionMargin":"0", + // "orderMargin":"0", + // "tokenId":"BUSDT" + // }, + // "TBTC":{ + // "total":"0.5", + // "availableMargin":"0.5", + // "positionMargin":"0", + // "orderMargin":"0", + // "tokenId":"TBTC" + // } + // } + // + // option + // + // { + // "optionAsset":"", + // "balances":[ + // { + // "tokenName":"USDT", + // "free":"0.0", + // "locked":"0.0", + // "margin":"0.0" + // }, + // { + // "tokenName":"BUSDT", + // "free":"0.0", + // "locked":"0.0", + // "margin":"0.0" + // } + // ] + // } + // + const balances = this.safeValue (response, 'balances'); + const result = { 'info': response }; + if (balances !== undefined) { + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString2 (balance, 'asset', 'tokenName'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + } else { + const currencyIds = Object.keys (response); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = response[currencyId]; + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableMargin'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchTrades (symbol, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.quoteGetTrades (this.extend (request, params)); + // + // [ + // {"price":"0.025344","time":1588084082060,"qty":"1","isBuyerMaker":false}, + // {"price":"0.02535","time":1588084086021,"qty":"0.553","isBuyerMaker":true}, + // {"price":"0.025348","time":1588084097037,"qty":"1","isBuyerMaker":false}, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1587906000000, // open time + // "0.1761", // open + // "0.1761", // high + // "0.1761", // low + // "0.1761", // close + // "0", // base volume + // 0, // close time + // "0", // quote volume + // 0, // number of trades + // "0", // taker buy base asset volume + // "0" // taker buy quote asset volume + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 500 + } + const response = await this.quoteGetKlines (this.extend (request, params)); + // + // [ + // [1587906000000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"], + // [1587906180000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"], + // [1587906360000,"0.1761","0.1848","0.1761","0.1848","53",0,"9.7944",1,"0","0"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // if only fromId is set,it will get orders < that fromId in descending order + // if only toId is set, it will get orders > that toId in ascending order + // if fromId is set and toId is set, it will get orders < that fromId and > that toId in descending order + // if fromId is not set and toId it not set, most recent order are returned in descending order + // 'fromId': '43287482374', + // 'toId': '43287482374', + // 'endTime': this.milliseconds (), // optional, spot only + }; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchMyTrades', {}); + const fetchMyTradesType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', fetchMyTradesType); + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + type = market['type']; + } + const query = this.omit (params, 'type'); + if (limit !== undefined) { + // spot default 500, max 1000 + // futures and options default 20, max 1000 + request['limit'] = limit; + } + let method = 'privateGetMyTrades'; + if (type === 'future') { + method = 'contractGetMyTrades'; + } else { + if (type === 'option') { + method = 'optionGetMyTrades'; + } else { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument for ' + type + ' markets'); + } + const market = this.market (symbol); + request['symbol'] = market['id']; + // spot only? + if (since !== undefined) { + request['startTime'] = since; + } + } + } + if (since !== undefined) { + request['startTime'] = since; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // "id":"616384027512920576", + // "symbol":"TBTCBUSDT", + // "orderId":"616384027202542080", + // "matchOrderId":"605124954767266560", + // "price":"6826.06", + // "qty":"0.1", + // "commission":"0.682606", + // "commissionAsset":"BUSDT", + // "time":"1588214701982", + // "isBuyer":false, + // "isMaker":false, + // "fee":{ + // "feeTokenId":"BUSDT", + // "feeTokenName":"BUSDT", + // "fee":"0.682606" + // } + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderSide = side.toUpperCase (); + const orderType = type.toUpperCase (); + const request = { + 'symbol': market['id'], + // BUY or SELL for spot and options + 'side': orderSide, + // GTC, FOK, IOC for spot and options + // GTC, FOK, IOC, LIMIT_MAKER for futures + // 'timeInForce': 'GTC', + }; + let query = params; + let method = 'privatePostOrder'; + if (market['type'] === 'future') { + if ((orderSide !== 'BUY_OPEN') && (orderSide !== 'SELL_OPEN') && (orderSide !== 'BUY_CLOSE') && (orderSide !== 'SELL_CLOSE')) { + throw new NotSupported (this.id + ' createOrder() does not support order side ' + side + ' for ' + market['type'] + ' markets, only BUY_OPEN, SELL_OPEN, BUY_CLOSE and SELL_CLOSE are supported'); + } + if ((orderType !== 'LIMIT') && (orderType !== 'STOP')) { + throw new NotSupported (this.id + ' createOrder() does not support order type ' + type + ' for ' + market['type'] + ' markets, only LIMIT and STOP are supported'); + } + const clientOrderId = this.safeValue (params, 'clientOrderId'); + if (clientOrderId === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a clientOrderId parameter for ' + market['type'] + ' markets, supply clientOrderId in the params argument'); + } + const leverage = this.safeValue (params, 'leverage'); + if (leverage === undefined && (orderSide === 'BUY_OPEN' || orderSide === 'SELL_OPEN')) { + throw new NotSupported (this.id + ' createOrder() requires a leverage parameter for ' + market['type'] + ' markets if orderSide is BUY_OPEN or SELL_OPEN'); + } + method = 'contractPostOrder'; + const priceType = this.safeString (params, 'priceType'); + if (priceType === undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } else { + request['priceType'] = priceType; + if (priceType === 'INPUT') { + request['price'] = this.priceToPrecision (symbol, price); + } + } + request['orderType'] = type.toUpperCase (); // LIMIT, STOP + request['quantity'] = this.amountToPrecision (symbol, amount); + // request['leverage'] = 1; // not required for closing orders + request['leverage'] = leverage; + request['clientOrderId'] = clientOrderId; + // optional + // request['priceType'] = 'INPUT', // INPUT, OPPONENT, QUEUE, OVER, MARKET + // request['triggerPrice'] = 123.45; + } else { + if (market['type'] === 'option') { + method = 'optionPostOrder'; + } + const newClientOrderId = this.safeValue2 (params, 'clientOrderId', 'newClientOrderId'); + if (newClientOrderId !== undefined) { + request['newClientOrderId'] = newClientOrderId; + } + request['type'] = orderType; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + request['quantity'] = this.amountToPrecision (symbol, amount); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + amount = amount * price; + } else { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument (the exchange-specific behaviour)"); + } + } + const precision = market['precision']['price']; + request['quantity'] = this.decimalToPrecision (amount, TRUNCATE, precision, this.precisionMode); + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + } + } + query = this.omit (query, [ 'clientOrderId', 'newClientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // "symbol":"TBTCBUSDT", + // "orderId":"616376654496877056", + // "clientOrderId":"158821382304516955", + // "transactTime":"1588213823080", + // "price":"0", + // "origQty":"1000", + // "executedQty":"0", + // "status":"NEW", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"BUY" + // } + // + // contract + // + // { + // 'time': '1570759718825', + // 'updateTime': '0', + // 'orderId': '469961015902208000', + // 'clientOrderId': '6423344174', + // 'symbol': 'BTC-PERP-REV', + // 'price': '8200', + // 'leverage': '12.08', + // 'origQty': '5', + // 'executedQty': '0', + // 'avgPrice': '0', + // 'marginLocked': '0.00005047', + // 'orderType': 'LIMIT', + // 'side': 'BUY_OPEN', + // 'fees': [], + // 'timeInForce': 'GTC', + // 'status': 'NEW', + // 'priceType': 'INPUT' + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + const request = {}; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'cancelOrder', {}); + const cancelOrderType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', cancelOrderType); + let query = this.omit (params, 'type'); + if (clientOrderId !== undefined) { + request['origClientOrderId'] = clientOrderId; + query = this.omit (query, [ 'origClientOrderId', 'clientOrderId' ]); + } else { + request['orderId'] = id; + } + let method = 'privateDeleteOrder'; + const orderType = this.safeString (query, 'orderType'); + if (orderType !== undefined) { + type = 'future'; + } + if (type === 'future') { + method = 'contractDeleteOrderCancel'; + if (orderType === undefined) { + throw new ArgumentsRequired (this.id + " cancelOrder() requires an orderType parameter, pass the { 'orderType': 'LIMIT' } or { 'orderType': 'STOP' } in params argument"); + } + request['orderType'] = orderType; + } else { + if (type === 'option') { + method = 'optionDeleteOrderCancel'; + } + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // 'exchangeId': '301', + // 'symbol': 'BHTUSDT', + // 'clientOrderId': '0', + // 'orderId': '499890200602846976', + // 'status': 'CANCELED' + // } + // + // futures + // + // { + // "time":"1588353669383", + // "updateTime":"0", + // "orderId":"617549770304599296", + // "clientOrderId":"test-001", + // "symbol":"BTC-PERP-REV", + // "price":"10000", + // "leverage":"1", + // "origQty":"100", + // "executedQty":"0", + // "avgPrice":"0", + // "marginLocked":"0", + // "orderType":"LIMIT", + // "side":"SELL_OPEN", + // "fees":[], + // "timeInForce":"GTC", + // "status":"CANCELED", + // "priceType":"INPUT", + // } + // + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // if orderId is set, it will get orders < that orderId otherwise most recent orders are returned + // 'orderId': '43287482374', + }; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + const fetchOpenOrdersType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', fetchOpenOrdersType); + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + type = market['type']; + } + const query = this.omit (params, 'type'); + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + let method = 'privateGetOpenOrders'; + if (type === 'future') { + method = 'contractGetOpenOrders'; + } else if (type === 'option') { + method = 'optionGetOpenOrders'; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // 'orderId': '499902955766523648', + // 'clientOrderId': '157432907618453', + // 'exchangeId': '301', + // 'symbol': 'BHTUSDT', + // 'price': '0.01', + // 'origQty': '50', + // 'executedQty': '0', + // 'cummulativeQuoteQty': '0', + // 'avgPrice': '0', + // 'status': 'NEW', + // 'timeInForce': 'GTC', + // 'type': 'LIMIT', + // 'side': 'BUY', + // 'stopPrice': '0.0', + // 'icebergQty': '0.0', + // 'time': '1574329076202', + // 'updateTime': '0', + // 'isWorking': true + // } + // ] + // + // futures + // + // [ + // { + // "time":"1588353669383", + // "updateTime":"0", + // "orderId":"617549770304599296", + // "clientOrderId":"test-001", + // "symbol":"BTC-PERP-REV", + // "price":"10000", + // "leverage":"1", + // "origQty":"100", + // "executedQty":"0", + // "avgPrice":"0", + // "marginLocked":"0.01", + // "orderType":"LIMIT", + // "side":"SELL_OPEN", + // "fees":[], + // "timeInForce":"GTC", + // "status":"NEW", + // "priceType":"INPUT" + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // if orderId is set, it will get orders < that orderId otherwise most recent orders are returned + // 'orderId': '43287482374', + // 'endTime': this.milliseconds (), // optional + }; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + const fetchClosedOrdersType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', fetchClosedOrdersType); + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + type = market['type']; + } + const query = this.omit (params, 'type'); + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + if (since !== undefined) { + request['startTime'] = since; + } + let method = 'privateGetHistoryOrders'; + if (type === 'future') { + method = 'contractGetHistoryOrders'; + } else if (type === 'option') { + method = 'optionGetHistoryOrders'; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // "orderId":"616384027202542080", + // "clientOrderId":"158821470194414688", + // "exchangeId":"301", + // "symbol":"TBTCBUSDT", + // "price":"0", + // "origQty":"0.1", + // "executedQty":"0.1", + // "cummulativeQuoteQty":"682.606", + // "avgPrice":"6826.06", + // "status":"FILLED", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"SELL", + // "stopPrice":"0.0", + // "icebergQty":"0.0", + // "time":"1588214701974", + // "updateTime":"0", + // "isWorking":true + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + const request = {}; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchOrder', {}); + const fetchOrderType = this.safeString (options, 'type', defaultType); + const type = this.safeString (params, 'type', fetchOrderType); + let query = this.omit (params, 'type'); + if (clientOrderId !== undefined) { + request['origClientOrderId'] = clientOrderId; + query = this.omit (query, [ 'origClientOrderId', 'clientOrderId' ]); + } else { + request['orderId'] = id; + } + let method = 'privateGetOrder'; + if (type === 'future') { + method = 'contractGetGetOrder'; + } else if (type === 'option') { + method = 'optionGetGetOrder'; + } + const response = await this[method] (this.extend (request, query)); + return this.parseOrder (response); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + // 'fromId': 'string', // if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned + }; + if (code !== undefined) { + currency = this.currency (code); + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetDepositOrders (this.extend (request, params)); + // + // [ + // { + // 'time': '1565769575929', + // 'orderId': '428100569859739648', + // 'token': 'USDT', + // 'address': '', + // 'addressTag': '', + // 'fromAddress': '', + // 'fromAddressTag': '', + // 'quantity': '1100', + // }, + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + // 'fromId': 'string', // if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned + }; + if (code !== undefined) { + currency = this.currency (code); + request['token'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetWithdrawalOrders (this.extend (request, params)); + // + // [ + // { + // "time":"1536232111669", + // "orderId":"90161227158286336", + // "accountId":"517256161325920", + // "tokenId":"BHC", + // "tokenName":"BHC", + // "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209", + // "addressExt":"address tag", + // "quantity":"14", // Withdrawal qty + // "arriveQuantity":"14", // Arrived qty + // "statusCode":"PROCESSING_STATUS", + // "status":3, + // "txid":"", + // "txidUrl":"", + // "walletHandleTime":"1536232111669", + // "feeTokenId":"BHC", + // "feeTokenName":"BHC", + // "fee":"0.1", + // "requiredConfirmNum":0, // Required confirmations + // "confirmNum":0, // Confirmations + // "kernelId":"", // BEAM and GRIN only + // "isInternalTransfer": false // True if this transfer is internal + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const clientOrderId = this.safeString (params, 'clientOrderId', this.uuid ()); + const request = { + 'clientOrderId': clientOrderId, + 'tokenId': currency['id'], + 'address': address, // the withdrawal address must be in current tag list in your PC/APP client + 'withdrawQuantity': amount, + // 'chainType': 'OMNI', // OMNI, ERC20, TRC20 + }; + if (tag !== undefined) { + request['addressExt'] = tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + // + // { + // "status": 0, + // "success": true, + // "needBrokerAudit": false, // Whether this request needs broker auit + // "orderId": "423885103582776064" // Id for successful withdrawal + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'orderId'), + }; + } + + async fetchAccounts (params = {}) { + const response = await this.privatePostSubAccountQuery (params); + // + // [ + // { + // "accountId": "122216245228131", + // "accountName": "createSubAccountByCurl", // sub-account name + // "accountType": 1, // 1 token trading, 2 options, 3 futures + // "accountIndex": 1, // 0 main account, 1 sub-account + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const account = response[i]; + const accountId = this.safeString (account, 'accountId'); + const accountType = this.safeString (account, 'accountType'); + let type = accountType; + if (accountType === '1') { + type = 'spot'; + } else if (accountType === '2') { + type = 'option'; + } else if (accountType === '3') { + type = 'future'; + } + result.push ({ + 'id': accountId, + 'type': type, + 'currency': undefined, + 'info': account, + }); + } + return result; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'accountType': 1, // spot 1, options 2, futures 3 + 'accountIndex': 0, // main 0, sub-account 1 + 'fromFlowId': '', // flowId to start from + 'endFlowId': '', // flowId to end with + 'endTime': 1588450533040, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['tokenId'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 500 + } + const response = await this.privateGetBalanceFlow (this.extend (request, params)); + // + // [ + // { + // "id": "539870570957903104", + // "accountId": "122216245228131", + // "tokenId": "BTC", + // "tokenName": "BTC", + // "flowTypeValue": 51, + // "flowType": "USER_ACCOUNT_TRANSFER", + // "flowName": "Transfer", + // "change": "-12.5", + // "total": "379.624059937852365", // after change + // "created": "1579093587214" + // }, + // { + // "id": "536072393645448960", + // "accountId": "122216245228131", + // "tokenId": "USDT", + // "tokenName": "USDT", + // "flowTypeValue": 7, + // "flowType": "AIRDROP", + // "flowName": "Airdrop", + // "change": "-2000", + // "total": "918662.0917630848", + // "created": "1578640809195" + // } + // ] + // + return this.parseLedger (response, currency, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "id": "539870570957903104", + // "accountId": "122216245228131", + // "tokenId": "BTC", + // "tokenName": "BTC", + // "flowTypeValue": 51, + // "flowType": "USER_ACCOUNT_TRANSFER", + // "flowName": "Transfer", + // "change": "-12.5", + // "total": "379.624059937852365", // after change + // "created": "1579093587214" + // } + // + // { + // "id": "536072393645448960", + // "accountId": "122216245228131", + // "tokenId": "USDT", + // "tokenName": "USDT", + // "flowTypeValue": 7, + // "flowType": "AIRDROP", + // "flowName": "Airdrop", + // "change": "-2000", + // "total": "918662.0917630848", + // "created": "1578640809195" + // } + // + const currencyId = this.safeString (item, 'tokenId'); + const code = this.safeCurrencyCode (currencyId, currency); + const amount = this.safeFloat (item, 'change'); + const after = this.safeFloat (item, 'total'); + const direction = (amount < 0) ? 'out' : 'in'; + let before = undefined; + if (after !== undefined && amount !== undefined) { + const difference = (direction === 'out') ? amount : -amount; + before = this.sum (after, difference); + } + const timestamp = this.safeInteger (item, 'created'); + const type = this.parseLedgerEntryType (this.safeString (item, 'flowType')); + const id = this.safeString (item, 'id'); + const account = this.safeString (item, 'accountId'); + return { + 'id': id, + 'currency': code, + 'account': account, + 'referenceAccount': undefined, + 'referenceId': undefined, + 'status': undefined, + 'amount': amount, + 'before': before, + 'after': after, + 'fee': undefined, + 'direction': direction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'type': type, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'TRADE': 'trade', + 'FEE': 'fee', + 'TRANSFER': 'transfer', + 'DEPOSIT': 'transaction', + 'MAKER_REWARD': 'rebate', + 'PNL': 'pnl', + 'SETTLEMENT': 'settlement', + 'LIQUIDATION': 'liquidation', + 'FUNDING_SETTLEMENT': 'settlement', + 'USER_ACCOUNT_TRANSFER': 'transfer', + 'OTC_BUY_COIN': 'trade', + 'OTC_SELL_COIN': 'trade', + 'OTC_FEE': 'fee', + 'OTC_TRADE': 'trade', + 'ACTIVITY_AWARD': 'referral', + 'INVITATION_REFERRAL_BONUS': 'referral', + 'REGISTER_BONUS': 'referral', + 'AIRDROP': 'airdrop', + 'MINE_REWARD': 'reward', + }; + return this.safeString (types, type, type); + } + + parseTransactionStatus (status) { + const statuses = { + 'BROKER_AUDITING_STATUS': 'pending', + 'BROKER_REJECT_STATUS': 'failed', + 'AUDITING_STATUS': 'pending', + 'AUDIT_REJECT_STATUS': 'failed', + 'PROCESSING_STATUS': 'pending', + 'WITHDRAWAL_SUCCESS_STATUS': 'ok', + 'WITHDRAWAL_FAILURE_STATUS': 'failed', + 'BLOCK_MINING_STATUS': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // 'time': '1565769575929', + // 'orderId': '428100569859739648', + // 'token': 'USDT', + // 'address': '', + // 'addressTag': '', + // 'fromAddress': '', + // 'fromAddressTag': '', + // 'quantity': '1100', + // } + // + // fetchWithdrawals + // + // { + // "time":"1536232111669", + // "orderId":"90161227158286336", + // "accountId":"517256161325920", + // "tokenId":"BHC", + // "tokenName":"BHC", + // "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209", + // "addressExt":"address tag", + // "quantity":"14", // Withdrawal qty + // "arriveQuantity":"14", // Arrived qty + // "statusCode":"PROCESSING_STATUS", + // "status":3, + // "txid":"", + // "txidUrl":"", + // "walletHandleTime":"1536232111669", + // "feeTokenId":"BHC", + // "feeTokenName":"BHC", + // "fee":"0.1", + // "requiredConfirmNum":0, // Required confirmations + // "confirmNum":0, // Confirmations + // "kernelId":"", // BEAM and GRIN only + // "isInternalTransfer": false // True if this transfer is internal + // } + // + const id = this.safeString (transaction, 'orderId'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString2 (transaction, 'addressExt', 'addressTag'); + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const addressFrom = this.safeString (transaction, 'fromAddress'); + let tagFrom = this.safeString (transaction, 'fromAddressTag'); + if (tagFrom !== undefined) { + if (tagFrom.length < 1) { + tagFrom = undefined; + } + } + const currencyId = this.safeString (transaction, 'tokenId'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeInteger (transaction, 'time'); + let txid = this.safeString (transaction, 'txid'); + if (txid === '') { + txid = undefined; + } + let type = undefined; + let status = this.parseTransactionStatus (this.safeString (transaction, 'statusCode')); + if (status === undefined) { + type = 'deposit'; + status = 'ok'; + } else { + type = 'withdrawal'; + } + const amount = this.safeFloat (transaction, 'quantity'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (transaction, 'feeTokenId'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'currency': feeCurrencyCode, + 'cost': feeCost, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'address': address, + 'addressTo': address, + 'tagFrom': tagFrom, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "time":1588069860794, + // "symbol":"BNB0501PS16", + // "bestBidPrice":"0.2129", + // "bestAskPrice":"0.3163", + // "volume":"33547", + // "quoteVolume":"10801.987", + // "lastPrice":"0.2625", + // "highPrice":"0.3918", + // "lowPrice":"0.2625", + // "openPrice":"0.362", + // } + // + // fetchBidAsk, fetchBidAsks + // + // { + // "symbol": "LTCBTC", + // "bidPrice": "4.00000000", + // "bidQty": "431.00000000", + // "askPrice": "4.00000200", + // "askQty": "9.00000000" + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (ticker, 'time'); + const open = this.safeFloat (ticker, 'openPrice'); + const close = this.safeFloat (ticker, 'lastPrice'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((open !== undefined) && (close !== undefined)) { + change = close - open; + average = this.sum (open, close) / 2; + if ((close !== undefined) && (close > 0)) { + percentage = (change / open) * 100; + } + } + const quoteVolume = this.safeFloat (ticker, 'quoteVolume'); + const baseVolume = this.safeFloat (ticker, 'volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat2 (ticker, 'bestBidPrice', 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat2 (ticker, 'bestAskPrice', 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market) { + // + // fetchTrades (public) + // + // { + // "price":"0.025344", + // "time":1588084082060, + // "qty":"1", + // "isBuyerMaker":false + // } + // + // fetchMyTrades (private) + // + // spot + // + // { + // "id":"616384027512920576", + // "symbol":"TBTCBUSDT", + // "orderId":"616384027202542080", + // "matchOrderId":"605124954767266560", + // "price":"6826.06", + // "qty":"0.1", + // "commission":"0.682606", + // "commissionAsset":"BUSDT", + // "time":"1588214701982", + // "isBuyer":false, + // "isMaker":false, + // "fee":{ + // "feeTokenId":"BUSDT", + // "feeTokenName":"BUSDT", + // "fee":"0.682606" + // } + // } + // + const id = this.safeString (trade, 'id'); + const timestamp = this.safeFloat (trade, 'time'); + const type = undefined; + const orderId = this.safeString (trade, 'orderId'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'qty'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let side = undefined; + let takerOrMaker = undefined; + if ('isBuyerMaker' in trade) { + side = trade['isBuyerMaker'] ? 'sell' : 'buy'; + } else { + const isMaker = this.safeValue (trade, 'isMaker'); + if (isMaker !== undefined) { + takerOrMaker = isMaker ? 'maker' : 'taker'; + } + const isBuyer = this.safeValue (trade, 'isBuyer'); + side = isBuyer ? 'buy' : 'sell'; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionAsset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'order': orderId, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "symbol":"TBTCBUSDT", + // "orderId":"616376654496877056", + // "clientOrderId":"158821382304516955", + // "transactTime":"1588213823080", + // "price":"0", + // "origQty":"1000", + // "executedQty":"0", + // "status":"NEW", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"BUY" + // } + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // spot + // + // { + // "orderId":"616384027202542080", + // "clientOrderId":"158821470194414688", + // "exchangeId":"301", + // "symbol":"TBTCBUSDT", + // "price":"0", + // "origQty":"0.1", + // "executedQty":"0.1", + // "cummulativeQuoteQty":"682.606", + // "avgPrice":"6826.06", + // "status":"FILLED", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"SELL", + // "stopPrice":"0.0", + // "icebergQty":"0.0", + // "time":"1588214701974", + // "updateTime":"0", + // "isWorking":true + // } + // + // future + // + // { + // time: "1588353669383", + // updateTime: "0", + // orderId: "617549770304599296", + // clientOrderId: "test-001", + // symbol: "BTC-PERP-REV", + // price: "10000", + // leverage: "1", + // origQty: "100", + // executedQty: "0", + // avgPrice: "0", + // marginLocked: "0", + // orderType: "LIMIT", + // side: "SELL_OPEN", + // fees: [], + // timeInForce: "GTC", + // status: "CANCELED", + // priceType: "INPUT" + // } + // + // + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + let timestamp = this.safeInteger (order, 'time'); + if (timestamp === undefined) { + timestamp = this.safeInteger (order, 'transactTime'); + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + let price = this.safeFloat (order, 'price'); + let average = this.safeFloat (order, 'avgPrice'); + let amount = undefined; + let cost = this.safeFloat (order, 'cummulativeQuoteQty'); + let filled = undefined; + let remaining = undefined; + if (type === undefined) { + type = this.safeStringLower (order, 'orderType'); + if ((market !== undefined) && market['inverse']) { + cost = this.safeFloat (order, 'executedQty'); + amount = undefined; + } + if (cost === 0.0) { + filled = 0; + } + } else { + amount = this.safeFloat (order, 'origQty'); + if (type === 'market') { + price = undefined; + if (side === 'buy') { + amount = undefined; + } + } + filled = this.safeFloat (order, 'executedQty'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + } + } + if (average === 0.0) { + average = undefined; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'trades': undefined, + 'fee': undefined, + 'fees': undefined, + }; + const fees = this.safeValue (order, 'fees', []); + const numFees = fees.length; + if (numFees > 0) { + result['fees'] = []; + for (let i = 0; i < fees.length; i++) { + const feeCost = this.safeFloat (fees[i], 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (fees[i], 'feeToken'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + result['fees'].push ({ + 'cost': feeCost, + 'currency': feeCurrencyCode, + }); + } + } + } + return result; + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'CANCELED': 'canceled', + 'FILLED': 'closed', + 'PENDING_CANCEL': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const isPublicContract = (api === 'contract') && ((path === 'insurance') || (path === 'fundingRate')); + if ((api === 'public') || (api === 'quote') || isPublicContract) { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + const timestamp = this.milliseconds (); + this.checkRequiredCredentials (); + const request = this.extend ({ + 'timestamp': timestamp, + }, query); + // 准备待签名数据 + const auth = this.urlencode (request); + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + request['signature'] = signature; + headers = { + 'X-BH-APIKEY': this.apiKey, + }; + if (method === 'POST') { + body = this.urlencode (request); + headers = this.extend ({ + 'Content-Type': 'application/x-www-form-urlencoded', + }, headers); + } else { + url += '?' + this.urlencode (request); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('code' in response) { + const code = this.safeString (response, 'code'); + if (code !== '0') { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],89:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, PermissionDenied, ExchangeError, ExchangeNotAvailable, OrderNotFound, InsufficientFunds, InvalidOrder, RequestTimeout, AuthenticationError } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class hitbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'hitbtc', + 'name': 'HitBTC', + 'countries': [ 'HK' ], + 'rateLimit': 1500, + 'version': '2', + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrder': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTransactions': true, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'M1', + '3m': 'M3', + '5m': 'M5', + '15m': 'M15', + '30m': 'M30', // default + '1h': 'H1', + '4h': 'H4', + '1d': 'D1', + '1w': 'D7', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766555-8eaec20e-5edc-11e7-9c5b-6dc69fc42f5e.jpg', + 'api': { + 'public': 'https://api.hitbtc.com', + 'private': 'https://api.hitbtc.com', + }, + 'www': 'https://hitbtc.com', + 'referral': 'https://hitbtc.com/?ref_id=5a5d39a65d466', + 'doc': [ + 'https://api.hitbtc.com', + 'https://github.com/hitbtc-com/hitbtc-api/blob/master/APIv2.md', + ], + 'fees': [ + 'https://hitbtc.com/fees-and-limits', + 'https://support.hitbtc.com/hc/en-us/articles/115005148605-Fees-and-limits', + ], + }, + 'api': { + 'public': { + 'get': [ + 'symbol', // Available Currency Symbols + 'symbol/{symbol}', // Get symbol info + 'currency', // Available Currencies + 'currency/{currency}', // Get currency info + 'ticker', // Ticker list for all symbols + 'ticker/{symbol}', // Ticker for symbol + 'trades', + 'trades/{symbol}', // Trades + 'orderbook', + 'orderbook/{symbol}', // Orderbook + 'candles', + 'candles/{symbol}', // Candles + ], + }, + 'private': { + 'get': [ + 'trading/balance', // Get trading balance + 'order', // List your current open orders + 'order/{clientOrderId}', // Get a single order by clientOrderId + 'trading/fee/all', // Get trading fee rate + 'trading/fee/{symbol}', // Get trading fee rate + 'history/order', // Get historical orders + 'history/trades', // Get historical trades + 'history/order/{orderId}/trades', // Get historical trades by specified order + 'account/balance', // Get main acccount balance + 'account/crypto/address/{currency}', // Get deposit crypro address + 'account/crypto/is-mine/{address}', + 'account/transactions', // Get account transactions + 'account/transactions/{id}', // Get account transaction by id + 'sub-acc', + 'sub-acc/acl', + 'sub-acc/balance/{subAccountUserID}', + 'sub-acc/deposit-address/{subAccountUserId}/{currency}', + ], + 'post': [ + 'order', // Create new order + 'account/crypto/address/{currency}', // Create new deposit crypro address + 'account/crypto/withdraw', // Withdraw crypro + 'account/crypto/transfer-convert', + 'account/transfer', // Transfer amount to trading + 'sub-acc/freeze', + 'sub-acc/activate', + 'sub-acc/transfer', + ], + 'put': [ + 'order/{clientOrderId}', // Create new order + 'account/crypto/withdraw/{id}', // Commit withdraw crypro + 'sub-acc/acl/{subAccountUserId}', + ], + 'delete': [ + 'order', // Cancel all open orders + 'order/{clientOrderId}', // Cancel order + 'account/crypto/withdraw/{id}', // Rollback withdraw crypro + ], + // outdated? + 'patch': [ + 'order/{clientOrderId}', // Cancel Replace order + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + }, + 'options': { + 'defaultTimeInForce': 'FOK', + }, + 'commonCurrencies': { + 'BCC': 'BCC', // initial symbol for Bitcoin Cash, now inactive + 'BET': 'DAO.Casino', + 'BOX': 'BOX Token', + 'CPT': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081 + 'GET': 'Themis', + 'HSR': 'HC', + 'IQ': 'IQ.Cash', + 'LNC': 'LinkerCoin', + 'PLA': 'PlayChip', + 'PNT': 'Penta', + 'TV': 'Tokenville', + 'USD': 'USDT', + 'XPNT': 'PNT', + }, + 'exceptions': { + '504': RequestTimeout, // {"error":{"code":504,"message":"Gateway Timeout"}} + '1002': AuthenticationError, // {"error":{"code":1002,"message":"Authorization failed","description":""}} + '1003': PermissionDenied, // "Action is forbidden for this API key" + '2010': InvalidOrder, // "Quantity not a valid number" + '2001': BadSymbol, // "Symbol not found" + '2011': InvalidOrder, // "Quantity too low" + '2020': InvalidOrder, // "Price not a valid number" + '20002': OrderNotFound, // canceling non-existent order + '20001': InsufficientFunds, // {"error":{"code":20001,"message":"Insufficient funds","description":"Check that the funds are sufficient, given commissions"}} + }, + 'orders': {}, // orders cache / emulation + }); + } + + feeToPrecision (symbol, fee) { + return this.decimalToPrecision (fee, TRUNCATE, 8, DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetSymbol (params); + // + // [ + // { + // "id":"BCNBTC", + // "baseCurrency":"BCN", + // "quoteCurrency":"BTC", + // "quantityIncrement":"100", + // "tickSize":"0.00000000001", + // "takeLiquidityRate":"0.002", + // "provideLiquidityRate":"0.001", + // "feeCurrency":"BTC" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + // bequant fix + if (id.indexOf ('_') >= 0) { + symbol = id; + } + const lot = this.safeFloat (market, 'quantityIncrement'); + const step = this.safeFloat (market, 'tickSize'); + const precision = { + 'price': step, + 'amount': lot, + }; + const taker = this.safeFloat (market, 'takeLiquidityRate'); + const maker = this.safeFloat (market, 'provideLiquidityRate'); + const feeCurrencyId = this.safeString (market, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + result.push (this.extend (this.fees['trading'], { + 'info': market, + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'taker': taker, + 'maker': maker, + 'precision': precision, + 'feeCurrency': feeCurrencyCode, + 'limits': { + 'amount': { + 'min': lot, + 'max': undefined, + }, + 'price': { + 'min': step, + 'max': undefined, + }, + 'cost': { + 'min': lot * step, + 'max': undefined, + }, + }, + })); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrency (params); + // + // [ + // { + // "id":"XPNT", + // "fullName":"pToken", + // "crypto":true, + // "payinEnabled":true, + // "payinPaymentId":false, + // "payinConfirmations":9, + // "payoutEnabled":true, + // "payoutIsPaymentId":false, + // "transferEnabled":true, + // "delisted":false, + // "payoutFee":"26.510000000000", + // "precisionPayout":18, + // "precisionTransfer":8 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'id'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const decimals = this.safeInteger (currency, 'precisionTransfer', 8); + const precision = 1 / Math.pow (10, decimals); + const code = this.safeCurrencyCode (id); + const payin = this.safeValue (currency, 'payinEnabled'); + const payout = this.safeValue (currency, 'payoutEnabled'); + const transfer = this.safeValue (currency, 'transferEnabled'); + let active = payin && payout && transfer; + if ('disabled' in currency) { + if (currency['disabled']) { + active = false; + } + } + let type = 'fiat'; + if (('crypto' in currency) && currency['crypto']) { + type = 'crypto'; + } + const name = this.safeString (currency, 'fullName'); + result[code] = { + 'id': id, + 'code': code, + 'type': type, + 'payin': payin, + 'payout': payout, + 'transfer': transfer, + 'info': currency, + 'name': name, + 'active': active, + 'fee': this.safeFloat (currency, 'payoutFee'), // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { + 'min': 1 / Math.pow (10, decimals), + 'max': Math.pow (10, decimals), + }, + 'price': { + 'min': 1 / Math.pow (10, decimals), + 'max': Math.pow (10, decimals), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = this.extend ({ + 'symbol': market['id'], + }, this.omit (params, 'symbol')); + const response = await this.privateGetTradingFeeSymbol (request); + // + // { + // takeLiquidityRate: '0.001', + // provideLiquidityRate: '-0.0001' + // } + // + return { + 'info': response, + 'maker': this.safeFloat (response, 'provideLiquidityRate'), + 'taker': this.safeFloat (response, 'takeLiquidityRate'), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const type = this.safeString (params, 'type', 'trading'); + const method = 'privateGet' + this.capitalize (type) + 'Balance'; + const query = this.omit (params, 'type'); + const response = await this[method] (query); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "timestamp":"2015-08-20T19:01:00.000Z", + // "open":"0.006", + // "close":"0.006", + // "min":"0.006", + // "max":"0.006", + // "volume":"0.003", + // "volumeQuote":"0.000018" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'max'), + this.safeFloat (ohlcv, 'min'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetCandlesSymbol (this.extend (request, params)); + // + // [ + // {"timestamp":"2015-08-20T19:01:00.000Z","open":"0.006","close":"0.006","min":"0.006","max":"0.006","volume":"0.003","volumeQuote":"0.000018"}, + // {"timestamp":"2015-08-20T19:03:00.000Z","open":"0.006","close":"0.006","min":"0.006","max":"0.006","volume":"0.013","volumeQuote":"0.000078"}, + // {"timestamp":"2015-08-20T19:06:00.000Z","open":"0.0055","close":"0.005","min":"0.005","max":"0.0055","volume":"0.003","volumeQuote":"0.0000155"}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 100, 0 = unlimited + } + const response = await this.publicGetOrderbookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bid', 'ask', 'price', 'size'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.parse8601 (ticker['timestamp']); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const baseVolume = this.safeFloat (ticker, 'volume'); + const quoteVolume = this.safeFloat (ticker, 'volumeQuote'); + const open = this.safeFloat (ticker, 'open'); + const last = this.safeFloat (ticker, 'last'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = response[i]; + const marketId = this.safeString (ticker, 'symbol'); + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTickerSymbol (this.extend (request, params)); + if ('message' in response) { + throw new ExchangeError (this.id + ' ' + response['message']); + } + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // createMarketOrder + // + // { fee: "0.0004644", + // id: 386394956, + // price: "0.4644", + // quantity: "1", + // timestamp: "2018-10-25T16:41:44.780Z" } + // + // fetchTrades + // + // { id: 974786185, + // price: '0.032462', + // quantity: '0.3673', + // side: 'buy', + // timestamp: '2020-10-16T12:57:39.846Z' } + // + // fetchMyTrades + // + // { id: 277210397, + // clientOrderId: '6e102f3e7f3f4e04aeeb1cdc95592f1a', + // orderId: 28102855393, + // symbol: 'ETHBTC', + // side: 'sell', + // quantity: '0.002', + // price: '0.073365', + // fee: '0.000000147', + // timestamp: '2018-04-28T18:39:55.345Z' } + const timestamp = this.parse8601 (trade['timestamp']); + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = market ? market['feeCurrency'] : undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const orderId = this.safeString (trade, 'clientOrderId'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const cost = price * amount; + const side = this.safeString (trade, 'side'); + const id = this.safeString (trade, 'id'); + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + } + const response = await this.privateGetAccountTransactions (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // id: 'd53ee9df-89bf-4d09-886e-849f8be64647', + // index: 1044718371, + // type: 'payout', + // status: 'success', + // currency: 'ETH', + // amount: '4.522683200000000000000000', + // createdAt: '2018-06-07T00:43:32.426Z', + // updatedAt: '2018-06-07T00:45:36.447Z', + // hash: '0x973e5683dfdf80a1fb1e0b96e19085b6489221d2ddf864daa46903c5ec283a0f', + // address: '0xC5a59b21948C1d230c8C54f05590000Eb3e1252c', + // fee: '0.00958', + // }, + // { + // id: 'e6c63331-467e-4922-9edc-019e75d20ba3', + // index: 1044714672, + // type: 'exchangeToBank', + // status: 'success', + // currency: 'ETH', + // amount: '4.532263200000000000', + // createdAt: '2018-06-07T00:42:39.543Z', + // updatedAt: '2018-06-07T00:42:39.683Z', + // }, + // { + // id: '3b052faa-bf97-4636-a95c-3b5260015a10', + // index: 1009280164, + // type: 'bankToExchange', + // status: 'success', + // currency: 'CAS', + // amount: '104797.875800000000000000', + // createdAt: '2018-05-19T02:34:36.750Z', + // updatedAt: '2018-05-19T02:34:36.857Z', + // }, + // { + // id: 'd525249f-7498-4c81-ba7b-b6ae2037dc08', + // index: 1009279948, + // type: 'payin', + // status: 'success', + // currency: 'CAS', + // amount: '104797.875800000000000000', + // createdAt: '2018-05-19T02:30:16.698Z', + // updatedAt: '2018-05-19T02:34:28.159Z', + // hash: '0xa6530e1231de409cf1f282196ed66533b103eac1df2aa4a7739d56b02c5f0388', + // address: '0xd53ed559a6d963af7cb3f3fcd0e7ca499054db8b', + // } + // + // { + // "id": "4f351f4f-a8ee-4984-a468-189ed590ddbd", + // "index": 3112719565, + // "type": "withdraw", + // "status": "success", + // "currency": "BCHOLD", + // "amount": "0.02423133", + // "createdAt": "2019-07-16T16:52:04.494Z", + // "updatedAt": "2019-07-16T16:54:07.753Z" + // } + const id = this.safeString (transaction, 'id'); + const timestamp = this.parse8601 (this.safeString (transaction, 'createdAt')); + const updated = this.parse8601 (this.safeString (transaction, 'updatedAt')); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const txid = this.safeString (transaction, 'hash'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + const type = this.parseTransactionType (this.safeString (transaction, 'type')); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'failed': 'failed', + 'success': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const types = { + 'payin': 'deposit', + 'payout': 'withdrawal', + 'withdraw': 'withdrawal', + }; + return this.safeString (types, type, type); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['sort'] = 'ASC'; + request['from'] = this.iso8601 (since); + } + const response = await this.publicGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + // their max accepted length is 32 characters + const uuid = this.uuid (); + const parts = uuid.split ('-'); + let clientOrderId = parts.join (''); + clientOrderId = clientOrderId.slice (0, 32); + amount = parseFloat (amount); + const request = { + 'clientOrderId': clientOrderId, + 'symbol': market['id'], + 'side': side, + 'quantity': this.amountToPrecision (symbol, amount), + 'type': type, + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } else { + request['timeInForce'] = this.options['defaultTimeInForce']; + } + const response = await this.privatePostOrder (this.extend (request, params)); + const order = this.parseOrder (response); + if (order['status'] === 'rejected') { + throw new InvalidOrder (this.id + ' order was rejected by the exchange ' + this.json (order)); + } + return order; + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + // their max accepted length is 32 characters + const uuid = this.uuid (); + const parts = uuid.split ('-'); + let requestClientId = parts.join (''); + requestClientId = requestClientId.slice (0, 32); + const request = { + 'clientOrderId': id, + 'requestClientId': requestClientId, + }; + if (amount !== undefined) { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePatchOrderClientOrderId (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const request = { + 'clientOrderId': id, + }; + const response = await this.privateDeleteOrderClientOrderId (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + 'new': 'open', + 'suspended': 'open', + 'partiallyFilled': 'open', + 'filled': 'closed', + 'canceled': 'canceled', + 'expired': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createMarketOrder + // + // { clientOrderId: "fe36aa5e190149bf9985fb673bbb2ea0", + // createdAt: "2018-10-25T16:41:44.780Z", + // cumQuantity: "1", + // id: "66799540063", + // quantity: "1", + // side: "sell", + // status: "filled", + // symbol: "XRPUSDT", + // timeInForce: "FOK", + // tradesReport: [ { fee: "0.0004644", + // id: 386394956, + // price: "0.4644", + // quantity: "1", + // timestamp: "2018-10-25T16:41:44.780Z" } ], + // type: "market", + // updatedAt: "2018-10-25T16:41:44.780Z" } + // + const created = this.parse8601 (this.safeString (order, 'createdAt')); + const updated = this.parse8601 (this.safeString (order, 'updatedAt')); + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const amount = this.safeFloat (order, 'quantity'); + const filled = this.safeFloat (order, 'cumQuantity'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const id = this.safeString (order, 'clientOrderId'); + const clientOrderId = id; + let price = this.safeFloat (order, 'price'); + let remaining = undefined; + let cost = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + if (price !== undefined) { + cost = filled * price; + } + } + } + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + let trades = this.safeValue (order, 'tradesReport'); + let fee = undefined; + let average = undefined; + if (trades !== undefined) { + trades = this.parseTrades (trades, market); + let feeCost = undefined; + const numTrades = trades.length; + let tradesCost = 0; + for (let i = 0; i < numTrades; i++) { + if (feeCost === undefined) { + feeCost = 0; + } + tradesCost = this.sum (tradesCost, trades[i]['cost']); + const tradeFee = this.safeValue (trades[i], 'fee', {}); + const tradeFeeCost = this.safeFloat (tradeFee, 'cost'); + if (tradeFeeCost !== undefined) { + feeCost = this.sum (feeCost, tradeFeeCost); + } + } + cost = tradesCost; + if ((filled !== undefined) && (filled > 0)) { + average = cost / filled; + if (type === 'market') { + if (price === undefined) { + price = average; + } + } + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'id': id, + 'clientOrderId': clientOrderId, // https://github.com/ccxt/ccxt/issues/5674 + 'timestamp': created, + 'datetime': this.iso8601 (created), + 'lastTradeTimestamp': updated, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'trades': trades, + 'info': order, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const request = { + 'clientOrderId': id, + }; + const response = await this.privateGetHistoryOrder (this.extend (request, params)); + const numOrders = response.length; + if (numOrders > 0) { + return this.parseOrder (response[0]); + } + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const request = { + 'clientOrderId': id, + }; + const response = await this.privateGetOrderClientOrderId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetOrder (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + const response = await this.privateGetHistoryOrder (this.extend (request, params)); + const parsedOrders = this.parseOrders (response, market); + const orders = []; + for (let i = 0; i < parsedOrders.length; i++) { + const order = parsedOrders[i]; + const status = order['status']; + if ((status === 'closed') || (status === 'canceled')) { + orders.push (order); + } + } + return this.filterBySinceLimit (orders, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': 'BTC/USD', // optional + // 'sort': 'DESC', // or 'ASC' + // 'by': 'timestamp', // or 'id' String timestamp by default, or id + // 'from': 'Datetime or Number', // ISO 8601 + // 'till': 'Datetime or Number', + // 'limit': 100, + // 'offset': 0, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetHistoryTrades (this.extend (request, params)); + // + // [ + // { + // "id": 9535486, + // "clientOrderId": "f8dbaab336d44d5ba3ff578098a68454", + // "orderId": 816088377, + // "symbol": "ETHBTC", + // "side": "sell", + // "quantity": "0.061", + // "price": "0.045487", + // "fee": "0.000002775", + // "timestamp": "2017-05-17T12:32:57.848Z" + // }, + // { + // "id": 9535437, + // "clientOrderId": "27b9bfc068b44194b1f453c7af511ed6", + // "orderId": 816088021, + // "symbol": "ETHBTC", + // "side": "buy", + // "quantity": "0.038", + // "price": "0.046000", + // "fee": "-0.000000174", + // "timestamp": "2017-05-17T12:30:57.848Z" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + // The id needed here is the exchange's id, and not the clientOrderID, + // which is the id that is stored in the unified order id + // To get the exchange's id you need to grab it from order['info']['id'] + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'orderId': id, + }; + const response = await this.privateGetHistoryOrderOrderIdTrades (this.extend (request, params)); + const numOrders = response.length; + if (numOrders > 0) { + return this.parseTrades (response, market, since, limit); + } + throw new OrderNotFound (this.id + ' order ' + id + ' not found, ' + this.id + '.fetchOrderTrades() requires an exchange-specific order id, you need to grab it from order["info"]["id"]'); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostAccountCryptoAddressCurrency (this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + const tag = this.safeString (response, 'paymentId'); + return { + 'currency': currency, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetAccountCryptoAddressCurrency (this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + const tag = this.safeString (response, 'paymentId'); + return { + 'currency': currency['code'], + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': parseFloat (amount), + 'address': address, + }; + if (tag) { + request['paymentId'] = tag; + } + const response = await this.privatePostAccountCryptoWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/api/' + this.version + '/'; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + url += api + '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + url += this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length) { + body = this.json (query); + } + const payload = this.encode (this.apiKey + ':' + this.secret); + const auth = this.stringToBase64 (payload); + headers = { + 'Authorization': 'Basic ' + this.decode (auth), + 'Content-Type': 'application/json', + }; + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code >= 400) { + const feedback = this.id + ' ' + body; + // {"code":504,"message":"Gateway Timeout","description":""} + if ((code === 503) || (code === 504)) { + throw new ExchangeNotAvailable (feedback); + } + // fallback to default error handler on rate limit errors + // {"code":429,"message":"Too many requests","description":"Too many requests"} + if (code === 429) { + return; + } + // {"error":{"code":20002,"message":"Order not found","description":""}} + if (body[0] === '{') { + if ('error' in response) { + const errorCode = this.safeString (response['error'], 'code'); + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + const message = this.safeString (response['error'], 'message'); + if (message === 'Duplicate clientOrderId') { + throw new InvalidOrder (feedback); + } + } + } + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],90:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadRequest, AuthenticationError, NetworkError, ArgumentsRequired, OrderNotFound, InsufficientFunds } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class hollaex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'hollaex', + 'name': 'HollaEx', + 'countries': [ 'KR' ], + 'rateLimit': 333, + 'version': 'v1', + 'has': { + 'CORS': false, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTrades': true, + 'fetchOHLCV': true, + 'fetchBalance': true, + 'createOrder': true, + 'createLimitBuyOrder': true, + 'createLimitSellOrder': true, + 'createMarketBuyOrder': true, + 'createMarketSellOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': false, + 'fetchOpenOrder': true, + 'fetchOrder': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchTransactions': false, + 'fetchOrders': false, + 'fetchMyTrades': true, + 'withdraw': true, + 'fetchDepositAddress': true, + }, + 'timeframes': { + '1h': '1h', + '1d': '1d', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/75841031-ca375180-5ddd-11ea-8417-b975674c23cb.jpg', + 'api': 'https://api.hollaex.com', + 'www': 'https://hollaex.com', + 'doc': 'https://apidocs.hollaex.com', + 'referral': 'https://pro.hollaex.com/signup?affiliation_code=QSWA6G', + }, + 'precisionMode': TICK_SIZE, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'api': { + 'public': { + 'get': [ + 'health', + 'constant', + 'ticker', + 'ticker/all', + 'orderbooks', + 'trades', + 'chart', + // TradingView data + 'udf/config', + 'udf/history', + 'udf/symbols', + ], + }, + 'private': { + 'get': [ + 'user', + 'user/balance', + 'user/trades', + 'user/orders', + 'user/orders/{order_id}', + 'user/deposits', + 'user/withdrawals', + 'user/withdraw/{currency}/fee', + ], + 'post': [ + 'user/request-withdrawal', + 'order', + ], + 'delete': [ + 'user/orders', + 'user/orders/{order_id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + }, + }, + 'exceptions': { + 'broad': { + 'Invalid token': AuthenticationError, + 'Order not found': OrderNotFound, + 'Insufficient balance': InsufficientFunds, + }, + 'exact': { + '400': BadRequest, + '403': AuthenticationError, + '404': BadRequest, + '405': BadRequest, + '410': BadRequest, + '429': BadRequest, + '500': NetworkError, + '503': NetworkError, + }, + }, + 'options': { + // how many seconds before the authenticated request expires + 'api-expires': parseInt (this.timeout / 1000), + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetConstant (params); + // + // { + // coins: { + // xmr: { + // id: 7, + // fullname: "Monero", + // symbol: "xmr", + // active: true, + // allow_deposit: true, + // allow_withdrawal: true, + // withdrawal_fee: 0.02, + // min: 0.001, + // max: 100000, + // increment_unit: 0.001, + // deposit_limits: { '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0 }, + // withdrawal_limits: { '1': 10, '2': 15, '3': 100, '4': 100, '5': 200, '6': 300, '7': 350, '8': 400, '9': 500, '10': -1 }, + // created_at: "2019-12-09T07:14:02.720Z", + // updated_at: "2020-01-16T12:12:53.162Z" + // }, + // // ... + // }, + // pairs: { + // 'btc-usdt': { + // id: 2, + // name: "btc-usdt", + // pair_base: "btc", + // pair_2: "usdt", + // taker_fees: { '1': 0.3, '2': 0.25, '3': 0.2, '4': 0.18, '5': 0.1, '6': 0.09, '7': 0.08, '8': 0.06, '9': 0.04, '10': 0 }, + // maker_fees: { '1': 0.1, '2': 0.08, '3': 0.05, '4': 0.03, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0, '10': 0 }, + // min_size: 0.0001, + // max_size: 1000, + // min_price: 100, + // max_price: 100000, + // increment_size: 0.0001, + // increment_price: 0.05, + // active: true, + // created_at: "2019-12-09T07:15:54.537Z", + // updated_at: "2019-12-09T07:15:54.537Z" + // }, + // }, + // config: { tiers: 10 }, + // status: true + // } + // + const pairs = this.safeValue (response, 'pairs', {}); + const keys = Object.keys (pairs); + const result = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const market = pairs[key]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'pair_base'); + const quoteId = this.safeString (market, 'pair_2'); + const base = this.commonCurrencyCode (baseId.toUpperCase ()); + const quote = this.commonCurrencyCode (quoteId.toUpperCase ()); + const symbol = base + '/' + quote; + const active = this.safeValue (market, 'active'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': { + 'price': this.safeFloat (market, 'increment_price'), + 'amount': this.safeFloat (market, 'increment_size'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_size'), + 'max': this.safeFloat (market, 'max_size'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetConstant (params); + const coins = this.safeValue (response, 'coins', {}); + const keys = Object.keys (coins); + const result = {}; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const currency = coins[key]; + const id = this.safeString (currency, 'symbol'); + const numericId = this.safeInteger (currency, 'id'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'fullname'); + const active = this.safeValue (currency, 'active'); + const fee = this.safeFloat (currency, 'withdrawal_fee'); + const precision = this.safeFloat (currency, 'increment_unit'); + const withdrawalLimits = this.safeValue (currency, 'withdrawal_limits', []); + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'min'), + 'max': this.safeFloat (currency, 'max'), + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': this.safeValue (withdrawalLimits, 0), + }, + }, + }; + } + return result; + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetOrderbooks (params); + const result = {}; + const marketIds = Object.keys (response); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const orderbook = response[marketId]; + const symbol = this.safeSymbol (marketId, undefined, '-'); + const timestamp = this.parse8601 (this.safeString (orderbook, 'timestamp')); + result[symbol] = this.parseOrderBook (response[marketId], timestamp); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + const response = await this.publicGetOrderbooks (this.extend (request, params)); + // + // { + // "btc-usdt": { + // "bids": [ + // [ 8836.4, 1.022 ], + // [ 8800, 0.0668 ], + // [ 8797.75, 0.2398 ], + // ], + // "asks": [ + // [ 8839.35, 1.5334 ], + // [ 8852.6, 0.0579 ], + // [ 8860.45, 0.1815 ], + // ], + // "timestamp": "2020-03-03T02:27:25.147Z" + // }, + // "eth-usdt": {}, + // // ... + // } + // + const orderbook = this.safeValue (response, marketId); + const timestamp = this.parse8601 (this.safeString (orderbook, 'timestamp')); + return this.parseOrderBook (orderbook, timestamp); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // open: 8615.55, + // close: 8841.05, + // high: 8921.1, + // low: 8607, + // last: 8841.05, + // volume: 20.2802, + // timestamp: '2020-03-03T03:11:18.964Z' + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickerAll (this.extend (params)); + // + // { + // "bch-usdt": { + // "time": "2020-03-02T04:29:45.011Z", + // "open": 341.65, + // "close":337.9, + // "high":341.65, + // "low":337.3, + // "last":337.9, + // "volume":0.054, + // "symbol":"bch-usdt" + // }, + // // ... + // } + // + return this.parseTickers (response, symbols); + } + + parseTickers (response, symbols = undefined) { + const result = {}; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const ticker = response[key]; + const marketId = this.safeString (ticker, 'symbol', key); + const market = this.safeMarket (marketId, undefined, '-'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // open: 8615.55, + // close: 8841.05, + // high: 8921.1, + // low: 8607, + // last: 8841.05, + // volume: 20.2802, + // timestamp: '2020-03-03T03:11:18.964Z', + // } + // + // fetchTickers + // + // { + // "time": "2020-03-02T04:29:45.011Z", + // "open": 341.65, + // "close": 337.9, + // "high": 341.65, + // "low": 337.3, + // "last": 337.9, + // "volume": 0.054, + // "symbol": "bch-usdt" + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString2 (ticker, 'time', 'timestamp')); + const close = this.safeFloat (ticker, 'close'); + const result = { + 'symbol': symbol, + 'info': ticker, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': close, + 'last': this.safeFloat (ticker, 'last', close), + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + }; + return result; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "btc-usdt": [ + // { + // "size": 0.5, + // "price": 8830, + // "side": "buy", + // "timestamp": "2020-03-03T04:44:33.034Z" + // }, + // // ... + // ] + // } + // + const trades = this.safeValue (response, market['id'], []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "size": 0.5, + // "price": 8830, + // "side": "buy", + // "timestamp": "2020-03-03T04:44:33.034Z" + // } + // + // fetchMyTrades (private) + // + // { + // "side": "buy", + // "symbol": "eth-usdt", + // "size": 0.086, + // "price": 226.19, + // "timestamp": "2020-03-03T08:03:55.459Z", + // "fee": 0.1 + // } + // + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market, '-'); + const symbol = market['symbol']; + const datetime = this.safeString (trade, 'timestamp'); + const timestamp = this.parse8601 (datetime); + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const quote = market['quote']; + const feeCurrencyCode = (market !== undefined) ? market['quote'] : quote; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': trade, + 'id': undefined, + 'timestamp': timestamp, + 'datetime': datetime, + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOHLCV (symbol, timeframe = '1h', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + " fetchOHLCV requires a 'since' or a 'limit' argument"); + } else { + const end = this.seconds (); + const start = end - duration * limit; + request['to'] = end; + request['from'] = start; + } + } else { + if (limit === undefined) { + request['from'] = parseInt (since / 1000); + request['to'] = this.seconds (); + } else { + const start = parseInt (since / 1000); + request['from'] = start; + request['to'] = this.sum (start, duration * limit); + } + } + const response = await this.publicGetChart (this.extend (request, params)); + // + // [ + // { + // "time":"2020-03-02T20:00:00.000Z", + // "close":8872.1, + // "high":8872.1, + // "low":8858.6, + // "open":8858.6, + // "symbol":"btc-usdt", + // "volume":1.2922 + // }, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOHLCV (response, market = undefined, timeframe = '1h', since = undefined, limit = undefined) { + // + // { + // "time":"2020-03-02T20:00:00.000Z", + // "close":8872.1, + // "high":8872.1, + // "low":8858.6, + // "open":8858.6, + // "symbol":"btc-usdt", + // "volume":1.2922 + // } + // + return [ + this.parse8601 (this.safeString (response, 'time')), + this.safeFloat (response, 'open'), + this.safeFloat (response, 'high'), + this.safeFloat (response, 'low'), + this.safeFloat (response, 'close'), + this.safeFloat (response, 'volume'), + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUserBalance (params); + // + // { + // "updated_at": "2020-03-02T22:27:38.428Z", + // "btc_balance": 0, + // "btc_pending": 0, + // "btc_available": 0, + // "eth_balance": 0, + // "eth_pending": 0, + // "eth_available": 0, + // // ... + // } + // + const result = { 'info': response }; + const currencyIds = Object.keys (this.currencies_by_id); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (response, currencyId + '_available'); + account['total'] = this.safeFloat (response, currencyId + '_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetUserOrdersOrderId (this.extend (request, params)); + // + // { + // "created_at": "2018-03-23T04:14:08.663Z", + // "title": "string", + // "side": "sell", + // "type": "limit", + // "price": 0, + // "size": 0, + // "symbol": "xht-usdt", + // "id": "string", + // "created_by": 1, + // "filled": 0 + // } + // + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetUserOrders (this.extend (request, params)); + // + // [ + // { + // "created_at":"2020-03-03T08:02:18.639Z", + // "title":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "side":"buy", + // "type":"limit", + // "price":226.19, + // "size":0.086, + // "symbol":"eth-usdt", + // "id":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "created_by":620, + // "filled":0 + // } + // ] + // + return this.parseOrders (response, market); + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrder, fetchOpenOrders + // + // { + // "created_at":"2020-03-03T08:02:18.639Z", + // "title":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "side":"buy", + // "type":"limit", + // "price":226.19, + // "size":0.086, + // "symbol":"eth-usdt", + // "id":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "created_by":620, + // "filled":0 + // } + // + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'filled'); + let cost = undefined; + let remaining = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (price !== undefined) { + cost = filled * price; + } + } + const status = (type === 'market') ? 'closed' : 'open'; + const result = { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const order = { + 'symbol': market['id'], + 'side': side, + 'size': amount, + 'type': type, + }; + if (type !== 'market') { + order['price'] = price; + } + const response = await this.privatePostOrder (this.extend (order, params)); + // + // { + // "symbol": "xht-usdt", + // "side": "sell", + // "size": 1, + // "type": "limit", + // "price": 0.1, + // "id": "string", + // "created_by": 34, + // "filled": 0, + // "status": "pending" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateDeleteUserOrdersOrderId (this.extend (request, params)); + // + // { + // "title": "string", + // "symbol": "xht-usdt", + // "side": "sell", + // "size": 1, + // "type": "limit", + // "price": 0.1, + // "id": "string", + // "created_by": 34, + // "filled": 0 + // } + // + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.markets (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateDeleteUserOrders (this.extend (request, params)); + // + // [ + // { + // "title": "string", + // "symbol": "xht-usdt", + // "side": "sell", + // "size": 1, + // "type": "limit", + // "price": 0.1, + // "id": "string", + // "created_by": 34, + // "filled": 0 + // } + // ] + // + return this.parseOrders (response, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': market['id'], + // 'limit': 50, // default 50, max 100 + // 'page': 1, // page of data to retrieve + // 'order_by': 'timestamp', // field to order data + // 'order': 'asc', // asc or desc + // 'start_date': 123, // starting date of queried data + // 'end_date': 321, // ending date of queried data + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + const response = await this.privateGetUserTrades (this.extend (request, params)); + // + // { + // "count": 1, + // "data": [ + // { + // "side": "buy", + // "symbol": "eth-usdt", + // "size": 0.086, + // "price": 226.19, + // "timestamp": "2020-03-03T08:03:55.459Z", + // "fee": 0.1 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const response = await this.privateGetUser (params); + // + // { + // "id": 620, + // "email": "email@gmail.com", + // "full_name": "", + // "name_verified": false, + // "gender": false, + // "nationality": "", + // "phone_number": "", + // "address": { "city": "", "address": "", "country": "", "postal_code": "" }, + // "id_data": { "note": "", "type": "", "number": "", "status": 0 }, + // "bank_account":[], + // "crypto_wallet":{ + // "xrp": "rJtoECs6rPkJoAfgtR8SDDshV6hRHe3X7y:391496555" + // "usdt":"0x1fb4248e167901dfa0d8cdda2243a2126d7ce48d" + // // ... + // }, + // "verification_level": 1, + // "otp_enabled": true, + // "activated": true, + // "note": "", + // "username": "user", + // "affiliation_code": "QSWA6G", + // "settings": { + // "chat": { "set_username": false }, + // "risk": { "order_portfolio_percentage": 20 }, + // "audio": { + // "public_trade": false, + // "order_completed": true, + // "order_partially_completed": true + // }, + // "language": "en", + // "interface": { "theme": "white","order_book_levels": 10 }, + // "notification": { + // "popup_order_completed": true, + // "popup_order_confirmation": true, + // "popup_order_partially_filled": true + // } + // }, + // "flagged": false, + // "is_hap": false, + // "pin": false, + // "discount": 0, + // "created_at": "2020-03-02T22:27:38.331Z", + // "updated_at": "2020-03-03T07:54:58.315Z", + // "balance": { + // "xht_balance": 0, + // "xht_pending": 0, + // "xht_available": 0, + // // ... + // "updated_at": "2020-03-03T10:21:05.430Z" + // }, + // "images": [], + // "fees": { + // "btc-usdt": { "maker_fee": 0.1, "taker_fee": 0.3 }, + // "eth-usdt": { "maker_fee": 0.1, "taker_fee": 0.3 }, + // // ... + // } + // } + // + const cryptoWallet = this.safeValue (response, 'crypto_wallet'); + let address = this.safeString (cryptoWallet, currency['id']); + let tag = undefined; + if (address !== undefined) { + const parts = address.split (':'); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': currency['id'], + // 'limit': 50, // default 50, max 100 + // 'page': 1, // page of data to retrieve + // 'order_by': 'timestamp', // field to order data + // 'order': 'asc', // asc or desc + // 'start_date': 123, // starting date of queried data + // 'end_date': 321, // ending date of queried data + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + const response = await this.privateGetUserDeposits (this.extend (request, params)); + // + // { + // "count": 1, + // "data": [ + // { + // "id": 539, + // "amount": 20, + // "fee": 0, + // "address": "0x5c0cc98270d7089408fcbcc8e2131287f5be2306", + // "transaction_id": "0xd4006327a5ec2c41adbdcf566eaaba6597c3d45906abe78ea1a4a022647c2e28", + // "status": true, + // "dismissed": false, + // "rejected": false, + // "description": "", + // "type": "deposit", + // "currency": "usdt", + // "created_at": "2020-03-03T07:56:36.198Z", + // "updated_at": "2020-03-03T08:00:05.674Z", + // "user_id": 620 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': currency['id'], + // 'limit': 50, // default 50, max 100 + // 'page': 1, // page of data to retrieve + // 'order_by': 'timestamp', // field to order data + // 'order': 'asc', // asc or desc + // 'start_date': 123, // starting date of queried data + // 'end_date': 321, // ending date of queried data + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + const response = await this.privateGetUserWithdrawals (this.extend (request, params)); + // + // { + // "count": 1, + // "data": [ + // { + // "id": 539, + // "amount": 20, + // "fee": 0, + // "address": "0x5c0cc98270d7089408fcbcc8e2131287f5be2306", + // "transaction_id": "0xd4006327a5ec2c41adbdcf566eaaba6597c3d45906abe78ea1a4a022647c2e28", + // "status": true, + // "dismissed": false, + // "rejected": false, + // "description": "", + // "type": "withdrawal", + // "currency": "usdt", + // "created_at": "2020-03-03T07:56:36.198Z", + // "updated_at": "2020-03-03T08:00:05.674Z", + // "user_id": 620 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": 539, + // "amount": 20, + // "fee": 0, + // "address": "0x5c0cc98270d7089408fcbcc8e2131287f5be2306", + // "transaction_id": "0xd4006327a5ec2c41adbdcf566eaaba6597c3d45906abe78ea1a4a022647c2e28", + // "status": true, + // "dismissed": false, + // "rejected": false, + // "description": "", + // "type": "withdrawal", + // "currency": "usdt", + // "created_at": "2020-03-03T07:56:36.198Z", + // "updated_at": "2020-03-03T08:00:05.674Z", + // "user_id": 620 + // } + // + const id = this.safeString (transaction, 'id'); + const txid = this.safeString (transaction, 'transaction_id'); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const updated = this.parse8601 (this.safeString (transaction, 'updated_at')); + const type = this.safeString (transaction, 'type'); + const amount = this.safeFloat (transaction, 'amount'); + let address = this.safeString (transaction, 'address'); + let addressTo = undefined; + const addressFrom = undefined; + let tag = undefined; + let tagTo = undefined; + const tagFrom = undefined; + if (address !== undefined) { + const parts = address.split (':'); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + addressTo = address; + tagTo = tag; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let status = this.safeValue (transaction, 'status'); + const dismissed = this.safeValue (transaction, 'dismissed'); + const rejected = this.safeValue (transaction, 'rejected'); + if (status) { + status = 'ok'; + } else if (dismissed) { + status = 'canceled'; + } else if (rejected) { + status = 'failed'; + } else { + status = 'pending'; + } + const fee = { + 'currency': code, + 'cost': this.safeFloat (transaction, 'fee'), + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'address': address, + 'addressTo': addressTo, + 'tagFrom': tagFrom, + 'tag': tag, + 'tagTo': tagTo, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag !== undefined) { + address += ':' + tag; + } + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + }; + // one time password + let otp = this.safeString (params, 'otp_code'); + if ((otp !== undefined) || (this.twofa !== undefined)) { + if (otp === undefined) { + otp = this.oath (); + } + request['otp_code'] = otp; + } + const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + path = '/' + this.version + '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + path += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + path; + if (api === 'private') { + this.checkRequiredCredentials (); + const defaultExpires = this.safeInteger2 (this.options, 'api-expires', 'expires', parseInt (this.timeout / 1000)); + const expires = this.sum (this.seconds (), defaultExpires); + const expiresString = expires.toString (); + let auth = method + path + expiresString; + headers = { + 'api-key': this.encode (this.apiKey), + 'api-expires': expiresString, + }; + if (method === 'POST') { + headers['Content-type'] = 'application/json'; + if (Object.keys (query).length) { + body = this.json (query); + auth += body; + } + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers['api-signature'] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if ((code >= 400) && (code <= 503)) { + // + // { "message": "Invalid token" } + // + const feedback = this.id + ' ' + body; + const message = this.safeString (response, 'message'); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + const status = code.toString (); + this.throwExactlyMatchedException (this.exceptions['exact'], status, feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],91:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const huobipro = require ('./huobipro.js'); +const { NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class huobijp extends huobipro { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'huobijp', + 'name': 'Huobi Japan', + 'countries': [ 'JP' ], + 'hostname': 'api-cloud.huobi.co.jp', + 'pro': true, + 'has': { + 'fetchDepositAddress': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/85734211-85755480-b705-11ea-8b35-0b7f1db33a2f.jpg', + 'api': { + 'market': 'https://{hostname}', + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + }, + 'www': 'https://www.huobi.co.jp', + 'referral': 'https://www.huobi.co.jp/register/?invite_code=znnq3', + 'doc': 'https://api-doc.huobi.co.jp', + 'fees': 'https://www.huobi.co.jp/support/fee', + }, + }); + } + + async fetchDepositAddress (code, params = {}) { + throw new NotSupported (this.id + ' fetchDepositAddress not supported yet'); + } +}; + +},{"./base/errors":7,"./huobipro.js":92}],92:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, PermissionDenied, ExchangeNotAvailable, OnMaintenance, InvalidOrder, OrderNotFound, InsufficientFunds, ArgumentsRequired, BadSymbol, BadRequest, RequestTimeout, NetworkError } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class huobipro extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'huobipro', + 'name': 'Huobi Pro', + 'countries': [ 'CN' ], + 'rateLimit': 2000, + 'userAgent': this.userAgents['chrome39'], + 'version': 'v1', + 'accounts': undefined, + 'accountsById': undefined, + 'hostname': 'api.huobi.pro', // api.testnet.huobi.pro + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingLimits': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '60min', + '4h': '4hour', + '1d': '1day', + '1w': '1week', + '1M': '1mon', + '1y': '1year', + }, + 'urls': { + 'test': { + 'market': 'https://api.testnet.huobi.pro', + 'public': 'https://api.testnet.huobi.pro', + 'private': 'https://api.testnet.huobi.pro', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg', + 'api': { + 'market': 'https://{hostname}', + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + 'v2Public': 'https://{hostname}', + 'v2Private': 'https://{hostname}', + }, + 'www': 'https://www.huobi.com', + 'referral': 'https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3', + 'doc': 'https://huobiapi.github.io/docs/spot/v1/cn/', + 'fees': 'https://www.huobi.com/about/fee/', + }, + 'api': { + 'v2Public': { + 'get': [ + 'reference/currencies', + ], + }, + 'v2Private': { + 'get': [ + 'account/ledger', + 'account/withdraw/quota', + 'account/withdraw/address', // 提币地址查询(限母用户可用) + 'account/deposit/address', + 'reference/transact-fee-rate', + 'account/asset-valuation', // 获取账户资产估值 + 'point/account', // 点卡余额查询 + 'sub-user/user-list', // 获取子用户列表 + 'sub-user/user-state', // 获取特定子用户的用户状态 + 'sub-user/account-list', // 获取特定子用户的账户列表 + 'sub-user/deposit-address', // 子用户充币地址查询 + 'sub-user/query-deposit', // 子用户充币记录查询 + 'user/api-key', // 母子用户API key信息查询 + ], + 'post': [ + 'point/transfer', // 点卡划转 + 'sub-user/management', // 冻结/解冻子用户 + 'sub-user/creation', // 子用户创建 + 'sub-user/tradable-market', // 设置子用户交易权限 + 'sub-user/transferability', // 设置子用户资产转出权限 + 'sub-user/api-key-generation', // 子用户API key创建 + 'sub-user/api-key-modification', // 修改子用户API key + 'sub-user/api-key-deletion', // 删除子用户API key + ], + }, + 'market': { + 'get': [ + 'history/kline', // 获取K线数据 + 'detail/merged', // 获取聚合行情(Ticker) + 'depth', // 获取 Market Depth 数据 + 'trade', // 获取 Trade Detail 数据 + 'history/trade', // 批量获取最近的交易记录 + 'detail', // 获取 Market Detail 24小时成交量数据 + 'tickers', + ], + }, + 'public': { + 'get': [ + 'common/symbols', // 查询系统支持的所有交易对 + 'common/currencys', // 查询系统支持的所有币种 + 'common/timestamp', // 查询系统当前时间 + 'common/exchange', // order limits + 'settings/currencys', // ?language=en-US + ], + }, + 'private': { + 'get': [ + 'account/accounts', // 查询当前用户的所有账户(即account-id) + 'account/accounts/{id}/balance', // 查询指定账户的余额 + 'account/accounts/{sub-uid}', + 'account/history', + 'cross-margin/loan-info', + 'margin/loan-info', // 查询借币币息率及额度 + 'fee/fee-rate/get', + 'order/openOrders', + 'order/orders', + 'order/orders/{id}', // 查询某个订单详情 + 'order/orders/{id}/matchresults', // 查询某个订单的成交明细 + 'order/orders/getClientOrder', + 'order/history', // 查询当前委托、历史委托 + 'order/matchresults', // 查询当前成交、历史成交 + 'dw/withdraw-virtual/addresses', // 查询虚拟币提现地址(Deprecated) + 'query/deposit-withdraw', + 'margin/loan-info', + 'margin/loan-orders', // 借贷订单 + 'margin/accounts/balance', // 借贷账户详情 + 'cross-margin/loan-orders', // 查询借币订单 + 'cross-margin/accounts/balance', // 借币账户详情 + 'points/actions', + 'points/orders', + 'subuser/aggregate-balance', + 'stable-coin/exchange_rate', + 'stable-coin/quote', + ], + 'post': [ + 'account/transfer', // 资产划转(该节点为母用户和子用户进行资产划转的通用接口。) + 'futures/transfer', + 'order/batch-orders', + 'order/orders/place', // 创建并执行一个新订单 (一步下单, 推荐使用) + 'order/orders/submitCancelClientOrder', + 'order/orders/batchCancelOpenOrders', + 'order/orders', // 创建一个新的订单请求 (仅创建订单,不执行下单) + 'order/orders/{id}/place', // 执行一个订单 (仅执行已创建的订单) + 'order/orders/{id}/submitcancel', // 申请撤销一个订单请求 + 'order/orders/batchcancel', // 批量撤销订单 + 'dw/balance/transfer', // 资产划转 + 'dw/withdraw/api/create', // 申请提现虚拟币 + 'dw/withdraw-virtual/create', // 申请提现虚拟币 + 'dw/withdraw-virtual/{id}/place', // 确认申请虚拟币提现(Deprecated) + 'dw/withdraw-virtual/{id}/cancel', // 申请取消提现虚拟币 + 'dw/transfer-in/margin', // 现货账户划入至借贷账户 + 'dw/transfer-out/margin', // 借贷账户划出至现货账户 + 'margin/orders', // 申请借贷 + 'margin/orders/{id}/repay', // 归还借贷 + 'cross-margin/transfer-in', // 资产划转 + 'cross-margin/transfer-out', // 资产划转 + 'cross-margin/orders', // 申请借币 + 'cross-margin/orders/{id}/repay', // 归还借币 + 'stable-coin/exchange', + 'subuser/transfer', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.002, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + // err-code + 'bad-request': BadRequest, + 'api-not-support-temp-addr': PermissionDenied, // {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null} + 'timeout': RequestTimeout, // {"ts":1571653730865,"status":"error","err-code":"timeout","err-msg":"Request Timeout"} + 'gateway-internal-error': ExchangeNotAvailable, // {"status":"error","err-code":"gateway-internal-error","err-msg":"Failed to load data. Try again later.","data":null} + 'account-frozen-balance-insufficient-error': InsufficientFunds, // {"status":"error","err-code":"account-frozen-balance-insufficient-error","err-msg":"trade account balance is not enough, left: `0.0027`","data":null} + 'invalid-amount': InvalidOrder, // eg "Paramemter `amount` is invalid." + 'order-limitorder-amount-min-error': InvalidOrder, // limit order amount error, min: `0.001` + 'order-limitorder-amount-max-error': InvalidOrder, // market order amount error, max: `1000000` + 'order-marketorder-amount-min-error': InvalidOrder, // market order amount error, min: `0.01` + 'order-limitorder-price-min-error': InvalidOrder, // limit order price error + 'order-limitorder-price-max-error': InvalidOrder, // limit order price error + 'order-orderstate-error': OrderNotFound, // canceling an already canceled order + 'order-queryorder-invalid': OrderNotFound, // querying a non-existent order + 'order-update-error': ExchangeNotAvailable, // undocumented error + 'api-signature-check-failed': AuthenticationError, + 'api-signature-not-valid': AuthenticationError, // {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: Incorrect Access key [Access key错误]","data":null} + 'base-record-invalid': OrderNotFound, // https://github.com/ccxt/ccxt/issues/5750 + 'base-symbol-trade-disabled': BadSymbol, // {"status":"error","err-code":"base-symbol-trade-disabled","err-msg":"Trading is disabled for this symbol","data":null} + 'base-symbol-error': BadSymbol, // {"status":"error","err-code":"base-symbol-error","err-msg":"The symbol is invalid","data":null} + 'system-maintenance': OnMaintenance, // {"status": "error", "err-code": "system-maintenance", "err-msg": "System is in maintenance!", "data": null} + // err-msg + 'invalid symbol': BadSymbol, // {"ts":1568813334794,"status":"error","err-code":"invalid-parameter","err-msg":"invalid symbol"} + 'symbol trade not open now': BadSymbol, // {"ts":1576210479343,"status":"error","err-code":"invalid-parameter","err-msg":"symbol trade not open now"} + }, + }, + 'options': { + // https://github.com/ccxt/ccxt/issues/5376 + 'fetchOrdersByStatesMethod': 'private_get_order_orders', // 'private_get_order_history' // https://github.com/ccxt/ccxt/pull/5392 + 'fetchOpenOrdersMethod': 'fetch_open_orders_v1', // 'fetch_open_orders_v2' // https://github.com/ccxt/ccxt/issues/5388 + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarketsMethod': 'publicGetCommonSymbols', + 'fetchBalanceMethod': 'privateGetAccountAccountsIdBalance', + 'createOrderMethod': 'privatePostOrderOrdersPlace', + 'language': 'en-US', + }, + 'commonCurrencies': { + // https://github.com/ccxt/ccxt/issues/6081 + // https://github.com/ccxt/ccxt/issues/3365 + // https://github.com/ccxt/ccxt/issues/2873 + 'GET': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol) + 'HOT': 'Hydro Protocol', // conflict with HOT (Holo) https://github.com/ccxt/ccxt/issues/4929 + // https://github.com/ccxt/ccxt/issues/7399 + // https://coinmarketcap.com/currencies/pnetwork/ + // https://coinmarketcap.com/currencies/penta/markets/ + // https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ + 'PNT': 'Penta', + }, + }); + } + + async fetchTradingLimits (symbols = undefined, params = {}) { + // this method should not be called directly, use loadTradingLimits () instead + // by default it will try load withdrawal fees of all currencies (with separate requests) + // however if you define symbols = [ 'ETH/BTC', 'LTC/BTC' ] in args it will only load those + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const result = {}; + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + result[symbol] = await this.fetchTradingLimitsById (this.marketId (symbol), params); + } + return result; + } + + async fetchTradingLimitsById (id, params = {}) { + const request = { + 'symbol': id, + }; + const response = await this.publicGetCommonExchange (this.extend (request, params)); + // + // { status: "ok", + // data: { symbol: "aidocbtc", + // 'buy-limit-must-less-than': 1.1, + // 'sell-limit-must-greater-than': 0.9, + // 'limit-order-must-greater-than': 1, + // 'limit-order-must-less-than': 5000000, + // 'market-buy-order-must-greater-than': 0.0001, + // 'market-buy-order-must-less-than': 100, + // 'market-sell-order-must-greater-than': 1, + // 'market-sell-order-must-less-than': 500000, + // 'circuit-break-when-greater-than': 10000, + // 'circuit-break-when-less-than': 10, + // 'market-sell-order-rate-must-less-than': 0.1, + // 'market-buy-order-rate-must-less-than': 0.1 } } + // + return this.parseTradingLimits (this.safeValue (response, 'data', {})); + } + + parseTradingLimits (limits, symbol = undefined, params = {}) { + // + // { symbol: "aidocbtc", + // 'buy-limit-must-less-than': 1.1, + // 'sell-limit-must-greater-than': 0.9, + // 'limit-order-must-greater-than': 1, + // 'limit-order-must-less-than': 5000000, + // 'market-buy-order-must-greater-than': 0.0001, + // 'market-buy-order-must-less-than': 100, + // 'market-sell-order-must-greater-than': 1, + // 'market-sell-order-must-less-than': 500000, + // 'circuit-break-when-greater-than': 10000, + // 'circuit-break-when-less-than': 10, + // 'market-sell-order-rate-must-less-than': 0.1, + // 'market-buy-order-rate-must-less-than': 0.1 } + // + return { + 'info': limits, + 'limits': { + 'amount': { + 'min': this.safeFloat (limits, 'limit-order-must-greater-than'), + 'max': this.safeFloat (limits, 'limit-order-must-less-than'), + }, + }, + }; + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['cost'], this.precisionMode); + } + + async fetchMarkets (params = {}) { + const method = this.options['fetchMarketsMethod']; + const response = await this[method] (params); + const markets = this.safeValue (response, 'data'); + const numMarkets = markets.length; + if (numMarkets < 1) { + throw new NetworkError (this.id + ' publicGetCommonSymbols returned empty response: ' + this.json (markets)); + } + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const baseId = this.safeString (market, 'base-currency'); + const quoteId = this.safeString (market, 'quote-currency'); + const id = baseId + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amount-precision'), + 'price': this.safeInteger (market, 'price-precision'), + 'cost': this.safeInteger (market, 'value-precision'), + }; + const maker = (base === 'OMG') ? 0 : 0.2 / 100; + const taker = (base === 'OMG') ? 0 : 0.2 / 100; + const minAmount = this.safeFloat (market, 'min-order-amt', Math.pow (10, -precision['amount'])); + const maxAmount = this.safeFloat (market, 'max-order-amt'); + const minCost = this.safeFloat (market, 'min-order-value', 0); + const state = this.safeString (market, 'state'); + const active = (state === 'online'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'taker': taker, + 'maker': maker, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "amount": 26228.672978342216, + // "open": 9078.95, + // "close": 9146.86, + // "high": 9155.41, + // "id": 209988544334, + // "count": 265846, + // "low": 8988.0, + // "version": 209988544334, + // "ask": [ 9146.87, 0.156134 ], + // "vol": 2.3822168242201668E8, + // "bid": [ 9146.86, 0.080758 ], + // } + // + // fetchTickers + // { + // symbol: "bhdht", + // open: 2.3938, + // high: 2.4151, + // low: 2.3323, + // close: 2.3909, + // amount: 628.992, + // vol: 1493.71841095, + // count: 2088, + // bid: 2.3643, + // bidSize: 0.7136, + // ask: 2.4061, + // askSize: 0.4156 + // } + // + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (ticker, 'ts'); + let bid = undefined; + let bidVolume = undefined; + let ask = undefined; + let askVolume = undefined; + if ('bid' in ticker) { + if (Array.isArray (ticker['bid'])) { + bid = this.safeFloat (ticker['bid'], 0); + bidVolume = this.safeFloat (ticker['bid'], 1); + } else { + bid = this.safeFloat (ticker, 'bid'); + bidVolume = this.safeValue (ticker, 'bidSize'); + } + } + if ('ask' in ticker) { + if (Array.isArray (ticker['ask'])) { + ask = this.safeFloat (ticker['ask'], 0); + askVolume = this.safeFloat (ticker['ask'], 1); + } else { + ask = this.safeFloat (ticker, 'ask'); + askVolume = this.safeValue (ticker, 'askSize'); + } + } + const open = this.safeFloat (ticker, 'open'); + const close = this.safeFloat (ticker, 'close'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((open !== undefined) && (close !== undefined)) { + change = close - open; + average = this.sum (open, close) / 2; + if ((close !== undefined) && (close > 0)) { + percentage = (change / open) * 100; + } + } + const baseVolume = this.safeFloat (ticker, 'amount'); + const quoteVolume = this.safeFloat (ticker, 'vol'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': bid, + 'bidVolume': bidVolume, + 'ask': ask, + 'askVolume': askVolume, + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'type': 'step0', + }; + const response = await this.marketGetDepth (this.extend (request, params)); + // + // { + // "status": "ok", + // "ch": "market.btcusdt.depth.step0", + // "ts": 1583474832790, + // "tick": { + // "bids": [ + // [ 9100.290000000000000000, 0.200000000000000000 ], + // [ 9099.820000000000000000, 0.200000000000000000 ], + // [ 9099.610000000000000000, 0.205000000000000000 ], + // ], + // "asks": [ + // [ 9100.640000000000000000, 0.005904000000000000 ], + // [ 9101.010000000000000000, 0.287311000000000000 ], + // [ 9101.030000000000000000, 0.012121000000000000 ], + // ], + // "ts":1583474832008, + // "version":104999698780 + // } + // } + // + if ('tick' in response) { + if (!response['tick']) { + throw new BadSymbol (this.id + ' fetchOrderBook() returned empty response: ' + this.json (response)); + } + const tick = this.safeValue (response, 'tick'); + const timestamp = this.safeInteger (tick, 'ts', this.safeInteger (response, 'ts')); + const result = this.parseOrderBook (tick, timestamp); + result['nonce'] = this.safeInteger (tick, 'version'); + return result; + } + throw new ExchangeError (this.id + ' fetchOrderBook() returned unrecognized response: ' + this.json (response)); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetDetailMerged (this.extend (request, params)); + // + // { + // "status": "ok", + // "ch": "market.btcusdt.detail.merged", + // "ts": 1583494336669, + // "tick": { + // "amount": 26228.672978342216, + // "open": 9078.95, + // "close": 9146.86, + // "high": 9155.41, + // "id": 209988544334, + // "count": 265846, + // "low": 8988.0, + // "version": 209988544334, + // "ask": [ 9146.87, 0.156134 ], + // "vol": 2.3822168242201668E8, + // "bid": [ 9146.86, 0.080758 ], + // } + // } + // + const ticker = this.parseTicker (response['tick'], market); + const timestamp = this.safeValue (response, 'ts'); + ticker['timestamp'] = timestamp; + ticker['datetime'] = this.iso8601 (timestamp); + return ticker; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.marketGetTickers (params); + const tickers = this.safeValue (response, 'data'); + const timestamp = this.safeInteger (response, 'ts'); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const marketId = this.safeString (tickers[i], 'symbol'); + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + const ticker = this.parseTicker (tickers[i], market); + ticker['timestamp'] = timestamp; + ticker['datetime'] = this.iso8601 (timestamp); + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "amount": 0.010411000000000000, + // "trade-id": 102090736910, + // "ts": 1583497692182, + // "id": 10500517034273194594947, + // "price": 9096.050000000000000000, + // "direction": "sell" + // } + // + // fetchMyTrades (private) + // + // { + // 'symbol': 'swftcbtc', + // 'fee-currency': 'swftc', + // 'filled-fees': '0', + // 'source': 'spot-api', + // 'id': 83789509854000, + // 'type': 'buy-limit', + // 'order-id': 83711103204909, + // 'filled-points': '0.005826843283532154', + // 'fee-deduct-currency': 'ht', + // 'filled-amount': '45941.53', + // 'price': '0.0000001401', + // 'created-at': 1597933260729, + // 'match-id': 100087455560, + // 'role': 'maker', + // 'trade-id': 100050305348 + // }, + // + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger2 (trade, 'ts', 'created-at'); + const order = this.safeString (trade, 'order-id'); + let side = this.safeString (trade, 'direction'); + let type = this.safeString (trade, 'type'); + if (type !== undefined) { + const typeParts = type.split ('-'); + side = typeParts[0]; + type = typeParts[1]; + } + const takerOrMaker = this.safeString (trade, 'role'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'filled-amount', 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + let fee = undefined; + let feeCost = this.safeFloat (trade, 'filled-fees'); + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee-currency')); + } + const filledPoints = this.safeFloat (trade, 'filled-points'); + if (filledPoints !== undefined) { + if ((feeCost === undefined) || (feeCost === 0.0)) { + feeCost = filledPoints; + feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee-deduct-currency')); + } + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const tradeId = this.safeString2 (trade, 'trade-id', 'tradeId'); + const id = this.safeString (trade, 'id', tradeId); + return { + 'id': id, + 'info': trade, + 'order': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['size'] = limit; // 1-100 orders, default is 100 + } + if (since !== undefined) { + request['start-date'] = this.ymd (since); // maximum query window size is 2 days, query window shift should be within past 120 days + } + const response = await this.privateGetOrderMatchresults (this.extend (request, params)); + const trades = this.parseTrades (response['data'], market, since, limit); + return trades; + } + + async fetchTrades (symbol, since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.marketGetHistoryTrade (this.extend (request, params)); + // + // { + // "status": "ok", + // "ch": "market.btcusdt.trade.detail", + // "ts": 1583497692365, + // "data": [ + // { + // "id": 105005170342, + // "ts": 1583497692182, + // "data": [ + // { + // "amount": 0.010411000000000000, + // "trade-id": 102090736910, + // "ts": 1583497692182, + // "id": 10500517034273194594947, + // "price": 9096.050000000000000000, + // "direction": "sell" + // } + // ] + // }, + // // ... + // ] + // } + // + const data = this.safeValue (response, 'data'); + let result = []; + for (let i = 0; i < data.length; i++) { + const trades = this.safeValue (data[i], 'data', []); + for (let j = 0; j < trades.length; j++) { + const trade = this.parseTrade (trades[j], market); + result.push (trade); + } + } + result = this.sortBy (result, 'timestamp'); + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "amount":1.2082, + // "open":0.025096, + // "close":0.025095, + // "high":0.025096, + // "id":1591515300, + // "count":6, + // "low":0.025095, + // "vol":0.0303205097 + // } + // + return [ + this.safeTimestamp (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'amount'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.marketGetHistoryKline (this.extend (request, params)); + // + // { + // "status":"ok", + // "ch":"market.ethbtc.kline.1min", + // "ts":1591515374371, + // "data":[ + // {"amount":0.0,"open":0.025095,"close":0.025095,"high":0.025095,"id":1591515360,"count":0,"low":0.025095,"vol":0.0}, + // {"amount":1.2082,"open":0.025096,"close":0.025095,"high":0.025096,"id":1591515300,"count":6,"low":0.025095,"vol":0.0303205097}, + // {"amount":0.0648,"open":0.025096,"close":0.025096,"high":0.025096,"id":1591515240,"count":2,"low":0.025096,"vol":0.0016262208}, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchAccounts (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountAccounts (params); + return response['data']; + } + + async fetchCurrencies (params = {}) { + const request = { + 'language': this.options['language'], + }; + const response = await this.publicGetSettingsCurrencys (this.extend (request, params)); + const currencies = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + // + // { name: "ctxc", + // 'display-name': "CTXC", + // 'withdraw-precision': 8, + // 'currency-type': "eth", + // 'currency-partition': "pro", + // 'support-sites': null, + // 'otc-enable': 0, + // 'deposit-min-amount': "2", + // 'withdraw-min-amount': "4", + // 'show-precision': "8", + // weight: "2988", + // visible: true, + // 'deposit-desc': "Please don’t deposit any other digital assets except CTXC t…", + // 'withdraw-desc': "Minimum withdrawal amount: 4 CTXC. !>_ 0)) { + average = cost / filled; + } + } + const feeCost = this.safeFloat2 (order, 'filled-fees', 'field-fees'); // typo in their API, filled fees + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = (side === 'sell') ? market['quote'] : market['base']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + const request = { + 'account-id': this.accounts[0]['id'], + 'symbol': market['id'], + 'type': side + '-' + type, + }; + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " market buy order requires price argument to calculate cost (total amount of quote currency to spend for buying, amount * price). To switch off this warning exception and specify cost in the amount argument, set .options['createMarketBuyOrderRequiresPrice'] = false. Make sure you know what you're doing."); + } else { + // despite that cost = amount * price is in quote currency and should have quote precision + // the exchange API requires the cost supplied in 'amount' to be of base precision + // more about it here: + // https://github.com/ccxt/ccxt/pull/4395 + // https://github.com/ccxt/ccxt/issues/7611 + // we use amountToPrecision here because the exchange requires cost in base precision + request['amount'] = this.costToPrecision (symbol, parseFloat (amount) * parseFloat (price)); + } + } else { + request['amount'] = this.costToPrecision (symbol, amount); + } + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if (type === 'limit' || type === 'ioc' || type === 'limit-maker') { + request['price'] = this.priceToPrecision (symbol, price); + } + const method = this.options['createOrderMethod']; + const response = await this[method] (this.extend (request, params)); + const timestamp = this.milliseconds (); + const id = this.safeString (response, 'data'); + return { + 'info': response, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const response = await this.privatePostOrderOrdersIdSubmitcancel ({ 'id': id }); + // + // let response = { + // 'status': 'ok', + // 'data': '10138899000', + // }; + // + return this.extend (this.parseOrder (response), { + 'id': id, + 'status': 'canceled', + }); + } + + currencyToPrecision (currency, fee) { + return this.decimalToPrecision (fee, 0, this.currencies[currency]['precision']); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let key = 'quote'; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (this.currencyToPrecision (market[key], cost)), + }; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // currency: "eth", + // address: "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", + // addressTag: "", + // chain: "eth" + // } + // + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'addressTag'); + const currencyId = this.safeString (depositAddress, 'currency'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.v2PrivateGetAccountDepositAddress (this.extend (request, params)); + // + // { + // code: 200, + // data: [ + // { + // currency: "eth", + // address: "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", + // addressTag: "", + // chain: "eth" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseDepositAddress (this.safeValue (data, 0, {}), currency); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (limit === undefined || limit > 100) { + limit = 100; + } + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = { + 'type': 'deposit', + 'from': 0, // From 'id' ... if you want to get results after a particular transaction id, pass the id in params.from + }; + if (currency !== undefined) { + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; // max 100 + } + const response = await this.privateGetQueryDepositWithdraw (this.extend (request, params)); + // return response + return this.parseTransactions (response['data'], currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (limit === undefined || limit > 100) { + limit = 100; + } + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = { + 'type': 'withdraw', + 'from': 0, // From 'id' ... if you want to get results after a particular transaction id, pass the id in params.from + }; + if (currency !== undefined) { + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; // max 100 + } + const response = await this.privateGetQueryDepositWithdraw (this.extend (request, params)); + // return response + return this.parseTransactions (response['data'], currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // 'id': 8211029, + // 'type': 'deposit', + // 'currency': 'eth', + // 'chain': 'eth', + // 'tx-hash': 'bd315....', + // 'amount': 0.81162421, + // 'address': '4b8b....', + // 'address-tag': '', + // 'fee': 0, + // 'state': 'safe', + // 'created-at': 1542180380965, + // 'updated-at': 1542180788077 + // } + // + // fetchWithdrawals + // + // { + // 'id': 6908275, + // 'type': 'withdraw', + // 'currency': 'btc', + // 'chain': 'btc', + // 'tx-hash': 'c1a1a....', + // 'amount': 0.80257005, + // 'address': '1QR....', + // 'address-tag': '', + // 'fee': 0.0005, + // 'state': 'confirmed', + // 'created-at': 1552107295685, + // 'updated-at': 1552108032859 + // } + // + const timestamp = this.safeInteger (transaction, 'created-at'); + const updated = this.safeInteger (transaction, 'updated-at'); + const code = this.safeCurrencyCode (this.safeString (transaction, 'currency')); + let type = this.safeString (transaction, 'type'); + if (type === 'withdraw') { + type = 'withdrawal'; + } + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const tag = this.safeString (transaction, 'address-tag'); + let feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'tx-hash'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'address'), + 'tag': tag, + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'currency': code, + 'cost': feeCost, + 'rate': undefined, + }, + }; + } + + parseTransactionStatus (status) { + const statuses = { + // deposit statuses + 'unknown': 'failed', + 'confirming': 'pending', + 'confirmed': 'ok', + 'safe': 'ok', + 'orphan': 'failed', + // withdrawal statuses + 'submitted': 'pending', + 'canceled': 'canceled', + 'reexamine': 'pending', + 'reject': 'failed', + 'pass': 'pending', + 'wallet-reject': 'failed', + // 'confirmed': 'ok', // present in deposit statuses + 'confirm-error': 'failed', + 'repealed': 'failed', + 'wallet-transfer': 'pending', + 'pre-transfer': 'pending', + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currency (code); + const request = { + 'address': address, // only supports existing addresses in your withdraw address list + 'amount': amount, + 'currency': currency['id'].toLowerCase (), + }; + if (tag !== undefined) { + request['addr-tag'] = tag; // only for XRP? + } + const response = await this.privatePostDwWithdrawApiCreate (this.extend (request, params)); + const id = this.safeString (response, 'data'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/'; + if (api === 'market') { + url += api; + } else if ((api === 'public') || (api === 'private')) { + url += this.version; + } else if ((api === 'v2Public') || (api === 'v2Private')) { + url += 'v2'; + } + url += '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'private' || api === 'v2Private') { + this.checkRequiredCredentials (); + const timestamp = this.ymdhms (this.milliseconds (), 'T'); + let request = { + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'AccessKeyId': this.apiKey, + 'Timestamp': timestamp, + }; + if (method !== 'POST') { + request = this.extend (request, query); + } + request = this.keysort (request); + let auth = this.urlencode (request); + // unfortunately, PHP demands double quotes for the escaped newline symbol + // eslint-disable-next-line quotes + const payload = [ method, this.hostname, url, auth ].join ("\n"); + const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64'); + auth += '&' + this.urlencode ({ 'Signature': signature }); + url += '?' + auth; + if (method === 'POST') { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } else { + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + } else { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }) + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('status' in response) { + // + // {"status":"error","err-code":"order-limitorder-amount-min-error","err-msg":"limit order amount error, min: `0.001`","data":null} + // + const status = this.safeString (response, 'status'); + if (status === 'error') { + const code = this.safeString (response, 'err-code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + const message = this.safeString (response, 'err-msg'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],93:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const huobipro = require ('./huobipro.js'); + +// --------------------------------------------------------------------------- + +module.exports = class huobiru extends huobipro { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'huobiru', + 'name': 'Huobi Russia', + 'countries': [ 'RU' ], + 'hostname': 'www.huobi.com.ru', + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/52978816-e8552e00-33e3-11e9-98ed-845acfece834.jpg', + 'api': { + 'market': 'https://{hostname}/api', + 'public': 'https://{hostname}/api', + 'private': 'https://{hostname}/api', + }, + 'www': 'https://www.huobi.com.ru/ru-ru', + 'referral': 'https://www.huobi.com.ru/invite?invite_code=esc74', + 'doc': 'https://github.com/cloudapidoc/API_Docs_en', + 'fees': 'https://www.huobi.com.ru/ru-ru/about/fee/', + }, + }); + } +}; + +},{"./huobipro.js":92}],94:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class ice3x extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'ice3x', + 'name': 'ICE3X', + 'countries': [ 'ZA' ], // South Africa + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460809-1dd06c00-c616-11ea-98ad-7d5e1cb7fcdd.jpg', + 'api': 'https://ice3x.com/api', + 'www': 'https://ice3x.com', // 'https://ice3x.co.za', + 'doc': 'https://ice3x.co.za/ice-cubed-bitcoin-exchange-api-documentation-1-june-2017', + 'fees': [ + 'https://help.ice3.com/support/solutions/articles/11000033293-trading-fees', + 'https://help.ice3.com/support/solutions/articles/11000033288-fees-explained', + 'https://help.ice3.com/support/solutions/articles/11000008131-what-are-your-fiat-deposit-and-withdrawal-fees-', + 'https://help.ice3.com/support/solutions/articles/11000033289-deposit-fees', + ], + 'referral': 'https://ice3x.com?ref=14341802', + }, + 'api': { + 'public': { + 'get': [ + 'currency/list', + 'currency/info', + 'pair/list', + 'pair/info', + 'stats/marketdepthfull', + 'stats/marketdepthbtcav', + 'stats/marketdepth', + 'orderbook/info', + 'trade/list', + 'trade/info', + ], + }, + 'private': { + 'post': [ + 'balance/list', + 'balance/info', + 'order/new', + 'order/cancel', + 'order/list', + 'order/info', + 'trade/list', + 'trade/info', + 'transaction/list', + 'transaction/info', + 'invoice/list', + 'invoice/info', + 'invoice/pdf', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.005, + 'taker': 0.005, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencyList (params); + const currencies = response['response']['entities']; + const precision = this.precision['amount']; + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currency_id'); + const currencyId = this.safeString (currency, 'iso'); + const code = this.safeCurrencyCode (currencyId); + result[code] = { + 'id': id, + 'code': code, + 'name': currency['name'], + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': currency, + 'fee': undefined, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + if (this.currencies_by_id === undefined) { + this.currencies = await this.fetchCurrencies (); + this.currencies_by_id = this.indexBy (this.currencies, 'id'); + } + const response = await this.publicGetPairList (params); + const markets = this.safeValue (response['response'], 'entities'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'pair_id'); + const baseId = this.safeString (market, 'currency_id_from'); + const quoteId = this.safeString (market, 'currency_id_to'); + const baseCurrency = this.currencies_by_id[baseId]; + const quoteCurrency = this.currencies_by_id[quoteId]; + const base = baseCurrency['code']; + const quote = quoteCurrency['code']; + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'info': market, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + const symbol = market['symbol']; + const last = this.safeFloat (ticker, 'last_price'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max'), + 'low': this.safeFloat (ticker, 'min'), + 'bid': this.safeFloat (ticker, 'max_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'min_ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'vol'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + }; + const response = await this.publicGetStatsMarketdepthfull (this.extend (request, params)); + const ticker = this.safeValue (response['response'], 'entity'); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetStatsMarketdepthfull (params); + const tickers = this.safeValue (response['response'], 'entities'); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const marketId = this.safeString (ticker, 'pair_id'); + const market = this.safeValue (this.marketsById, marketId); + if (market !== undefined) { + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair_id': this.marketId (symbol), + }; + if (limit !== undefined) { + const type = this.safeString (params, 'type'); + if ((type !== 'ask') && (type !== 'bid')) { + // eslint-disable-next-line quotes + throw new ArgumentsRequired (this.id + " fetchOrderBook requires an exchange-specific extra 'type' param ('bid' or 'ask') when used with a limit"); + } else { + request['items_per_page'] = limit; + } + } + const response = await this.publicGetOrderbookInfo (this.extend (request, params)); + const orderbook = this.safeValue (response['response'], 'entities'); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'amount'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'created'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, 'trade_id'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + }; + const response = await this.publicGetTradeList (this.extend (request, params)); + const trades = this.safeValue (response['response'], 'entities'); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalanceList (params); + const result = { 'info': response }; + const balances = this.safeValue (response['response'], 'entities', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + // currency ids are numeric strings + const currencyId = this.safeString (balance, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + const pairId = this.safeInteger (order, 'pair_id'); + let symbol = undefined; + if (pairId && !market && (pairId in this.marketsById)) { + market = this.marketsById[pairId]; + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (order, 'created'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + let status = this.safeInteger (order, 'active'); + let remaining = this.safeFloat (order, 'remaining'); + let filled = undefined; + if (status === 1) { + status = 'open'; + } else { + status = 'closed'; + remaining = 0; + filled = amount; + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + }; + if (market !== undefined) { + fee['currency'] = market['quote']; + } + } + return { + 'id': this.safeString (order, 'order_id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': this.safeString (order, 'type'), + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + 'type': side, + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostOrderNew (this.extend (request, params)); + const order = this.parseOrder ({ + 'order_id': response['response']['entity']['order_id'], + 'created': this.seconds (), + 'active': 1, + 'type': side, + 'price': price, + 'volume': amount, + 'remaining': amount, + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.privatePostOrderCancel (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order _id': id, + }; + const response = await this.privatePostOrderInfo (this.extend (request, params)); + const data = this.safeValue (response, 'response', {}); + const order = this.safeValue (data, 'entity'); + return this.parseOrder (order); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrderList (params); + const data = this.safeValue (response, 'response', {}); + const orders = this.safeValue (data, 'entities', []); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + }; + if (limit !== undefined) { + request['items_per_page'] = limit; + } + if (since !== undefined) { + request['date_from'] = parseInt (since / 1000); + } + const response = await this.privatePostTradeList (this.extend (request, params)); + const data = this.safeValue (response, 'response', {}); + const trades = this.safeValue (data, 'entities', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_id': currency['id'], + }; + const response = await this.privatePostBalanceInfo (this.extend (request, params)); + const data = this.safeValue (response, 'response', {}); + const balance = this.safeValue (data, 'entity', {}); + const address = this.safeString (balance, 'address'); + const status = address ? 'ok' : 'none'; + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'status': status, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'nonce': this.nonce (), + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const errors = this.safeValue (response, 'errors'); + const data = this.safeValue (response, 'response'); + if (errors || !data) { + const authErrorKeys = [ 'Key', 'user_id', 'Sign' ]; + for (let i = 0; i < authErrorKeys.length; i++) { + const errorKey = authErrorKeys[i]; + const errorMessage = this.safeString (errors, errorKey); + if (!errorMessage) { + continue; + } + if (errorKey === 'user_id' && errorMessage.indexOf ('authorization') < 0) { + continue; + } + throw new AuthenticationError (errorMessage); + } + throw new ExchangeError (this.json (errors)); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],95:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { PAD_WITH_ZERO } = require ('./base/functions/number'); +const { InvalidOrder, InsufficientFunds, ExchangeError, ExchangeNotAvailable, DDoSProtection, BadRequest, NotSupported, InvalidAddress, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class idex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'idex', + 'name': 'IDEX', + 'countries': [ 'US' ], + 'rateLimit': 1500, + 'version': 'v2', + 'certified': true, + 'pro': true, + 'requiresWeb3': true, + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchOrders': false, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'test': { + 'public': 'https://api-sandbox.idex.io', + 'private': 'https://api-sandbox.idex.io', + }, + 'logo': 'https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg', + 'api': { + 'public': 'https://api.idex.io', + 'private': 'https://api.idex.io', + }, + 'www': 'https://idex.io', + 'doc': [ + 'https://docs.idex.io/', + ], + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'time', + 'exchange', + 'assets', + 'markets', + 'tickers', + 'candles', + 'trades', + 'orderbook', + 'wsToken', + ], + }, + 'private': { + 'get': [ + 'user', + 'wallets', + 'balances', + 'orders', + 'fills', + 'deposits', + 'withdrawals', + ], + 'post': [ + 'wallets', + 'orders', + 'orders/test', + 'withdrawals', + ], + 'delete': [ + 'orders', + ], + }, + }, + 'options': { + 'defaultTimeInForce': 'gtc', + 'defaultSelfTradePrevention': 'cn', + }, + 'exceptions': { + 'INVALID_ORDER_QUANTITY': InvalidOrder, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': DDoSProtection, + 'INVALID_PARAMETER': BadRequest, + 'WALLET_NOT_ASSOCIATED': InvalidAddress, + 'INVALID_WALLET_SIGNATURE': AuthenticationError, + }, + 'requiredCredentials': { + 'walletAddress': true, + 'privateKey': true, + 'apiKey': true, + 'secret': true, + }, + 'paddingMode': PAD_WITH_ZERO, + 'commonCurrencies': {}, + 'requireCredentials': { + 'privateKey': true, + 'apiKey': true, + 'secret': true, + }, + }); + } + + async fetchMarkets (params = {}) { + // [ + // { + // market: 'DIL-ETH', + // status: 'active', + // baseAsset: 'DIL', + // baseAssetPrecision: 8, + // quoteAsset: 'ETH', + // quoteAssetPrecision: 8 + // }, ... + // ] + const response = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const marketId = this.safeString (entry, 'market'); + const baseId = this.safeString (entry, 'baseAsset'); + const quoteId = this.safeString (entry, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const basePrecision = this.safeInteger (entry, 'baseAssetPrecision'); + const quotePrecision = this.safeInteger (entry, 'quoteAssetPrecision'); + const status = this.safeString (entry, 'status'); + const active = status === 'active'; + const precision = { + 'amount': basePrecision, + 'price': quotePrecision, + }; + result.push ({ + 'symbol': symbol, + 'id': marketId, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': entry, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + // [ + // { + // market: 'DIL-ETH', + // time: 1598367493008, + // open: '0.09695361', + // high: '0.10245881', + // low: '0.09572507', + // close: '0.09917079', + // closeQuantity: '0.71320950', + // baseVolume: '309.17380612', + // quoteVolume: '30.57633981', + // percentChange: '2.28', + // numTrades: 205, + // ask: '0.09910476', + // bid: '0.09688340', + // sequence: 3902 + // } + // ] + const response = await this.publicGetTickers (this.extend (request, params)); + const ticker = this.safeValue (response, 0); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + // [ + // { + // market: 'DIL-ETH', + // time: 1598367493008, + // open: '0.09695361', + // high: '0.10245881', + // low: '0.09572507', + // close: '0.09917079', + // closeQuantity: '0.71320950', + // baseVolume: '309.17380612', + // quoteVolume: '30.57633981', + // percentChange: '2.28', + // numTrades: 205, + // ask: '0.09910476', + // bid: '0.09688340', + // sequence: 3902 + // }, ... + // ] + const response = await this.publicGetTickers (params); + return this.parseTickers (response, symbols); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // { + // market: 'DIL-ETH', + // time: 1598367493008, + // open: '0.09695361', + // high: '0.10245881', + // low: '0.09572507', + // close: '0.09917079', + // closeQuantity: '0.71320950', + // baseVolume: '309.17380612', + // quoteVolume: '30.57633981', + // percentChange: '2.28', + // numTrades: 205, + // ask: '0.09910476', + // bid: '0.09688340', + // sequence: 3902 + // } + const marketId = this.safeString (ticker, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const baseVolume = this.safeFloat (ticker, 'baseVolume'); + const quoteVolume = this.safeFloat (ticker, 'quoteVolume'); + const timestamp = this.safeInteger (ticker, 'time'); + const open = this.safeFloat (ticker, 'open'); + const high = this.safeFloat (ticker, 'high'); + const low = this.safeFloat (ticker, 'low'); + const close = this.safeFloat (ticker, 'close'); + const ask = this.safeFloat (ticker, 'ask'); + const bid = this.safeFloat (ticker, 'bid'); + let percentage = this.safeFloat (ticker, 'percentChange'); + if (percentage !== undefined) { + percentage = 1 + percentage / 100; + } + let change = undefined; + if ((close !== undefined) && (open !== undefined)) { + change = close - open; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'interval': timeframe, + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetCandles (this.extend (request, params)); + if (Array.isArray (response)) { + // [ + // { + // start: 1598345580000, + // open: '0.09771286', + // high: '0.09771286', + // low: '0.09771286', + // close: '0.09771286', + // volume: '1.45340410', + // sequence: 3853 + // }, ... + // ] + return this.parseOHLCVs (response, market, timeframe, since, limit); + } else { + // {"nextTime":1595536440000} + return []; + } + } + + parseOHLCV (ohlcv, market = undefined) { + // { + // start: 1598345580000, + // open: '0.09771286', + // high: '0.09771286', + // low: '0.09771286', + // close: '0.09771286', + // volume: '1.45340410', + // sequence: 3853 + // } + const timestamp = this.safeInteger (ohlcv, 'start'); + const open = this.safeFloat (ohlcv, 'open'); + const high = this.safeFloat (ohlcv, 'high'); + const low = this.safeFloat (ohlcv, 'low'); + const close = this.safeFloat (ohlcv, 'close'); + const volume = this.safeFloat (ohlcv, 'volume'); + return [ timestamp, open, high, low, close, volume ]; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + // [ + // { + // fillId: 'b5467d00-b13e-3fa9-8216-dd66735550fc', + // price: '0.09771286', + // quantity: '1.45340410', + // quoteQuantity: '0.14201627', + // time: 1598345638994, + // makerSide: 'buy', + // sequence: 3853 + // }, ... + // ] + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // public trades + // { + // fillId: 'b5467d00-b13e-3fa9-8216-dd66735550fc', + // price: '0.09771286', + // quantity: '1.45340410', + // quoteQuantity: '0.14201627', + // time: 1598345638994, + // makerSide: 'buy', + // sequence: 3853 + // } + // private trades + // { + // fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478762, + // makerSide: 'sell', + // sequence: 5053, + // market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // side: 'buy', + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65', + // txStatus: 'mined' + // } + const id = this.safeString (trade, 'fillId'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const cost = this.safeFloat (trade, 'quoteQuantity'); + const timestamp = this.safeInteger (trade, 'time'); + const marketId = this.safeString (trade, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + // this code handles the duality of public vs private trades + const makerSide = this.safeString (trade, 'makerSide'); + const oppositeSide = (makerSide === 'buy') ? 'sell' : 'buy'; + const side = this.safeString (trade, 'side', oppositeSide); + const takerOrMaker = this.safeString (trade, 'liquidity', 'taker'); + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeAsset'); + fee = { + 'cost': feeCost, + 'currency': this.safeCurrencyCode (feeCurrencyId), + }; + } + const orderId = this.safeString (trade, 'orderId'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'level': 2, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + // { + // sequence: 36416753, + // bids: [ + // [ '0.09672815', '8.22284267', 1 ], + // [ '0.09672814', '1.83685554', 1 ], + // [ '0.09672143', '4.10962617', 1 ], + // [ '0.09658884', '4.03863759', 1 ], + // [ '0.09653781', '3.35730684', 1 ], + // [ '0.09624660', '2.54163586', 1 ], + // [ '0.09617490', '1.93065030', 1 ] + // ], + // asks: [ + // [ '0.09910476', '3.22840154', 1 ], + // [ '0.09940587', '3.39796593', 1 ], + // [ '0.09948189', '4.25088898', 1 ], + // [ '0.09958362', '2.42195784', 1 ], + // [ '0.09974393', '4.25234367', 1 ], + // [ '0.09995250', '3.40192141', 1 ] + // ] + // } + const response = await this.publicGetOrderbook (this.extend (request, params)); + const nonce = this.safeInteger (response, 'sequence'); + return { + 'timestamp': undefined, + 'datetime': undefined, + 'nonce': nonce, + 'bids': this.parseSide (response, 'bids'), + 'asks': this.parseSide (response, 'asks'), + }; + } + + parseSide (book, side) { + const bookSide = this.safeValue (book, side, []); + const result = []; + for (let i = 0; i < bookSide.length; i++) { + const order = bookSide[i]; + const price = this.safeFloat (order, 0); + const amount = this.safeFloat (order, 1); + const orderCount = this.safeInteger (order, 2); + result.push ([ price, amount, orderCount ]); + } + const descending = side === 'bids'; + return this.sortBy (result, 0, descending); + } + + async fetchCurrencies (params = {}) { + // [ + // { + // name: 'Ether', + // symbol: 'ETH', + // contractAddress: '0x0000000000000000000000000000000000000000', + // assetDecimals: 18, + // exchangeDecimals: 8 + // }, .. + // ] + const response = await this.publicGetAssets (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const name = this.safeString (entry, 'name'); + const currencyId = this.safeString (entry, 'symbol'); + const precision = this.safeInteger (entry, 'exchangeDecimals'); + const code = this.safeCurrencyCode (currencyId); + const lot = Math.pow (-10, precision); + result[code] = { + 'id': currencyId, + 'code': code, + 'info': entry, + 'type': undefined, + 'name': name, + 'active': undefined, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { 'min': lot, 'max': undefined }, + 'price': { 'min': lot, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': lot, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + const nonce1 = this.uuidv1 (); + const request = { + 'nonce': nonce1, + 'wallet': this.walletAddress, + }; + // [ + // { + // asset: 'DIL', + // quantity: '0.00000000', + // availableForTrade: '0.00000000', + // locked: '0.00000000', + // usdValue: null + // }, ... + // ] + const extendedRequest = this.extend (request, params); + if (extendedRequest['wallet'] === undefined) { + throw new BadRequest (this.id + ' wallet is undefined, set this.walletAddress or "address" in params'); + } + let response = undefined; + try { + response = await this.privateGetBalances (extendedRequest); + } catch (e) { + if (e instanceof InvalidAddress) { + const walletAddress = extendedRequest['wallet']; + await this.associateWallet (walletAddress); + response = await this.privateGetBalances (extendedRequest); + } else { + throw e; + } + } + const result = { + 'info': response, + }; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const currencyId = this.safeString (entry, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const total = this.safeFloat (entry, 'quantity'); + const free = this.safeFloat (entry, 'availableForTrade'); + const used = this.safeFloat (entry, 'locked'); + result[code] = { + 'free': free, + 'used': used, + 'total': total, + }; + } + return this.parseBalance (result); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + let market = undefined; + const request = { + 'nonce': this.uuidv1 (), + 'wallet': this.walletAddress, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + // [ + // { + // fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478762, + // makerSide: 'sell', + // sequence: 5053, + // market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // side: 'buy', + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65', + // txStatus: 'mined' + // } + // ] + const extendedRequest = this.extend (request, params); + if (extendedRequest['wallet'] === undefined) { + throw new BadRequest (this.id + ' walletAddress is undefined, set this.walletAddress or "address" in params'); + } + let response = undefined; + try { + response = await this.privateGetFills (extendedRequest); + } catch (e) { + if (e instanceof InvalidAddress) { + const walletAddress = extendedRequest['wallet']; + await this.associateWallet (walletAddress); + response = await this.privateGetFills (extendedRequest); + } else { + throw e; + } + } + return this.parseTrades (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + const request = { + 'orderId': id, + }; + return await this.fetchOrdersHelper (symbol, undefined, undefined, this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'closed': false, + }; + return await this.fetchOrdersHelper (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'closed': true, + }; + return await this.fetchOrdersHelper (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrdersHelper (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'nonce': this.uuidv1 (), + 'wallet': this.walletAddress, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // fetchClosedOrders / fetchOpenOrders + // [ + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + // } + // ] + // fetchOrder + // { market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // wallet: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1', + // time: 1598873478650, + // status: 'filled', + // type: 'limit', + // side: 'buy', + // originalQuantity: '0.40000000', + // executedQuantity: '0.40000000', + // cumulativeQuoteQuantity: '0.03962396', + // avgExecutionPrice: '0.09905990', + // price: '1.00000000', + // fills: + // [ { fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478650, + // makerSide: 'sell', + // sequence: 5053, + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65', + // txStatus: 'mined' } ] } + if (Array.isArray (response)) { + return this.parseOrders (response, market, since, limit); + } else { + return this.parseOrder (response, market); + } + } + + parseOrderStatus (status) { + // https://docs.idex.io/#order-states-amp-lifecycle + const statuses = { + 'active': 'open', + 'partiallyFilled': 'open', + 'rejected': 'canceled', + 'filled': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + // } + // + const timestamp = this.safeInteger (order, 'time'); + const fills = this.safeValue (order, 'fills', []); + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + const marketId = this.safeString (order, 'market'); + const side = this.safeString (order, 'side'); + const symbol = this.safeSymbol (marketId, market, '-'); + const trades = this.parseTrades (fills, market); + const type = this.safeString (order, 'type'); + const amount = this.safeFloat (order, 'originalQuantity'); + const filled = this.safeFloat (order, 'executedQuantity'); + let remaining = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = amount - filled; + } + const average = this.safeFloat (order, 'avgExecutionPrice'); + const price = this.safeFloat (order, 'price', average); // for market orders + let cost = undefined; + if ((amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const rawStatus = this.safeString (order, 'status'); + const status = this.parseOrderStatus (rawStatus); + const fee = { + 'currency': undefined, + 'cost': undefined, + }; + let lastTrade = undefined; + for (let i = 0; i < trades.length; i++) { + lastTrade = trades[i]; + fee['currency'] = lastTrade['fee']['currency']; + fee['cost'] = this.sum (fee['cost'], lastTrade['fee']['cost']); + } + const lastTradeTimestamp = this.safeInteger (lastTrade, 'timestamp'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async associateWallet (walletAddress, params = {}) { + const nonce = this.uuidv1 (); + const noPrefix = this.remove0xPrefix (walletAddress); + const byteArray = [ + this.base16ToBinary (nonce), + this.base16ToBinary (noPrefix), + ]; + const binary = this.binaryConcatArray (byteArray); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + // { + // address: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1', + // totalPortfolioValueUsd: '0.00', + // time: 1598468353626 + // } + const request = { + 'parameters': { + 'nonce': nonce, + 'wallet': walletAddress, + }, + 'signature': signature, + }; + const result = await this.privatePostWallets (request); + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + // https://docs.idex.io/#create-order + this.checkRequiredCredentials (); + await this.loadMarkets (); + const market = this.market (symbol); + const nonce = this.uuidv1 (); + let typeEnum = undefined; + const stopLossTypeEnums = { + 'stopLoss': 3, + 'stopLossLimit': 4, + 'takeProfit': 5, + 'takeProfitLimit': 6, + }; + let stopPriceString = undefined; + if ((type === 'stopLossLimit') || (type === 'takeProfitLimit') || ('stopPrice' in params)) { + if (!('stopPrice' in params)) { + throw new BadRequest (this.id + ' stopPrice is a required parameter for ' + type + 'orders'); + } + stopPriceString = this.priceToPrecision (symbol, params['stopPrice']); + } + const limitTypeEnums = { + 'limit': 1, + 'limitMaker': 2, + }; + let priceString = undefined; + const typeLower = type.toLowerCase (); + const limitOrder = typeLower.indexOf ('limit') > -1; + if (type in limitTypeEnums) { + typeEnum = limitTypeEnums[type]; + priceString = this.priceToPrecision (symbol, price); + } else if (type in stopLossTypeEnums) { + typeEnum = stopLossTypeEnums[type]; + priceString = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + typeEnum = 0; + } else { + throw new BadRequest (this.id + ' ' + type + ' is not a valid order type'); + } + let amountEnum = 0; // base quantity + if ('quoteOrderQuantity' in params) { + if (type !== 'market') { + throw new NotSupported (this.id + ' quoteOrderQuantity is not supported for ' + type + ' orders, only supported for market orders'); + } + amountEnum = 1; + amount = this.safeFloat (params, 'quoteOrderQuantity'); + } + const sideEnum = (side === 'buy') ? 0 : 1; + const walletBytes = this.remove0xPrefix (this.walletAddress); + const orderVersion = 1; + const amountString = this.amountToPrecision (symbol, amount); + // https://docs.idex.io/#time-in-force + const timeInForceEnums = { + 'gtc': 0, + 'ioc': 2, + 'fok': 3, + }; + const defaultTimeInForce = this.safeString (this.options, 'defaultTimeInForce', 'gtc'); + const timeInForce = this.safeString (params, 'timeInForce', defaultTimeInForce); + let timeInForceEnum = undefined; + if (timeInForce in timeInForceEnums) { + timeInForceEnum = timeInForceEnums[timeInForce]; + } else { + const allOptions = Object.keys (timeInForceEnums); + const asString = allOptions.join (', '); + throw new BadRequest (this.id + ' ' + timeInForce + ' is not a valid timeInForce, please choose one of ' + asString); + } + // https://docs.idex.io/#self-trade-prevention + const selfTradePreventionEnums = { + 'dc': 0, + 'co': 1, + 'cn': 2, + 'cb': 3, + }; + const defaultSelfTradePrevention = this.safeString (this.options, 'defaultSelfTradePrevention', 'cn'); + const selfTradePrevention = this.safeString (params, 'selfTradePrevention', defaultSelfTradePrevention); + let selfTradePreventionEnum = undefined; + if (selfTradePrevention in selfTradePreventionEnums) { + selfTradePreventionEnum = selfTradePreventionEnums[selfTradePrevention]; + } else { + const allOptions = Object.keys (selfTradePreventionEnums); + const asString = allOptions.join (', '); + throw new BadRequest (this.id + ' ' + selfTradePrevention + ' is not a valid selfTradePrevention, please choose one of ' + asString); + } + const byteArray = [ + this.numberToBE (orderVersion, 1), + this.base16ToBinary (nonce), + this.base16ToBinary (walletBytes), + this.stringToBinary (this.encode (market['id'])), // TODO: refactor to remove either encode or stringToBinary + this.numberToBE (typeEnum, 1), + this.numberToBE (sideEnum, 1), + this.stringToBinary (this.encode (amountString)), + this.numberToBE (amountEnum, 1), + ]; + if (limitOrder) { + const encodedPrice = this.stringToBinary (this.encode (priceString)); + byteArray.push (encodedPrice); + } + if (type in stopLossTypeEnums) { + const encodedPrice = this.stringToBinary (this.encode (stopPriceString || priceString)); + byteArray.push (encodedPrice); + } + const clientOrderId = this.safeString (params, 'clientOrderId'); + if (clientOrderId !== undefined) { + byteArray.push (this.stringToBinary (this.encode (clientOrderId))); + } + const after = [ + this.numberToBE (timeInForceEnum, 1), + this.numberToBE (selfTradePreventionEnum, 1), + this.numberToBE (0, 8), // unused + ]; + const allBytes = this.arrayConcat (byteArray, after); + const binary = this.binaryConcatArray (allBytes); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + const request = { + 'parameters': { + 'nonce': nonce, + 'market': market['id'], + 'side': side, + 'type': type, + 'wallet': this.walletAddress, + 'timeInForce': timeInForce, + 'selfTradePrevention': selfTradePrevention, + }, + 'signature': signature, + }; + if (limitOrder) { + request['parameters']['price'] = priceString; + } + if (type in stopLossTypeEnums) { + request['parameters']['stopPrice'] = stopPriceString || priceString; + } + if (amountEnum === 0) { + request['parameters']['quantity'] = amountString; + } else { + request['parameters']['quoteOrderQuantity'] = amountString; + } + if (clientOrderId !== undefined) { + request['parameters']['clientOrderId'] = clientOrderId; + } + // { + // market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // wallet: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1', + // time: 1598873478650, + // status: 'filled', + // type: 'limit', + // side: 'buy', + // originalQuantity: '0.40000000', + // executedQuantity: '0.40000000', + // cumulativeQuoteQuantity: '0.03962396', + // price: '1.00000000', + // fills: [ + // { + // fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478650, + // makerSide: 'sell', + // sequence: 5053, + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txStatus: 'pending' + // } + // ], + // avgExecutionPrice: '0.09905990' + // } + // we don't use extend here because it is a signed endpoint + const response = await this.privatePostOrders (request); + return this.parseOrder (response, market); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + const nonce = this.uuidv1 (); + const amountString = this.currencyToPrecision (code, amount); + const currency = this.currency (code); + const walletBytes = this.remove0xPrefix (this.walletAddress); + const byteArray = [ + this.base16ToBinary (nonce), + this.base16ToBinary (walletBytes), + this.stringToBinary (this.encode (currency['id'])), + this.stringToBinary (this.encode (amountString)), + this.numberToBE (1, 1), // bool set to true + ]; + const binary = this.binaryConcatArray (byteArray); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + const request = { + 'parameters': { + 'nonce': nonce, + 'wallet': address, + 'asset': currency['id'], + 'quantity': amountString, + }, + 'signature': signature, + }; + // { + // withdrawalId: 'a61dcff0-ec4d-11ea-8b83-c78a6ecb3180', + // asset: 'ETH', + // assetContractAddress: '0x0000000000000000000000000000000000000000', + // quantity: '0.20000000', + // time: 1598962883190, + // fee: '0.00024000', + // txStatus: 'pending', + // txId: null + // } + const response = await this.privatePostWithdrawals (request); + const id = this.safeString (response, 'withdrawalId'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const nonce = this.uuidv1 (); + const walletBytes = this.remove0xPrefix (this.walletAddress); + const byteArray = [ + this.base16ToBinary (nonce), + this.base16ToBinary (walletBytes), + this.stringToBinary (this.encode (id)), + ]; + const binary = this.binaryConcatArray (byteArray); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + const request = { + 'parameters': { + 'nonce': nonce, + 'wallet': this.walletAddress, + 'orderId': id, + }, + 'signature': signature, + }; + // [ { orderId: '688336f0-ec50-11ea-9842-b332f8a34d0e' } ] + const response = await this.privateDeleteOrders (this.extend (request, params)); + const canceledOrder = this.safeValue (response, 0); + return this.parseOrder (canceledOrder, market); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + if (errorCode in this.exceptions) { + const Exception = this.exceptions[errorCode]; + throw new Exception (this.id + ' ' + message); + } + if (errorCode !== undefined) { + throw new ExchangeError (this.id + ' ' + message); + } + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + params = this.extend ({ + 'method': 'privateGetDeposits', + }, params); + return this.fetchTransactionsHelper (code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + params = this.extend ({ + 'method': 'privateGetWithdrawals', + }, params); + return this.fetchTransactionsHelper (code, since, limit, params); + } + + async fetchTransactionsHelper (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const nonce = this.uuidv1 (); + const request = { + 'nonce': nonce, + 'wallet': this.walletAddress, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + // [ + // { + // depositId: 'e9970cc0-eb6b-11ea-9e89-09a5ebc1f98e', + // asset: 'ETH', + // quantity: '1.00000000', + // txId: '0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142', + // txTime: 1598865853000, + // confirmationTime: 1598865930231 + // } + // ] + const method = params['method']; + params = this.omit (params, 'method'); + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'mined': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // fetchDeposits + // { + // depositId: 'e9970cc0-eb6b-11ea-9e89-09a5ebc1f98f', + // asset: 'ETH', + // quantity: '1.00000000', + // txId: '0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142', + // txTime: 1598865853000, + // confirmationTime: 1598865930231 + // } + // fetchWithdrwalas + // { + // withdrawalId: 'a62d8760-ec4d-11ea-9fa6-47904c19499b', + // asset: 'ETH', + // assetContractAddress: '0x0000000000000000000000000000000000000000', + // quantity: '0.20000000', + // time: 1598962883288, + // fee: '0.00024000', + // txId: '0x305e9cdbaa85ad029f50578d13d31d777c085de573ed5334d95c19116d8c03ce', + // txStatus: 'mined' + // } + let type = undefined; + if ('depositId' in transaction) { + type = 'deposit'; + } else if ('withdrawalId' in transaction) { + type = 'withdrawal'; + } + const id = this.safeString2 (transaction, 'depositId', 'withdrawId'); + const code = this.safeCurrencyCode (this.safeString (transaction, 'asset'), currency); + const amount = this.safeFloat (transaction, 'quantity'); + const txid = this.safeString (transaction, 'txId'); + const timestamp = this.safeInteger (transaction, 'txTime'); + let fee = undefined; + if ('fee' in transaction) { + fee = { + 'cost': this.safeFloat (transaction, 'fee'), + 'currency': 'ETH', + }; + } + const rawStatus = this.safeString (transaction, 'txStatus'); + const status = this.parseTransactionStatus (rawStatus); + const updated = this.safeInteger (transaction, 'confirmationTime'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': undefined, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const version = this.safeString (this.options, 'version', 'v1'); + let url = this.urls['api'][api] + '/' + version + '/' + path; + const keys = Object.keys (params); + const length = keys.length; + let query = undefined; + if (length > 0) { + if (method === 'GET') { + query = this.urlencode (params); + url = url + '?' + query; + } else { + body = this.json (params); + } + } + headers = { + 'Content-Type': 'application/json', + }; + if (this.apiKey !== undefined) { + headers['IDEX-API-Key'] = this.apiKey; + } + if (api === 'private') { + let payload = undefined; + if (method === 'GET') { + payload = query; + } else { + payload = body; + } + headers['IDEX-HMAC-Signature'] = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'hex'); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],96:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class independentreserve extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'independentreserve', + 'name': 'Independent Reserve', + 'countries': [ 'AU', 'NZ' ], // Australia, New Zealand + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182090-1e9e9080-c2ec-11ea-8e49-563db9a38f37.jpg', + 'api': { + 'public': 'https://api.independentreserve.com/Public', + 'private': 'https://api.independentreserve.com/Private', + }, + 'www': 'https://www.independentreserve.com', + 'doc': 'https://www.independentreserve.com/API', + }, + 'api': { + 'public': { + 'get': [ + 'GetValidPrimaryCurrencyCodes', + 'GetValidSecondaryCurrencyCodes', + 'GetValidLimitOrderTypes', + 'GetValidMarketOrderTypes', + 'GetValidOrderTypes', + 'GetValidTransactionTypes', + 'GetMarketSummary', + 'GetOrderBook', + 'GetAllOrders', + 'GetTradeHistorySummary', + 'GetRecentTrades', + 'GetFxRates', + ], + }, + 'private': { + 'post': [ + 'PlaceLimitOrder', + 'PlaceMarketOrder', + 'CancelOrder', + 'GetOpenOrders', + 'GetClosedOrders', + 'GetClosedFilledOrders', + 'GetOrderDetails', + 'GetAccounts', + 'GetTransactions', + 'GetDigitalCurrencyDepositAddress', + 'GetDigitalCurrencyDepositAddresses', + 'SynchDigitalCurrencyDepositAddressWithBlockchain', + 'WithdrawDigitalCurrency', + 'RequestFiatWithdrawal', + 'GetTrades', + 'GetBrokerageFees', + ], + }, + }, + 'fees': { + 'trading': { + 'taker': 0.5 / 100, + 'maker': 0.5 / 100, + 'percentage': true, + 'tierBased': false, + }, + }, + 'commonCurrencies': { + 'PLA': 'PlayChip', + }, + }); + } + + async fetchMarkets (params = {}) { + const baseCurrencies = await this.publicGetGetValidPrimaryCurrencyCodes (params); + const quoteCurrencies = await this.publicGetGetValidSecondaryCurrencyCodes (params); + const result = []; + for (let i = 0; i < baseCurrencies.length; i++) { + const baseId = baseCurrencies[i]; + const base = this.safeCurrencyCode (baseId); + for (let j = 0; j < quoteCurrencies.length; j++) { + const quoteId = quoteCurrencies[j]; + const quote = this.safeCurrencyCode (quoteId); + const id = baseId + '/' + quoteId; + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': id, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privatePostGetAccounts (params); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'CurrencyCode'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'AvailableBalance'); + account['total'] = this.safeFloat (balance, 'TotalBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + }; + const response = await this.publicGetGetOrderBook (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (response, 'CreatedTimestampUtc')); + return this.parseOrderBook (response, timestamp, 'BuyOrders', 'SellOrders', 'Price', 'Volume'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.parse8601 (this.safeString (ticker, 'CreatedTimestampUtc')); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'LastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'DayHighestPrice'), + 'low': this.safeFloat (ticker, 'DayLowestPrice'), + 'bid': this.safeFloat (ticker, 'CurrentHighestBidPrice'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'CurrentLowestOfferPrice'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'DayAvgPrice'), + 'baseVolume': this.safeFloat (ticker, 'DayVolumeXbtInSecondaryCurrrency'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + }; + const response = await this.publicGetGetMarketSummary (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // "OrderGuid": "c7347e4c-b865-4c94-8f74-d934d4b0b177", + // "CreatedTimestampUtc": "2014-09-23T12:39:34.3817763Z", + // "Type": "MarketBid", + // "VolumeOrdered": 5.0, + // "VolumeFilled": 5.0, + // "Price": null, + // "AvgPrice": 100.0, + // "ReservedAmount": 0.0, + // "Status": "Filled", + // "PrimaryCurrencyCode": "Xbt", + // "SecondaryCurrencyCode": "Usd" + // } + // + // fetchOpenOrders & fetchClosedOrders + // + // { + // "OrderGuid": "b8f7ad89-e4e4-4dfe-9ea3-514d38b5edb3", + // "CreatedTimestampUtc": "2020-09-08T03:04:18.616367Z", + // "OrderType": "LimitOffer", + // "Volume": 0.0005, + // "Outstanding": 0.0005, + // "Price": 113885.83, + // "AvgPrice": 113885.83, + // "Value": 56.94, + // "Status": "Open", + // "PrimaryCurrencyCode": "Xbt", + // "SecondaryCurrencyCode": "Usd", + // "FeePercent": 0.005, + // } + // + let symbol = undefined; + const baseId = this.safeString (order, 'PrimaryCurrencyCode'); + const quoteId = this.safeString (order, 'SecondaryCurrencyCode'); + let base = undefined; + let quote = undefined; + if ((baseId !== undefined) && (quoteId !== undefined)) { + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else if (market !== undefined) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let orderType = this.safeString2 (order, 'Type', 'OrderType'); + let side = undefined; + if (orderType.indexOf ('Bid') >= 0) { + side = 'buy'; + } else if (orderType.indexOf ('Offer') >= 0) { + side = 'sell'; + } + if (orderType.indexOf ('Market') >= 0) { + orderType = 'market'; + } else if (orderType.indexOf ('Limit') >= 0) { + orderType = 'limit'; + } + const timestamp = this.parse8601 (this.safeString (order, 'CreatedTimestampUtc')); + const amount = this.safeFloat2 (order, 'VolumeOrdered', 'Volume'); + let filled = this.safeFloat (order, 'VolumeFilled'); + let remaining = this.safeFloat (order, 'Outstanding'); + if (filled === undefined) { + if ((remaining !== undefined) && (amount !== undefined)) { + filled = Math.max (0, amount - remaining); + } + } + if (remaining === undefined) { + if ((filled !== undefined) && (amount !== undefined)) { + remaining = Math.max (0, amount - filled); + } + } + const feeRate = this.safeFloat (order, 'FeePercent'); + let feeCost = undefined; + if (feeRate !== undefined) { + feeCost = feeRate * filled; + } + const fee = { + 'rate': feeRate, + 'cost': feeCost, + 'currency': base, + }; + const id = this.safeString (order, 'OrderGuid'); + const status = this.parseOrderStatus (this.safeString (order, 'Status')); + const cost = this.safeFloat (order, 'Value'); + const average = this.safeFloat (order, 'AvgPrice'); + const price = this.safeFloat (order, 'Price', average); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'Open': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'PartiallyFilledAndCancelled': 'canceled', + 'Cancelled': 'canceled', + 'PartiallyFilledAndExpired': 'canceled', + 'Expired': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetOrderDetails (this.extend ({ + 'orderGuid': id, + }, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = this.ordered ({}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['primaryCurrencyCode'] = market['baseId']; + request['secondaryCurrencyCode'] = market['quoteId']; + } + if (limit === undefined) { + limit = 50; + } + request['pageIndex'] = 1; + request['pageSize'] = limit; + const response = await this.privatePostGetOpenOrders (this.extend (request, params)); + const data = this.safeValue (response, 'Data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = this.ordered ({}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['primaryCurrencyCode'] = market['baseId']; + request['secondaryCurrencyCode'] = market['quoteId']; + } + if (limit === undefined) { + limit = 50; + } + request['pageIndex'] = 1; + request['pageSize'] = limit; + const response = await this.privatePostGetClosedOrders (this.extend (request, params)); + const data = this.safeValue (response, 'Data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const pageIndex = this.safeInteger (params, 'pageIndex', 1); + if (limit === undefined) { + limit = 50; + } + const request = this.ordered ({ + 'pageIndex': pageIndex, + 'pageSize': limit, + }); + const response = await this.privatePostGetTrades (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (response['Data'], market, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (trade['TradeTimestampUtc']); + const id = this.safeString (trade, 'TradeGuid'); + const orderId = this.safeString (trade, 'OrderGuid'); + const price = this.safeFloat2 (trade, 'Price', 'SecondaryCurrencyTradePrice'); + const amount = this.safeFloat2 (trade, 'VolumeTraded', 'PrimaryCurrencyAmount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let side = this.safeString (trade, 'OrderType'); + if (side !== undefined) { + if (side.indexOf ('Bid') >= 0) { + side = 'buy'; + } else if (side.indexOf ('Offer') >= 0) { + side = 'sell'; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + 'numberOfRecentTradesToRetrieve': 50, // max = 50 + }; + const response = await this.publicGetGetRecentTrades (this.extend (request, params)); + return this.parseTrades (response['Trades'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const capitalizedOrderType = this.capitalize (type); + const method = 'privatePostPlace' + capitalizedOrderType + 'Order'; + let orderType = capitalizedOrderType; + orderType += (side === 'sell') ? 'Offer' : 'Bid'; + const request = this.ordered ({ + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + 'orderType': orderType, + }); + if (type === 'limit') { + request['price'] = price; + } + request['volume'] = amount; + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['OrderGuid'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderGuid': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const auth = [ + url, + 'apiKey=' + this.apiKey, + 'nonce=' + nonce.toString (), + ]; + const keys = Object.keys (params); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = params[key].toString (); + auth.push (key + '=' + value); + } + const message = auth.join (','); + const signature = this.hmac (this.encode (message), this.encode (this.secret)); + const query = this.ordered ({}); + query['apiKey'] = this.apiKey; + query['nonce'] = nonce; + query['signature'] = signature.toUpperCase (); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + query[key] = params[key]; + } + body = this.json (query); + headers = { 'Content-Type': 'application/json' }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5}],97:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InsufficientFunds, InvalidOrder, OrderNotFound, AuthenticationError, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class indodax extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'indodax', + 'name': 'INDODAX', + 'countries': [ 'ID' ], // Indonesia + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTime': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'version': '2.0', // as of 9 April 2018 + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87070508-9358c880-c221-11ea-8dc5-5391afbbb422.jpg', + 'api': { + 'public': 'https://indodax.com/api', + 'private': 'https://indodax.com/tapi', + }, + 'www': 'https://www.indodax.com', + 'doc': 'https://github.com/btcid/indodax-official-api-docs', + 'referral': 'https://indodax.com/ref/testbitcoincoid/1', + }, + 'api': { + 'public': { + 'get': [ + 'server_time', + 'pairs', + '{pair}/ticker', + '{pair}/trades', + '{pair}/depth', + ], + }, + 'private': { + 'post': [ + 'getInfo', + 'transHistory', + 'trade', + 'tradeHistory', + 'getOrder', + 'openOrders', + 'cancelOrder', + 'orderHistory', + 'withdrawCoin', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0, + 'taker': 0.003, + }, + }, + 'exceptions': { + 'exact': { + 'invalid_pair': BadSymbol, // {"error":"invalid_pair","error_description":"Invalid Pair"} + 'Insufficient balance.': InsufficientFunds, + 'invalid order.': OrderNotFound, + 'Invalid credentials. API not found or session has expired.': AuthenticationError, + 'Invalid credentials. Bad sign.': AuthenticationError, + }, + 'broad': { + 'Minimum price': InvalidOrder, + 'Minimum order': InvalidOrder, + }, + }, + // exchange-specific options + 'options': { + 'recvWindow': 5 * 1000, // default 5 sec + 'timeDifference': 0, // the difference between system clock and exchange clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + }, + 'commonCurrencies': { + 'STR': 'XLM', + 'BCHABC': 'BCH', + 'BCHSV': 'BSV', + 'DRK': 'DASH', + 'NEM': 'XEM', + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetServerTime (params); + // + // { + // "timezone": "UTC", + // "server_time": 1571205969552 + // } + // + return this.safeInteger (response, 'server_time'); + } + + async loadTimeDifference (params = {}) { + const serverTime = await this.fetchTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = after - serverTime; + return this.options['timeDifference']; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPairs (params); + // + // [ + // { + // "id": "btcidr", + // "symbol": "BTCIDR", + // "base_currency": "idr", + // "traded_currency": "btc", + // "traded_currency_unit": "BTC", + // "description": "BTC/IDR", + // "ticker_id": "btc_idr", + // "volume_precision": 0, + // "price_precision": 1000, + // "price_round": 8, + // "pricescale": 1000, + // "trade_min_base_currency": 10000, + // "trade_min_traded_currency": 0.00007457, + // "has_memo": false, + // "memo_name": false, + // "has_payment_id": false, + // "trade_fee_percent": 0.3, + // "url_logo": "https://indodax.com/v2/logo/svg/color/btc.svg", + // "url_logo_png": "https://indodax.com/v2/logo/png/color/btc.png", + // "is_maintenance": 0 + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'ticker_id'); + const baseId = this.safeString (market, 'traded_currency'); + const quoteId = this.safeString (market, 'base_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const taker = this.safeFloat (market, 'trade_fee_percent'); + const isMaintenance = this.safeInteger (market, 'is_maintenance'); + const active = (isMaintenance) ? false : true; + const pricePrecision = this.safeInteger (market, 'price_round'); + const precision = { + 'amount': 8, + 'price': pricePrecision, + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'trade_min_traded_currency'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (market, 'trade_min_base_currency'), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'taker': taker, + 'percentage': true, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfo (params); + const balances = this.safeValue (response, 'return', {}); + const free = this.safeValue (balances, 'balance', {}); + const used = this.safeValue (balances, 'balance_hold', {}); + const result = { 'info': response }; + const currencyIds = Object.keys (free); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (free, currencyId); + account['used'] = this.safeFloat (used, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const orderbook = await this.publicGetPairDepth (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'buy', 'sell'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTicker (this.extend (request, params)); + // + // { + // "ticker": { + // "high":"0.01951", + // "low":"0.01877", + // "vol_eth":"39.38839319", + // "vol_btc":"0.75320886", + // "last":"0.01896", + // "buy":"0.01896", + // "sell":"0.019", + // "server_time":1565248908 + // } + // } + // + const ticker = response['ticker']; + const timestamp = this.safeTimestamp (ticker, 'server_time'); + const baseVolume = 'vol_' + market['baseId'].toLowerCase (); + const quoteVolume = 'vol_' + market['quoteId'].toLowerCase (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, baseVolume), + 'quoteVolume': this.safeFloat (ticker, quoteVolume), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const type = undefined; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'open': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "order_id": "12345", + // "submit_time": "1392228122", + // "price": "8000000", + // "type": "sell", + // "order_ltc": "100000000", + // "remain_ltc": "100000000" + // } + // + let side = undefined; + if ('type' in order) { + side = order['type']; + } + const status = this.parseOrderStatus (this.safeString (order, 'status', 'open')); + let symbol = undefined; + let cost = undefined; + const price = this.safeFloat (order, 'price'); + let amount = undefined; + let remaining = undefined; + let filled = undefined; + if (market !== undefined) { + symbol = market['symbol']; + let quoteId = market['quoteId']; + let baseId = market['baseId']; + if ((market['quoteId'] === 'idr') && ('order_rp' in order)) { + quoteId = 'rp'; + } + if ((market['baseId'] === 'idr') && ('remain_rp' in order)) { + baseId = 'rp'; + } + cost = this.safeFloat (order, 'order_' + quoteId); + if (cost) { + amount = cost / price; + const remainingCost = this.safeFloat (order, 'remain_' + quoteId); + if (remainingCost !== undefined) { + remaining = remainingCost / price; + filled = amount - remaining; + } + } else { + amount = this.safeFloat (order, 'order_' + baseId); + cost = price * amount; + remaining = this.safeFloat (order, 'remain_' + baseId); + filled = amount - remaining; + } + } + let average = undefined; + if (filled) { + average = cost / filled; + } + const timestamp = this.safeInteger (order, 'submit_time'); + const fee = undefined; + const id = this.safeString (order, 'order_id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'order_id': id, + }; + const response = await this.privatePostGetOrder (this.extend (request, params)); + const orders = response['return']; + const order = this.parseOrder (this.extend ({ 'id': id }, orders['order']), market); + return this.extend ({ 'info': response }, order); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostOpenOrders (this.extend (request, params)); + const rawOrders = response['return']['orders']; + // { success: 1, return: { orders: null }} if no orders + if (!rawOrders) { + return []; + } + // { success: 1, return: { orders: [ ... objects ] }} for orders fetched by symbol + if (symbol !== undefined) { + return this.parseOrders (rawOrders, market, since, limit); + } + // { success: 1, return: { orders: { marketid: [ ... objects ] }}} if all orders are fetched + const marketIds = Object.keys (rawOrders); + let exchangeOrders = []; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const marketOrders = rawOrders[marketId]; + market = this.markets_by_id[marketId]; + const parsedOrders = this.parseOrders (marketOrders, market, since, limit); + exchangeOrders = this.arrayConcat (exchangeOrders, parsedOrders); + } + return exchangeOrders; + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostOrderHistory (this.extend (request, params)); + let orders = this.parseOrders (response['return']['orders'], market); + orders = this.filterBy (orders, 'status', 'closed'); + return this.filterBySymbolSinceLimit (orders, symbol, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'price': price, + }; + const currency = market['baseId']; + if (side === 'buy') { + request[market['quoteId']] = amount * price; + } else { + request[market['baseId']] = amount; + } + request[currency] = amount; + const result = await this.privatePostTrade (this.extend (request, params)); + const data = this.safeValue (result, 'return', {}); + const id = this.safeString (data, 'order_id'); + return { + 'info': result, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + const side = this.safeValue (params, 'side'); + if (side === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires an extra "side" param'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'pair': market['id'], + 'type': side, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // Custom string you need to provide to identify each withdrawal. + // Will be passed to callback URL (assigned via website to the API key) + // so your system can identify the request and confirm it. + // Alphanumeric, max length 255. + const requestId = this.milliseconds (); + // Alternatively: + // let requestId = this.uuid (); + const request = { + 'currency': currency['id'], + 'withdraw_amount': amount, + 'withdraw_address': address, + 'request_id': requestId.toString (), + }; + if (tag) { + request['withdraw_memo'] = tag; + } + const response = await this.privatePostWithdrawCoin (this.extend (request, params)); + // + // { + // "success": 1, + // "status": "approved", + // "withdraw_currency": "xrp", + // "withdraw_address": "rwWr7KUZ3ZFwzgaDGjKBysADByzxvohQ3C", + // "withdraw_amount": "10000.00000000", + // "fee": "2.00000000", + // "amount_after_fee": "9998.00000000", + // "submit_time": "1509469200", + // "withdraw_id": "xrp-12345", + // "txid": "", + // "withdraw_memo": "123123" + // } + // + let id = undefined; + if (('txid' in response) && (response['txid'].length > 0)) { + id = response['txid']; + } + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api === 'public') { + url += '/' + this.implodeParams (path, params); + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'method': path, + 'timestamp': this.nonce (), + 'recvWindow': this.options['recvWindow'], + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // { success: 0, error: "invalid order." } + // or + // [{ data, ... }, { ... }, ... ] + if (Array.isArray (response)) { + return; // public endpoints may return []-arrays + } + const error = this.safeValue (response, 'error', ''); + if (!('success' in response) && error === '') { + return; // no 'success' property on public responses + } + if (this.safeInteger (response, 'success', 0) === 1) { + // { success: 1, return: { orders: [] }} + if (!('return' in response)) { + throw new ExchangeError (this.id + ': malformed response: ' + this.json (response)); + } else { + return; + } + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],98:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class itbit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'itbit', + 'name': 'itBit', + 'countries': [ 'US' ], + 'rateLimit': 2000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27822159-66153620-60ad-11e7-89e7-005f6d7f3de0.jpg', + 'api': 'https://api.itbit.com', + 'www': 'https://www.itbit.com', + 'doc': [ + 'https://api.itbit.com/docs', + 'https://www.itbit.com/api', + ], + }, + 'api': { + 'public': { + 'get': [ + 'markets/{symbol}/ticker', + 'markets/{symbol}/order_book', + 'markets/{symbol}/trades', + ], + }, + 'private': { + 'get': [ + 'wallets', + 'wallets/{walletId}', + 'wallets/{walletId}/balances/{currencyCode}', + 'wallets/{walletId}/funding_history', + 'wallets/{walletId}/trades', + 'wallets/{walletId}/orders', + 'wallets/{walletId}/orders/{id}', + ], + 'post': [ + 'wallet_transfers', + 'wallets', + 'wallets/{walletId}/cryptocurrency_deposits', + 'wallets/{walletId}/cryptocurrency_withdrawals', + 'wallets/{walletId}/orders', + 'wire_withdrawal', + ], + 'delete': [ + 'wallets/{walletId}/orders/{id}', + ], + }, + }, + 'markets': { + 'BTC/USD': { 'id': 'XBTUSD', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'XBT', 'quoteId': 'USD' }, + 'BTC/SGD': { 'id': 'XBTSGD', 'symbol': 'BTC/SGD', 'base': 'BTC', 'quote': 'SGD', 'baseId': 'XBT', 'quoteId': 'SGD' }, + 'BTC/EUR': { 'id': 'XBTEUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'XBT', 'quoteId': 'EUR' }, + 'ETH/USD': { 'id': 'ETHUSD', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'ETH', 'quoteId': 'USD' }, + 'ETH/EUR': { 'id': 'ETHEUR', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'ETH', 'quoteId': 'EUR' }, + 'ETH/SGD': { 'id': 'ETHSGD', 'symbol': 'ETH/SGD', 'base': 'ETH', 'quote': 'SGD', 'baseId': 'ETH', 'quoteId': 'SGD' }, + 'PAXGUSD': { 'id': 'PAXGUSD', 'symbol': 'PAXG/USD', 'base': 'PAXG', 'quote': 'USD', 'baseId': 'PAXG', 'quoteId': 'USD' }, + 'BCHUSD': { 'id': 'BCHUSD', 'symbol': 'BCH/USD', 'base': 'BCH', 'quote': 'USD', 'baseId': 'BCH', 'quoteId': 'USD' }, + 'LTCUSD': { 'id': 'LTCUSD', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'LTC', 'quoteId': 'USD' }, + }, + 'fees': { + 'trading': { + 'maker': -0.03 / 100, + 'taker': 0.35 / 100, + }, + }, + 'commonCurrencies': { + 'XBT': 'BTC', + }, + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const orderbook = await this.publicGetMarketsSymbolOrderBook (this.extend (request, params)); + return this.parseOrderBook (orderbook); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const ticker = await this.publicGetMarketsSymbolTicker (this.extend (request, params)); + const serverTimeUTC = this.safeString (ticker, 'serverTimeUTC'); + if (!serverTimeUTC) { + throw new ExchangeError (this.id + ' fetchTicker returned a bad response: ' + this.json (ticker)); + } + const timestamp = this.parse8601 (serverTimeUTC); + const vwap = this.safeFloat (ticker, 'vwap24h'); + const baseVolume = this.safeFloat (ticker, 'volume24h'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'lastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24h'), + 'low': this.safeFloat (ticker, 'low24h'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'openToday'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // timestamp: "2015-05-22T17:45:34.7570000Z", + // matchNumber: "5CR1JEUBBM8J", + // price: "351.45000000", + // amount: "0.00010000" + // } + // + // fetchMyTrades (private) + // + // { + // "orderId": "248ffda4-83a0-4033-a5bb-8929d523f59f", + // "timestamp": "2015-05-11T14:48:01.9870000Z", + // "instrument": "XBTUSD", + // "direction": "buy", // buy or sell + // "currency1": "XBT", // base currency + // "currency1Amount": "0.00010000", // order amount in base currency + // "currency2": "USD", // quote currency + // "currency2Amount": "0.0250530000000000", // order cost in quote currency + // "rate": "250.53000000", + // "commissionPaid": "0.00000000", // net trade fee paid after using any available rebate balance + // "commissionCurrency": "USD", + // "rebatesApplied": "-0.000125265", // negative values represent amount of rebate balance used for trades removing liquidity from order book; positive values represent amount of rebate balance earned from trades adding liquidity to order book + // "rebateCurrency": "USD", + // "executionId": "23132" + // } + // + const id = this.safeString2 (trade, 'executionId', 'matchNumber'); + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const side = this.safeString (trade, 'direction'); + const orderId = this.safeString (trade, 'orderId'); + let feeCost = this.safeFloat (trade, 'commissionPaid'); + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + let rebatesApplied = this.safeFloat (trade, 'rebatesApplied'); + if (rebatesApplied !== undefined) { + rebatesApplied = -rebatesApplied; + } + const rebateCurrencyId = this.safeString (trade, 'rebateCurrency'); + const rebateCurrency = this.safeCurrencyCode (rebateCurrencyId); + const price = this.safeFloat2 (trade, 'price', 'rate'); + const amount = this.safeFloat2 (trade, 'currency1Amount', 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + const marketId = this.safeString (trade, 'instrument'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (trade, 'currency1'); + const quoteId = this.safeString (trade, 'currency2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } + } + const result = { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + if (feeCost !== undefined) { + if (rebatesApplied !== undefined) { + if (feeCurrency === rebateCurrency) { + feeCost = this.sum (feeCost, rebatesApplied); + result['fee'] = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } else { + result['fees'] = [ + { + 'cost': feeCost, + 'currency': feeCurrency, + }, + { + 'cost': rebatesApplied, + 'currency': rebateCurrency, + }, + ]; + } + } else { + result['fee'] = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + } + if (!('fee' in result)) { + if (!('fees' in result)) { + result['fee'] = undefined; + } + } + return result; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const walletId = this.safeString (params, 'walletId'); + if (walletId === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a walletId parameter'); + } + const request = { + 'walletId': walletId, + }; + if (limit !== undefined) { + request['perPage'] = limit; // default 50, max 50 + } + const response = await this.privateGetWalletsWalletIdFundingHistory (this.extend (request, params)); + // { bankName: 'USBC (usd)', + // withdrawalId: 94740, + // holdingPeriodCompletionDate: '2018-04-16T07:57:05.9606869', + // time: '2018-04-16T07:57:05.9600000', + // currency: 'USD', + // transactionType: 'Withdrawal', + // amount: '2186.72000000', + // walletName: 'Wallet', + // status: 'completed' }, + // + // { "time": "2018-01-02T19:52:22.4176503", + // "amount": "0.50000000", + // "status": "completed", + // "txnHash": "1b6fff67ed83cb9e9a38ca4976981fc047322bc088430508fe764a127d3ace95", + // "currency": "XBT", + // "walletName": "Wallet", + // "transactionType": "Deposit", + // "destinationAddress": "3AAWTH9et4e8o51YKp9qPpmujrNXKwHWNX"} + const items = response['fundingHistory']; + const result = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const time = this.safeString (item, 'time'); + const timestamp = this.parse8601 (time); + const currency = this.safeString (item, 'currency'); + const destinationAddress = this.safeString (item, 'destinationAddress'); + const txnHash = this.safeString (item, 'txnHash'); + const transactionType = this.safeStringLower (item, 'transactionType'); + const transactionStatus = this.safeString (item, 'status'); + const status = this.parseTransferStatus (transactionStatus); + result.push ({ + 'id': this.safeString (item, 'withdrawalId'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'currency': this.safeCurrencyCode (currency), + 'address': destinationAddress, + 'tag': undefined, + 'txid': txnHash, + 'type': transactionType, + 'status': status, + 'amount': this.safeFloat (item, 'amount'), + 'fee': undefined, + 'info': item, + }); + } + return result; + } + + parseTransferStatus (status) { + const options = { + 'cancelled': 'canceled', + 'completed': 'ok', + }; + return this.safeString (options, status, 'pending'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const walletId = this.safeString (params, 'walletId'); + if (walletId === undefined) { + throw new ExchangeError (this.id + ' fetchMyTrades requires a walletId parameter'); + } + const request = { + 'walletId': walletId, + }; + if (since !== undefined) { + request['rangeStart'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['perPage'] = limit; // default 50, max 50 + } + const response = await this.privateGetWalletsWalletIdTrades (this.extend (request, params)); + // + // { + // "totalNumberOfRecords": "2", + // "currentPageNumber": "1", + // "latestExecutionId": "332", // most recent execution at time of response + // "recordsPerPage": "50", + // "tradingHistory": [ + // { + // "orderId": "248ffda4-83a0-4033-a5bb-8929d523f59f", + // "timestamp": "2015-05-11T14:48:01.9870000Z", + // "instrument": "XBTUSD", + // "direction": "buy", // buy or sell + // "currency1": "XBT", // base currency + // "currency1Amount": "0.00010000", // order amount in base currency + // "currency2": "USD", // quote currency + // "currency2Amount": "0.0250530000000000", // order cost in quote currency + // "rate": "250.53000000", + // "commissionPaid": "0.00000000", // net trade fee paid after using any available rebate balance + // "commissionCurrency": "USD", + // "rebatesApplied": "-0.000125265", // negative values represent amount of rebate balance used for trades removing liquidity from order book; positive values represent amount of rebate balance earned from trades adding liquidity to order book + // "rebateCurrency": "USD", + // "executionId": "23132" + // }, + // ], + // } + // + const trades = this.safeValue (response, 'tradingHistory', []); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (trades, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketsSymbolTrades (this.extend (request, params)); + // + // { + // count: 3, + // recentTrades: [ + // { + // timestamp: "2015-05-22T17:45:34.7570000Z", + // matchNumber: "5CR1JEUBBM8J", + // price: "351.45000000", + // amount: "0.00010000" + // }, + // ] + // } + // + const trades = this.safeValue (response, 'recentTrades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.fetchWallets (params); + const balances = response[0]['balances']; + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableBalance'); + account['total'] = this.safeFloat (balance, 'totalBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchWallets (params = {}) { + await this.loadMarkets (); + if (!this.uid) { + throw new AuthenticationError (this.id + ' fetchWallets requires uid API credential'); + } + const request = { + 'userId': this.uid, + }; + return await this.privateGetWallets (this.extend (request, params)); + } + + async fetchWallet (walletId, params = {}) { + await this.loadMarkets (); + const request = { + 'walletId': walletId, + }; + return await this.privateGetWalletsWalletId (this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'open', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'filled', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' fetchOrders requires a walletId parameter'); + } + const walletId = params['walletId']; + const request = { + 'walletId': walletId, + }; + const response = await this.privateGetWalletsWalletIdOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'submitted': 'open', // order pending book entry + 'open': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + 'rejected': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": "13d6af57-8b0b-41e5-af30-becf0bcc574d", + // "walletId": "7e037345-1288-4c39-12fe-d0f99a475a98", + // "side": "buy", + // "instrument": "XBTUSD", + // "type": "limit", + // "currency": "XBT", + // "amount": "2.50000000", + // "displayAmount": "2.50000000", + // "price": "650.00000000", + // "volumeWeightedAveragePrice": "0.00000000", + // "amountFilled": "0.00000000", + // "createdTime": "2014-02-11T17:05:15Z", + // "status": "submitted", + // "funds": null, + // "metadata": {}, + // "clientOrderIdentifier": null, + // "postOnly": "False" + // } + // + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'type'); + const symbol = this.markets_by_id[order['instrument']]['symbol']; + const timestamp = this.parse8601 (order['createdTime']); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'amountFilled'); + let remaining = undefined; + let cost = undefined; + const fee = undefined; + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'volumeWeightedAveragePrice'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (average !== undefined) { + cost = filled * average; + } + } + const clientOrderId = this.safeString (order, 'clientOrderIdentifier'); + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': this.parseOrderStatus (this.safeString (order, 'status')), + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + // 'trades': this.parseTrades (order['trades'], market), + 'trades': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' createOrder requires a walletId parameter'); + } + amount = amount.toString (); + price = price.toString (); + const market = this.market (symbol); + const request = { + 'side': side, + 'type': type, + 'currency': market['id'].replace (market['quote'], ''), + 'amount': amount, + 'display': amount, + 'price': price, + 'instrument': market['id'], + }; + const response = await this.privatePostWalletsWalletIdOrders (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' fetchOrder requires a walletId parameter'); + } + const request = { + 'id': id, + }; + const response = await this.privateGetWalletsWalletIdOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' cancelOrder requires a walletId parameter'); + } + const request = { + 'id': id, + }; + return await this.privateDeleteWalletsWalletIdOrdersId (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET' && Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + if (method === 'POST' && Object.keys (query).length) { + body = this.json (query); + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const timestamp = nonce; + const authBody = (method === 'POST') ? body : ''; + const auth = [ method, url, authBody, nonce, timestamp ]; + const message = nonce + this.json (auth).replace ('\\/', '/'); + const hash = this.hash (this.encode (message), 'sha256', 'binary'); + const binaryUrl = this.stringToBinary (this.encode (url)); + const binhash = this.binaryConcat (binaryUrl, hash); + const signature = this.hmac (binhash, this.encode (this.secret), 'sha512', 'base64'); + headers = { + 'Authorization': this.apiKey + ':' + signature, + 'Content-Type': 'application/json', + 'X-Auth-Timestamp': timestamp, + 'X-Auth-Nonce': nonce, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('code' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],99:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeNotAvailable, ArgumentsRequired, PermissionDenied, AuthenticationError, ExchangeError, OrderNotFound, DDoSProtection, InvalidNonce, InsufficientFunds, CancelPending, InvalidOrder, InvalidAddress } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class kraken extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'kraken', + 'name': 'Kraken', + 'countries': [ 'US' ], + 'version': '0', + 'rateLimit': 3000, + 'certified': true, + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchLedgerEntry': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': 'emulated', + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'marketsByAltname': {}, + 'timeframes': { + '1m': 1, + '5m': 5, + '15m': 15, + '30m': 30, + '1h': 60, + '4h': 240, + '1d': 1440, + '1w': 10080, + '2w': 21600, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg', + 'api': { + 'public': 'https://api.kraken.com', + 'private': 'https://api.kraken.com', + 'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles', // use the public zendesk api to receive article bodies and bypass new anti-spam protections + }, + 'www': 'https://www.kraken.com', + 'doc': 'https://www.kraken.com/features/api', + 'fees': 'https://www.kraken.com/en-us/features/fee-schedule', + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.26 / 100, + 'maker': 0.16 / 100, + 'tiers': { + 'taker': [ + [0, 0.0026], + [50000, 0.0024], + [100000, 0.0022], + [250000, 0.0020], + [500000, 0.0018], + [1000000, 0.0016], + [2500000, 0.0014], + [5000000, 0.0012], + [10000000, 0.0001], + ], + 'maker': [ + [0, 0.0016], + [50000, 0.0014], + [100000, 0.0012], + [250000, 0.0010], + [500000, 0.0008], + [1000000, 0.0006], + [2500000, 0.0004], + [5000000, 0.0002], + [10000000, 0.0], + ], + }, + }, + // this is a bad way of hardcoding fees that change on daily basis + // hardcoding is now considered obsolete, we will remove all of it eventually + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.001, + 'ETH': 0.005, + 'XRP': 0.02, + 'XLM': 0.00002, + 'LTC': 0.02, + 'DOGE': 2, + 'ZEC': 0.00010, + 'ICN': 0.02, + 'REP': 0.01, + 'ETC': 0.005, + 'MLN': 0.003, + 'XMR': 0.05, + 'DASH': 0.005, + 'GNO': 0.01, + 'EOS': 0.5, + 'BCH': 0.001, + 'XTZ': 0.05, + 'USD': 5, // if domestic wire + 'EUR': 5, // if domestic wire + 'CAD': 10, // CAD EFT Withdrawal + 'JPY': 300, // if domestic wire + }, + 'deposit': { + 'BTC': 0, + 'ETH': 0, + 'XRP': 0, + 'XLM': 0, + 'LTC': 0, + 'DOGE': 0, + 'ZEC': 0, + 'ICN': 0, + 'REP': 0, + 'ETC': 0, + 'MLN': 0, + 'XMR': 0, + 'DASH': 0, + 'GNO': 0, + 'EOS': 0, + 'BCH': 0, + 'XTZ': 0.05, + 'USD': 5, // if domestic wire + 'EUR': 0, // free deposit if EUR SEPA Deposit + 'CAD': 5, // if domestic wire + 'JPY': 0, // Domestic Deposit (Free, ¥5,000 deposit minimum) + }, + }, + }, + 'api': { + 'zendesk': { + 'get': [ + // we should really refrain from putting fixed fee numbers and stop hardcoding + // we will be using their web APIs to scrape all numbers from these articles + '360000292886', // -What-are-the-deposit-fees- + '201893608', // -What-are-the-withdrawal-fees- + ], + }, + 'public': { + 'get': [ + 'Assets', + 'AssetPairs', + 'Depth', + 'OHLC', + 'Spread', + 'Ticker', + 'Time', + 'Trades', + ], + }, + 'private': { + 'post': [ + 'AddOrder', + 'AddExport', + 'Balance', + 'CancelOrder', + 'ClosedOrders', + 'DepositAddresses', + 'DepositMethods', + 'DepositStatus', + 'ExportStatus', + 'GetWebSocketsToken', + 'Ledgers', + 'OpenOrders', + 'OpenPositions', + 'QueryLedgers', + 'QueryOrders', + 'QueryTrades', + 'RetrieveExport', + 'RemoveExport', + 'TradeBalance', + 'TradesHistory', + 'TradeVolume', + 'Withdraw', + 'WithdrawCancel', + 'WithdrawInfo', + 'WithdrawStatus', + ], + }, + }, + 'commonCurrencies': { + 'XBT': 'BTC', + 'XDG': 'DOGE', + 'REPV2': 'REP', + 'REP': 'REPV1', + }, + 'options': { + 'cacheDepositMethodsOnFetchDepositAddress': true, // will issue up to two calls in fetchDepositAddress + 'depositMethods': {}, + 'delistedMarketsById': {}, + // cannot withdraw/deposit these + 'inactiveCurrencies': [ 'CAD', 'USD', 'JPY', 'GBP' ], + }, + 'exceptions': { + 'EQuery:Invalid asset pair': BadSymbol, // {"error":["EQuery:Invalid asset pair"]} + 'EAPI:Invalid key': AuthenticationError, + 'EFunding:Unknown withdraw key': ExchangeError, + 'EFunding:Invalid amount': InsufficientFunds, + 'EService:Unavailable': ExchangeNotAvailable, + 'EDatabase:Internal error': ExchangeNotAvailable, + 'EService:Busy': ExchangeNotAvailable, + 'EQuery:Unknown asset': ExchangeError, + 'EAPI:Rate limit exceeded': DDoSProtection, + 'EOrder:Rate limit exceeded': DDoSProtection, + 'EGeneral:Internal error': ExchangeNotAvailable, + 'EGeneral:Temporary lockout': DDoSProtection, + 'EGeneral:Permission denied': PermissionDenied, + 'EOrder:Unknown order': InvalidOrder, + 'EOrder:Order minimum not met': InvalidOrder, + }, + }); + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); + } + + feeToPrecision (symbol, fee) { + return this.decimalToPrecision (fee, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAssetPairs (params); + // + // { + // "error":[], + // "result":{ + // "ADAETH":{ + // "altname":"ADAETH", + // "wsname":"ADA\/ETH", + // "aclass_base":"currency", + // "base":"ADA", + // "aclass_quote":"currency", + // "quote":"XETH", + // "lot":"unit", + // "pair_decimals":7, + // "lot_decimals":8, + // "lot_multiplier":1, + // "leverage_buy":[], + // "leverage_sell":[], + // "fees":[ + // [0,0.26], + // [50000,0.24], + // [100000,0.22], + // [250000,0.2], + // [500000,0.18], + // [1000000,0.16], + // [2500000,0.14], + // [5000000,0.12], + // [10000000,0.1] + // ], + // "fees_maker":[ + // [0,0.16], + // [50000,0.14], + // [100000,0.12], + // [250000,0.1], + // [500000,0.08], + // [1000000,0.06], + // [2500000,0.04], + // [5000000,0.02], + // [10000000,0] + // ], + // "fee_volume_currency":"ZUSD", + // "margin_call":80, + // "margin_stop":40, + // "ordermin": "1" + // }, + // } + // } + // + const keys = Object.keys (response['result']); + let result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = response['result'][id]; + const baseId = market['base']; + const quoteId = market['quote']; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const darkpool = id.indexOf ('.d') >= 0; + const symbol = darkpool ? market['altname'] : (base + '/' + quote); + let maker = undefined; + if ('fees_maker' in market) { + maker = parseFloat (market['fees_maker'][0][1]) / 100; + } + const precision = { + 'amount': market['lot_decimals'], + 'price': market['pair_decimals'], + }; + const minAmount = this.safeFloat (market, 'ordermin'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'darkpool': darkpool, + 'info': market, + 'altname': market['altname'], + 'maker': maker, + 'taker': parseFloat (market['fees'][0][1]) / 100, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + }); + } + result = this.appendInactiveMarkets (result); + this.marketsByAltname = this.indexBy (result, 'altname'); + return result; + } + + safeCurrencyCode (currencyId, currency = undefined) { + if (currencyId.length > 3) { + if ((currencyId.indexOf ('X') === 0) || (currencyId.indexOf ('Z') === 0)) { + currencyId = currencyId.slice (1); + } + } + return super.safeCurrencyCode (currencyId, currency); + } + + appendInactiveMarkets (result) { + // result should be an array to append to + const precision = { 'amount': 8, 'price': 8 }; + const costLimits = { 'min': 0, 'max': undefined }; + const priceLimits = { 'min': Math.pow (10, -precision['price']), 'max': undefined }; + const amountLimits = { 'min': Math.pow (10, -precision['amount']), 'max': Math.pow (10, precision['amount']) }; + const limits = { 'amount': amountLimits, 'price': priceLimits, 'cost': costLimits }; + const defaults = { + 'darkpool': false, + 'info': undefined, + 'maker': undefined, + 'taker': undefined, + 'active': false, + 'precision': precision, + 'limits': limits, + }; + const markets = [ + // { 'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR' }, + ]; + for (let i = 0; i < markets.length; i++) { + result.push (this.extend (defaults, markets[i])); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetAssets (params); + // + // { + // "error": [], + // "result": { + // "ADA": { "aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6 }, + // "BCH": { "aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5 }, + // ... + // }, + // } + // + const currencies = this.safeValue (response, 'result'); + const ids = Object.keys (currencies); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = currencies[id]; + // todo: will need to rethink the fees + // see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees- + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const code = this.safeCurrencyCode (this.safeString (currency, 'altname')); + const precision = this.safeInteger (currency, 'decimals'); + // assumes all currencies are active except those listed above + const active = !this.inArray (code, this.options['inactiveCurrencies']); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + this.checkRequiredCredentials (); + const response = await this.privatePostTradeVolume (params); + const tradedVolume = this.safeFloat (response['result'], 'volume'); + const tiers = this.fees['trading']['tiers']; + let taker = tiers['taker'][1]; + let maker = tiers['maker'][1]; + for (let i = 0; i < tiers['taker'].length; i++) { + if (tradedVolume >= tiers['taker'][i][0]) { + taker = tiers['taker'][i][1]; + } + } + for (let i = 0; i < tiers['maker'].length; i++) { + if (tradedVolume >= tiers['maker'][i][0]) { + maker = tiers['maker'][i][1]; + } + } + return { + 'info': response, + 'maker': maker, + 'taker': taker, + }; + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + const price = this.safeFloat (bidask, priceKey); + const amount = this.safeFloat (bidask, amountKey); + const timestamp = this.safeInteger (bidask, 2); + return [ price, amount, timestamp ]; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (market['darkpool']) { + throw new ExchangeError (this.id + ' does not provide an order book for darkpool symbol ' + symbol); + } + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['count'] = limit; // 100 + } + const response = await this.publicGetDepth (this.extend (request, params)); + // + // { + // "error":[], + // "result":{ + // "XETHXXBT":{ + // "asks":[ + // ["0.023480","4.000",1586321307], + // ["0.023490","50.095",1586321306], + // ["0.023500","28.535",1586321302], + // ], + // "bids":[ + // ["0.023470","59.580",1586321307], + // ["0.023460","20.000",1586321301], + // ["0.023440","67.832",1586321306], + // ] + // } + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const orderbook = this.safeValue (result, market['id']); + return this.parseOrderBook (orderbook); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const baseVolume = parseFloat (ticker['v'][1]); + const vwap = parseFloat (ticker['p'][1]); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = parseFloat (ticker['c'][0]); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': parseFloat (ticker['h'][1]), + 'low': parseFloat (ticker['l'][1]), + 'bid': parseFloat (ticker['b'][0]), + 'bidVolume': undefined, + 'ask': parseFloat (ticker['a'][0]), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'o'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + symbols = (symbols === undefined) ? this.symbols : symbols; + const marketIds = []; + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + const market = this.markets[symbol]; + if (market['active'] && !market['darkpool']) { + marketIds.push (market['id']); + } + } + const request = { + 'pair': marketIds.join (','), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const tickers = response['result']; + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.markets_by_id[id]; + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const darkpool = symbol.indexOf ('.d') >= 0; + if (darkpool) { + throw new ExchangeError (this.id + ' does not provide a ticker for darkpool symbol ' + symbol); + } + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = response['result'][market['id']]; + return this.parseTicker (ticker, market); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591475640, + // "0.02500", + // "0.02500", + // "0.02500", + // "0.02500", + // "0.02500", + // "9.12201000", + // 5 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 6), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['since'] = parseInt ((since - 1) / 1000); + } + const response = await this.publicGetOHLC (this.extend (request, params)); + // + // { + // "error":[], + // "result":{ + // "XETHXXBT":[ + // [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0], + // [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5], + // [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2], + // [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1], + // ], + // "last":1591517580 + // } + // } + const result = this.safeValue (response, 'result', {}); + const ohlcvs = this.safeValue (result, market['id'], []); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } + + parseLedgerEntryType (type) { + const types = { + 'trade': 'trade', + 'withdrawal': 'transaction', + 'deposit': 'transaction', + 'transfer': 'transfer', + 'margin': 'margin', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // 'LTFK7F-N2CUX-PNY4SX': { + // refid: "TSJTGT-DT7WN-GPPQMJ", + // time: 1520102320.555, + // type: "trade", + // aclass: "currency", + // asset: "XETH", + // amount: "0.1087194600", + // fee: "0.0000000000", + // balance: "0.2855851000" + // }, + // ... + // } + // + const id = this.safeString (item, 'id'); + let direction = undefined; + const account = undefined; + const referenceId = this.safeString (item, 'refid'); + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const code = this.safeCurrencyCode (this.safeString (item, 'asset'), currency); + let amount = this.safeFloat (item, 'amount'); + if (amount < 0) { + direction = 'out'; + amount = Math.abs (amount); + } else { + direction = 'in'; + } + const time = this.safeFloat (item, 'time'); + let timestamp = undefined; + if (time !== undefined) { + timestamp = parseInt (time * 1000); + } + const fee = { + 'cost': this.safeFloat (item, 'fee'), + 'currency': code, + }; + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + // https://www.kraken.com/features/api#get-ledgers-info + await this.loadMarkets (); + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostLedgers (this.extend (request, params)); + // { error: [], + // result: { ledger: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", + // time: 1520103488.314, + // type: "withdrawal", + // aclass: "currency", + // asset: "XETH", + // amount: "-0.2805800000", + // fee: "0.0050000000", + // balance: "0.0000051000" }, + const result = this.safeValue (response, 'result', {}); + const ledger = this.safeValue (result, 'ledger', {}); + const keys = Object.keys (ledger); + const items = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = ledger[key]; + value['id'] = key; + items.push (value); + } + return this.parseLedger (items, currency, since, limit); + } + + async fetchLedgerEntriesByIds (ids, code = undefined, params = {}) { + // https://www.kraken.com/features/api#query-ledgers + await this.loadMarkets (); + ids = ids.join (','); + const request = this.extend ({ + 'id': ids, + }, params); + const response = await this.privatePostQueryLedgers (request); + // { error: [], + // result: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", + // time: 1520103488.314, + // type: "withdrawal", + // aclass: "currency", + // asset: "XETH", + // amount: "-0.2805800000", + // fee: "0.0050000000", + // balance: "0.0000051000" } } } + const result = response['result']; + const keys = Object.keys (result); + const items = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = result[key]; + value['id'] = key; + items.push (value); + } + return this.parseLedger (items); + } + + async fetchLedgerEntry (id, code = undefined, params = {}) { + const items = await this.fetchLedgerEntriesByIds ([ id ], code, params); + return items[0]; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // "0.032310", // price + // "4.28169434", // amount + // 1541390792.763, // timestamp + // "s", // sell or buy + // "l", // limit or market + // "" + // ] + // + // fetchOrderTrades (private) + // + // { + // id: 'TIMIRG-WUNNE-RRJ6GT', // injected from outside + // ordertxid: 'OQRPN2-LRHFY-HIFA7D', + // postxid: 'TKH2SE-M7IF5-CFI7LT', + // pair: 'USDCUSDT', + // time: 1586340086.457, + // type: 'sell', + // ordertype: 'market', + // price: '0.99860000', + // cost: '22.16892001', + // fee: '0.04433784', + // vol: '22.20000000', + // margin: '0.00000000', + // misc: '' + // } + // + let timestamp = undefined; + let side = undefined; + let type = undefined; + let price = undefined; + let amount = undefined; + let cost = undefined; + let id = undefined; + let order = undefined; + let fee = undefined; + let symbol = undefined; + if (Array.isArray (trade)) { + timestamp = this.safeTimestamp (trade, 2); + side = (trade[3] === 's') ? 'sell' : 'buy'; + type = (trade[4] === 'l') ? 'limit' : 'market'; + price = this.safeFloat (trade, 0); + amount = this.safeFloat (trade, 1); + const tradeLength = trade.length; + if (tradeLength > 6) { + id = this.safeString (trade, 6); // artificially added as per #1794 + } + } else if (typeof trade === 'string') { + id = trade; + } else if ('ordertxid' in trade) { + const marketId = this.safeString (trade, 'pair'); + const foundMarket = this.findMarketByAltnameOrId (marketId); + if (foundMarket !== undefined) { + market = foundMarket; + } else if (marketId !== undefined) { + // delisted market ids go here + market = this.getDelistedMarketById (marketId); + } + order = trade['ordertxid']; + id = this.safeString2 (trade, 'id', 'postxid'); + timestamp = this.safeTimestamp (trade, 'time'); + side = this.safeString (trade, 'type'); + type = this.safeString (trade, 'ordertype'); + price = this.safeFloat (trade, 'price'); + amount = this.safeFloat (trade, 'vol'); + if ('fee' in trade) { + let currency = undefined; + if (market !== undefined) { + currency = market['quote']; + } + fee = { + 'cost': this.safeFloat (trade, 'fee'), + 'currency': currency, + }; + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'order': order, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const id = market['id']; + const request = { + 'pair': id, + }; + // https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API + // https://github.com/ccxt/ccxt/issues/5677 + if (since !== undefined) { + // php does not format it properly + // therefore we use string concatenation here + request['since'] = since * 1e6; + request['since'] = since.toString () + '000000'; // expected to be in nanoseconds + } + // https://github.com/ccxt/ccxt/issues/5698 + if (limit !== undefined && limit !== 1000) { + const fetchTradesWarning = this.safeValue (this.options, 'fetchTradesWarning', true); + if (fetchTradesWarning) { + throw new ExchangeError (this.id + ' fetchTrades() cannot serve ' + limit.toString () + " trades without breaking the pagination, see https://github.com/ccxt/ccxt/issues/5698 for more details. Set exchange.options['fetchTradesWarning'] to acknowledge this warning and silence it."); + } + } + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "error": [], + // "result": { + // "XETHXXBT": [ + // ["0.032310","4.28169434",1541390792.763,"s","l",""] + // ], + // "last": "1541439421200678657" + // } + // } + // + const result = response['result']; + const trades = result[id]; + // trades is a sorted array: last (most recent trade) goes last + const length = trades.length; + if (length <= 0) { + return []; + } + const lastTrade = trades[length - 1]; + const lastTradeId = this.safeString (result, 'last'); + lastTrade.push (lastTradeId); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + const response = await this.privatePostBalance (params); + const balances = this.safeValue (response, 'result', {}); + const result = { 'info': balances }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balances, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'ordertype': type, + 'volume': this.amountToPrecision (symbol, amount), + }; + const clientOrderId = this.safeString2 (params, 'userref', 'clientOrderId'); + const query = this.omit (params, [ 'userref', 'clientOrderId' ]); + if (clientOrderId !== undefined) { + request['userref'] = clientOrderId; + } + const priceIsDefined = (price !== undefined); + const marketOrder = (type === 'market'); + const limitOrder = (type === 'limit'); + const shouldIncludePrice = limitOrder || (!marketOrder && priceIsDefined); + if (shouldIncludePrice) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostAddOrder (this.extend (request, query)); + // + // { + // error: [], + // result: { + // descr: { order: 'buy 0.02100000 ETHUSDT @ limit 330.00' }, + // txid: [ 'OEKVV2-IH52O-TPL6GZ' ] + // } + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result); + } + + findMarketByAltnameOrId (id) { + if (id in this.marketsByAltname) { + return this.marketsByAltname[id]; + } else if (id in this.markets_by_id) { + return this.markets_by_id[id]; + } + return undefined; + } + + getDelistedMarketById (id) { + if (id === undefined) { + return id; + } + let market = this.safeValue (this.options['delistedMarketsById'], id); + if (market !== undefined) { + return market; + } + const baseIdStart = 0; + let baseIdEnd = 3; + let quoteIdStart = 3; + let quoteIdEnd = 6; + if (id.length === 8) { + baseIdEnd = 4; + quoteIdStart = 4; + quoteIdEnd = 8; + } else if (id.length === 7) { + baseIdEnd = 4; + quoteIdStart = 4; + quoteIdEnd = 7; + } + const baseId = id.slice (baseIdStart, baseIdEnd); + const quoteId = id.slice (quoteIdStart, quoteIdEnd); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + market = { + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + }; + this.options['delistedMarketsById'][id] = market; + return market; + } + + parseOrderStatus (status) { + const statuses = { + 'pending': 'open', // order pending book entry + 'open': 'open', + 'closed': 'closed', + 'canceled': 'canceled', + 'expired': 'expired', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // descr: { order: 'buy 0.02100000 ETHUSDT @ limit 330.00' }, + // txid: [ 'OEKVV2-IH52O-TPL6GZ' ] + // } + // + const description = this.safeValue (order, 'descr', {}); + const orderDescription = this.safeString (description, 'order'); + let side = undefined; + let type = undefined; + let marketId = undefined; + let price = undefined; + let amount = undefined; + if (orderDescription !== undefined) { + const parts = orderDescription.split (' '); + side = this.safeString (parts, 0); + amount = this.safeFloat (parts, 1); + marketId = this.safeString (parts, 2); + type = this.safeString (parts, 4); + price = this.safeFloat (parts, 5); + } + side = this.safeString (description, 'type', side); + type = this.safeString (description, 'ordertype', type); + marketId = this.safeString (description, 'pair', marketId); + const foundMarket = this.findMarketByAltnameOrId (marketId); + let symbol = undefined; + if (foundMarket !== undefined) { + market = foundMarket; + } else if (marketId !== undefined) { + // delisted market ids go here + market = this.getDelistedMarketById (marketId); + } + const timestamp = this.safeTimestamp (order, 'opentm'); + amount = this.safeFloat (order, 'vol', amount); + const filled = this.safeFloat (order, 'vol_exec'); + let remaining = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = amount - filled; + } + let fee = undefined; + const cost = this.safeFloat (order, 'cost'); + price = this.safeFloat (description, 'price', price); + if ((price === undefined) || (price === 0.0)) { + price = this.safeFloat (description, 'price2'); + } + if ((price === undefined) || (price === 0.0)) { + price = this.safeFloat (order, 'price', price); + } + const average = this.safeFloat (order, 'price'); + if (market !== undefined) { + symbol = market['symbol']; + if ('fee' in order) { + const flags = order['oflags']; + const feeCost = this.safeFloat (order, 'fee'); + fee = { + 'cost': feeCost, + 'rate': undefined, + }; + if (flags.indexOf ('fciq') >= 0) { + fee['currency'] = market['quote']; + } else if (flags.indexOf ('fcib') >= 0) { + fee['currency'] = market['base']; + } + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let id = this.safeString (order, 'id'); + if (id === undefined) { + const txid = this.safeValue (order, 'txid'); + id = this.safeString (txid, 0); + } + const clientOrderId = this.safeString (order, 'userref'); + const rawTrades = this.safeValue (order, 'trades'); + let trades = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { 'order': id }); + } + const stopPrice = this.safeFloat (order, 'stopprice'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'average': average, + 'remaining': remaining, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeValue2 (params, 'userref', 'clientOrderId'); + const request = { + 'trades': true, // whether or not to include trades in output (optional, default false) + // 'txid': id, // do not comma separate a list of ids - use fetchOrdersByIds instead + // 'userref': 'optional', // restrict results to given user reference id (optional) + }; + let query = params; + if (clientOrderId !== undefined) { + request['userref'] = clientOrderId; + query = this.omit (params, [ 'userref', 'clientOrderId' ]); + } else { + request['txid'] = id; + } + const response = await this.privatePostQueryOrders (this.extend (request, query)); + // + // { + // "error":[], + // "result":{ + // "OTLAS3-RRHUF-NDWH5A":{ + // "refid":null, + // "userref":null, + // "status":"closed", + // "reason":null, + // "opentm":1586822919.3342, + // "closetm":1586822919.365, + // "starttm":0, + // "expiretm":0, + // "descr":{ + // "pair":"XBTUSDT", + // "type":"sell", + // "ordertype":"market", + // "price":"0", + // "price2":"0", + // "leverage":"none", + // "order":"sell 0.21804000 XBTUSDT @ market", + // "close":"" + // }, + // "vol":"0.21804000", + // "vol_exec":"0.21804000", + // "cost":"1493.9", + // "fee":"3.8", + // "price":"6851.5", + // "stopprice":"0.00000", + // "limitprice":"0.00000", + // "misc":"", + // "oflags":"fciq", + // "trades":["TT5UC3-GOIRW-6AZZ6R"] + // } + // } + // } + // + const orders = this.safeValue (response, 'result', []); + const order = this.parseOrder (this.extend ({ 'id': id }, orders[id])); + return this.extend ({ 'info': response }, order); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orderTrades = this.safeValue (params, 'trades'); + const tradeIds = []; + if (orderTrades === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrderTrades requires a unified order structure in the params argument or a 'trades' param (an array of trade id strings)"); + } else { + for (let i = 0; i < orderTrades.length; i++) { + const orderTrade = orderTrades[i]; + if (typeof orderTrade === 'string') { + tradeIds.push (orderTrade); + } else { + tradeIds.push (orderTrade['id']); + } + } + } + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchOrderTrades', {}); + const batchSize = this.safeInteger (options, 'batchSize', 20); + let numBatches = parseInt (tradeIds / batchSize); + numBatches = this.sum (numBatches, 1); + const numTradeIds = tradeIds.length; + let result = []; + for (let j = 0; j < numBatches; j++) { + const requestIds = []; + for (let k = 0; k < batchSize; k++) { + const index = this.sum (j * batchSize, k); + if (index < numTradeIds) { + requestIds.push (tradeIds[index]); + } + } + const request = { + 'txid': requestIds.join (','), + }; + const response = await this.privatePostQueryTrades (request); + // + // { + // error: [], + // result: { + // 'TIMIRG-WUNNE-RRJ6GT': { + // ordertxid: 'OQRPN2-LRHFY-HIFA7D', + // postxid: 'TKH2SE-M7IF5-CFI7LT', + // pair: 'USDCUSDT', + // time: 1586340086.457, + // type: 'sell', + // ordertype: 'market', + // price: '0.99860000', + // cost: '22.16892001', + // fee: '0.04433784', + // vol: '22.20000000', + // margin: '0.00000000', + // misc: '' + // } + // } + // } + // + const rawTrades = this.safeValue (response, 'result'); + const ids = Object.keys (rawTrades); + for (let i = 0; i < ids.length; i++) { + rawTrades[ids[i]]['id'] = ids[i]; + } + const trades = this.parseTrades (rawTrades, undefined, since, limit); + const tradesFilteredBySymbol = this.filterBySymbol (trades, symbol); + result = this.arrayConcat (result, tradesFilteredBySymbol); + } + return result; + } + + async fetchOrdersByIds (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostQueryOrders (this.extend ({ + 'trades': true, // whether or not to include trades in output (optional, default false) + 'txid': ids.join (','), // comma delimited list of transaction ids to query info about (20 maximum) + }, params)); + const result = this.safeValue (response, 'result', {}); + const orders = []; + const orderIds = Object.keys (result); + for (let i = 0; i < orderIds.length; i++) { + const id = orderIds[i]; + const item = result[id]; + const order = this.parseOrder (this.extend ({ 'id': id }, item)); + orders.push (order); + } + return orders; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'type': 'all', // any position, closed position, closing position, no position + // 'trades': false, // whether or not to include trades related to position in output + // 'start': 1234567890, // starting unix timestamp or trade tx id of results (exclusive) + // 'end': 1234567890, // ending unix timestamp or trade tx id of results (inclusive) + // 'ofs' = result offset + }; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostTradesHistory (this.extend (request, params)); + // + // { + // "error": [], + // "result": { + // "trades": { + // "GJ3NYQ-XJRTF-THZABF": { + // "ordertxid": "TKH2SE-ZIF5E-CFI7LT", + // "postxid": "OEN3VX-M7IF5-JNBJAM", + // "pair": "XICNXETH", + // "time": 1527213229.4491, + // "type": "sell", + // "ordertype": "limit", + // "price": "0.001612", + // "cost": "0.025792", + // "fee": "0.000026", + // "vol": "16.00000000", + // "margin": "0.000000", + // "misc": "" + // }, + // ... + // }, + // "count": 9760, + // }, + // } + // + const trades = response['result']['trades']; + const ids = Object.keys (trades); + for (let i = 0; i < ids.length; i++) { + trades[ids[i]]['id'] = ids[i]; + } + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (trades, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let response = undefined; + try { + response = await this.privatePostCancelOrder (this.extend ({ + 'txid': id, + }, params)); + } catch (e) { + if (this.last_http_response) { + if (this.last_http_response.indexOf ('EOrder:Unknown order') >= 0) { + throw new OrderNotFound (this.id + ' cancelOrder() error ' + this.last_http_response); + } + } + throw e; + } + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostOpenOrders (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const result = this.safeValue (response, 'result', {}); + const orders = this.safeValue (result, 'open', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostClosedOrders (this.extend (request, params)); + // + // { + // "error":[], + // "result":{ + // "closed":{ + // "OETZYO-UL524-QJMXCT":{ + // "refid":null, + // "userref":null, + // "status":"canceled", + // "reason":"User requested", + // "opentm":1601489313.3898, + // "closetm":1601489346.5507, + // "starttm":0, + // "expiretm":0, + // "descr":{ + // "pair":"ETHUSDT", + // "type":"buy", + // "ordertype":"limit", + // "price":"330.00", + // "price2":"0", + // "leverage":"none", + // "order":"buy 0.02100000 ETHUSDT @ limit 330.00", + // "close":"" + // }, + // "vol":"0.02100000", + // "vol_exec":"0.00000000", + // "cost":"0.00000", + // "fee":"0.00000", + // "price":"0.00000", + // "stopprice":"0.00000", + // "limitprice":"0.00000", + // "misc":"", + // "oflags":"fciq" + // }, + // }, + // "count":16 + // } + // } + // + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const result = this.safeValue (response, 'result', {}); + const orders = this.safeValue (result, 'closed', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchDepositMethods (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privatePostDepositMethods (this.extend (request, params)); + return this.safeValue (response, 'result'); + } + + parseTransactionStatus (status) { + // IFEX transaction states + const statuses = { + 'Initial': 'pending', + 'Pending': 'pending', + 'Success': 'ok', + 'Settled': 'pending', + 'Failure': 'failed', + 'Partial': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { method: "Ether (Hex)", + // aclass: "currency", + // asset: "XETH", + // refid: "Q2CANKL-LBFVEE-U4Y2WQ", + // txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…", + // info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290", + // amount: "7.9999257900", + // fee: "0.0000000000", + // time: 1529223212, + // status: "Success" } + // + // fetchWithdrawals + // + // { method: "Ether", + // aclass: "currency", + // asset: "XETH", + // refid: "A2BF34S-O7LBNQ-UE4Y4O", + // txid: "0x288b83c6b0904d8400ef44e1c9e2187b5c8f7ea3d838222d53f701a15b5c274d", + // info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0", + // amount: "9.9950000000", + // fee: "0.0050000000", + // time: 1530481750, + // status: "Success" } + // + const id = this.safeString (transaction, 'refid'); + const txid = this.safeString (transaction, 'txid'); + const timestamp = this.safeTimestamp (transaction, 'time'); + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const address = this.safeString (transaction, 'info'); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const type = this.safeString (transaction, 'type'); // injected from the outside + let feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost === undefined) { + if (type === 'deposit') { + feeCost = 0; + } + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseTransactionsByType (type, transactions, code = undefined, since = undefined, limit = undefined) { + const result = []; + for (let i = 0; i < transactions.length; i++) { + const transaction = this.parseTransaction (this.extend ({ + 'type': type, + }, transactions[i])); + result.push (transaction); + } + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + // https://www.kraken.com/en-us/help/api#deposit-status + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privatePostDepositStatus (this.extend (request, params)); + // + // { error: [], + // result: [ { method: "Ether (Hex)", + // aclass: "currency", + // asset: "XETH", + // refid: "Q2CANKL-LBFVEE-U4Y2WQ", + // txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…", + // info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290", + // amount: "7.9999257900", + // fee: "0.0000000000", + // time: 1529223212, + // status: "Success" } ] } + // + return this.parseTransactionsByType ('deposit', response['result'], code, since, limit); + } + + async fetchTime (params = {}) { + // https://www.kraken.com/en-us/features/api#get-server-time + const response = await this.publicGetTime (params); + // + // { + // "error": [], + // "result": { + // "unixtime": 1591502873, + // "rfc1123": "Sun, 7 Jun 20 04:07:53 +0000" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.safeTimestamp (result, 'unixtime'); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + // https://www.kraken.com/en-us/help/api#withdraw-status + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privatePostWithdrawStatus (this.extend (request, params)); + // + // { error: [], + // result: [ { method: "Ether", + // aclass: "currency", + // asset: "XETH", + // refid: "A2BF34S-O7LBNQ-UE4Y4O", + // txid: "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d", + // info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0", + // amount: "9.9950000000", + // fee: "0.0050000000", + // time: 1530481750, + // status: "Success" } ] } + // + return this.parseTransactionsByType ('withdrawal', response['result'], code, since, limit); + } + + async createDepositAddress (code, params = {}) { + const request = { + 'new': 'true', + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + // eslint-disable-next-line quotes + let method = this.safeString (params, 'method'); + if (method === undefined) { + if (this.options['cacheDepositMethodsOnFetchDepositAddress']) { + // cache depositMethods + if (!(code in this.options['depositMethods'])) { + this.options['depositMethods'][code] = await this.fetchDepositMethods (code); + } + method = this.options['depositMethods'][code][0]['method']; + } else { + throw new ArgumentsRequired (this.id + ' fetchDepositAddress() requires an extra `method` parameter. Use fetchDepositMethods ("' + code + '") to get a list of available deposit methods or enable the exchange property .options["cacheDepositMethodsOnFetchDepositAddress"] = true'); + } + } + const request = { + 'asset': currency['id'], + 'method': method, + }; + const response = await this.privatePostDepositAddresses (this.extend (request, params)); // overwrite methods + const result = response['result']; + const numResults = result.length; + if (numResults < 1) { + throw new InvalidAddress (this.id + ' privatePostDepositAddresses() returned no addresses'); + } + const address = this.safeString (result[0], 'address'); + const tag = this.safeString2 (result[0], 'tag', 'memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + if ('key' in params) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + 'amount': amount, + // 'address': address, // they don't allow withdrawals to direct addresses + }; + const response = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': response['result'], + }; + } + throw new ExchangeError (this.id + " withdraw requires a 'key' parameter (withdrawal key name, as set up on your account)"); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.version + '/' + api + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, params)); + const auth = this.encode (nonce + body); + const hash = this.hash (auth, 'sha256', 'binary'); + const binary = this.stringToBinary (this.encode (url)); + const binhash = this.binaryConcat (binary, hash); + const secret = this.base64ToBinary (this.secret); + const signature = this.hmac (binhash, secret, 'sha512', 'base64'); + headers = { + 'API-Key': this.apiKey, + 'API-Sign': signature, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } else { + url = '/' + path; + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + nonce () { + return this.milliseconds (); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code === 520) { + throw new ExchangeNotAvailable (this.id + ' ' + code.toString () + ' ' + reason); + } + // todo: rewrite this for "broad" exceptions matching + if (body.indexOf ('Invalid order') >= 0) { + throw new InvalidOrder (this.id + ' ' + body); + } + if (body.indexOf ('Invalid nonce') >= 0) { + throw new InvalidNonce (this.id + ' ' + body); + } + if (body.indexOf ('Insufficient funds') >= 0) { + throw new InsufficientFunds (this.id + ' ' + body); + } + if (body.indexOf ('Cancel pending') >= 0) { + throw new CancelPending (this.id + ' ' + body); + } + if (body.indexOf ('Invalid arguments:volume') >= 0) { + throw new InvalidOrder (this.id + ' ' + body); + } + if (body[0] === '{') { + if (typeof response !== 'string') { + if ('error' in response) { + const numErrors = response['error'].length; + if (numErrors) { + const message = this.id + ' ' + body; + for (let i = 0; i < response['error'].length; i++) { + const error = response['error'][i]; + this.throwExactlyMatchedException (this.exceptions, error, message); + } + throw new ExchangeError (message); + } + } + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],100:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, AccountSuspended, InvalidNonce, NotSupported, BadRequest, AuthenticationError, BadSymbol, RateLimitExceeded, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class kucoin extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'kucoin', + 'name': 'KuCoin', + 'countries': [ 'SC' ], + 'rateLimit': 334, + 'version': 'v2', + 'certified': false, + 'pro': true, + 'comment': 'Platform 2.0', + 'has': { + 'CORS': false, + 'fetchStatus': true, + 'fetchTime': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOrderBook': true, + 'fetchOrder': true, + 'fetchClosedOrders': true, + 'fetchOpenOrders': true, + 'fetchDepositAddress': true, + 'createDepositAddress': true, + 'withdraw': true, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchBalance': true, + 'fetchTrades': true, + 'fetchMyTrades': true, + 'createOrder': true, + 'cancelOrder': true, + 'fetchAccounts': true, + 'fetchFundingFee': true, + 'fetchOHLCV': true, + 'fetchLedger': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg', + 'referral': 'https://www.kucoin.com/?rcode=E5wkqe', + 'api': { + 'public': 'https://openapi-v2.kucoin.com', + 'private': 'https://openapi-v2.kucoin.com', + }, + 'test': { + 'public': 'https://openapi-sandbox.kucoin.com', + 'private': 'https://openapi-sandbox.kucoin.com', + }, + 'www': 'https://www.kucoin.com', + 'doc': [ + 'https://docs.kucoin.com', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'api': { + 'public': { + 'get': [ + 'timestamp', + 'status', + 'symbols', + 'markets', + 'market/allTickers', + 'market/orderbook/level{level}', + 'market/orderbook/level2', + 'market/orderbook/level2_20', + 'market/orderbook/level2_100', + 'market/orderbook/level3', + 'market/histories', + 'market/candles', + 'market/stats', + 'currencies', + 'currencies/{currency}', + 'prices', + 'mark-price/{symbol}/current', + 'margin/config', + ], + 'post': [ + 'bullet-public', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/{accountId}', + 'accounts/{accountId}/ledgers', + 'accounts/{accountId}/holds', + 'accounts/transferable', + 'sub/user', + 'sub-accounts', + 'sub-accounts/{subUserId}', + 'deposit-addresses', + 'deposits', + 'hist-deposits', + 'hist-orders', + 'hist-withdrawals', + 'withdrawals', + 'withdrawals/quotas', + 'orders', + 'orders/{orderId}', + 'limit/orders', + 'fills', + 'limit/fills', + 'margin/account', + 'margin/borrow', + 'margin/borrow/outstanding', + 'margin/borrow/borrow/repaid', + 'margin/lend/active', + 'margin/lend/done', + 'margin/lend/trade/unsettled', + 'margin/lend/trade/settled', + 'margin/lend/assets', + 'margin/market', + 'margin/trade/last', + ], + 'post': [ + 'accounts', + 'accounts/inner-transfer', + 'accounts/sub-transfer', + 'deposit-addresses', + 'withdrawals', + 'orders', + 'orders/multi', + 'margin/borrow', + 'margin/repay/all', + 'margin/repay/single', + 'margin/lend', + 'margin/toggle-auto-lend', + 'bullet-private', + ], + 'delete': [ + 'withdrawals/{withdrawalId}', + 'orders', + 'orders/{orderId}', + 'margin/lend/{orderId}', + ], + }, + }, + 'timeframes': { + '1m': '1min', + '3m': '3min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '8h': '8hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + }, + 'exceptions': { + 'exact': { + 'order not exist': OrderNotFound, + 'order not exist.': OrderNotFound, // duplicated error temporarily + 'order_not_exist': OrderNotFound, // {"code":"order_not_exist","msg":"order_not_exist"} ¯\_(ツ)_/¯ + 'order_not_exist_or_not_allow_to_cancel': InvalidOrder, // {"code":"400100","msg":"order_not_exist_or_not_allow_to_cancel"} + 'Order size below the minimum requirement.': InvalidOrder, // {"code":"400100","msg":"Order size below the minimum requirement."} + 'The withdrawal amount is below the minimum requirement.': ExchangeError, // {"code":"400100","msg":"The withdrawal amount is below the minimum requirement."} + '400': BadRequest, + '401': AuthenticationError, + '403': NotSupported, + '404': NotSupported, + '405': NotSupported, + '429': RateLimitExceeded, + '500': ExchangeNotAvailable, // Internal Server Error -- We had a problem with our server. Try again later. + '503': ExchangeNotAvailable, + '101030': PermissionDenied, // {"code":"101030","msg":"You haven't yet enabled the margin trading"} + '200004': InsufficientFunds, + '230003': InsufficientFunds, // {"code":"230003","msg":"Balance insufficient!"} + '260100': InsufficientFunds, // {"code":"260100","msg":"account.noBalance"} + '300000': InvalidOrder, + '400000': BadSymbol, + '400001': AuthenticationError, + '400002': InvalidNonce, + '400003': AuthenticationError, + '400004': AuthenticationError, + '400005': AuthenticationError, + '400006': AuthenticationError, + '400007': AuthenticationError, + '400008': NotSupported, + '400100': BadRequest, + '411100': AccountSuspended, + '415000': BadRequest, // {"code":"415000","msg":"Unsupported Media Type"} + '500000': ExchangeError, + }, + 'broad': { + 'Exceeded the access frequency': RateLimitExceeded, + 'require more permission': PermissionDenied, + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'commonCurrencies': { + 'HOT': 'HOTNOW', + 'EDGE': 'DADI', // https://github.com/ccxt/ccxt/issues/5756 + 'WAX': 'WAXP', + 'TRY': 'Trias', + }, + 'options': { + 'version': 'v1', + 'symbolSeparator': '-', + 'fetchMyTradesMethod': 'private_get_fills', + 'fetchBalance': { + 'type': 'trade', // or 'main' + }, + // endpoint versions + 'versions': { + 'public': { + 'GET': { + 'status': 'v1', + 'market/orderbook/level{level}': 'v1', + 'market/orderbook/level2': 'v2', + 'market/orderbook/level2_20': 'v1', + 'market/orderbook/level2_100': 'v1', + }, + }, + 'private': { + 'POST': { + 'accounts/inner-transfer': 'v2', + 'accounts/sub-transfer': 'v2', + }, + }, + }, + }, + }); + } + + nonce () { + return this.milliseconds (); + } + + async loadTimeDifference (params = {}) { + const response = await this.publicGetTimestamp (params); + const after = this.milliseconds (); + const kucoinTime = this.safeInteger (response, 'data'); + this.options['timeDifference'] = parseInt (after - kucoinTime); + return this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTimestamp (params); + // + // { + // "code":"200000", + // "msg":"success", + // "data":1546837113087 + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchStatus (params = {}) { + const response = await this.publicGetStatus (params); + // + // { + // "code":"200000", + // "data":{ + // "msg":"", + // "status":"open" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let status = this.safeValue (data, 'status'); + if (status !== undefined) { + status = (status === 'open') ? 'ok' : 'maintenance'; + this.status = this.extend (this.status, { + 'status': status, + 'updated': this.milliseconds (), + }); + } + return this.status; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetSymbols (params); + // + // { + // quoteCurrency: 'BTC', + // symbol: 'KCS-BTC', + // quoteMaxSize: '9999999', + // quoteIncrement: '0.000001', + // baseMinSize: '0.01', + // quoteMinSize: '0.00001', + // enableTrading: true, + // priceIncrement: '0.00000001', + // name: 'KCS-BTC', + // baseIncrement: '0.01', + // baseMaxSize: '9999999', + // baseCurrency: 'KCS' + // } + // + const data = response['data']; + const result = []; + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const id = this.safeString (market, 'symbol'); + const [ baseId, quoteId ] = id.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = this.safeValue (market, 'enableTrading'); + const baseMaxSize = this.safeFloat (market, 'baseMaxSize'); + const baseMinSize = this.safeFloat (market, 'baseMinSize'); + const quoteMaxSize = this.safeFloat (market, 'quoteMaxSize'); + const quoteMinSize = this.safeFloat (market, 'quoteMinSize'); + // const quoteIncrement = this.safeFloat (market, 'quoteIncrement'); + const precision = { + 'amount': this.precisionFromString (this.safeString (market, 'baseIncrement')), + 'price': this.precisionFromString (this.safeString (market, 'priceIncrement')), + }; + const limits = { + 'amount': { + 'min': baseMinSize, + 'max': baseMaxSize, + }, + 'price': { + 'min': this.safeFloat (market, 'priceIncrement'), + 'max': quoteMaxSize / baseMinSize, + }, + 'cost': { + 'min': quoteMinSize, + 'max': quoteMaxSize, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "currency": "OMG", + // "name": "OMG", + // "fullName": "OmiseGO", + // "precision": 8, + // "confirms": 12, + // "withdrawalMinSize": "4", + // "withdrawalMinFee": "1.25", + // "isWithdrawEnabled": false, + // "isDepositEnabled": false, + // "isMarginEnabled": false, + // "isDebitEnabled": false + // } + // + const responseData = response['data']; + const result = {}; + for (let i = 0; i < responseData.length; i++) { + const entry = responseData[i]; + const id = this.safeString (entry, 'currency'); + const name = this.safeString (entry, 'fullName'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (entry, 'precision'); + const isWithdrawEnabled = this.safeValue (entry, 'isWithdrawEnabled', false); + const isDepositEnabled = this.safeValue (entry, 'isDepositEnabled', false); + const fee = this.safeFloat (entry, 'withdrawalMinFee'); + const active = (isWithdrawEnabled && isDepositEnabled); + result[code] = { + 'id': id, + 'name': name, + 'code': code, + 'precision': precision, + 'info': entry, + 'active': active, + 'fee': fee, + 'limits': this.limits, + }; + } + return result; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccounts (params); + // + // { + // code: "200000", + // data: [ + // { + // balance: "0.00009788", + // available: "0.00009788", + // holds: "0", + // currency: "BTC", + // id: "5c6a4fd399a1d81c4f9cc4d0", + // type: "trade" + // }, + // { + // balance: "0.00000001", + // available: "0.00000001", + // holds: "0", + // currency: "ETH", + // id: "5c6a49ec99a1d819392e8e9f", + // type: "trade" + // } + // ] + // } + // + const data = this.safeValue (response, 'data'); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const accountId = this.safeString (account, 'id'); + const currencyId = this.safeString (account, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const type = this.safeString (account, 'type'); // main or trade + result.push ({ + 'id': accountId, + 'type': type, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async fetchFundingFee (code, params = {}) { + const currencyId = this.currencyId (code); + const request = { + 'currency': currencyId, + }; + const response = await this.privateGetWithdrawalsQuotas (this.extend (request, params)); + const data = response['data']; + const withdrawFees = {}; + withdrawFees[code] = this.safeFloat (data, 'withdrawMinFee'); + return { + 'info': response, + 'withdraw': withdrawFees, + 'deposit': {}, + }; + } + + parseTicker (ticker, market = undefined) { + // + // { + // symbol: "ETH-BTC", + // high: "0.019518", + // vol: "7997.82836194", + // last: "0.019329", + // low: "0.019", + // buy: "0.019329", + // sell: "0.01933", + // changePrice: "-0.000139", + // time: 1580553706304, + // averagePrice: "0.01926386", + // changeRate: "-0.0071", + // volValue: "154.40791568183474" + // } + // + // { + // "trading": true, + // "symbol": "KCS-BTC", + // "buy": 0.00011, + // "sell": 0.00012, + // "sort": 100, + // "volValue": 3.13851792584, //total + // "baseCurrency": "KCS", + // "market": "BTC", + // "quoteCurrency": "BTC", + // "symbolCode": "KCS-BTC", + // "datetime": 1548388122031, + // "high": 0.00013, + // "vol": 27514.34842, + // "low": 0.0001, + // "changePrice": -1.0e-5, + // "changeRate": -0.0769, + // "lastTradedPrice": 0.00012, + // "board": 0, + // "mark": 0 + // } + // + let percentage = this.safeFloat (ticker, 'changeRate'); + if (percentage !== undefined) { + percentage = percentage * 100; + } + const last = this.safeFloat2 (ticker, 'last', 'lastTradedPrice'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const baseVolume = this.safeFloat (ticker, 'vol'); + const quoteVolume = this.safeFloat (ticker, 'volValue'); + const vwap = this.vwap (baseVolume, quoteVolume); + const timestamp = this.safeInteger2 (ticker, 'time', 'datetime'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'changePrice'), + 'percentage': percentage, + 'average': this.safeFloat (ticker, 'averagePrice'), + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketAllTickers (params); + // + // { + // "code": "200000", + // "data": { + // "date": 1550661940645, + // "ticker": [ + // 'buy': '0.00001168', + // 'changePrice': '-0.00000018', + // 'changeRate': '-0.0151', + // 'datetime': 1550661146316, + // 'high': '0.0000123', + // 'last': '0.00001169', + // 'low': '0.00001159', + // 'sell': '0.00001182', + // 'symbol': 'LOOM-BTC', + // 'vol': '44399.5669' + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'ticker', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = this.safeString (ticker, 'symbol'); + if (symbol !== undefined) { + result[symbol] = ticker; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketStats (this.extend (request, params)); + // + // { + // "code": "200000", + // "data": { + // 'buy': '0.00001168', + // 'changePrice': '-0.00000018', + // 'changeRate': '-0.0151', + // 'datetime': 1550661146316, + // 'high': '0.0000123', + // 'last': '0.00001169', + // 'low': '0.00001159', + // 'sell': '0.00001182', + // 'symbol': 'LOOM-BTC', + // 'vol': '44399.5669' + // }, + // } + // + return this.parseTicker (response['data'], market); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "1545904980", // Start time of the candle cycle + // "0.058", // opening price + // "0.049", // closing price + // "0.058", // highest price + // "0.049", // lowest price + // "0.018", // base volume + // "0.000945", // quote volume + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '15m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketId = market['id']; + const request = { + 'symbol': marketId, + 'type': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe) * 1000; + let endAt = this.milliseconds (); // required param + if (since !== undefined) { + request['startAt'] = parseInt (Math.floor (since / 1000)); + if (limit === undefined) { + // https://docs.kucoin.com/#get-klines + // https://docs.kucoin.com/#details + // For each query, the system would return at most 1500 pieces of data. + // To obtain more data, please page the data by time. + limit = this.safeInteger (this.options, 'fetchOHLCVLimit', 1500); + } + endAt = this.sum (since, limit * duration); + } else if (limit !== undefined) { + since = endAt - limit * duration; + request['startAt'] = parseInt (Math.floor (since / 1000)); + } + request['endAt'] = parseInt (Math.floor (endAt / 1000)); + const response = await this.publicGetMarketCandles (this.extend (request, params)); + // + // { + // "code":"200000", + // "data":[ + // ["1591517700","0.025078","0.025069","0.025084","0.025064","18.9883256","0.4761861079404"], + // ["1591516800","0.025089","0.025079","0.025089","0.02506","99.4716622","2.494143499081"], + // ["1591515900","0.025079","0.02509","0.025091","0.025068","59.83701271","1.50060885172798"], + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currencyId = this.currencyId (code); + const request = { 'currency': currencyId }; + const response = await this.privatePostDepositAddresses (this.extend (request, params)); + // BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}} + // BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}} + const data = this.safeValue (response, 'data', {}); + let address = this.safeString (data, 'address'); + // BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address + if (address !== undefined) { + address = address.replace ('bitcoincash:', ''); + } + const tag = this.safeString (data, 'memo'); + this.checkAddress (address); + return { + 'info': response, + 'currency': code, + 'address': address, + 'tag': tag, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currencyId = this.currencyId (code); + const request = { 'currency': currencyId }; + const response = await this.privateGetDepositAddresses (this.extend (request, params)); + // BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}} + // BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}} + const data = this.safeValue (response, 'data', {}); + let address = this.safeString (data, 'address'); + // BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address + if (address !== undefined) { + address = address.replace ('bitcoincash:', ''); + } + const tag = this.safeString (data, 'memo'); + this.checkAddress (address); + return { + 'info': response, + 'currency': code, + 'address': address, + 'tag': tag, + }; + } + + async fetchL3OrderBook (symbol, limit = undefined, params = {}) { + return await this.fetchOrderBook (symbol, limit, { 'level': 3 }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + const level = this.safeInteger (params, 'level', 2); + let levelLimit = level.toString (); + if (levelLimit === '2') { + if (limit !== undefined) { + if ((limit !== 20) && (limit !== 100)) { + throw new ExchangeError (this.id + ' fetchOrderBook limit argument must be undefined, 20 or 100'); + } + levelLimit += '_' + limit.toString (); + } + } + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { 'symbol': marketId, 'level': levelLimit }; + const response = await this.publicGetMarketOrderbookLevelLevel (this.extend (request, params)); + // + // 'market/orderbook/level2' + // 'market/orderbook/level2_20' + // 'market/orderbook/level2_100' + // + // { + // "code":"200000", + // "data":{ + // "sequence":"1583235112106", + // "asks":[ + // // ... + // ["0.023197","12.5067468"], + // ["0.023194","1.8"], + // ["0.023191","8.1069672"] + // ], + // "bids":[ + // ["0.02319","1.6000002"], + // ["0.023189","2.2842325"], + // ], + // "time":1586584067274 + // } + // } + // + // 'market/orderbook/level3' + // + // { + // "code":"200000", + // "data":{ + // "sequence":"1583731857120", + // "asks":[ + // // id, price, size, timestamp in nanoseconds + // ["5e915f8acd26670009675300","6925.7","0.2","1586585482194286069"], + // ["5e915f8ace35a200090bba48","6925.7","0.001","1586585482229569826"], + // ["5e915f8a8857740009ca7d33","6926","0.00001819","1586585482149148621"], + // ], + // "bids":[ + // ["5e915f8acca406000ac88194","6925.6","0.05","1586585482384384842"], + // ["5e915f93cd26670009676075","6925.6","0.08","1586585491334914600"], + // ["5e915f906aa6e200099b49f6","6925.4","0.2","1586585488941126340"], + // ], + // "time":1586585492487 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'time'); + const orderbook = this.parseOrderBook (data, timestamp, 'bids', 'asks', level - 2, level - 1); + orderbook['nonce'] = this.safeInteger (data, 'sequence'); + return orderbook; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + // required param, cannot be used twice + const clientOrderId = this.safeString2 (params, 'clientOid', 'clientOrderId', this.uuid ()); + params = this.omit (params, [ 'clientOid', 'clientOrderId' ]); + const request = { + 'clientOid': clientOrderId, + 'side': side, + 'symbol': marketId, + 'type': type, + }; + if (type !== 'market') { + request['price'] = this.priceToPrecision (symbol, price); + request['size'] = this.amountToPrecision (symbol, amount); + } else { + if (this.safeValue (params, 'quoteAmount')) { + // used to create market order by quote amount - https://github.com/ccxt/ccxt/issues/4876 + request['funds'] = this.amountToPrecision (symbol, amount); + } else { + request['size'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "orderId": "5bd6e9286d99522a52e458de" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.milliseconds (); + const id = this.safeString (data, 'orderId'); + const order = { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': data, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': undefined, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': undefined, + 'fee': undefined, + 'trades': undefined, + }; + if (!this.safeValue (params, 'quoteAmount')) { + order['amount'] = amount; + } + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { 'orderId': id }; + const response = await this.privateDeleteOrdersOrderId (this.extend (request, params)); + return response; + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': status, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['startAt'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "currentPage": 1, + // "pageSize": 1, + // "totalNum": 153408, + // "totalPage": 153408, + // "items": [ + // { + // "id": "5c35c02703aa673ceec2a168", //orderid + // "symbol": "BTC-USDT", //symbol + // "opType": "DEAL", // operation type,deal is pending order,cancel is cancel order + // "type": "limit", // order type,e.g. limit,markrt,stop_limit. + // "side": "buy", // transaction direction,include buy and sell + // "price": "10", // order price + // "size": "2", // order quantity + // "funds": "0", // order funds + // "dealFunds": "0.166", // deal funds + // "dealSize": "2", // deal quantity + // "fee": "0", // fee + // "feeCurrency": "USDT", // charge fee currency + // "stp": "", // self trade prevention,include CN,CO,DC,CB + // "stop": "", // stop type + // "stopTriggered": false, // stop order is triggered + // "stopPrice": "0", // stop price + // "timeInForce": "GTC", // time InForce,include GTC,GTT,IOC,FOK + // "postOnly": false, // postOnly + // "hidden": false, // hidden order + // "iceberg": false, // iceberg order + // "visibleSize": "0", // display quantity for iceberg order + // "cancelAfter": 0, // cancel orders time,requires timeInForce to be GTT + // "channel": "IOS", // order source + // "clientOid": "", // user-entered order unique mark + // "remark": "", // remark + // "tags": "", // tag order source + // "isActive": false, // status before unfilled or uncancelled + // "cancelExist": false, // order cancellation transaction record + // "createdAt": 1547026471000 // time + // }, + // ] + // } + // } + const responseData = this.safeValue (response, 'data', {}); + const orders = this.safeValue (responseData, 'items', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('done', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('active', symbol, since, limit, params); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // a special case for undefined ids + // otherwise a wrong endpoint for all orders will be triggered + // https://github.com/ccxt/ccxt/issues/7234 + if (id === undefined) { + throw new InvalidOrder (this.id + ' fetchOrder requires an order id'); + } + const request = { + 'orderId': id, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privateGetOrdersOrderId (this.extend (request, params)); + const responseData = this.safeValue (response, 'data'); + return this.parseOrder (responseData, market); + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrders, fetchClosedOrders + // + // { + // "id": "5c35c02703aa673ceec2a168", //orderid + // "symbol": "BTC-USDT", //symbol + // "opType": "DEAL", // operation type,deal is pending order,cancel is cancel order + // "type": "limit", // order type,e.g. limit,markrt,stop_limit. + // "side": "buy", // transaction direction,include buy and sell + // "price": "10", // order price + // "size": "2", // order quantity + // "funds": "0", // order funds + // "dealFunds": "0.166", // deal funds + // "dealSize": "2", // deal quantity + // "fee": "0", // fee + // "feeCurrency": "USDT", // charge fee currency + // "stp": "", // self trade prevention,include CN,CO,DC,CB + // "stop": "", // stop type + // "stopTriggered": false, // stop order is triggered + // "stopPrice": "0", // stop price + // "timeInForce": "GTC", // time InForce,include GTC,GTT,IOC,FOK + // "postOnly": false, // postOnly + // "hidden": false, // hidden order + // "iceberg": false, // iceberg order + // "visibleSize": "0", // display quantity for iceberg order + // "cancelAfter": 0, // cancel orders time,requires timeInForce to be GTT + // "channel": "IOS", // order source + // "clientOid": "", // user-entered order unique mark + // "remark": "", // remark + // "tags": "", // tag order source + // "isActive": false, // status before unfilled or uncancelled + // "cancelExist": false, // order cancellation transaction record + // "createdAt": 1547026471000 // time + // } + // + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const orderId = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const timestamp = this.safeInteger (order, 'createdAt'); + const datetime = this.iso8601 (timestamp); + let price = this.safeFloat (order, 'price'); + const side = this.safeString (order, 'side'); + const feeCurrencyId = this.safeString (order, 'feeCurrency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + const feeCost = this.safeFloat (order, 'fee'); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'dealSize'); + const cost = this.safeFloat (order, 'dealFunds'); + const remaining = amount - filled; + // bool + const isActive = this.safeValue (order, 'isActive', false); + const cancelExist = this.safeValue (order, 'cancelExist', false); + let status = isActive ? 'open' : 'closed'; + status = cancelExist ? 'canceled' : status; + const fee = { + 'currency': feeCurrency, + 'cost': feeCost, + }; + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + const clientOrderId = this.safeString (order, 'clientOid'); + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'id': orderId, + 'clientOrderId': clientOrderId, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'amount': amount, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'timestamp': timestamp, + 'datetime': datetime, + 'fee': fee, + 'status': status, + 'info': order, + 'lastTradeTimestamp': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const method = this.options['fetchMyTradesMethod']; + let parseResponseData = false; + if (method === 'private_get_fills') { + // does not return trades earlier than 2019-02-18T00:00:00Z + if (since !== undefined) { + // only returns trades up to one week after the since param + request['startAt'] = since; + } + } else if (method === 'private_get_limit_fills') { + // does not return trades earlier than 2019-02-18T00:00:00Z + // takes no params + // only returns first 1000 trades (not only "in the last 24 hours" as stated in the docs) + parseResponseData = true; + } else if (method === 'private_get_hist_orders') { + // despite that this endpoint is called `HistOrders` + // it returns historical trades instead of orders + // returns trades earlier than 2019-02-18T00:00:00Z only + if (since !== undefined) { + request['startAt'] = parseInt (since / 1000); + } + } else { + throw new ExchangeError (this.id + ' invalid fetchClosedOrder method'); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "currentPage": 1, + // "pageSize": 50, + // "totalNum": 1, + // "totalPage": 1, + // "items": [ + // { + // "symbol":"BTC-USDT", // symbol + // "tradeId":"5c35c02709e4f67d5266954e", // trade id + // "orderId":"5c35c02703aa673ceec2a168", // order id + // "counterOrderId":"5c1ab46003aa676e487fa8e3", // counter order id + // "side":"buy", // transaction direction,include buy and sell + // "liquidity":"taker", // include taker and maker + // "forceTaker":true, // forced to become taker + // "price":"0.083", // order price + // "size":"0.8424304", // order quantity + // "funds":"0.0699217232", // order funds + // "fee":"0", // fee + // "feeRate":"0", // fee rate + // "feeCurrency":"USDT", // charge fee currency + // "stop":"", // stop type + // "type":"limit", // order type, e.g. limit, market, stop_limit. + // "createdAt":1547026472000 // time + // }, + // //------------------------------------------------------ + // // v1 (historical) trade response structure + // { + // "symbol": "SNOV-ETH", + // "dealPrice": "0.0000246", + // "dealValue": "0.018942", + // "amount": "770", + // "fee": "0.00001137", + // "side": "sell", + // "createdAt": 1540080199 + // "id":"5c4d389e4c8c60413f78e2e5", + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + let trades = undefined; + if (parseResponseData) { + trades = data; + } else { + trades = this.safeValue (data, 'items', []); + } + return this.parseTrades (trades, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startAt'] = Math.floor (since / 1000); + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this.publicGetMarketHistories (this.extend (request, params)); + // + // { + // "code": "200000", + // "data": [ + // { + // "sequence": "1548764654235", + // "side": "sell", + // "size":"0.6841354", + // "price":"0.03202", + // "time":1548848575203567174 + // } + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "sequence": "1548764654235", + // "side": "sell", + // "size":"0.6841354", + // "price":"0.03202", + // "time":1548848575203567174 + // } + // + // { + // sequence: '1568787654360', + // symbol: 'BTC-USDT', + // side: 'buy', + // size: '0.00536577', + // price: '9345', + // takerOrderId: '5e356c4a9f1a790008f8d921', + // time: '1580559434436443257', + // type: 'match', + // makerOrderId: '5e356bffedf0010008fa5d7f', + // tradeId: '5e356c4aeefabd62c62a1ece' + // } + // + // fetchMyTrades (private) v2 + // + // { + // "symbol":"BTC-USDT", + // "tradeId":"5c35c02709e4f67d5266954e", + // "orderId":"5c35c02703aa673ceec2a168", + // "counterOrderId":"5c1ab46003aa676e487fa8e3", + // "side":"buy", + // "liquidity":"taker", + // "forceTaker":true, + // "price":"0.083", + // "size":"0.8424304", + // "funds":"0.0699217232", + // "fee":"0", + // "feeRate":"0", + // "feeCurrency":"USDT", + // "stop":"", + // "type":"limit", + // "createdAt":1547026472000 + // } + // + // fetchMyTrades v2 alternative format since 2019-05-21 https://github.com/ccxt/ccxt/pull/5162 + // + // { + // symbol: "OPEN-BTC", + // forceTaker: false, + // orderId: "5ce36420054b4663b1fff2c9", + // fee: "0", + // feeCurrency: "", + // type: "", + // feeRate: "0", + // createdAt: 1558417615000, + // size: "12.8206", + // stop: "", + // price: "0", + // funds: "0", + // tradeId: "5ce390cf6e0db23b861c6e80" + // } + // + // fetchMyTrades (private) v1 (historical) + // + // { + // "symbol": "SNOV-ETH", + // "dealPrice": "0.0000246", + // "dealValue": "0.018942", + // "amount": "770", + // "fee": "0.00001137", + // "side": "sell", + // "createdAt": 1540080199 + // "id":"5c4d389e4c8c60413f78e2e5", + // } + // + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const id = this.safeString2 (trade, 'tradeId', 'id'); + const orderId = this.safeString (trade, 'orderId'); + const takerOrMaker = this.safeString (trade, 'liquidity'); + const amount = this.safeFloat2 (trade, 'size', 'amount'); + let timestamp = this.safeInteger (trade, 'time'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp / 1000000); + } else { + timestamp = this.safeInteger (trade, 'createdAt'); + // if it's a historical v1 trade, the exchange returns timestamp in seconds + if (('dealValue' in trade) && (timestamp !== undefined)) { + timestamp = timestamp * 1000; + } + } + const price = this.safeFloat2 (trade, 'price', 'dealPrice'); + const side = this.safeString (trade, 'side'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + let feeCurrency = this.safeCurrencyCode (feeCurrencyId); + if (feeCurrency === undefined) { + if (market !== undefined) { + feeCurrency = (side === 'sell') ? market['quote'] : market['base']; + } + } + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': this.safeFloat (trade, 'feeRate'), + }; + } + let type = this.safeString (trade, 'type'); + if (type === 'match') { + type = undefined; + } + let cost = this.safeFloat2 (trade, 'funds', 'dealValue'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currencyId (code); + const request = { + 'currency': currency, + 'address': address, + 'amount': amount, + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const response = await this.privatePostWithdrawals (this.extend (request, params)); + // + // https://github.com/ccxt/ccxt/issues/5558 + // + // { + // "code": 200000, + // "data": { + // "withdrawalId": "abcdefghijklmnopqrstuvwxyz" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return { + 'id': this.safeString (data, 'withdrawalId'), + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'SUCCESS': 'ok', + 'PROCESSING': 'ok', + 'FAILURE': 'failed', + }; + return this.safeString (statuses, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989", + // "memo": "5c247c8a03aa677cea2a251d", + // "amount": 1, + // "fee": 0.0001, + // "currency": "KCS", + // "isInner": false, + // "walletTxId": "5bbb57386d99522d9f954c5a@test004", + // "status": "SUCCESS", + // "createdAt": 1544178843000, + // "updatedAt": 1544178891000 + // } + // + // fetchWithdrawals + // + // { + // "id": "5c2dc64e03aa675aa263f1ac", + // "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9", + // "memo": "", + // "currency": "ETH", + // "amount": 1.0000000, + // "fee": 0.0100000, + // "walletTxId": "3e2414d82acce78d38be7fe9", + // "isInner": false, + // "status": "FAILURE", + // "createdAt": 1546503758000, + // "updatedAt": 1546504603000 + // } + // + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + let address = this.safeString (transaction, 'address'); + const amount = this.safeFloat (transaction, 'amount'); + let txid = this.safeString (transaction, 'walletTxId'); + if (txid !== undefined) { + const txidParts = txid.split ('@'); + const numTxidParts = txidParts.length; + if (numTxidParts > 1) { + if (address === undefined) { + if (txidParts[1].length > 1) { + address = txidParts[1]; + } + } + } + txid = txidParts[0]; + } + let type = (txid === undefined) ? 'withdrawal' : 'deposit'; + const rawStatus = this.safeString (transaction, 'status'); + const status = this.parseTransactionStatus (rawStatus); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + let rate = undefined; + if (amount !== undefined) { + rate = feeCost / amount; + } + fee = { + 'cost': feeCost, + 'rate': rate, + 'currency': code, + }; + } + const tag = this.safeString (transaction, 'memo'); + let timestamp = this.safeInteger2 (transaction, 'createdAt', 'createAt'); + const id = this.safeString (transaction, 'id'); + let updated = this.safeInteger (transaction, 'updatedAt'); + const isV1 = !('createdAt' in transaction); + // if it's a v1 structure + if (isV1) { + type = ('address' in transaction) ? 'withdrawal' : 'deposit'; + if (timestamp !== undefined) { + timestamp = timestamp * 1000; + } + if (updated !== undefined) { + updated = updated * 1000; + } + } + return { + 'id': id, + 'address': address, + 'tag': tag, + 'currency': code, + 'amount': amount, + 'txid': txid, + 'type': type, + 'status': status, + 'fee': fee, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'updated': updated, + 'info': transaction, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + let method = 'privateGetDeposits'; + if (since !== undefined) { + // if since is earlier than 2019-02-18T00:00:00Z + if (since < 1550448000000) { + request['startAt'] = parseInt (since / 1000); + method = 'privateGetHistDeposits'; + } else { + request['startAt'] = since; + } + } + const response = await this[method] (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "currentPage": 1, + // "pageSize": 5, + // "totalNum": 2, + // "totalPage": 1, + // "items": [ + // //-------------------------------------------------- + // // version 2 deposit response structure + // { + // "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989", + // "memo": "5c247c8a03aa677cea2a251d", + // "amount": 1, + // "fee": 0.0001, + // "currency": "KCS", + // "isInner": false, + // "walletTxId": "5bbb57386d99522d9f954c5a@test004", + // "status": "SUCCESS", + // "createdAt": 1544178843000, + // "updatedAt": 1544178891000 + // }, + // //-------------------------------------------------- + // // version 1 (historical) deposit response structure + // { + // "currency": "BTC", + // "createAt": 1528536998, + // "amount": "0.03266638", + // "walletTxId": "55c643bc2c68d6f17266383ac1be9e454038864b929ae7cee0bc408cc5c869e8@12ffGWmMMD1zA1WbFm7Ho3JZ1w6NYXjpFk@234", + // "isInner": false, + // "status": "SUCCESS", + // } + // ] + // } + // } + // + const responseData = response['data']['items']; + return this.parseTransactions (responseData, currency, since, limit, { 'type': 'deposit' }); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + let method = 'privateGetWithdrawals'; + if (since !== undefined) { + // if since is earlier than 2019-02-18T00:00:00Z + if (since < 1550448000000) { + request['startAt'] = parseInt (since / 1000); + method = 'privateGetHistWithdrawals'; + } else { + request['startAt'] = since; + } + } + const response = await this[method] (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "currentPage": 1, + // "pageSize": 5, + // "totalNum": 2, + // "totalPage": 1, + // "items": [ + // //-------------------------------------------------- + // // version 2 withdrawal response structure + // { + // "id": "5c2dc64e03aa675aa263f1ac", + // "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9", + // "memo": "", + // "currency": "ETH", + // "amount": 1.0000000, + // "fee": 0.0100000, + // "walletTxId": "3e2414d82acce78d38be7fe9", + // "isInner": false, + // "status": "FAILURE", + // "createdAt": 1546503758000, + // "updatedAt": 1546504603000 + // }, + // //-------------------------------------------------- + // // version 1 (historical) withdrawal response structure + // { + // "currency": "BTC", + // "createAt": 1526723468, + // "amount": "0.534", + // "address": "33xW37ZSW4tQvg443Pc7NLCAs167Yc2XUV", + // "walletTxId": "aeacea864c020acf58e51606169240e96774838dcd4f7ce48acf38e3651323f4", + // "isInner": false, + // "status": "SUCCESS" + // } + // ] + // } + // } + // + const responseData = response['data']['items']; + return this.parseTransactions (responseData, currency, since, limit, { 'type': 'withdrawal' }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + let type = undefined; + const request = {}; + if ('type' in params) { + type = params['type']; + if (type !== undefined) { + request['type'] = type; + } + params = this.omit (params, 'type'); + } else { + const options = this.safeValue (this.options, 'fetchBalance', {}); + type = this.safeString (options, 'type', 'trade'); + } + const response = await this.privateGetAccounts (this.extend (request, params)); + // + // { + // "code":"200000", + // "data":[ + // {"balance":"0.00009788","available":"0.00009788","holds":"0","currency":"BTC","id":"5c6a4fd399a1d81c4f9cc4d0","type":"trade"}, + // {"balance":"3.41060034","available":"3.41060034","holds":"0","currency":"SOUL","id":"5c6a4d5d99a1d8182d37046d","type":"trade"}, + // {"balance":"0.01562641","available":"0.01562641","holds":"0","currency":"NEO","id":"5c6a4f1199a1d8165a99edb1","type":"trade"}, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const result = { 'info': response }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const balanceType = this.safeString (balance, 'type'); + if (balanceType === type) { + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'holds'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchLedger requires a code param'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const currency = this.currency (code); + let accountId = this.safeString (params, 'accountId'); + if (accountId === undefined) { + for (let i = 0; i < this.accounts.length; i++) { + const account = this.accounts[i]; + if (account['currency'] === code && account['type'] === 'main') { + accountId = account['id']; + break; + } + } + } + if (accountId === undefined) { + throw new ExchangeError (this.id + ' ' + code + 'main account is not loaded in loadAccounts'); + } + const request = { + 'accountId': accountId, + }; + if (since !== undefined) { + request['startAt'] = Math.floor (since / 1000); + } + const response = await this.privateGetAccountsAccountIdLedgers (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // totalNum: 1, + // totalPage: 1, + // pageSize: 50, + // currentPage: 1, + // items: [ + // { + // createdAt: 1561897880000, + // amount: '0.0111123', + // bizType: 'Exchange', + // balance: '0.13224427', + // fee: '0.0000111', + // context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}', + // currency: 'ETH', + // direction: 'out' + // } + // ] + // } + // } + // + const items = response['data']['items']; + return this.parseLedger (items, currency, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // trade + // + // { + // createdAt: 1561897880000, + // amount: '0.0111123', + // bizType: 'Exchange', + // balance: '0.13224427', + // fee: '0.0000111', + // context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}', + // currency: 'ETH', + // direction: 'out' + // } + // + // withdrawal + // + // { + // createdAt: 1561900264000, + // amount: '0.14333217', + // bizType: 'Withdrawal', + // balance: '0', + // fee: '0.01', + // context: '{"orderId":"5d18b4e687111437cf1c48b9","txId":"0x1d136ee065c5c4c5caa293faa90d43e213c953d7cdd575c89ed0b54eb87228b8"}', + // currency: 'ETH', + // direction: 'out' + // } + // + const currencyId = this.safeString (item, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const fee = { + 'cost': this.safeFloat (item, 'fee'), + 'code': code, + }; + const amount = this.safeFloat (item, 'amount'); + const after = this.safeFloat (item, 'balance'); + const direction = this.safeString (item, 'direction'); + let before = undefined; + if (after !== undefined && amount !== undefined) { + const difference = (direction === 'out') ? amount : -amount; + before = this.sum (after, difference); + } + const timestamp = this.safeInteger (item, 'createdAt'); + const type = this.parseLedgerEntryType (this.safeString (item, 'bizType')); + const contextString = this.safeString (item, 'context'); + let id = undefined; + let referenceId = undefined; + if (this.isJsonEncodedObject (contextString)) { + const context = this.parseJson (contextString); + id = this.safeString (context, 'orderId'); + if (type === 'trade') { + referenceId = this.safeString (context, 'tradeId'); + } else if (type === 'transaction') { + referenceId = this.safeString (context, 'txId'); + } + } + return { + 'id': id, + 'currency': code, + 'account': undefined, + 'referenceAccount': undefined, + 'referenceId': referenceId, + 'status': undefined, + 'amount': amount, + 'before': before, + 'after': after, + 'fee': fee, + 'direction': direction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'type': type, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'Exchange': 'trade', + 'Withdrawal': 'transaction', + 'Deposit': 'transaction', + 'Transfer': 'transfer', + }; + return this.safeString (types, type, type); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + // + // the v2 URL is https://openapi-v2.kucoin.com/api/v1/endpoint + // † ↑ + // + const versions = this.safeValue (this.options, 'versions', {}); + const apiVersions = this.safeValue (versions, api); + const methodVersions = this.safeValue (apiVersions, method, {}); + const defaultVersion = this.safeString (methodVersions, path, this.options['version']); + const version = this.safeString (params, 'version', defaultVersion); + params = this.omit (params, 'version'); + let endpoint = '/api/' + version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + let endpart = ''; + headers = (headers !== undefined) ? headers : {}; + if (Object.keys (query).length) { + if (method !== 'GET') { + body = this.json (query); + endpart = body; + headers['Content-Type'] = 'application/json'; + } else { + endpoint += '?' + this.urlencode (query); + } + } + const url = this.urls['api'][api] + endpoint; + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.nonce ().toString (); + headers = this.extend ({ + 'KC-API-KEY': this.apiKey, + 'KC-API-TIMESTAMP': timestamp, + 'KC-API-PASSPHRASE': this.password, + }, headers); + const payload = timestamp + method + endpoint + endpart; + const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64'); + headers['KC-API-SIGN'] = signature; + const partner = this.safeValue (this.options, 'partner', {}); + const partnerId = this.safeString (partner, 'id'); + const partnerSecret = this.safeString (partner, 'secret'); + if ((partnerId !== undefined) && (partnerSecret !== undefined)) { + const partnerPayload = timestamp + partnerId + this.apiKey; + const partnerSignature = this.hmac (this.encode (partnerPayload), this.encode (partnerSecret), 'sha256', 'base64'); + headers['KC-API-PARTNER-SIGN'] = partnerSignature; + headers['KC-API-PARTNER'] = partnerId; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + this.throwBroadlyMatchedException (this.exceptions['broad'], body, body); + return; + } + // + // bad + // { "code": "400100", "msg": "validation.createOrder.clientOidIsRequired" } + // good + // { code: '200000', data: { ... }} + // + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'msg'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, message); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, message); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],101:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const acx = require ('./acx.js'); +const { ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class kuna extends acx { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'kuna', + 'name': 'Kuna', + 'countries': [ 'UA' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'CORS': false, + 'fetchTickers': true, + 'fetchOHLCV': 'emulated', + 'fetchOpenOrders': true, + 'fetchMyTrades': true, + 'withdraw': false, + }, + 'timeframes': undefined, + 'urls': { + 'referral': 'https://kuna.io?r=kunaid-gvfihe8az7o4', + 'logo': 'https://user-images.githubusercontent.com/51840849/87153927-f0578b80-c2c0-11ea-84b6-74612568e9e1.jpg', + 'api': 'https://kuna.io', + 'www': 'https://kuna.io', + 'doc': 'https://kuna.io/documents/api', + 'fees': 'https://kuna.io/documents/api', + }, + 'fees': { + 'trading': { + 'taker': 0.25 / 100, + 'maker': 0.25 / 100, + }, + 'funding': { + 'withdraw': { + 'UAH': '1%', + 'BTC': 0.001, + 'BCH': 0.001, + 'ETH': 0.01, + 'WAVES': 0.01, + 'GOL': 0.0, + 'GBG': 0.0, + // 'RMC': 0.001 BTC + // 'ARN': 0.01 ETH + // 'R': 0.01 ETH + // 'EVR': 0.01 ETH + }, + 'deposit': { + // 'UAH': (amount) => amount * 0.001 + 5 + }, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const quotes = [ 'btc', 'eth', 'eurs', 'rub', 'uah', 'usd', 'usdt', 'gol' ]; + const pricePrecisions = { + 'UAH': 0, + }; + const markets = []; + const response = await this.publicGetTickers (params); + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + for (let j = 0; j < quotes.length; j++) { + const quoteId = quotes[j]; + const index = id.indexOf (quoteId); + const slice = id.slice (index); + if ((index > 0) && (slice === quoteId)) { + const baseId = id.replace (quoteId, ''); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 6, + 'price': this.safeInteger (pricePrecisions, quote, 6), + }; + markets.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'active': undefined, + 'info': undefined, + }); + break; + } + } + } + return markets; + } + + async fetchL3OrderBook (symbol, limit = undefined, params = {}) { + return await this.fetchOrderBook (symbol, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privateGetOrders (this.extend (request, params)); + // todo emulation of fetchClosedOrders, fetchOrders, fetchOrder + // with order cache + fetchOpenOrders + // as in BTC-e, Liqui, Yobit, DSX, Tidex, WEX + return this.parseOrders (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + let side = this.safeString2 (trade, 'side', 'trend'); + if (side !== undefined) { + const sideMap = { + 'ask': 'sell', + 'bid': 'buy', + }; + side = this.safeString (sideMap, side, side); + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + const cost = this.safeFloat (trade, 'funds'); + const orderId = this.safeString (trade, 'order_id'); + const id = this.safeString (trade, 'id'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': orderId, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privateGetTradesMy (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const trades = await this.fetchTrades (symbol, since, limit, params); + const ohlcvc = this.buildOHLCVC (trades, timeframe, since, limit); + const result = []; + for (let i = 0; i < ohlcvc.length; i++) { + const ohlcv = ohlcvc[i]; + result.push ([ + ohlcv[0], + ohlcv[1], + ohlcv[2], + ohlcv[3], + ohlcv[4], + ohlcv[5], + ]); + } + return result; + } +}; + +},{"./acx.js":3,"./base/errors":7}],102:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, BadSymbol, InvalidOrder, InsufficientFunds } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class lakebtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'lakebtc', + 'name': 'LakeBTC', + 'countries': [ 'US' ], + 'version': 'api_v2', + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28074120-72b7c38a-6660-11e7-92d9-d9027502281d.jpg', + 'api': 'https://api.lakebtc.com', + 'www': 'https://www.lakebtc.com', + 'doc': [ + 'https://www.lakebtc.com/s/api_v2', + 'https://www.lakebtc.com/s/api', + ], + }, + 'api': { + 'public': { + 'get': [ + 'bcorderbook', + 'bctrades', + 'ticker', + ], + }, + 'private': { + 'post': [ + 'buyOrder', + 'cancelOrders', + 'getAccountInfo', + 'getExternalAccounts', + 'getOrders', + 'getTrades', + 'openOrders', + 'sellOrder', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.15 / 100, + 'taker': 0.2 / 100, + }, + }, + 'exceptions': { + 'broad': { + 'Signature': AuthenticationError, + 'invalid symbol': BadSymbol, + 'Volume doit': InvalidOrder, + 'insufficient_balance': InsufficientFunds, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTicker (params); + const result = []; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = response[id]; + const baseId = id.slice (0, 3); + const quoteId = id.slice (3, 6); + const base = baseId.toUpperCase (); + const quote = quoteId.toUpperCase (); + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetAccountInfo (params); + const balances = this.safeValue (response, 'balance', {}); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balances, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetBcorderbook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const marketId = ids[i]; + const ticker = response[marketId]; + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tickers = await this.publicGetTicker (params); + return this.parseTicker (tickers[market['id']], market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetBctrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const method = 'privatePost' + this.capitalize (side) + 'Order'; + const market = this.market (symbol); + const order = { + 'params': [ price, amount, market['id'] ], + }; + const response = await this[method] (this.extend (order, params)); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'params': [ id ], + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + nonce () { + return this.microseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version; + if (api === 'public') { + url += '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const nonceAsString = nonce.toString (); + const requestId = this.seconds (); + let queryParams = ''; + if ('params' in params) { + const paramsList = params['params']; + const stringParams = []; + for (let i = 0; i < paramsList.length; i++) { + let param = paramsList[i]; + if (typeof paramsList !== 'string') { + param = param.toString (); + } + stringParams.push (param); + } + queryParams = stringParams.join (','); + body = { + 'method': path, + 'params': params['params'], + 'id': requestId, + }; + } else { + body = { + 'method': path, + 'params': '', + 'id': requestId, + }; + } + body = this.json (body); + let query = [ + 'tonce=' + nonceAsString, + 'accesskey=' + this.apiKey, + 'requestmethod=' + method.toLowerCase (), + 'id=' + requestId.toString (), + 'method=' + path, + 'params=' + queryParams, + ]; + query = query.join ('&'); + const signature = this.hmac (this.encode (query), this.encode (this.secret), 'sha1'); + const auth = this.apiKey + ':' + signature; + const signature64 = this.decode (this.stringToBase64 (auth)); + headers = { + 'Json-Rpc-Tonce': nonceAsString, + 'Authorization': 'Basic ' + signature64, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to the default error handler + } + // + // {"error":"Failed to submit order: invalid symbol"} + // {"error":"Failed to submit order: La validation a échoué : Volume doit être supérieur ou égal à 1.0"} + // {"error":"Failed to submit order: insufficient_balance"} + // + const feedback = this.id + ' ' + body; + const error = this.safeString (response, 'error'); + if (error !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],103:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InvalidNonce, OrderNotFound, InvalidOrder, DDoSProtection, BadRequest, AuthenticationError } = require ('./base/errors'); +const { ROUND } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class latoken extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'latoken', + 'name': 'Latoken', + 'countries': [ 'KY' ], // Cayman Islands + 'version': 'v1', + 'rateLimit': 2000, + 'certified': false, + 'userAgent': this.userAgents['chrome'], + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCanceledOrders': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': false, + 'fetchOrdersByStatus': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg', + 'api': 'https://api.latoken.com', + 'www': 'https://latoken.com', + 'doc': [ + 'https://api.latoken.com', + ], + }, + 'api': { + 'public': { + 'get': [ + 'ExchangeInfo/time', + 'ExchangeInfo/limits', + 'ExchangeInfo/pairs', + 'ExchangeInfo/pairs/{currency}', + 'ExchangeInfo/pair', + 'ExchangeInfo/currencies', + 'ExchangeInfo/currencies/{symbol}', + 'MarketData/tickers', + 'MarketData/ticker/{symbol}', + 'MarketData/orderBook/{symbol}', + 'MarketData/orderBook/{symbol}/{limit}', + 'MarketData/trades/{symbol}', + 'MarketData/trades/{symbol}/{limit}', + ], + }, + 'private': { + 'get': [ + 'Account/balances', + 'Account/balances/{currency}', + 'Order/status', + 'Order/active', + 'Order/get_order', + 'Order/trades', + ], + 'post': [ + 'Order/new', + 'Order/test-order', + 'Order/cancel', + 'Order/cancel_all', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + }, + 'commonCurrencies': { + 'MT': 'Monarch', + 'TSL': 'Treasure SL', + }, + 'options': { + 'createOrderMethod': 'private_post_order_new', // private_post_order_test_order + }, + 'exceptions': { + 'exact': { + 'Signature or ApiKey is not valid': AuthenticationError, + 'Request is out of time': InvalidNonce, + 'Symbol must be specified': BadRequest, + }, + 'broad': { + 'Request limit reached': DDoSProtection, + 'Pair': BadRequest, + 'Price needs to be greater than': InvalidOrder, + 'Amount needs to be greater than': InvalidOrder, + 'The Symbol field is required': InvalidOrder, + 'OrderType is not valid': InvalidOrder, + 'Side is not valid': InvalidOrder, + 'Cancelable order whit': OrderNotFound, + 'Order': OrderNotFound, + }, + }, + }); + } + + nonce () { + return this.milliseconds (); + } + + async fetchTime (params = {}) { + const response = await this.publicGetExchangeInfoTime (params); + // + // { + // "time": "2019-04-18T9:00:00.0Z", + // "unixTimeSeconds": 1555578000, + // "unixTimeMiliseconds": 1555578000000 + // } + // + return this.safeInteger (response, 'unixTimeMiliseconds'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeInfoPairs (params); + // + // [ + // { + // "pairId": 502, + // "symbol": "LAETH", + // "baseCurrency": "LA", + // "quotedCurrency": "ETH", + // "makerFee": 0.01, + // "takerFee": 0.01, + // "pricePrecision": 8, + // "amountPrecision": 8, + // "minQty": 0.1 + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'symbol'); + // the exchange shows them inverted + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quotedCurrency'); + const numericId = this.safeInteger (market, 'pairId'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'pricePrecision'), + 'amount': this.safeInteger (market, 'amountPrecision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minQty'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'numericId': numericId, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, // assuming true + 'precision': precision, + 'limits': limits, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetExchangeInfoCurrencies (params); + // + // [ + // { + // "currencyId": 102, + // "symbol": "LA", + // "name": "Latoken", + // "precission": 8, + // "type": "ERC20", + // "fee": 0.1 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const numericId = this.safeInteger (currency, 'currencyId'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (currency, 'precission'); + const fee = this.safeFloat (currency, 'fee'); + const active = undefined; + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountBalances (params); + // + // [ + // { + // "currencyId": 102, + // "symbol": "LA", + // "name": "Latoken", + // "amount": 1054.66, + // "available": 900.66, + // "frozen": 154, + // "pending": 0 + // } + // ] + // + const result = { + 'info': response, + }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'symbol'); + const code = this.safeCurrencyCode (currencyId); + const frozen = this.safeFloat (balance, 'frozen'); + const pending = this.safeFloat (balance, 'pending'); + const used = this.sum (frozen, pending); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': used, + 'total': this.safeFloat (balance, 'amount'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'limit': 10, + }; + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 100 + } + const response = await this.publicGetMarketDataOrderBookSymbolLimit (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "spread": 0.07, + // "asks": [ + // { "price": 136.3, "quantity": 7.024 } + // ], + // "bids": [ + // { "price": 136.2, "quantity": 6.554 } + // ] + // } + // + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'quantity'); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "pairId":"63b41092-f3f6-4ea4-9e7c-4525ed250dad", + // "symbol":"ETHBTC", + // "volume":11317.037494474000000000, + // "open":0.020033000000000000, + // "low":0.019791000000000000, + // "high":0.020375000000000000, + // "close":0.019923000000000000, + // "priceChange":-0.1500 + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const open = this.safeFloat (ticker, 'open'); + const close = this.safeFloat (ticker, 'close'); + let change = undefined; + if (open !== undefined && close !== undefined) { + change = close - open; + } + const percentage = this.safeFloat (ticker, 'priceChange'); + const timestamp = this.nonce (); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'low': this.safeFloat (ticker, 'low'), + 'high': this.safeFloat (ticker, 'high'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketDataTickerSymbol (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "volume": 1023314.3202, + // "open": 134.82, + // "low": 133.95, + // "high": 136.22, + // "close": 135.12, + // "priceChange": 0.22 + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketDataTickers (params); + // + // [ + // { + // "pairId": 502, + // "symbol": "LAETH", + // "volume": 1023314.3202, + // "open": 134.82, + // "low": 133.95, + // "high": 136.22, + // "close": 135.12, + // "priceChange": 0.22 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // side: 'buy', + // price: 0.33634, + // amount: 0.01, + // timestamp: 1564240008000 // milliseconds + // } + // + // fetchMyTrades (private) + // + // { + // id: '1564223032.892829.3.tg15', + // orderId: '1564223032.671436.707548@1379:1', + // commission: 0, + // side: 'buy', + // price: 0.32874, + // amount: 0.607, + // timestamp: 1564223033 // seconds + // } + // + const type = undefined; + let timestamp = this.safeInteger2 (trade, 'timestamp', 'time'); + if (timestamp !== undefined) { + // 03 Jan 2009 - first block + if (timestamp < 1230940800000) { + timestamp *= 1000; + } + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const side = this.safeString (trade, 'side'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'orderId'); + const feeCost = this.safeFloat (trade, 'commission'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': type, + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + const response = await this.publicGetMarketDataTradesSymbol (this.extend (request, params)); + // + // { + // "pairId":370, + // "symbol":"ETHBTC", + // "tradeCount":51, + // "trades": [ + // { + // side: 'buy', + // price: 0.33634, + // amount: 0.01, + // timestamp: 1564240008000 // milliseconds + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privateGetOrderTrades (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "tradeCount": 1, + // "trades": [ + // { + // id: '1564223032.892829.3.tg15', + // orderId: '1564223032.671436.707548@1379:1', + // commission: 0, + // side: 'buy', + // price: 0.32874, + // amount: 0.607, + // timestamp: 1564223033 // seconds + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'active': 'open', + 'partiallyFilled': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "orderId":"1563460093.134037.704945@0370:2", + // "cliOrdId":"", + // "pairId":370, + // "symbol":"ETHBTC", + // "side":"sell", + // "orderType":"limit", + // "price":1.0, + // "amount":1.0 + // } + // + // cancelOrder, fetchOrder, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders + // + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // + const id = this.safeString (order, 'orderId'); + const timestamp = this.safeTimestamp (order, 'timeCreated'); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'orderType'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'executedAmount'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'orderStatus')); + let cost = undefined; + if (filled !== undefined) { + if (price !== undefined) { + cost = filled * price; + } + } + const timeFilled = this.safeTimestamp (order, 'timeFilled'); + let lastTradeTimestamp = undefined; + if ((timeFilled !== undefined) && (timeFilled > 0)) { + lastTradeTimestamp = timeFilled; + } + const clientOrderId = this.safeString (order, 'cliOrdId'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'average': undefined, + 'remaining': remaining, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersWithMethod ('private_get_order_active', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByStatus ('filled', symbol, since, limit, params); + } + + async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByStatus ('cancelled', symbol, since, limit, params); + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': status, + }; + return this.fetchOrdersWithMethod ('private_get_order_status', symbol, since, limit, this.extend (request, params)); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrdersWithMethod requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100 + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateGetOrderGetOrder (this.extend (request, params)); + // + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // + return this.parseOrder (response); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'symbol': this.marketId (symbol), + 'side': side, + 'price': this.priceToPrecision (symbol, price), + 'amount': this.amountToPrecision (symbol, amount), + 'orderType': type, + }; + const method = this.safeString (this.options, 'createOrderMethod', 'private_post_order_new'); + const response = await this[method] (this.extend (request, params)); + // + // { + // "orderId":"1563460093.134037.704945@0370:2", + // "cliOrdId":"", + // "pairId":370, + // "symbol":"ETHBTC", + // "side":"sell", + // "orderType":"limit", + // "price":1.0, + // "amount":1.0 + // } + // + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + // + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + const response = await this.privatePostOrderCancelAll (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "cancelledOrders": [ + // "1555492358.126073.126767@0502:2" + // ] + // } + // + const result = []; + const canceledOrders = this.safeValue (response, 'cancelledOrders', []); + for (let i = 0; i < canceledOrders.length; i++) { + const order = this.parseOrder ({ + 'symbol': marketId, + 'orderId': canceledOrders[i], + 'orderStatus': 'canceled', + }); + result.push (order); + } + return result; + } + + sign (path, api = 'public', method = 'GET', params = undefined, headers = undefined, body = undefined) { + let request = '/api/' + this.version + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + const nonce = this.nonce (); + query = this.extend ({ + 'timestamp': nonce, + }, query); + } + const urlencodedQuery = this.urlencode (query); + if (Object.keys (query).length) { + request += '?' + urlencodedQuery; + } + if (api === 'private') { + this.checkRequiredCredentials (); + const signature = this.hmac (this.encode (request), this.encode (this.secret)); + headers = { + 'X-LA-KEY': this.apiKey, + 'X-LA-SIGNATURE': signature, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + body = urlencodedQuery; + } + } + const url = this.urls['api'] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; + } + // + // { "message": "Request limit reached!", "details": "Request limit reached. Maximum allowed: 1 per 1s. Please try again in 1 second(s)." } + // { "error": { "message": "Pair 370 is not found","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Signature or ApiKey is not valid","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Request is out of time", "errorType": "RequestError", "statusCode":400 }} + // { "error": { "message": "Price needs to be greater than 0","errorType":"ValidationError","statusCode":400 }} + // { "error": { "message": "Side is not valid, Price needs to be greater than 0, Amount needs to be greater than 0, The Symbol field is required., OrderType is not valid","errorType":"ValidationError","statusCode":400 }} + // { "error": { "message": "Cancelable order whit ID 1563460289.571254.704945@0370:1 not found","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Symbol must be specified","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Order 1563460289.571254.704945@0370:1 is not found","errorType":"RequestError","statusCode":400 }} + // + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + if (message !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + const error = this.safeValue (response, 'error', {}); + const errorMessage = this.safeString (error, 'message'); + if (errorMessage !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], errorMessage, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorMessage, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],104:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, DDoSProtection, AuthenticationError, InvalidOrder, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class lbank extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'lbank', + 'name': 'LBank', + 'countries': [ 'CN' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': false, // status 0 API doesn't work + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'minute1', + '5m': 'minute5', + '15m': 'minute15', + '30m': 'minute30', + '1h': 'hour1', + '2h': 'hour2', + '4h': 'hour4', + '6h': 'hour6', + '8h': 'hour8', + '12h': 'hour12', + '1d': 'day1', + '1w': 'week1', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/38063602-9605e28a-3302-11e8-81be-64b1e53c4cfb.jpg', + 'api': 'https://api.lbank.info', + 'www': 'https://www.lbank.info', + 'doc': 'https://github.com/LBank-exchange/lbank-official-api-docs', + 'fees': 'https://lbankinfo.zendesk.com/hc/en-gb/articles/360012072873-Trading-Fees', + 'referral': 'https://www.lbex.io/invite?icode=7QCY', + }, + 'api': { + 'public': { + 'get': [ + 'currencyPairs', + 'ticker', + 'depth', + 'trades', + 'kline', + 'accuracy', + ], + }, + 'private': { + 'post': [ + 'user_info', + 'create_order', + 'cancel_order', + 'orders_info', + 'orders_info_history', + 'withdraw', + 'withdrawCancel', + 'withdraws', + 'withdrawConfigs', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': undefined, + 'ZEC': 0.01, + 'ETH': 0.01, + 'ETC': 0.01, + // 'QTUM': amount => Math.max (0.01, amount * (0.1 / 100)), + 'VEN': 10.0, + 'BCH': 0.0002, + 'SC': 50.0, + 'BTM': 20.0, + 'NAS': 1.0, + 'EOS': 1.0, + 'XWC': 5.0, + 'BTS': 1.0, + 'INK': 10.0, + 'BOT': 3.0, + 'YOYOW': 15.0, + 'TGC': 10.0, + 'NEO': 0.0, + 'CMT': 20.0, + 'SEER': 2000.0, + 'FIL': undefined, + 'BTG': undefined, + }, + }, + }, + 'commonCurrencies': { + 'VET_ERC20': 'VEN', + 'PNT': 'Penta', + }, + 'options': { + 'cacheSecretAsPem': true, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAccuracy (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = market['symbol']; + const parts = id.split ('_'); + let baseId = undefined; + let quoteId = undefined; + const numParts = parts.length; + // lbank will return symbols like "vet_erc20_usdt" + if (numParts > 2) { + baseId = parts[0] + '_' + parts[1]; + quoteId = parts[2]; + } else { + baseId = parts[0]; + quoteId = parts[1]; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'quantityAccuracy'), + 'price': this.safeInteger (market, 'priceAccuracy'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': id, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeString (ticker, 'symbol'); + if (marketId in this.markets_by_id) { + const market = this.marketsById[marketId]; + symbol = market['symbol']; + } else { + const parts = marketId.split ('_'); + let baseId = undefined; + let quoteId = undefined; + const numParts = parts.length; + // lbank will return symbols like "vet_erc20_usdt" + if (numParts > 2) { + baseId = parts[0] + '_' + parts[1]; + quoteId = parts[2]; + } else { + baseId = parts[0]; + quoteId = parts[1]; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + const timestamp = this.safeInteger (ticker, 'timestamp'); + const info = ticker; + ticker = info['ticker']; + const last = this.safeFloat (ticker, 'latest'); + const percentage = this.safeFloat (ticker, 'change'); + let open = undefined; + if (percentage !== undefined) { + const relativeChange = this.sum (1, percentage / 100); + if (relativeChange > 0) { + open = last / this.sum (1, relativeChange); + } + } + let change = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + } + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'turnover'), + 'info': info, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': 'all', + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = 60, params = {}) { + await this.loadMarkets (); + let size = 60; + if (limit !== undefined) { + size = Math.min (limit, size); + } + const request = { + 'symbol': this.marketId (symbol), + 'size': size, + }; + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (trade, 'date_ms'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const id = this.safeString (trade, 'tid'); + const type = undefined; + const side = this.safeString (trade, 'type'); + return { + 'id': id, + 'info': this.safeValue (trade, 'info', trade), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'size': 100, + }; + if (since !== undefined) { + request['time'] = parseInt (since); + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1590969600, + // 0.02451657, + // 0.02452675, + // 0.02443701, + // 0.02447814, + // 238.38210000 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (since === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a `since` argument'); + } + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a `limit` argument'); + } + const request = { + 'symbol': market['id'], + 'type': this.timeframes[timeframe], + 'size': limit, + 'time': parseInt (since / 1000), + }; + const response = await this.publicGetKline (this.extend (request, params)); + // + // [ + // [1590969600,0.02451657,0.02452675,0.02443701,0.02447814,238.38210000], + // [1590969660,0.02447814,0.02449883,0.02443209,0.02445973,212.40270000], + // [1590969720,0.02445973,0.02452067,0.02445909,0.02446151,266.16920000], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostUserInfo (params); + // + // { + // "result":"true", + // "info":{ + // "freeze":{ + // "iog":"0.00000000", + // "ssc":"0.00000000", + // "eon":"0.00000000", + // }, + // "asset":{ + // "iog":"0.00000000", + // "ssc":"0.00000000", + // "eon":"0.00000000", + // }, + // "free":{ + // "iog":"0.00000000", + // "ssc":"0.00000000", + // "eon":"0.00000000", + // }, + // } + // } + // + const result = { 'info': response }; + const info = this.safeValue (response, 'info', {}); + const free = this.safeValue (info, 'free', {}); + const freeze = this.safeValue (info, 'freeze', {}); + const asset = this.safeValue (info, 'asset', {}); + const currencyIds = Object.keys (free); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (free, currencyId); + account['used'] = this.safeFloat (freeze, currencyId); + account['total'] = this.safeFloat (asset, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + '-1': 'cancelled', // cancelled + '0': 'open', // not traded + '1': 'open', // partial deal + '2': 'closed', // complete deal + '4': 'closed', // disposal processing + }; + return this.safeString (statuses, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "symbol":"eth_btc", + // "amount":10.000000, + // "create_time":1484289832081, + // "price":5000.000000, + // "avg_price":5277.301200, + // "type":"sell", + // "order_id":"ab704110-af0d-48fd-a083-c218f19a4a55", + // "deal_amount":10.000000, + // "status":2 + // } + // + let symbol = undefined; + const responseMarket = this.safeValue (this.marketsById, order['symbol']); + if (responseMarket !== undefined) { + symbol = responseMarket['symbol']; + } else if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (order, 'create_time'); + // Limit Order Request Returns: Order Price + // Market Order Returns: cny amount of market order + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount', 0.0); + const filled = this.safeFloat (order, 'deal_amount', 0.0); + const av_price = this.safeFloat (order, 'avg_price'); + let cost = undefined; + if (av_price !== undefined) { + cost = filled * av_price; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString (order, 'order_id'); + const type = this.safeString (order, 'order_type'); + const side = this.safeString (order, 'type'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': this.safeValue (order, 'info', order), + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let order = { + 'symbol': market['id'], + 'type': side, + 'amount': amount, + }; + if (type === 'market') { + order['type'] += '_market'; + } else { + order['price'] = price; + } + const response = await this.privatePostCreateOrder (this.extend (order, params)); + order = this.omit (order, 'type'); + order['order_id'] = response['order_id']; + order['type'] = side; + order['order_type'] = type; + order['create_time'] = this.milliseconds (); + order['info'] = response; + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'order_id': id, + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + // Id can be a list of ids delimited by a comma + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'order_id': id, + }; + const response = await this.privatePostOrdersInfo (this.extend (request, params)); + const data = this.safeValue (response, 'orders', []); + const orders = this.parseOrders (data, market); + const numOrders = orders.length; + if (numOrders === 1) { + return orders[0]; + } else { + return orders; + } + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'current_page': 1, + 'page_length': limit, + }; + const response = await this.privatePostOrdersInfoHistory (this.extend (request, params)); + const data = this.safeValue (response, 'orders', []); + return this.parseOrders (data, undefined, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + const closed = this.filterBy (orders, 'status', 'closed'); + const canceled = this.filterBy (orders, 'status', 'cancelled'); // cancelled orders may be partially filled + const allOrders = this.arrayConcat (closed, canceled); + return this.filterBySymbolSinceLimit (allOrders, symbol, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // mark and fee are optional params, mark is a note and must be less than 255 characters + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'assetCode': currency['id'], + 'amount': amount, + 'account': address, + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const response = this.privatePostWithdraw (this.extend (request, params)); + return { + 'id': this.safeString (response, 'id'), + 'info': response, + }; + } + + convertSecretToPem (secret) { + const lineLength = 64; + const secretLength = secret.length - 0; + let numLines = parseInt (secretLength / lineLength); + numLines = this.sum (numLines, 1); + let pem = "-----BEGIN PRIVATE KEY-----\n"; // eslint-disable-line + for (let i = 0; i < numLines; i++) { + const start = i * lineLength; + const end = this.sum (start, lineLength); + pem += this.secret.slice (start, end) + "\n"; // eslint-disable-line + } + return pem + '-----END PRIVATE KEY-----'; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + // Every endpoint ends with ".do" + url += '.do'; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const query = this.keysort (this.extend ({ + 'api_key': this.apiKey, + }, params)); + const queryString = this.rawencode (query); + const message = this.hash (this.encode (queryString)).toUpperCase (); + const cacheSecretAsPem = this.safeValue (this.options, 'cacheSecretAsPem', true); + let pem = undefined; + if (cacheSecretAsPem) { + pem = this.safeValue (this.options, 'pem'); + if (pem === undefined) { + pem = this.convertSecretToPem (this.secret); + this.options['pem'] = pem; + } + } else { + pem = this.convertSecretToPem (this.secret); + } + const sign = this.binaryToBase64 (this.rsa (message, this.encode (pem), 'RS256')); + query['sign'] = sign; + body = this.urlencode (query); + headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const success = this.safeString (response, 'result'); + if (success === 'false') { + const errorCode = this.safeString (response, 'error_code'); + const message = this.safeString ({ + '10000': 'Internal error', + '10001': 'The required parameters can not be empty', + '10002': 'verification failed', + '10003': 'Illegal parameters', + '10004': 'User requests are too frequent', + '10005': 'Key does not exist', + '10006': 'user does not exist', + '10007': 'Invalid signature', + '10008': 'This currency pair is not supported', + '10009': 'Limit orders can not be missing orders and the number of orders', + '10010': 'Order price or order quantity must be greater than 0', + '10011': 'Market orders can not be missing the amount of the order', + '10012': 'market sell orders can not be missing orders', + '10013': 'is less than the minimum trading position 0.001', + '10014': 'Account number is not enough', + '10015': 'The order type is wrong', + '10016': 'Account balance is not enough', + '10017': 'Abnormal server', + '10018': 'order inquiry can not be more than 50 less than one', + '10019': 'withdrawal orders can not be more than 3 less than one', + '10020': 'less than the minimum amount of the transaction limit of 0.001', + '10022': 'Insufficient key authority', + }, errorCode, this.json (response)); + const ErrorClass = this.safeValue ({ + '10002': AuthenticationError, + '10004': DDoSProtection, + '10005': AuthenticationError, + '10006': AuthenticationError, + '10007': AuthenticationError, + '10009': InvalidOrder, + '10010': InvalidOrder, + '10011': InvalidOrder, + '10012': InvalidOrder, + '10013': InvalidOrder, + '10014': InvalidOrder, + '10015': InvalidOrder, + '10016': InvalidOrder, + '10022': AuthenticationError, + }, errorCode, ExchangeError); + throw new ErrorClass (message); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],105:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { ExchangeError, ArgumentsRequired, InvalidNonce, OrderNotFound, InvalidOrder, InsufficientFunds, AuthenticationError, DDoSProtection, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class liquid extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'liquid', + 'name': 'Liquid', + 'countries': [ 'JP', 'CN', 'TW' ], + 'version': '2', + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/45798859-1a872600-bcb4-11e8-8746-69291ce87b04.jpg', + 'api': 'https://api.liquid.com', + 'www': 'https://www.liquid.com', + 'doc': [ + 'https://developers.liquid.com', + ], + 'fees': 'https://help.liquid.com/getting-started-with-liquid/the-platform/fee-structure', + 'referral': 'https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'products', + 'products/{id}', + 'products/{id}/price_levels', + 'executions', + 'ir_ladders/{currency}', + 'fees', // add fetchFees, fetchTradingFees, fetchFundingFees + ], + }, + 'private': { + 'get': [ + 'accounts', // undocumented https://github.com/ccxt/ccxt/pull/7493 + 'accounts/balance', + 'accounts/main_asset', + 'accounts/{id}', + 'accounts/{currency}/reserved_balance_details', + 'crypto_accounts', // add fetchAccounts + 'crypto_withdrawals', // add fetchWithdrawals + 'executions/me', + 'fiat_accounts', // add fetchAccounts + 'fund_infos', // add fetchDeposits + 'loan_bids', + 'loans', + 'orders', + 'orders/{id}', + 'orders/{id}/trades', // add fetchOrderTrades + 'trades', + 'trades/{id}/loans', + 'trading_accounts', + 'trading_accounts/{id}', + 'transactions', + 'withdrawals', // add fetchWithdrawals + ], + 'post': [ + 'crypto_withdrawals', + 'fund_infos', + 'fiat_accounts', + 'loan_bids', + 'orders', + 'withdrawals', + ], + 'put': [ + 'crypto_withdrawal/{id}/cancel', + 'loan_bids/{id}/close', + 'loans/{id}', + 'orders/{id}', // add editOrder + 'orders/{id}/cancel', + 'trades/{id}', + 'trades/{id}/adjust_margin', + 'trades/{id}/close', + 'trades/close_all', + 'trading_accounts/{id}', + 'withdrawals/{id}/cancel', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.0015, + 'maker': 0.0000, + 'tiers': { + 'perpetual': { + 'maker': [ + [ 0, 0.0000 ], + [ 25000, 0.0000 ], + [ 50000, -0.00025 ], + [ 100000, -0.00025 ], + [ 1000000, -0.00025 ], + [ 10000000, -0.00025 ], + [ 25000000, -0.00025 ], + [ 50000000, -0.00025 ], + [ 75000000, -0.00025 ], + [ 100000000, -0.00025 ], + [ 200000000, -0.00025 ], + [ 300000000, -0.00025 ], + ], + 'taker': [ + [ 0, 0.000600 ], + [ 25000, 0.000575 ], + [ 50000, 0.000550 ], + [ 100000, 0.000525 ], + [ 1000000, 0.000500 ], + [ 10000000, 0.000475 ], + [ 25000000, 0.000450 ], + [ 50000000, 0.000425 ], + [ 75000000, 0.000400 ], + [ 100000000, 0.000375 ], + [ 200000000, 0.000350 ], + [ 300000000, 0.000325 ], + ], + }, + 'spot': { + 'taker': [ + [ 0, 0.0015 ], + [ 10000, 0.0015 ], + [ 20000, 0.0014 ], + [ 50000, 0.0013 ], + [ 100000, 0.0010 ], + [ 1000000, 0.0008 ], + [ 5000000, 0.0006 ], + [ 10000000, 0.0005 ], + [ 25000000, 0.0005 ], + [ 50000000, 0.00045 ], + [ 100000000, 0.0004 ], + [ 200000000, 0.0003 ], + ], + 'maker': [ + [ 0, 0.0000 ], + [ 10000, 0.0015 ], + [ 20000, 0.1400 ], + [ 50000, 0.1300 ], + [ 100000, 0.0800 ], + [ 1000000, 0.0004 ], + [ 5000000, 0.00035 ], + [ 10000000, 0.00025 ], + [ 25000000, 0.0000 ], + [ 50000000, 0.0000 ], + [ 100000000, 0.0000 ], + [ 200000000, 0.0000 ], + ], + }, + }, + }, + }, + 'precisionMode': TICK_SIZE, + 'exceptions': { + 'API rate limit exceeded. Please retry after 300s': DDoSProtection, + 'API Authentication failed': AuthenticationError, + 'Nonce is too small': InvalidNonce, + 'Order not found': OrderNotFound, + 'Can not update partially filled order': InvalidOrder, + 'Can not update non-live order': OrderNotFound, + 'not_enough_free_balance': InsufficientFunds, + 'must_be_positive': InvalidOrder, + 'less_than_order_size': InvalidOrder, + 'price_too_high': InvalidOrder, + }, + 'commonCurrencies': { + 'WIN': 'WCOIN', + 'HOT': 'HOT Token', + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + }, + 'options': { + 'cancelOrderException': true, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // currency_type: 'fiat', + // currency: 'USD', + // symbol: '$', + // assets_precision: 2, + // quoting_precision: 5, + // minimum_withdrawal: '15.0', + // withdrawal_fee: 5, + // minimum_fee: null, + // minimum_order_quantity: null, + // display_precision: 2, + // depositable: true, + // withdrawable: true, + // discount_fee: 0.5, + // }, + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const active = currency['depositable'] && currency['withdrawable']; + const amountPrecision = this.safeInteger (currency, 'display_precision'); + const pricePrecision = this.safeInteger (currency, 'quoting_precision'); + const precision = Math.max (amountPrecision, pricePrecision); + const decimalPrecision = 1 / Math.pow (10, precision); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawal_fee'), + 'precision': decimalPrecision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -amountPrecision), + 'max': Math.pow (10, amountPrecision), + }, + 'price': { + 'min': Math.pow (10, -pricePrecision), + 'max': Math.pow (10, pricePrecision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minimum_withdrawal'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const spot = await this.publicGetProducts (params); + // + // [ + // { + // "id":"637", + // "product_type":"CurrencyPair", + // "code":"CASH", + // "name":null, + // "market_ask":"0.00000797", + // "market_bid":"0.00000727", + // "indicator":null, + // "currency":"BTC", + // "currency_pair_code":"TFTBTC", + // "symbol":null, + // "btc_minimum_withdraw":null, + // "fiat_minimum_withdraw":null, + // "pusher_channel":"product_cash_tftbtc_637", + // "taker_fee":"0.0", + // "maker_fee":"0.0", + // "low_market_bid":"0.00000685", + // "high_market_ask":"0.00000885", + // "volume_24h":"3696.0755956", + // "last_price_24h":"0.00000716", + // "last_traded_price":"0.00000766", + // "last_traded_quantity":"1748.0377978", + // "average_price":null, + // "quoted_currency":"BTC", + // "base_currency":"TFT", + // "tick_size":"0.00000001", + // "disabled":false, + // "margin_enabled":false, + // "cfd_enabled":false, + // "perpetual_enabled":false, + // "last_event_timestamp":"1596962820.000797146", + // "timestamp":"1596962820.000797146", + // "multiplier_up":"9.0", + // "multiplier_down":"0.1", + // "average_time_interval":null + // }, + // ] + // + const perpetual = await this.publicGetProducts ({ 'perpetual': '1' }); + // + // [ + // { + // "id":"604", + // "product_type":"Perpetual", + // "code":"CASH", + // "name":null, + // "market_ask":"11721.5", + // "market_bid":"11719.0", + // "indicator":null, + // "currency":"USD", + // "currency_pair_code":"P-BTCUSD", + // "symbol":"$", + // "btc_minimum_withdraw":null, + // "fiat_minimum_withdraw":null, + // "pusher_channel":"product_cash_p-btcusd_604", + // "taker_fee":"0.0012", + // "maker_fee":"0.0", + // "low_market_bid":"11624.5", + // "high_market_ask":"11859.0", + // "volume_24h":"0.271", + // "last_price_24h":"11621.5", + // "last_traded_price":"11771.5", + // "last_traded_quantity":"0.09", + // "average_price":"11771.5", + // "quoted_currency":"USD", + // "base_currency":"P-BTC", + // "tick_size":"0.5", + // "disabled":false, + // "margin_enabled":false, + // "cfd_enabled":false, + // "perpetual_enabled":true, + // "last_event_timestamp":"1596963309.418853092", + // "timestamp":"1596963309.418853092", + // "multiplier_up":null, + // "multiplier_down":"0.1", + // "average_time_interval":300, + // "index_price":"11682.8124", + // "mark_price":"11719.96781", + // "funding_rate":"0.00273", + // "fair_price":"11720.2745" + // }, + // ] + // + const currencies = await this.fetchCurrencies (); + const currenciesByCode = this.indexBy (currencies, 'code'); + const result = []; + const markets = this.arrayConcat (spot, perpetual); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quoted_currency'); + const productType = this.safeString (market, 'product_type'); + let type = 'spot'; + let spot = true; + let swap = false; + if (productType === 'Perpetual') { + spot = false; + swap = true; + type = 'swap'; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = undefined; + if (swap) { + symbol = this.safeString (market, 'currency_pair_code'); + } else { + symbol = base + '/' + quote; + } + let maker = this.fees['trading']['maker']; + let taker = this.fees['trading']['taker']; + if (type === 'swap') { + maker = this.safeFloat (market, 'maker_fee', this.fees['trading']['maker']); + taker = this.safeFloat (market, 'taker_fee', this.fees['trading']['taker']); + } + const disabled = this.safeValue (market, 'disabled', false); + const active = !disabled; + const baseCurrency = this.safeValue (currenciesByCode, base); + const precision = { + 'amount': 0.00000001, + 'price': this.safeFloat (market, 'tick_size'), + }; + let minAmount = undefined; + if (baseCurrency !== undefined) { + minAmount = this.safeFloat (baseCurrency['info'], 'minimum_order_quantity'); + } + const limits = { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': type, + 'spot': spot, + 'swap': swap, + 'maker': maker, + 'taker': taker, + 'limits': limits, + 'precision': precision, + 'active': active, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + // + // { + // crypto_accounts: [ + // { + // id: 2221179, + // currency: 'USDT', + // balance: '0.0', + // reserved_balance: '0.0', + // pusher_channel: 'user_xxxxx_account_usdt', + // lowest_offer_interest_rate: null, + // highest_offer_interest_rate: null, + // address: '0', + // currency_symbol: 'USDT', + // minimum_withdraw: null, + // currency_type: 'crypto' + // }, + // ], + // fiat_accounts: [ + // { + // id: 1112734, + // currency: 'USD', + // balance: '0.0', + // reserved_balance: '0.0', + // pusher_channel: 'user_xxxxx_account_usd', + // lowest_offer_interest_rate: null, + // highest_offer_interest_rate: null, + // currency_symbol: '$', + // send_to_btc_address: null, + // exchange_rate: '1.0', + // currency_type: 'fiat' + // } + // ] + // } + // + const result = { 'info': response }; + const crypto = this.safeValue (response, 'crypto_accounts', []); + const fiat = this.safeValue (response, 'fiat_accounts', []); + for (let i = 0; i < crypto.length; i++) { + const balance = crypto[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'reserved_balance'); + result[code] = account; + } + for (let i = 0; i < fiat.length; i++) { + const balance = fiat[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'reserved_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.publicGetProductsIdPriceLevels (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'buy_price_levels', 'sell_price_levels'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let last = undefined; + if ('last_traded_price' in ticker) { + if (ticker['last_traded_price']) { + const length = ticker['last_traded_price'].length; + if (length > 0) { + last = this.safeFloat (ticker, 'last_traded_price'); + } + } + } + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeString (ticker, 'id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (ticker, 'base_currency'); + const quoteId = this.safeString (ticker, 'quoted_currency'); + if (symbol in this.markets) { + market = this.markets[symbol]; + } else { + symbol = this.safeCurrencyCode (baseId) + '/' + this.safeCurrencyCode (quoteId); + } + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let change = undefined; + let percentage = undefined; + let average = undefined; + const open = this.safeFloat (ticker, 'last_price_24h'); + if (open !== undefined && last !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_market_ask'), + 'low': this.safeFloat (ticker, 'low_market_bid'), + 'bid': this.safeFloat (ticker, 'market_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'market_ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume_24h'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetProducts (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetProductsId (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // { id: 12345, + // quantity: "6.789", + // price: "98765.4321", + // taker_side: "sell", + // created_at: 1512345678, + // my_side: "buy" } + const timestamp = this.safeTimestamp (trade, 'created_at'); + const orderId = this.safeString (trade, 'order_id'); + // 'taker_side' gets filled for both fetchTrades and fetchMyTrades + const takerSide = this.safeString (trade, 'taker_side'); + // 'my_side' gets filled for fetchMyTrades only and may differ from 'taker_side' + const mySide = this.safeString (trade, 'my_side'); + const side = (mySide !== undefined) ? mySide : takerSide; + let takerOrMaker = undefined; + if (mySide !== undefined) { + takerOrMaker = (takerSide === mySide) ? 'taker' : 'maker'; + } + let cost = undefined; + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const id = this.safeString (trade, 'id'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_id': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + // timestamp should be in seconds, whereas we use milliseconds in since and everywhere + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.publicGetExecutions (this.extend (request, params)); + const result = (since !== undefined) ? response : response['models']; + return this.parseTrades (result, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // the `with_details` param is undocumented - it adds the order_id to the results + const request = { + 'product_id': market['id'], + 'with_details': true, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetExecutionsMe (this.extend (request, params)); + return this.parseTrades (response['models'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_order_id'); + params = this.omit (params, [ 'clientOrderId', 'client_order_id' ]); + const request = { + 'order_type': type, + 'product_id': this.marketId (symbol), + 'side': side, + 'quantity': this.amountToPrecision (symbol, amount), + }; + if (clientOrderId !== undefined) { + request['client_order_id'] = clientOrderId; + } + if ((type === 'limit') || (type === 'limit_post_only') || (type === 'market_with_range') || (type === 'stop')) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "id": 2157474, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.0", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "live", + // "leverage_level": 1, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0", + // "client_order_id": null, + // } + // + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePutOrdersIdCancel (this.extend (request, params)); + const order = this.parseOrder (response); + if (order['status'] === 'closed') { + if (this.options['cancelOrderException']) { + throw new OrderNotFound (this.id + ' order closed already: ' + this.json (response)); + } + } + return order; + } + + async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires the price argument'); + } + const request = { + 'order': { + 'quantity': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), + }, + 'id': id, + }; + const response = await this.privatePutOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + 'live': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": 2157474, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.0", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "live", + // "leverage_level": 1, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0" + // "client_order_id": null, + // } + // + // fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "id": 2157479, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.01", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "filled", + // "leverage_level": 2, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0", + // "executions": [ + // { + // "id": 4566133, + // "quantity": "0.01", + // "price": "500.0", + // "taker_side": "buy", + // "my_side": "sell", + // "created_at": 1465396785 + // } + // ] + // } + // + const orderId = this.safeString (order, 'id'); + const timestamp = this.safeTimestamp (order, 'created_at'); + const marketId = this.safeString (order, 'product_id'); + market = this.safeValue (this.markets_by_id, marketId); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const amount = this.safeFloat (order, 'quantity'); + let filled = this.safeFloat (order, 'filled_quantity'); + const price = this.safeFloat (order, 'price'); + let symbol = undefined; + let feeCurrency = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } + const type = this.safeString (order, 'order_type'); + let tradeCost = 0; + let tradeFilled = 0; + let average = this.safeFloat (order, 'average_price'); + const trades = this.parseTrades (this.safeValue (order, 'executions', []), market, undefined, undefined, { + 'order': orderId, + 'type': type, + }); + const numTrades = trades.length; + for (let i = 0; i < numTrades; i++) { + // php copies values upon assignment, but not references them + // todo rewrite this (shortly) + const trade = trades[i]; + trade['order'] = orderId; + trade['type'] = type; + tradeFilled = this.sum (tradeFilled, trade['amount']); + tradeCost = this.sum (tradeCost, trade['cost']); + } + let cost = undefined; + let lastTradeTimestamp = undefined; + if (numTrades > 0) { + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + if (!average && (tradeFilled > 0)) { + average = tradeCost / tradeFilled; + } + if (cost === undefined) { + cost = tradeCost; + } + if (filled === undefined) { + filled = tradeFilled; + } + } + let remaining = undefined; + if (amount !== undefined && filled !== undefined) { + remaining = amount - filled; + } + const side = this.safeString (order, 'side'); + const clientOrderId = this.safeString (order, 'client_order_id'); + return { + 'id': orderId, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'type': type, + 'timeInForce': undefined, + 'status': status, + 'symbol': symbol, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'cost': cost, + 'remaining': remaining, + 'average': average, + 'trades': trades, + 'fee': { + 'currency': feeCurrency, + 'cost': this.safeFloat (order, 'order_fee'), + }, + 'info': order, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // 'funding_currency': market['quoteId'], // filter orders based on "funding" currency (quote currency) + // 'product_id': market['id'], + // 'status': 'live', // 'filled', 'cancelled' + // 'trading_type': 'spot', // 'margin', 'cfd' + 'with_details': 1, // return full order details including executions + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // "models": [ + // { + // "id": 2157474, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.0", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "live", + // "leverage_level": 1, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0", + // "executions": [], // optional + // } + // ], + // "current_page": 1, + // "total_pages": 1 + // } + // + const orders = this.safeValue (response, 'models', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'status': 'live' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'status': 'filled' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + // 'auth_code': '', // optional 2fa code + 'currency': currency['id'], + 'address': address, + 'amount': this.currencyToPrecision (code, amount), + // 'payment_id': tag, // for XRP only + // 'memo_type': 'text', // 'text', 'id' or 'hash', for XLM only + // 'memo_value': tag, // for XLM only + }; + if (tag !== undefined) { + if (code === 'XRP') { + request['payment_id'] = tag; + } else if (code === 'XLM') { + request['memo_type'] = 'text'; // overrideable via params + request['memo_value'] = tag; + } else { + throw new NotSupported (this.id + ' withdraw() only supports a tag along the address for XRP or XLM'); + } + } + const response = await this.privatePostCryptoWithdrawals (this.extend (request, params)); + // + // { + // "id": 1353, + // "address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", + // "amount": 1.0, + // "state": "pending", + // "currency": "BTC", + // "withdrawal_fee": 0.0, + // "created_at": 1568016450, + // "updated_at": 1568016450, + // "payment_id": null + // } + // + return this.parseTransaction (response, currency); + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'cancelled': 'canceled', + 'approved': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "id": 1353, + // "address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", + // "amount": 1.0, + // "state": "pending", + // "currency": "BTC", + // "withdrawal_fee": 0.0, + // "created_at": 1568016450, + // "updated_at": 1568016450, + // "payment_id": null + // } + // + // fetchDeposits, fetchWithdrawals + // + // ... + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString2 (transaction, 'payment_id', 'memo_value'); + const txid = undefined; + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeTimestamp (transaction, 'created_at'); + const updated = this.safeTimestamp (transaction, 'updated_at'); + const type = 'withdrawal'; + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const amount = this.safeFloat (transaction, 'amount'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + headers = { + 'X-Quoine-API-Version': this.version, + 'Content-Type': 'application/json', + }; + if (api === 'private') { + this.checkRequiredCredentials (); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length) { + body = this.json (query); + } + const nonce = this.nonce (); + const request = { + 'path': url, + 'token_id': this.apiKey, + 'iat': Math.floor (nonce / 1000), // issued at + }; + if (!('client_order_id' in query)) { + request['nonce'] = nonce; + } + headers['X-Quoine-Auth'] = this.jwt (request, this.encode (this.secret)); + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + url = this.urls['api'] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code >= 200 && code < 300) { + return; + } + if (code === 401) { + // expected non-json response + this.throwExactlyMatchedException (this.exceptions, body, body); + return; + } + if (code === 429) { + throw new DDoSProtection (this.id + ' ' + body); + } + if (response === undefined) { + return; + } + const feedback = this.id + ' ' + body; + const message = this.safeString (response, 'message'); + const errors = this.safeValue (response, 'errors'); + if (message !== undefined) { + // + // { "message": "Order not found" } + // + this.throwExactlyMatchedException (this.exceptions, message, feedback); + } else if (errors !== undefined) { + // + // { "errors": { "user": ["not_enough_free_balance"] }} + // { "errors": { "quantity": ["less_than_order_size"] }} + // { "errors": { "order": ["Can not update partially filled order"] }} + // + const types = Object.keys (errors); + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const errorMessages = errors[type]; + for (let j = 0; j < errorMessages.length; j++) { + const message = errorMessages[j]; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + } + } + } else { + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],106:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, OnMaintenance, BadResponse, ArgumentsRequired, AuthenticationError, NotSupported, InvalidOrder, OrderNotFound, ExchangeNotAvailable, RateLimitExceeded, InsufficientFunds } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class livecoin extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'livecoin', + 'name': 'LiveCoin', + 'countries': [ 'US', 'UK', 'RU' ], + 'rateLimit': 1000, + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27980768-f22fc424-638a-11e7-89c9-6010a54ff9be.jpg', + 'api': 'https://api.livecoin.net', + 'www': 'https://www.livecoin.net', + 'doc': 'https://www.livecoin.net/api?lang=en', + 'referral': 'https://livecoin.net/?from=Livecoin-CQ1hfx44', + }, + 'api': { + 'public': { + 'get': [ + 'exchange/all/order_book', + 'exchange/last_trades', + 'exchange/maxbid_minask', + 'exchange/order_book', + 'exchange/restrictions', + 'exchange/ticker', // omit params to get all tickers at once + 'info/coinInfo', + ], + }, + 'private': { + 'get': [ + 'exchange/client_orders', + 'exchange/order', + 'exchange/trades', + 'exchange/commission', + 'exchange/commissionCommonInfo', + 'payment/balances', + 'payment/balance', + 'payment/get/address', + 'payment/history/size', + 'payment/history/transactions', + ], + 'post': [ + 'exchange/buylimit', + 'exchange/buymarket', + 'exchange/cancellimit', + 'exchange/selllimit', + 'exchange/sellmarket', + 'payment/out/capitalist', + 'payment/out/card', + 'payment/out/coin', + 'payment/out/okpay', + 'payment/out/payeer', + 'payment/out/perfectmoney', + 'payment/voucher/amount', + 'payment/voucher/make', + 'payment/voucher/redeem', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.18 / 100, + 'taker': 0.18 / 100, + }, + }, + 'commonCurrencies': { + 'BIT': 'First Bitcoin', + 'BTCH': 'Bithash', + 'CPC': 'Capricoin', + 'CBC': 'CryptoBossCoin', // conflict with CBC (CashBet Coin) + 'CPT': 'Cryptos', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081 + 'EDR': 'E-Dinar Coin', // conflicts with EDR for Endor Protocol and EDRCoin + 'eETT': 'EETT', + 'FirstBlood': '1ST', + 'FORTYTWO': '42', + 'LEO': 'LeoCoin', + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + 'ORE': 'Orectic', + 'PLN': 'Plutaneum', // conflict with Polish Zloty + 'RUR': 'RUB', + 'SCT': 'SpaceCoin', + 'TPI': 'ThaneCoin', + 'UNUS': 'LEO', // https://github.com/ccxt/ccxt/issues/7496 + 'WAX': 'WAXP', + 'wETT': 'WETT', + 'XBT': 'Bricktox', + }, + 'exceptions': { + 'exact': { + '1': ExchangeError, + '10': AuthenticationError, + '100': ExchangeError, // invalid parameters + '101': AuthenticationError, + '102': AuthenticationError, + '103': InvalidOrder, // invalid currency + '104': InvalidOrder, // invalid amount + '105': InvalidOrder, // unable to block funds + '11': AuthenticationError, + '12': AuthenticationError, + '2': AuthenticationError, // "User not found" + '20': AuthenticationError, + '30': AuthenticationError, + '31': NotSupported, + '32': ExchangeError, + '429': RateLimitExceeded, + '503': ExchangeNotAvailable, + }, + 'broad': { + 'insufficient funds': InsufficientFunds, // https://github.com/ccxt/ccxt/issues/5749 + 'NOT FOUND': OrderNotFound, + 'Cannot find order': OrderNotFound, + 'Minimal amount is': InvalidOrder, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeTicker (params); + const restrictions = await this.publicGetExchangeRestrictions (); + const restrictionsById = this.indexBy (restrictions['restrictions'], 'currencyPair'); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'symbol'); + const [ baseId, quoteId ] = id.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const coinRestrictions = this.safeValue (restrictionsById, symbol); + const precision = { + 'price': 5, + 'amount': 8, + 'cost': 8, + }; + const limits = { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + }; + if (coinRestrictions) { + precision['price'] = this.safeInteger (coinRestrictions, 'priceScale', 5); + limits['amount']['min'] = this.safeFloat (coinRestrictions, 'minLimitQuantity', limits['amount']['min']); + } + limits['price'] = { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetInfoCoinInfo (params); + if (typeof response === 'string') { + if (response.indexOf ('site is under maintenance') >= 0) { + throw new OnMaintenance (this.id + ' fetchCurrencies() failed to fetch the currencies, the exchange is on maintenance'); + } else { + throw new BadResponse (this.id + ' fetchCurrencies() failed to fetch the currencies'); + } + } + const currencies = this.safeValue (response, 'info'); + let result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'symbol'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const code = this.safeCurrencyCode (id); + const precision = 8; // default precision, todo: fix "magic constants" + const walletStatus = this.safeString (currency, 'walletStatus'); + const active = (walletStatus === 'normal'); + const name = this.safeString (currency, 'name'); + const fee = this.safeFloat (currency, 'withdrawFee'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'minOrderAmount'), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': this.safeFloat (currency, 'minOrderAmount'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawAmount'), + 'max': Math.pow (10, precision), + }, + 'deposit': { + 'min': this.safeFloat (currency, 'minDepositAmount'), + 'max': undefined, + }, + }, + }; + } + result = this.appendFiatCurrencies (result); + return result; + } + + appendFiatCurrencies (result) { + const precision = 8; + const defaults = { + 'info': undefined, + 'active': true, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'withdraw': { 'min': undefined, 'max': undefined }, + 'deposit': { 'min': undefined, 'max': undefined }, + 'amount': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + }, + 'id': undefined, + 'code': undefined, + 'name': undefined, + }; + const currencies = [ + { 'id': 'USD', 'code': 'USD', 'name': 'US Dollar' }, + { 'id': 'EUR', 'code': 'EUR', 'name': 'Euro' }, + // { 'id': 'RUR', 'code': 'RUB', 'name': 'Russian ruble' }, + ]; + currencies.push ({ + 'id': 'RUR', + 'code': this.safeCurrencyCode ('RUR'), + 'name': 'Russian ruble', + }); + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const code = currency['code']; + result[code] = this.extend (defaults, currency); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetPaymentBalances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let account = undefined; + if (code in result) { + account = result[code]; + } else { + account = this.account (); + } + if (balance['type'] === 'total') { + account['total'] = this.safeFloat (balance, 'value'); + } + if (balance['type'] === 'available') { + account['free'] = this.safeFloat (balance, 'value'); + } + if (balance['type'] === 'trade') { + account['used'] = this.safeFloat (balance, 'value'); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetExchangeCommissionCommonInfo (params); + const commission = this.safeFloat (response, 'commission'); + return { + 'info': response, + 'maker': commission, + 'taker': commission, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + 'groupByPrice': 'false', + }; + if (limit !== undefined) { + request['depth'] = limit; // 100 + } + const response = await this.publicGetExchangeOrderBook (this.extend (request, params)); + const timestamp = this.safeInteger (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': undefined, + 'vwap': this.safeFloat (ticker, 'vwap'), + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetExchangeTicker (params); + const tickers = this.indexBy (response, 'symbol'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const ticker = await this.publicGetExchangeTicker (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "time": 1409935047, + // "id": 99451, + // "price": 350, + // "quantity": 2.85714285, + // "type": "BUY" + // } + // + // fetchMyTrades (private) + // + // { + // "datetime": 1435844369, + // "id": 30651619, + // "type": "sell", + // "symbol": "BTC/EUR", + // "price": 230, + // "quantity": 0.1, + // "commission": 0, + // "clientorderid": 1472837650 + // } + const timestamp = this.safeTimestamp2 (trade, 'time', 'datetime'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrency = market ? market['quote'] : undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'clientorderid'); + const id = this.safeString (trade, 'id'); + const side = this.safeStringLower (trade, 'type'); + const amount = this.safeFloat (trade, 'quantity'); + const price = this.safeFloat (trade, 'price'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '/'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currencyPair': market['id'], + // 'orderDesc': 'true', // or 'false', if true then new orders will be first, otherwise old orders will be first. + // 'offset': 0, // page offset, position of the first item on the page + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['currencyPair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetExchangeTrades (this.extend (request, params)); + // + // [ + // { + // "datetime": 1435844369, + // "id": 30651619, + // "type": "sell", + // "symbol": "BTC/EUR", + // "price": 230, + // "quantity": 0.1, + // "commission": 0, + // "clientorderid": 1472837650 + // }, + // { + // "datetime": 1435844356, + // "id": 30651618, + // "type": "sell", + // "symbol": "BTC/EUR", + // "price": 230, + // "quantity": 0.2, + // "commission": 0.092, + // "clientorderid": 1472837651 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const response = await this.publicGetExchangeLastTrades (this.extend (request, params)); + // + // [ + // { + // "time": 1409935047, + // "id": 99451, + // "price": 350, + // "quantity": 2.85714285, + // "type": "BUY" + // }, + // { + // "time": 1409934792, + // "id": 99450, + // "price": 350, + // "quantity": 0.57142857, + // "type": "SELL" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateGetExchangeOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + 'OPEN': 'open', + 'PARTIALLY_FILLED': 'open', + 'EXECUTED': 'closed', + 'CANCELLED': 'canceled', + 'PARTIALLY_FILLED_AND_CANCELLED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + let timestamp = undefined; + if ('lastModificationTime' in order) { + timestamp = this.safeString (order, 'lastModificationTime'); + if (timestamp !== undefined) { + if (timestamp.indexOf ('T') >= 0) { + timestamp = this.parse8601 (timestamp); + } else { + timestamp = this.safeInteger (order, 'lastModificationTime'); + } + } + } + // TODO currently not supported by livecoin + // let trades = this.parseTrades (order['trades'], market, since, limit); + const trades = undefined; + const status = this.parseOrderStatus (this.safeString2 (order, 'status', 'orderStatus')); + const marketId = this.safeString2 (order, 'symbol', 'currencyPair'); + const symbol = this.safeSymbol (marketId, market, '/'); + let type = this.safeStringLower (order, 'type'); + let side = undefined; + if (type !== undefined) { + const orderType = type.split ('_'); + type = orderType[0]; + side = orderType[1]; + } + const price = this.safeFloat (order, 'price'); + // of the next two lines the latter overrides the former, if present in the order structure + let remaining = this.safeFloat (order, 'remainingQuantity'); + remaining = this.safeFloat (order, 'remaining_quantity', remaining); + const amount = this.safeFloat (order, 'quantity', remaining); + let filled = undefined; + if (remaining !== undefined) { + filled = amount - remaining; + } + let cost = undefined; + if (filled !== undefined && price !== undefined) { + cost = filled * price; + } + const feeRate = this.safeFloat (order, 'commission_rate'); + let feeCost = undefined; + if (cost !== undefined && feeRate !== undefined) { + feeCost = cost * feeRate; + } + if ((market === undefined) && (symbol in this.markets)) { + market = this.markets[symbol]; + } + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = market['quote']; + } + return { + 'info': order, + 'id': order['id'], + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }, + 'average': undefined, + }; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['currencyPair'] = market['id']; + } + if (since !== undefined) { + request['issuedFrom'] = parseInt (since); + } + if (limit !== undefined) { + request['endRow'] = limit - 1; + } + const response = await this.privateGetExchangeClientOrders (this.extend (request, params)); + const result = []; + let rawOrders = []; + if (response['data']) { + rawOrders = response['data']; + } + for (let i = 0; i < rawOrders.length; i++) { + const order = rawOrders[i]; + result.push (this.parseOrder (order, market)); + } + return this.sortBy (result, 'timestamp'); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'openClosed': 'OPEN', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'openClosed': 'CLOSED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostExchange' + this.capitalize (side) + type; + const market = this.market (symbol); + const request = { + 'quantity': this.amountToPrecision (symbol, amount), + 'currencyPair': market['id'], + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this[method] (this.extend (request, params)); + const result = { + 'info': response, + 'id': response['orderId'].toString (), + }; + const success = this.safeValue (response, 'success'); + if (success) { + result['status'] = 'open'; + } + return result; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'orderId': id, + 'currencyPair': market['id'], + }; + const response = await this.privatePostExchangeCancellimit (this.extend (request, params)); + const message = this.safeString (response, 'message', this.json (response)); + if ('success' in response) { + if (!response['success']) { + throw new InvalidOrder (message); + } else if ('cancelled' in response) { + if (response['cancelled']) { + return { + 'status': 'canceled', + 'info': response, + }; + } else { + throw new OrderNotFound (message); + } + } + } + throw new ExchangeError (this.id + ' cancelOrder() failed: ' + this.json (response)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // Sometimes the response with be { key: null } for all keys. + // An example is if you attempt to withdraw more than is allowed when withdrawal fees are considered. + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + let wallet = address; + if (tag !== undefined) { + wallet += '::' + tag; + } + const request = { + 'amount': this.decimalToPrecision (amount, TRUNCATE, currency['precision'], DECIMAL_PLACES), + 'currency': currency['id'], + 'wallet': wallet, + }; + const response = await this.privatePostPaymentOutCoin (this.extend (request, params)); + const id = this.safeInteger (response, 'id'); + if (id === undefined) { + throw new InsufficientFunds (this.id + ' insufficient funds to cover requested withdrawal amount post fees ' + this.json (response)); + } + return { + 'info': response, + 'id': id, + }; + } + + parseTransaction (transaction, currency = undefined) { + // { + // "id": "c853093d5aa06df1c92d79c2...", (tx on deposits, address on withdrawals) + // "type": "DEPOSIT", + // "date": 1553186482676, + // "amount": 712.61266, + // "fee": 0, + // "fixedCurrency": "XVG", + // "taxCurrency": "XVG", + // "variableAmount": null, + // "variableCurrency": null, + // "external": "Coin", + // "login": "USERNAME", + // "externalKey": "....87diPBy......3hTtuwUT78Yi", (address on deposits, tx on withdrawals) + // "documentId": 1110662453 + // }, + let txid = undefined; + let address = undefined; + const id = this.safeString (transaction, 'documentId'); + const amount = this.safeFloat (transaction, 'amount'); + const timestamp = this.safeInteger (transaction, 'date'); + const type = this.safeStringLower (transaction, 'type'); + const currencyId = this.safeString (transaction, 'fixedCurrency'); + const feeCost = this.safeFloat (transaction, 'fee'); + const code = this.safeCurrencyCode (currencyId, currency); + if (type === 'withdrawal') { + txid = this.safeString (transaction, 'externalKey'); + address = this.safeString (transaction, 'id'); + } else if (type === 'deposit') { + address = this.safeString (transaction, 'externalKey'); + txid = this.safeString (transaction, 'id'); + } + let status = undefined; + if (type === 'deposit') { + status = 'ok'; // Deposits is not registered until they are in account. Withdrawals are left as undefined, not entirely sure about theyre status. + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const endtime = 2505600000; // 29 days - exchange has maximum 30 days. + const now = this.milliseconds (); + const request = { + 'types': 'DEPOSIT', + 'end': now, + 'start': (since !== undefined) ? parseInt (since) : now - endtime, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + const response = await this.privateGetPaymentHistoryTransactions (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const endtime = 2505600000; // 29 days - exchange has maximum 30 days. + const now = this.milliseconds (); + const request = { + 'types': 'WITHDRAWAL', + 'end': now, + 'start': (since !== undefined) ? parseInt (since) : now - endtime, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + if (since !== undefined) { + request['start'] = since; + } + const response = await this.privateGetPaymentHistoryTransactions (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDepositAddress (currency, params = {}) { + const request = { + 'currency': currency, + }; + const response = await this.privateGetPaymentGetAddress (this.extend (request, params)); + let address = this.safeString (response, 'wallet'); + let tag = undefined; + if (address.indexOf (':') >= 0) { + const parts = address.split (':'); + address = parts[0]; + tag = parts[2]; + } + this.checkAddress (address); + return { + 'currency': currency, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + path; + const query = this.urlencode (this.keysort (params)); + if (method === 'GET') { + if (Object.keys (params).length) { + url += '?' + query; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + if (method === 'POST') { + body = query; + } + const signature = this.hmac (this.encode (query), this.encode (this.secret), 'sha256'); + headers = { + 'Api-Key': this.apiKey, + 'Sign': signature.toUpperCase (), + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (code >= 300) { + const feedback = this.id + ' ' + body; + const errorCode = this.safeString (response, 'errorCode'); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); + } + // returns status code 200 even if success === false + const success = this.safeValue (response, 'success', true); + if (!success) { + const feedback = this.id + ' ' + body; + const message = this.safeString2 (response, 'message', 'exception'); + if (message !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); + } + } +}; + + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],107:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class luno extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'luno', + 'name': 'luno', + 'countries': [ 'GB', 'SG', 'ZA' ], + 'rateLimit': 1000, + 'version': '1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + }, + 'urls': { + 'referral': 'https://www.luno.com/invite/44893A', + 'logo': 'https://user-images.githubusercontent.com/1294454/27766607-8c1a69d8-5ede-11e7-930c-540b5eb9be24.jpg', + 'api': 'https://api.luno.com/api', + 'www': 'https://www.luno.com', + 'doc': [ + 'https://www.luno.com/en/api', + 'https://npmjs.org/package/bitx', + 'https://github.com/bausmeier/node-bitx', + ], + }, + 'api': { + 'public': { + 'get': [ + 'orderbook', + 'orderbook_top', + 'ticker', + 'tickers', + 'trades', + ], + }, + 'private': { + 'get': [ + 'accounts/{id}/pending', + 'accounts/{id}/transactions', + 'balance', + 'beneficiaries', + 'fee_info', + 'funding_address', + 'listorders', + 'listtrades', + 'orders/{id}', + 'quotes/{id}', + 'withdrawals', + 'withdrawals/{id}', + ], + 'post': [ + 'accounts', + 'accounts/{id}/name', + 'postorder', + 'marketorder', + 'stoporder', + 'funding_address', + 'withdrawals', + 'send', + 'quotes', + 'oauth2/grant', + ], + 'put': [ + 'accounts/{id}/name', + 'quotes/{id}', + ], + 'delete': [ + 'quotes/{id}', + 'withdrawals/{id}', + ], + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTickers (params); + const result = []; + for (let i = 0; i < response['tickers'].length; i++) { + const market = response['tickers'][i]; + const id = market['pair']; + const baseId = id.slice (0, 3); + const quoteId = id.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetBalance (params); + const wallets = this.safeValue (response, 'balance', []); + const result = []; + for (let i = 0; i < wallets.length; i++) { + const account = wallets[i]; + const accountId = this.safeString (account, 'account_id'); + const currencyId = this.safeString (account, 'asset'); + const code = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': accountId, + 'type': undefined, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const wallets = this.safeValue (response, 'balance', []); + const result = { 'info': response }; + for (let i = 0; i < wallets.length; i++) { + const wallet = wallets[i]; + const currencyId = this.safeString (wallet, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const reserved = this.safeFloat (wallet, 'reserved'); + const unconfirmed = this.safeFloat (wallet, 'unconfirmed'); + const balance = this.safeFloat (wallet, 'balance'); + const account = this.account (); + account['used'] = this.sum (reserved, unconfirmed); + account['total'] = this.sum (balance, unconfirmed); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + let method = 'publicGetOrderbook'; + if (limit !== undefined) { + if (limit <= 100) { + method += 'Top'; // get just the top of the orderbook when limit is low + } + } + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this[method] (this.extend (request, params)); + const timestamp = this.safeInteger (response, 'timestamp'); + return this.parseOrderBook (response, timestamp, 'bids', 'asks', 'price', 'volume'); + } + + parseOrderStatus (status) { + const statuses = { + // todo add other statuses + 'PENDING': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "base": "string", + // "completed_timestamp": "string", + // "counter": "string", + // "creation_timestamp": "string", + // "expiration_timestamp": "string", + // "fee_base": "string", + // "fee_counter": "string", + // "limit_price": "string", + // "limit_volume": "string", + // "order_id": "string", + // "pair": "string", + // "state": "PENDING", + // "type": "BID" + // } + // + const timestamp = this.safeInteger (order, 'creation_timestamp'); + let status = this.parseOrderStatus (this.safeString (order, 'state')); + status = (status === 'open') ? status : status; + const side = (order['type'] === 'ASK') ? 'sell' : 'buy'; + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const price = this.safeFloat (order, 'limit_price'); + const amount = this.safeFloat (order, 'limit_volume'); + const quoteFee = this.safeFloat (order, 'fee_counter'); + const baseFee = this.safeFloat (order, 'fee_base'); + const filled = this.safeFloat (order, 'base'); + const cost = this.safeFloat (order, 'counter'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + const fee = { 'currency': undefined }; + if (quoteFee) { + fee['cost'] = quoteFee; + if (market !== undefined) { + fee['currency'] = market['quote']; + } + } else { + fee['cost'] = baseFee; + if (market !== undefined) { + fee['currency'] = market['base']; + } + } + const id = this.safeString (order, 'order_id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'cost': cost, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrdersByState (state = undefined, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (state !== undefined) { + request['state'] = state; + } + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privateGetListorders (this.extend (request, params)); + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState (undefined, symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('PENDING', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('COMPLETE', symbol, since, limit, params); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeInteger (ticker, 'timestamp'); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last_trade'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'rolling_24_hour_volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const tickers = this.indexBy (response['tickers'], 'pair'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market) { + // For public trade data (is_buy === True) indicates 'buy' side but for private trade data + // is_buy indicates maker or taker. The value of "type" (ASK/BID) indicate sell/buy side. + // Private trade data includes ID field which public trade data does not. + const orderId = this.safeString (trade, 'order_id'); + let takerOrMaker = undefined; + let side = undefined; + if (orderId !== undefined) { + side = (trade['type'] === 'ASK') ? 'sell' : 'buy'; + if (side === 'sell' && trade['is_buy']) { + takerOrMaker = 'maker'; + } else if (side === 'buy' && !trade['is_buy']) { + takerOrMaker = 'maker'; + } else { + takerOrMaker = 'taker'; + } + } else { + side = trade['is_buy'] ? 'buy' : 'sell'; + } + const feeBase = this.safeFloat (trade, 'fee_base'); + const feeCounter = this.safeFloat (trade, 'fee_counter'); + let feeCurrency = undefined; + let feeCost = undefined; + if (feeBase !== undefined) { + if (feeBase !== 0.0) { + feeCurrency = market['base']; + feeCost = feeBase; + } + } else if (feeCounter !== undefined) { + if (feeCounter !== 0.0) { + feeCurrency = market['quote']; + feeCost = feeCounter; + } + } + const timestamp = this.safeInteger (trade, 'timestamp'); + return { + 'info': trade, + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': this.safeFloat (trade, 'price'), + 'amount': this.safeFloat (trade, 'volume'), + // Does not include potential fee costs + 'cost': this.safeFloat (trade, 'counter'), + 'fee': { + 'cost': feeCost, + 'currency': feeCurrency, + }, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (since !== undefined) { + request['since'] = since; + } + const response = await this.publicGetTrades (this.extend (request, params)); + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetListtrades (this.extend (request, params)); + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetFeeInfo (params); + return { + 'info': response, + 'maker': this.safeFloat (response, 'maker_fee'), + 'taker': this.safeFloat (response, 'taker_fee'), + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let method = 'privatePost'; + const request = { + 'pair': this.marketId (symbol), + }; + if (type === 'market') { + method += 'Marketorder'; + request['type'] = side.toUpperCase (); + if (side === 'buy') { + request['counter_volume'] = amount; + } else { + request['base_volume'] = amount; + } + } else { + method += 'Postorder'; + request['volume'] = amount; + request['price'] = price; + request['type'] = (side === 'buy') ? 'BID' : 'ASK'; + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['order_id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + return await this.privatePostStoporder (this.extend (request, params)); + } + + async fetchLedgerByEntries (code = undefined, entry = -1, limit = 1, params = {}) { + // by default without entry number or limit number, return most recent entry + const since = undefined; + const request = { + 'min_row': entry, + 'max_row': this.sum (entry, limit), + }; + return await this.fetchLedger (code, since, limit, this.extend (request, params)); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let currency = undefined; + let id = this.safeString (params, 'id'); // account id + let min_row = this.safeValue (params, 'min_row'); + let max_row = this.safeValue (params, 'max_row'); + if (id === undefined) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchLedger() requires a currency code argument if no account id specified in params'); + } + currency = this.currency (code); + const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency'); + const account = this.safeValue (accountsByCurrencyCode, code); + if (account === undefined) { + throw new ExchangeError (this.id + ' fetchLedger() could not find account id for ' + code); + } + id = account['id']; + } + if (min_row === undefined && max_row === undefined) { + max_row = 0; // Default to most recent transactions + min_row = -1000; // Maximum number of records supported + } else if (min_row === undefined || max_row === undefined) { + throw new ExchangeError (this.id + " fetchLedger() require both params 'max_row' and 'min_row' or neither to be defined"); + } + if (limit !== undefined && max_row - min_row > limit) { + if (max_row <= 0) { + min_row = max_row - limit; + } else if (min_row > 0) { + max_row = min_row + limit; + } + } + if (max_row - min_row > 1000) { + throw new ExchangeError (this.id + " fetchLedger() requires the params 'max_row' - 'min_row' <= 1000"); + } + const request = { + 'id': id, + 'min_row': min_row, + 'max_row': max_row, + }; + const response = await this.privateGetAccountsIdTransactions (this.extend (params, request)); + const entries = this.safeValue (response, 'transactions', []); + return this.parseLedger (entries, currency, since, limit); + } + + parseLedgerComment (comment) { + const words = comment.split (' '); + const types = { + 'Withdrawal': 'fee', + 'Trading': 'fee', + 'Payment': 'transaction', + 'Sent': 'transaction', + 'Deposit': 'transaction', + 'Received': 'transaction', + 'Released': 'released', + 'Reserved': 'reserved', + 'Sold': 'trade', + 'Bought': 'trade', + 'Failure': 'failed', + }; + let referenceId = undefined; + const firstWord = this.safeString (words, 0); + const thirdWord = this.safeString (words, 2); + const fourthWord = this.safeString (words, 3); + let type = this.safeString (types, firstWord, undefined); + if ((type === undefined) && (thirdWord === 'fee')) { + type = 'fee'; + } + if ((type === 'reserved') && (fourthWord === 'order')) { + referenceId = this.safeString (words, 4); + } + return { + 'type': type, + 'referenceId': referenceId, + }; + } + + parseLedgerEntry (entry, currency = undefined) { + // const details = this.safeValue (entry, 'details', {}); + const id = this.safeString (entry, 'row_index'); + const account_id = this.safeString (entry, 'account_id'); + const timestamp = this.safeValue (entry, 'timestamp'); + const currencyId = this.safeString (entry, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const available_delta = this.safeFloat (entry, 'available_delta'); + const balance_delta = this.safeFloat (entry, 'balance_delta'); + const after = this.safeFloat (entry, 'balance'); + const comment = this.safeString (entry, 'description'); + let before = after; + let amount = 0.0; + const result = this.parseLedgerComment (comment); + const type = result['type']; + const referenceId = result['referenceId']; + let direction = undefined; + let status = undefined; + if (balance_delta !== 0.0) { + before = after - balance_delta; // TODO: float precision + status = 'ok'; + amount = Math.abs (balance_delta); + } else if (available_delta < 0.0) { + status = 'pending'; + amount = Math.abs (available_delta); + } else if (available_delta > 0.0) { + status = 'canceled'; + amount = Math.abs (available_delta); + } + if (balance_delta > 0 || available_delta > 0) { + direction = 'in'; + } else if (balance_delta < 0 || available_delta < 0) { + direction = 'out'; + } + return { + 'id': id, + 'direction': direction, + 'account': account_id, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'before': before, + 'after': after, + 'status': status, + 'fee': undefined, + 'info': entry, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + if (api === 'private') { + this.checkRequiredCredentials (); + const auth = this.stringToBase64 (this.apiKey + ':' + this.secret); + headers = { + 'Authorization': 'Basic ' + this.decode (auth), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('error' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],108:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class lykke extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'lykke', + 'name': 'Lykke', + 'countries': [ 'CH' ], + 'version': 'v1', + 'rateLimit': 200, + 'has': { + 'CORS': false, + 'fetchOHLCV': false, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchTrades': true, + 'fetchMyTrades': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + }, + 'timeframes': { + '1m': 'Minute', + '5m': 'Min5', + '15m': 'Min15', + '30m': 'Min30', + '1h': 'Hour', + '4h': 'Hour4', + '6h': 'Hour6', + '12h': 'Hour12', + '1d': 'Day', + '1w': 'Week', + '1M': 'Month', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/34487620-3139a7b0-efe6-11e7-90f5-e520cef74451.jpg', + 'api': { + 'mobile': 'https://public-api.lykke.com/api', + 'public': 'https://hft-api.lykke.com/api', + 'private': 'https://hft-api.lykke.com/api', + }, + 'test': { + 'mobile': 'https://public-api.lykke.com/api', + 'public': 'https://hft-service-dev.lykkex.net/api', + 'private': 'https://hft-service-dev.lykkex.net/api', + }, + 'www': 'https://www.lykke.com', + 'doc': [ + 'https://hft-api.lykke.com/swagger/ui/', + 'https://www.lykke.com/lykke_api', + ], + 'fees': 'https://www.lykke.com/trading-conditions', + }, + 'api': { + 'mobile': { + 'get': [ + 'AssetPairs/rate', + 'AssetPairs/rate/{assetPairId}', + 'AssetPairs/dictionary/{market}', + 'Assets/dictionary', + 'Candles/history/{market}/available', + 'Candles/history/{market}/{assetPair}/{period}/{type}/{from}/{to}', + 'Company/ownershipStructure', + 'Company/registrationsCount', + 'IsAlive', + 'Market', + 'Market/{market}', + 'Market/capitalization/{market}', + 'OrderBook', + 'OrderBook/{assetPairId}', + 'Trades/{AssetPairId}', + 'Trades/Last/{assetPair}/{n}', + ], + 'post': [ + 'AssetPairs/rate/history', + 'AssetPairs/rate/history/{assetPairId}', + ], + }, + 'public': { + 'get': [ + 'AssetPairs', + 'AssetPairs/{id}', + 'IsAlive', + 'OrderBooks', + 'OrderBooks/{AssetPairId}', + ], + }, + 'private': { + 'get': [ + 'Orders', + 'Orders/{id}', + 'Wallets', + 'History/trades', + ], + 'post': [ + 'Orders/limit', + 'Orders/market', + 'Orders/{id}/Cancel', + 'Orders/v2/market', + 'Orders/v2/limit', + 'Orders/stoplimit', + 'Orders/bulk', + ], + 'delete': [ + 'Orders', + 'Orders/{id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0, // as of 7 Feb 2018, see https://github.com/ccxt/ccxt/issues/1863 + 'taker': 0.0, // https://www.lykke.com/cp/wallet-fees-and-limits + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.001, + }, + 'deposit': { + 'BTC': 0, + }, + }, + }, + 'commonCurrencies': { + 'XPD': 'Lykke XPD', + }, + }); + } + + parseTrade (trade, market) { + // + // public fetchTrades + // + // { + // "id": "d5983ab8-e9ec-48c9-bdd0-1b18f8e80a71", + // "assetPairId": "BTCUSD", + // "dateTime": "2019-05-15T06:52:02.147Z", + // "volume": 0.00019681, + // "index": 0, + // "price": 8023.333, + // "action": "Buy" + // } + // + // private fetchMyTrades + // { + // Id: '3500b83c-9963-4349-b3ee-b3e503073cea', + // OrderId: '83b50feb-8615-4dc6-b606-8a4168ecd708', + // DateTime: '2020-05-19T11:17:39.31+00:00', + // Timestamp: '2020-05-19T11:17:39.31+00:00', + // State: null, + // Amount: -0.004, + // BaseVolume: -0.004, + // QuotingVolume: 39.3898, + // Asset: 'BTC', + // BaseAssetId: 'BTC', + // QuotingAssetId: 'USD', + // AssetPair: 'BTCUSD', + // AssetPairId: 'BTCUSD', + // Price: 9847.427, + // Fee: { Amount: null, Type: 'Unknown', FeeAssetId: null } + // }, + const marketId = this.safeString (trade, 'AssetPairId'); + const symbol = this.safeSymbol (marketId, market); + const id = this.safeString2 (trade, 'id', 'Id'); + const orderId = this.safeString (trade, 'OrderId'); + const timestamp = this.parse8601 (this.safeString2 (trade, 'dateTime', 'DateTime')); + const price = this.safeFloat2 (trade, 'price', 'Price'); + let amount = this.safeFloat2 (trade, 'volume', 'Amount'); + let side = this.safeStringLower (trade, 'action'); + if (side === undefined) { + if (amount < 0) { + side = 'sell'; + } else { + side = 'buy'; + } + } + amount = Math.abs (amount); + const cost = price * amount; + const fee = { + 'cost': 0, // There are no fees for trading. https://www.lykke.com/wallet-fees-and-limits/ + 'currency': market['quote'], + }; + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': orderId, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; + } + const request = { + 'AssetPairId': market['id'], + 'skip': 0, + 'take': limit, + }; + const response = await this.mobileGetTradesAssetPairId (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (limit !== undefined) { + request['take'] = limit; // How many maximum items have to be returned, max 1000 default 100. + } + if (symbol !== undefined) { + market = this.market (symbol); + request['assetPairId'] = market['id']; + } + const response = await this.privateGetHistoryTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWallets (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'AssetId'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'Balance'); + account['used'] = this.safeFloat (balance, 'Reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { 'id': id }; + return await this.privateDeleteOrdersId (this.extend (request, params)); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['assetPairId'] = market['id']; + } + return await this.privateDeleteOrders (this.extend (request, params)); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const query = { + 'AssetPairId': market['id'], + 'OrderAction': this.capitalize (side), + 'Volume': amount, + 'Asset': market['baseId'], + }; + if (type === 'limit') { + query['Price'] = price; + } + const method = 'privatePostOrdersV2' + this.capitalize (type); + const result = await this[method] (this.extend (query, params)); + // + // market + // + // { + // "Price": 0 + // } + // + // limit + // + // { + // "Id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + // } + // + const id = this.safeString (result, 'Id'); + price = this.safeFloat (result, 'Price'); + return { + 'id': id, + 'info': result, + 'clientOrderId': undefined, + 'timestamp': undefined, + 'datetime': undefined, + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': undefined, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetAssetPairs (); + // + // [ { Id: "AEBTC", + // Name: "AE/BTC", + // Accuracy: 6, + // InvertedAccuracy: 8, + // BaseAssetId: "6f75280b-a005-4016-a3d8-03dc644e8912", + // QuotingAssetId: "BTC", + // MinVolume: 0.4, + // MinInvertedVolume: 0.0001 }, + // { Id: "AEETH", + // Name: "AE/ETH", + // Accuracy: 6, + // InvertedAccuracy: 8, + // BaseAssetId: "6f75280b-a005-4016-a3d8-03dc644e8912", + // QuotingAssetId: "ETH", + // MinVolume: 0.4, + // MinInvertedVolume: 0.001 } ] + // + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'Id'); + const name = this.safeString (market, 'Name'); + const [ baseId, quoteId ] = name.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'Accuracy'), + 'amount': this.safeInteger (market, 'InvertedAccuracy'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'active': true, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'baseId': undefined, + 'quoteId': undefined, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const close = this.safeFloat (ticker, 'lastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'volume24H'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const ticker = await this.mobileGetMarketMarket (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseOrderStatus (status) { + const statuses = { + 'Open': 'open', + 'Pending': 'open', + 'InOrderBook': 'open', + 'Processing': 'open', + 'Matched': 'closed', + 'Cancelled': 'canceled', + 'Rejected': 'rejected', + 'Replaced': 'canceled', + 'Placed': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "Id": "string", + // "Status": "Unknown", + // "AssetPairId": "string", + // "Volume": 0, + // "Price": 0, + // "RemainingVolume": 0, + // "LastMatchTime": "2020-03-26T20:58:50.710Z", + // "CreatedAt": "2020-03-26T20:58:50.710Z", + // "Type": "Unknown", + // "LowerLimitPrice": 0, + // "LowerPrice": 0, + // "UpperLimitPrice": 0, + // "UpperPrice": 0 + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'Status')); + const marketId = this.safeString (order, 'AssetPairId'); + const symbol = this.safeSymbol (marketId, market); + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'LastMatchTime')); + let timestamp = undefined; + if (('Registered' in order) && (order['Registered'])) { + timestamp = this.parse8601 (order['Registered']); + } else if (('CreatedAt' in order) && (order['CreatedAt'])) { + timestamp = this.parse8601 (order['CreatedAt']); + } + const price = this.safeFloat (order, 'Price'); + let side = undefined; + let amount = this.safeFloat (order, 'Volume'); + if (amount < 0) { + side = 'sell'; + amount = Math.abs (amount); + } else { + side = 'buy'; + } + const remaining = Math.abs (this.safeFloat (order, 'RemainingVolume')); + const filled = amount - remaining; + const cost = filled * price; + const id = this.safeString (order, 'Id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetOrders (params); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'InOrderBook', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'Matched', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetOrderBooksAssetPairId (this.extend ({ + 'AssetPairId': this.marketId (symbol), + }, params)); + const orderbook = { + 'timestamp': undefined, + 'bids': [], + 'asks': [], + }; + let timestamp = undefined; + for (let i = 0; i < response.length; i++) { + const side = response[i]; + if (side['IsBuy']) { + orderbook['bids'] = this.arrayConcat (orderbook['bids'], side['Prices']); + } else { + orderbook['asks'] = this.arrayConcat (orderbook['asks'], side['Prices']); + } + const sideTimestamp = this.parse8601 (side['Timestamp']); + timestamp = (timestamp === undefined) ? sideTimestamp : Math.max (timestamp, sideTimestamp); + } + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'Price', 'Volume'); + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + const price = this.safeFloat (bidask, priceKey); + let amount = this.safeFloat (bidask, amountKey); + if (amount < 0) { + amount = -amount; + } + return [ price, amount ]; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'mobile') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + if ((method === 'GET') || (method === 'DELETE')) { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + this.checkRequiredCredentials (); + headers = { + 'api-key': this.apiKey, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }; + if (method === 'POST') { + if (Object.keys (params).length) { + body = this.json (params); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5}],109:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class mercado extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'mercado', + 'name': 'Mercado Bitcoin', + 'countries': [ 'BR' ], // Brazil + 'rateLimit': 1000, + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '2w': '2w', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27837060-e7c58714-60ea-11e7-9192-f05e86adb83f.jpg', + 'api': { + 'public': 'https://www.mercadobitcoin.net/api', + 'private': 'https://www.mercadobitcoin.net/tapi', + 'v4Public': 'https://www.mercadobitcoin.com.br/v4', + }, + 'www': 'https://www.mercadobitcoin.com.br', + 'doc': [ + 'https://www.mercadobitcoin.com.br/api-doc', + 'https://www.mercadobitcoin.com.br/trade-api', + ], + }, + 'api': { + 'public': { + 'get': [ + '{coin}/orderbook/', // last slash critical + '{coin}/ticker/', + '{coin}/trades/', + '{coin}/trades/{from}/', + '{coin}/trades/{from}/{to}', + '{coin}/day-summary/{year}/{month}/{day}/', + ], + }, + 'private': { + 'post': [ + 'cancel_order', + 'get_account_info', + 'get_order', + 'get_withdrawal', + 'list_system_messages', + 'list_orders', + 'list_orderbook', + 'place_buy_order', + 'place_sell_order', + 'place_market_buy_order', + 'place_market_sell_order', + 'withdraw_coin', + ], + }, + 'v4Public': { + 'get': [ + '{coin}/candle/', + ], + }, + }, + 'markets': { + 'BTC/BRL': { 'id': 'BRLBTC', 'symbol': 'BTC/BRL', 'base': 'BTC', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Bitcoin' }, + 'LTC/BRL': { 'id': 'BRLLTC', 'symbol': 'LTC/BRL', 'base': 'LTC', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Litecoin' }, + 'BCH/BRL': { 'id': 'BRLBCH', 'symbol': 'BCH/BRL', 'base': 'BCH', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'BCash' }, + 'XRP/BRL': { 'id': 'BRLXRP', 'symbol': 'XRP/BRL', 'base': 'XRP', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Ripple' }, + 'ETH/BRL': { 'id': 'BRLETH', 'symbol': 'ETH/BRL', 'base': 'ETH', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Ethereum' }, + 'USDC/BRL': { 'id': 'BRLUSDC', 'symbol': 'USDC/BRL', 'base': 'USDC', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'USDC' }, + }, + 'fees': { + 'trading': { + 'maker': 0.3 / 100, + 'taker': 0.7 / 100, + }, + }, + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['base'], + }; + const response = await this.publicGetCoinOrderbook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['base'], + }; + const response = await this.publicGetCoinTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'ticker', {}); + const timestamp = this.safeTimestamp (ticker, 'date'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'tid'); + const type = undefined; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = 'publicGetCoinTrades'; + const request = { + 'coin': market['base'], + }; + if (since !== undefined) { + method += 'From'; + request['from'] = parseInt (since / 1000); + } + const to = this.safeInteger (params, 'to'); + if (to !== undefined) { + method += 'To'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetAccountInfo (params); + const data = this.safeValue (response, 'response_data', {}); + const balances = this.safeValue (data, 'balance', {}); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + if (currencyId in balances) { + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'coin_pair': this.marketId (symbol), + }; + let method = this.capitalize (side) + 'Order'; + if (type === 'limit') { + method = 'privatePostPlace' + method; + request['limit_price'] = this.priceToPrecision (symbol, price); + request['quantity'] = this.amountToPrecision (symbol, amount); + } else { + method = 'privatePostPlaceMarket' + method; + if (side === 'buy') { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount'); + } + request['cost'] = this.priceToPrecision (symbol, amount * price); + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this[method] (this.extend (request, params)); + // TODO: replace this with a call to parseOrder for unification + return { + 'info': response, + 'id': response['response_data']['order']['order_id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder () requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin_pair': market['id'], + 'order_id': id, + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + // + // { + // response_data: { + // order: { + // order_id: 2176769, + // coin_pair: 'BRLBCH', + // order_type: 2, + // status: 3, + // has_fills: false, + // quantity: '0.10000000', + // limit_price: '1996.15999', + // executed_quantity: '0.00000000', + // executed_price_avg: '0.00000', + // fee: '0.00000000', + // created_timestamp: '1536956488', + // updated_timestamp: '1536956499', + // operations: [] + // } + // }, + // status_code: 100, + // server_unix_timestamp: '1536956499' + // } + // + const responseData = this.safeValue (response, 'response_data', {}); + const order = this.safeValue (responseData, 'order', {}); + return this.parseOrder (order, market); + } + + parseOrderStatus (status) { + const statuses = { + '2': 'open', + '3': 'canceled', + '4': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "order_id": 4, + // "coin_pair": "BRLBTC", + // "order_type": 1, + // "status": 2, + // "has_fills": true, + // "quantity": "2.00000000", + // "limit_price": "900.00000", + // "executed_quantity": "1.00000000", + // "executed_price_avg": "900.00000", + // "fee": "0.00300000", + // "created_timestamp": "1453838494", + // "updated_timestamp": "1453838494", + // "operations": [ + // { + // "operation_id": 1, + // "quantity": "1.00000000", + // "price": "900.00000", + // "fee_rate": "0.30", + // "executed_timestamp": "1453838494", + // }, + // ], + // } + // + const id = this.safeString (order, 'order_id'); + let side = undefined; + if ('order_type' in order) { + side = (order['order_type'] === 1) ? 'buy' : 'sell'; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'coin_pair'); + market = this.safeMarket (marketId, market); + const timestamp = this.safeTimestamp (order, 'created_timestamp'); + const fee = { + 'cost': this.safeFloat (order, 'fee'), + 'currency': market['quote'], + }; + const price = this.safeFloat (order, 'limit_price'); + // price = this.safeFloat (order, 'executed_price_avg', price); + const average = this.safeFloat (order, 'executed_price_avg'); + const amount = this.safeFloat (order, 'quantity'); + const filled = this.safeFloat (order, 'executed_quantity'); + const remaining = amount - filled; + const cost = filled * average; + const lastTradeTimestamp = this.safeTimestamp (order, 'updated_timestamp'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': market['symbol'], + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, // todo parse trades (operations) + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder () requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin_pair': market['id'], + 'order_id': parseInt (id), + }; + const response = await this.privatePostGetOrder (this.extend (request, params)); + const responseData = this.safeValue (response, 'response_data', {}); + const order = this.safeValue (responseData, 'order'); + return this.parseOrder (order, market); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'quantity': amount.toFixed (10), + 'address': address, + }; + if (code === 'BRL') { + const account_ref = ('account_ref' in params); + if (!account_ref) { + throw new ArgumentsRequired (this.id + ' requires account_ref parameter to withdraw ' + code); + } + } else if (code !== 'LTC') { + const tx_fee = ('tx_fee' in params); + if (!tx_fee) { + throw new ArgumentsRequired (this.id + ' requires tx_fee parameter to withdraw ' + code); + } + if (code === 'XRP') { + if (tag === undefined) { + if (!('destination_tag' in params)) { + throw new ArgumentsRequired (this.id + ' requires a tag argument or destination_tag parameter to withdraw ' + code); + } + } else { + request['destination_tag'] = tag; + } + } + } + const response = await this.privatePostWithdrawCoin (this.extend (request, params)); + return { + 'info': response, + 'id': response['response_data']['withdrawal']['id'], + }; + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeTimestamp (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'precision': this.timeframes[timeframe], + 'coin': market['id'].toLowerCase (), + }; + if (limit !== undefined && since !== undefined) { + request['from'] = parseInt (since / 1000); + request['to'] = this.sum (request['from'], limit * this.parseTimeframe (timeframe)); + } else if (since !== undefined) { + request['from'] = parseInt (since / 1000); + request['to'] = this.sum (this.seconds (), 1); + } else if (limit !== undefined) { + request['to'] = this.seconds (); + request['from'] = request['to'] - (limit * this.parseTimeframe (timeframe)); + } + const response = await this.v4PublicGetCoinCandle (this.extend (request, params)); + const candles = this.safeValue (response, 'candles', []); + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders () requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin_pair': market['id'], + }; + const response = await this.privatePostListOrders (this.extend (request, params)); + const responseData = this.safeValue (response, 'response_data', {}); + const orders = this.safeValue (responseData, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public' || (api === 'v4Public')) { + url += this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + url += this.version + '/'; + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'tapi_method': path, + 'tapi_nonce': nonce, + }, params)); + const auth = '/tapi/' + this.version + '/' + '?' + body; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'TAPI-ID': this.apiKey, + 'TAPI-MAC': this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('error_message' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],110:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class mixcoins extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'mixcoins', + 'name': 'MixCoins', + 'countries': [ 'GB', 'HK' ], + 'rateLimit': 1500, + 'version': 'v1', + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460810-1dd06c00-c616-11ea-9276-956f400d6ffa.jpg', + 'api': 'https://mixcoins.com/api', + 'www': 'https://mixcoins.com', + 'doc': 'https://mixcoins.com/help/api/', + }, + 'api': { + 'public': { + 'get': [ + 'ticker/', + 'trades/', + 'depth/', + ], + }, + 'private': { + 'post': [ + 'cancel', + 'info', + 'orders', + 'order', + 'transactions', + 'trade', + ], + }, + }, + 'markets': { + 'BTC/USDT': { 'id': 'btc_usdt', 'symbol': 'BTC/USDT', 'base': 'BTC', 'quote': 'USDT', 'baseId': 'btc', 'quoteId': 'usdt', 'maker': 0.0015, 'taker': 0.0025 }, + 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.001, 'taker': 0.0015 }, + 'BCH/BTC': { 'id': 'bch_btc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bch', 'quoteId': 'btc', 'maker': 0.001, 'taker': 0.0015 }, + 'LSK/BTC': { 'id': 'lsk_btc', 'symbol': 'LSK/BTC', 'base': 'LSK', 'quote': 'BTC', 'baseId': 'lsk', 'quoteId': 'btc', 'maker': 0.0015, 'taker': 0.0025 }, + 'BCH/USDT': { 'id': 'bch_usdt', 'symbol': 'BCH/USDT', 'base': 'BCH', 'quote': 'USDT', 'baseId': 'bch', 'quoteId': 'usdt', 'maker': 0.001, 'taker': 0.0015 }, + 'ETH/USDT': { 'id': 'eth_usdt', 'symbol': 'ETH/USDT', 'base': 'ETH', 'quote': 'USDT', 'baseId': 'eth', 'quoteId': 'usdt', 'maker': 0.001, 'taker': 0.0015 }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostInfo (params); + const balances = this.safeValue (response['result'], 'wallet'); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'avail'); + account['used'] = this.safeFloat (balance, 'lock'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response['result']); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'result'); + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response['result'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'op': side, + 'amount': amount, + }; + if (type === 'market') { + request['order_type'] = 1; + request['price'] = price; + } else { + request['order_type'] = 0; + } + const response = await this.privatePostTrade (this.extend (request, params)); + return { + 'info': response, + 'id': response['result']['id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.secret, 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('status' in response) { + // + // todo add a unified standard handleErrors with this.exceptions in describe() + // + // {"status":503,"message":"Maintenancing, try again later","result":null} + // + if (response['status'] === 200) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],111:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, PermissionDenied, BadRequest, CancelPending, OrderNotFound, InsufficientFunds, RateLimitExceeded, InvalidOrder, AccountSuspended, BadSymbol, OnMaintenance } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class novadax extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'novadax', + 'name': 'NovaDAX', + 'countries': [ 'BR' ], // Brazil + 'rateLimit': 50, + 'version': 'v1', + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelOrder': true, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'ONE_MIN', + '5m': 'FIVE_MIN', + '15m': 'FIFTEEN_MIN', + '30m': 'HALF_HOU', + '1h': 'ONE_HOU', + '1d': 'ONE_DAY', + '1w': 'ONE_WEE', + '1M': 'ONE_MON', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/92337550-2b085500-f0b3-11ea-98e7-5794fb07dd3b.jpg', + 'api': { + 'public': 'https://api.novadax.com', + 'private': 'https://api.novadax.com', + }, + 'www': 'https://www.novadax.com.br', + 'doc': [ + 'https://doc.novadax.com/pt-BR/', + ], + 'fees': 'https://www.novadax.com.br/fees-and-limits', + 'referral': 'https://www.novadax.com.br/?s=ccxt', + }, + 'api': { + 'public': { + 'get': [ + 'common/symbol', + 'common/symbols', + 'common/timestamp', + 'market/tickers', + 'market/ticker', + 'market/depth', + 'market/trades', + 'market/kline/history', + ], + }, + 'private': { + 'get': [ + 'orders/get', + 'orders/list', + 'orders/fill', + 'orders/fills', + 'account/getBalance', + 'account/subs', + 'account/subs/balance', + 'account/subs/transfer/record', + 'wallet/query/deposit-withdraw', + ], + 'post': [ + 'orders/create', + 'orders/cancel', + 'account/withdraw/coin', + 'account/subs/transfer', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.5 / 100, + 'maker': 0.3 / 100, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'exceptions': { + 'exact': { + 'A99999': ExchangeError, // 500 Failed Internal error + // 'A10000': ExchangeError, // 200 Success Successful request + 'A10001': BadRequest, // 400 Params error Parameter is invalid + 'A10002': ExchangeError, // 404 Api not found API used is irrelevant + 'A10003': AuthenticationError, // 403 Authentication failed Authentication is failed + 'A10004': RateLimitExceeded, // 429 Too many requests Too many requests are made + 'A10005': PermissionDenied, // 403 Kyc required Need to complete KYC firstly + 'A10006': AccountSuspended, // 403 Customer canceled Account is canceled + 'A10007': BadRequest, // 400 Account not exist Sub account does not exist + 'A10011': BadSymbol, // 400 Symbol not exist Trading symbol does not exist + 'A10012': BadSymbol, // 400 Symbol not trading Trading symbol is temporarily not available + 'A10013': OnMaintenance, // 503 Symbol maintain Trading symbol is in maintain + 'A30001': OrderNotFound, // 400 Order not found Queried order is not found + 'A30002': InvalidOrder, // 400 Order amount is too small Order amount is too small + 'A30003': InvalidOrder, // 400 Order amount is invalid Order amount is invalid + 'A30004': InvalidOrder, // 400 Order value is too small Order value is too small + 'A30005': InvalidOrder, // 400 Order value is invalid Order value is invalid + 'A30006': InvalidOrder, // 400 Order price is invalid Order price is invalid + 'A30007': InsufficientFunds, // 400 Insufficient balance The balance is insufficient + 'A30008': InvalidOrder, // 400 Order was closed The order has been executed + 'A30009': InvalidOrder, // 400 Order canceled The order has been cancelled + 'A30010': CancelPending, // 400 Order cancelling The order is being cancelled + 'A30011': InvalidOrder, // 400 Order price too high The order price is too high + 'A30012': InvalidOrder, // 400 Order price too low The order price is too low + }, + 'broad': { + }, + }, + 'options': { + 'fetchOHLCV': { + 'volume': 'amount', // 'amount' for base volume or 'vol' for quote volume + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetCommonTimestamp (params); + // + // { + // "code":"A10000", + // "data":1599090512080, + // "message":"Success" + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetCommonSymbols (params); + // + // { + // "code":"A10000", + // "data":[ + // { + // "amountPrecision":8, + // "baseCurrency":"BTC", + // "minOrderAmount":"0.001", + // "minOrderValue":"25", + // "pricePrecision":2, + // "quoteCurrency":"BRL", + // "status":"ONLINE", + // "symbol":"BTC_BRL", + // "valuePrecision":2 + // }, + // ], + // "message":"Success" + // } + // + const result = []; + const data = this.safeValue (response, 'data', []); + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const id = this.safeString (market, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amountPrecision'), + 'price': this.safeInteger (market, 'pricePrecision'), + 'cost': this.safeInteger (market, 'valuePrecision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minOrderAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minOrderValue'), + 'max': undefined, + }, + }; + const status = this.safeString (market, 'status'); + const active = (status === 'ONLINE'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "ask":"61946.1", + // "baseVolume24h":"164.41930186", + // "bid":"61815", + // "high24h":"64930.72", + // "lastPrice":"61928.41", + // "low24h":"61156.32", + // "open24h":"64512.46", + // "quoteVolume24h":"10308157.95", + // "symbol":"BTC_BRL", + // "timestamp":1599091115090 + // } + // + const timestamp = this.safeInteger (ticker, 'timestamp'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const open = this.safeFloat (ticker, 'open24h'); + const last = this.safeFloat (ticker, 'lastPrice'); + let percentage = undefined; + let change = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + percentage = change / open * 100; + average = this.sum (last, open) / 2; + } + const baseVolume = this.safeFloat (ticker, 'baseVolume24h'); + const quoteVolume = this.safeFloat (ticker, 'quoteVolume24h'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24h'), + 'low': this.safeFloat (ticker, 'low24h'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketTicker (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data":{ + // "ask":"61946.1", + // "baseVolume24h":"164.41930186", + // "bid":"61815", + // "high24h":"64930.72", + // "lastPrice":"61928.41", + // "low24h":"61156.32", + // "open24h":"64512.46", + // "quoteVolume24h":"10308157.95", + // "symbol":"BTC_BRL", + // "timestamp":1599091115090 + // }, + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketTickers (params); + // + // { + // "code":"A10000", + // "data":[ + // { + // "ask":"61879.36", + // "baseVolume24h":"164.40955092", + // "bid":"61815", + // "high24h":"64930.72", + // "lastPrice":"61820.04", + // "low24h":"61156.32", + // "open24h":"64624.19", + // "quoteVolume24h":"10307493.92", + // "symbol":"BTC_BRL", + // "timestamp":1599091291083 + // }, + // ], + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < data.length; i++) { + const ticker = this.parseTicker (data[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 20 + } + const response = await this.publicGetMarketDepth (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data":{ + // "asks":[ + // ["0.037159","0.3741"], + // ["0.037215","0.2706"], + // ["0.037222","1.8459"], + // ], + // "bids":[ + // ["0.037053","0.3857"], + // ["0.036969","0.8101"], + // ["0.036953","1.5226"], + // ], + // "timestamp":1599280414448 + // }, + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'timestamp'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks'); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "amount":"0.0632", + // "price":"0.037288", + // "side":"BUY", + // "timestamp":1599279694576 + // } + // + // private fetchOrderTrades + // + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // } + // + // private fetchMyTrades + // + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "feeAmount": "0.0000988", + // "feeCurrency": "BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // } + // + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'orderId'); + const timestamp = this.safeInteger (trade, 'timestamp'); + const side = this.safeStringLower (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = this.safeFloat (trade, 'volume'); + if ((cost === undefined) && (amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const takerOrMaker = this.safeStringLower (trade, 'role'); + const feeString = this.safeString (trade, 'fee'); + let fee = undefined; + if (feeString !== undefined) { + const parts = feeString.split (' '); + const feeCurrencyId = this.safeString (parts, 1); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': this.safeFloat (parts, 0), + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100 + } + const response = await this.publicGetMarketTrades (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data":[ + // {"amount":"0.0632","price":"0.037288","side":"BUY","timestamp":1599279694576}, + // {"amount":"0.0052","price":"0.03715","side":"SELL","timestamp":1599276606852}, + // {"amount":"0.0058","price":"0.037188","side":"SELL","timestamp":1599275187812}, + // ], + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'unit': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + const now = this.seconds (); + if (limit === undefined) { + limit = 3000; // max + } + if (since === undefined) { + request['from'] = now - limit * duration; + request['to'] = now; + } else { + const startFrom = parseInt (since / 1000); + request['from'] = startFrom; + request['to'] = this.sum (startFrom, limit * duration); + } + const response = await this.publicGetMarketKlineHistory (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "amount": 8.25709100, + // "closePrice": 62553.20, + // "count": 29, + // "highPrice": 62592.87, + // "lowPrice": 62553.20, + // "openPrice": 62554.23, + // "score": 1602501480, + // "symbol": "BTC_BRL", + // "vol": 516784.2504067500 + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "amount": 8.25709100, + // "closePrice": 62553.20, + // "count": 29, + // "highPrice": 62592.87, + // "lowPrice": 62553.20, + // "openPrice": 62554.23, + // "score": 1602501480, + // "symbol": "BTC_BRL", + // "vol": 516784.2504067500 + // } + // + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const volumeField = this.safeString (options, 'volume', 'amount'); // or vol + return [ + this.safeTimestamp (ohlcv, 'score'), + this.safeFloat (ohlcv, 'openPrice'), + this.safeFloat (ohlcv, 'highPrice'), + this.safeFloat (ohlcv, 'lowPrice'), + this.safeFloat (ohlcv, 'closePrice'), + this.safeFloat (ohlcv, volumeField), + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountGetBalance (params); + // + // { + // "code": "A10000", + // "data": [ + // { + // "available": "1.23", + // "balance": "0.23", + // "currency": "BTC", + // "hold": "1" + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + const result = { 'info': response }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'available'); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'hold'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const uppercaseSide = side.toUpperCase (); + const request = { + 'symbol': market['id'], + 'type': uppercaseType, // LIMIT, MARKET + 'side': uppercaseSide, // or SELL + // 'accountId': '...', // subaccount id, optional + // 'amount': this.amountToPrecision (symbol, amount), + // "price": "1234.5678", // required for LIMIT and STOP orders + }; + if (uppercaseType === 'LIMIT') { + request['price'] = this.priceToPrecision (symbol, price); + request['amount'] = this.amountToPrecision (symbol, amount); + } else if (uppercaseType === 'MARKET') { + if (uppercaseSide === 'SELL') { + request['amount'] = this.amountToPrecision (symbol, amount); + } else if (uppercaseSide === 'BUY') { + let value = this.safeFloat (params, 'value'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (value === undefined) { + value = amount * price; + } + } else if (value === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'value' extra parameter (the exchange-specific behaviour)"); + } + } else { + value = (value === undefined) ? amount : value; + } + const precision = market['precision']['price']; + request['value'] = this.decimalToPrecision (value, TRUNCATE, precision, this.precisionMode); + } + } + const response = await this.privatePostOrdersCreate (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": { + // "amount": "0.001", + // "averagePrice": null, + // "filledAmount": "0", + // "filledFee": "0", + // "filledValue": "0", + // "id": "633679992971251712", + // "price": "35000", + // "side": "BUY", + // "status": "PROCESSING", + // "symbol": "BTC_BRL", + // "timestamp": 1571122683535, + // "type": "LIMIT", + // "value": "35" + // }, + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrdersCancel (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": { + // "result": true + // }, + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersGet (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": { + // "id": "608695623247466496", + // "symbol": "BTC_BRL", + // "type": "MARKET", + // "side": "SELL", + // "price": null, + // "averagePrice": "0", + // "amount": "0.123", + // "filledAmount": "0", + // "value": null, + // "filledValue": "0", + // "filledFee": "0", + // "status": "REJECTED", + // "timestamp": 1565165945588 + // }, + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': market['id'], + // 'status': 'SUBMITTED,PROCESSING', // SUBMITTED, PROCESSING, PARTIAL_FILLED, CANCELING, FILLED, CANCELED, REJECTED + // 'fromId': '...', // order id to begin with + // 'toId': '...', // order id to end up with + // 'fromTimestamp': since, + // 'toTimestamp': this.milliseconds (), + // 'limit': limit, // default 100, max 100 + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 100 + } + if (since !== undefined) { + request['fromTimestamp'] = since; + } + const response = await this.privateGetOrdersList (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "608695678650028032", + // "symbol": "BTC_BRL", + // "type": "MARKET", + // "side": "SELL", + // "price": null, + // "averagePrice": "0", + // "amount": "0.123", + // "filledAmount": "0", + // "value": null, + // "filledValue": "0", + // "filledFee": "0", + // "status": "REJECTED", + // "timestamp": 1565165958796 + // }, + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'SUBMITTED,PROCESSING,PARTIAL_FILLED,CANCELING', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'FILLED,CANCELED,REJECTED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersFill (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const data = this.safeValue (response, 'data', []); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // }, + // ], + // "message": "Success" + // } + // + return this.parseTrades (data, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'SUBMITTED': 'open', + 'PROCESSING': 'open', + 'PARTIAL_FILLED': 'open', + 'CANCELING': 'open', + 'FILLED': 'closed', + 'CANCELED': 'canceled', + 'REJECTED': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder, fetchOrders, fetchOrder + // + // { + // "amount": "0.001", + // "averagePrice": null, + // "filledAmount": "0", + // "filledFee": "0", + // "filledValue": "0", + // "id": "633679992971251712", + // "price": "35000", + // "side": "BUY", + // "status": "PROCESSING", + // "symbol": "BTC_BRL", + // "timestamp": 1571122683535, + // "type": "LIMIT", + // "value": "35" + // } + // + // cancelOrder + // + // { + // "result": true + // } + // + const id = this.safeString (order, 'id'); + const amount = this.safeFloat (order, 'amount'); + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'filledValue'); + const type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeInteger (order, 'timestamp'); + const average = this.safeFloat (order, 'averagePrice'); + const filled = this.safeFloat (order, 'filledAmount'); + let remaining = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = Math.max (0, amount - filled); + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'filledFee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'code': currency['id'], + 'amount': this.currencyToPrecision (code, amount), + 'wallet': address, + }; + if (tag !== undefined) { + request['tag'] = tag; + } + const response = await this.privatePostAccountWithdrawCoin (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data": "DR123", + // "message":"Success" + // } + // + return this.parseTransaction (response, currency); + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccountSubs (params); + // + // { + // "code": "A10000", + // "data": [ + // { + // "subId": "CA648856083527372800", + // "state": "Normal", + // "subAccount": "003", + // "subIdentify": "003" + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const accountId = this.safeString (account, 'subId'); + const type = this.safeString (account, 'subAccount'); + result.push ({ + 'id': accountId, + 'type': type, + 'currency': undefined, + 'info': account, + }); + } + return result; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'coin_in', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'coin_out', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': currency['id'], + // 'type': 'coin_in', // 'coin_out' + // 'direct': 'asc', // 'desc' + // 'size': limit, // default 100 + // 'start': id, // offset id + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.privateGetWalletQueryDepositWithdraw (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "DR562339304588709888", + // "type": "COIN_IN", + // "currency": "XLM", + // "chain": "XLM", + // "address": "GCUTK7KHPJC3ZQJ3OMWWFHAK2OXIBRD4LNZQRCCOVE7A2XOPP2K5PU5Q", + // "addressTag": "1000009", + // "amount": 1.0, + // "state": "SUCCESS", + // "txHash": "39210645748822f8d4ce673c7559aa6622e6e9cdd7073bc0fcae14b1edfda5f4", + // "createdAt": 1554113737000, + // "updatedAt": 1601371273000 + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + parseTransactionStatus (status) { + // Pending the record is wait broadcast to chain + // x/M confirming the comfirming state of tx, the M is total confirmings needed + // SUCCESS the record is success full + // FAIL the record failed + const parts = status.split (' '); + status = this.safeString (parts, 1, status); + const statuses = { + 'Pending': 'pending', + 'confirming': 'pending', + 'SUCCESS': 'ok', + 'FAIL': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "code":"A10000", + // "data": "DR123", + // "message":"Success" + // } + // + // fetchTransactions + // + // { + // "id": "DR562339304588709888", + // "type": "COIN_IN", + // "currency": "XLM", + // "chain": "XLM", + // "address": "GCUTK7KHPJC3ZQJ3OMWWFHAK2OXIBRD4LNZQRCCOVE7A2XOPP2K5PU5Q", + // "addressTag": "1000009", + // "amount": 1.0, + // "state": "SUCCESS", + // "txHash": "39210645748822f8d4ce673c7559aa6622e6e9cdd7073bc0fcae14b1edfda5f4", + // "createdAt": 1554113737000, + // "updatedAt": 1601371273000 + // } + // + const id = this.safeString2 (transaction, 'id', 'data'); + let type = this.safeString (transaction, 'type'); + if (type === 'COIN_IN') { + type = 'deposit'; + } else if (type === 'COIN_OUT') { + type = 'withdraw'; + } + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'addressTag'); + const txid = this.safeString (transaction, 'txHash'); + const timestamp = this.safeInteger (transaction, 'createdAt'); + const updated = this.safeInteger (transaction, 'updatedAt'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'orderId': id, // Order ID, string + // 'symbol': market['id'], // The trading symbol, like BTC_BRL, string + // 'fromId': fromId, // Search fill id to begin with, string + // 'toId': toId, // Search fill id to end up with, string + // 'fromTimestamp': since, // Search order fill time to begin with, in milliseconds, string + // 'toTimestamp': this.milliseconds (), // Search order fill time to end up with, in milliseconds, string + // 'limit': limit, // The number of fills to return, default 100, max 100, string + // 'accountId': subaccountId, // Sub account ID, if not informed, the fills will be return under master account, string + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['fromTimestamp'] = since; + } + const response = await this.privateGetOrdersFills (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "feeAmount": "0.0000988", + // "feeCurrency": "BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // }, + // { + // "id": "608717065729085441", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0242", + // "price": "45514.76", + // "fee": "0.0000242 BTC", + // "feeAmount": "0.0000988", + // "feeCurrency": "BTC", + // "role": "MAKER", + // "timestamp": 1565171057882 + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = '/' + this.version + '/' + this.implodeParams (path, params); + let url = this.urls['api'][api] + request; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + headers = { + 'X-Nova-Access-Key': this.apiKey, + 'X-Nova-Timestamp': timestamp, + }; + let queryString = undefined; + if (method === 'POST') { + body = this.json (query); + queryString = this.hash (body, 'md5'); + headers['Content-Type'] = 'application/json'; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + queryString = this.urlencode (this.keysort (query)); + } + const auth = method + "\n" + request + "\n" + queryString + "\n" + timestamp; // eslint-disable-line quotes + headers['X-Nova-Signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"code":"A10003","data":[],"message":"Authentication failed, Invalid accessKey."} + // + const errorCode = this.safeString (response, 'code'); + if (errorCode !== 'A10000') { + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],112:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ArgumentsRequired, BadRequest, InvalidOrder, InsufficientFunds, OrderNotFound, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class oceanex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'oceanex', + 'name': 'OceanEx', + 'countries': [ 'US' ], + 'version': 'v1', + 'rateLimit': 3000, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/58385970-794e2d80-8001-11e9-889c-0567cd79b78e.jpg', + 'api': 'https://api.oceanex.pro', + 'www': 'https://www.oceanex.pro.com', + 'doc': 'https://api.oceanex.pro/doc/v1', + 'referral': 'https://oceanex.pro/signup?referral=VE24QX', + }, + 'has': { + 'fetchMarkets': true, + 'fetchCurrencies': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTrades': true, + 'fetchTradingLimits': false, + 'fetchTradingFees': false, + 'fetchAllTradingFees': true, + 'fetchFundingFees': false, + 'fetchTime': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchBalance': true, + 'createMarketOrder': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelOrders': true, + 'cancelAllOrders': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'tickers/{pair}', + 'tickers_multi', + 'order_book', + 'order_book/multi', + 'fees/trading', + 'trades', + 'timestamp', + ], + }, + 'private': { + 'get': [ + 'key', + 'members/me', + 'orders', + 'orders/filter', + ], + 'post': [ + 'orders', + 'orders/multi', + 'order/delete', + 'order/delete/multi', + 'orders/clear', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + }, + 'commonCurrencies': { + 'PLA': 'Plair', + }, + 'exceptions': { + 'codes': { + '-1': BadRequest, + '-2': BadRequest, + '1001': BadRequest, + '1004': ArgumentsRequired, + '1006': AuthenticationError, + '1008': AuthenticationError, + '1010': AuthenticationError, + '1011': PermissionDenied, + '2001': AuthenticationError, + '2002': InvalidOrder, + '2004': OrderNotFound, + '9003': PermissionDenied, + }, + 'exact': { + 'market does not have a valid value': BadRequest, + 'side does not have a valid value': BadRequest, + 'Account::AccountError: Cannot lock funds': InsufficientFunds, + 'The account does not exist': AuthenticationError, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const request = { 'show_details': true }; + const response = await this.publicGetMarkets (this.extend (request, params)); + const result = []; + const markets = this.safeValue (response, 'data'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeValue (market, 'id'); + const name = this.safeValue (market, 'name'); + let [ baseId, quoteId ] = name.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + baseId = baseId.toLowerCase (); + quoteId = quoteId.toLowerCase (); + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'info': market, + 'precision': { + 'amount': this.safeValue (market, 'amount_precision'), + 'price': this.safeValue (market, 'price_precision'), + 'base': this.safeValue (market, 'ask_precision'), + 'quote': this.safeValue (market, 'bid_precision'), + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeValue (market, 'minimum_trading_amount'), + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTickersPair (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": { + // "at":1559431729, + // "ticker": { + // "buy":"0.0065", + // "sell":"0.00677", + // "low":"0.00677", + // "high":"0.00677", + // "last":"0.00677", + // "vol":"2000.0" + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const marketIds = this.marketIds (symbols); + const request = { 'markets': marketIds }; + const response = await this.publicGetTickersMulti (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": { + // "at":1559431729, + // "ticker": { + // "buy":"0.0065", + // "sell":"0.00677", + // "low":"0.00677", + // "high":"0.00677", + // "last":"0.00677", + // "vol":"2000.0" + // } + // } + // } + // + const data = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < data.length; i++) { + const ticker = data[i]; + const marketId = this.safeString (ticker, 'market'); + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTicker (data, market = undefined) { + // + // { + // "at":1559431729, + // "ticker": { + // "buy":"0.0065", + // "sell":"0.00677", + // "low":"0.00677", + // "high":"0.00677", + // "last":"0.00677", + // "vol":"2000.0" + // } + // } + // + const ticker = this.safeValue (data, 'ticker', {}); + const timestamp = this.safeTimestamp (data, 'at'); + return { + 'symbol': market['symbol'], + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': this.safeFloat (ticker, 'last'), + 'last': this.safeFloat (ticker, 'last'), + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": { + // "timestamp":1559433057, + // "asks": [ + // ["100.0","20.0"], + // ["4.74","2000.0"], + // ["1.74","4000.0"], + // ], + // "bids":[ + // ["0.0065","5482873.4"], + // ["0.00649","4781956.2"], + // ["0.00648","2876006.8"], + // ], + // } + // } + // + const orderbook = this.safeValue (response, 'data', {}); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const marketIds = this.marketIds (symbols); + const request = { + 'markets': marketIds, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBookMulti (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": [ + // { + // "timestamp":1559433057, + // "market": "bagvet", + // "asks": [ + // ["100.0","20.0"], + // ["4.74","2000.0"], + // ["1.74","4000.0"], + // ], + // "bids":[ + // ["0.0065","5482873.4"], + // ["0.00649","4781956.2"], + // ["0.00648","2876006.8"], + // ], + // }, + // ..., + // ], + // } + // + const data = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < data.length; i++) { + const orderbook = data[i]; + const marketId = this.safeString (orderbook, 'market'); + const symbol = this.safeSymbol (marketId); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + result[symbol] = this.parseOrderBook (orderbook, timestamp); + } + return result; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + parseTrade (trade, market = undefined) { + let side = this.safeValue (trade, 'side'); + if (side === 'bid') { + side = 'buy'; + } else if (side === 'ask') { + side = 'sell'; + } + const marketId = this.safeValue (trade, 'market'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = this.safeTimestamp (trade, 'created_on'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': this.safeString (trade, 'id'), + 'order': undefined, + 'type': 'limit', + 'takerOrMaker': undefined, + 'side': side, + 'price': this.safeFloat (trade, 'price'), + 'amount': this.safeFloat (trade, 'volume'), + 'cost': undefined, + 'fee': undefined, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTimestamp (params); + // + // {"code":0,"message":"Operation successful","data":1559433420} + // + return this.safeTimestamp (response, 'data'); + } + + async fetchAllTradingFees (params = {}) { + const response = await this.publicGetFeesTrading (params); + const data = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < data.length; i++) { + const group = data[i]; + const maker = this.safeValue (group, 'ask_fee', {}); + const taker = this.safeValue (group, 'bid_fee', {}); + const marketId = this.safeString (group, 'market'); + const symbol = this.safeSymbol (marketId); + result[symbol] = { + 'info': group, + 'symbol': symbol, + 'maker': this.safeFloat (maker, 'value'), + 'taker': this.safeFloat (taker, 'value'), + }; + } + return result; + } + + async fetchKey (params = {}) { + const response = await this.privateGetKey (params); + return this.safeValue (response, 'data'); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetMembersMe (params); + const data = this.safeValue (response, 'data'); + const balances = this.safeValue (data, 'accounts'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeValue (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': side, + 'ord_type': type, + 'volume': this.amountToPrecision (symbol, amount), + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + let ids = id; + if (!Array.isArray (id)) { + ids = [ id ]; + } + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { 'ids': ids }; + const response = await this.privateGetOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const dataLength = data.length; + if (data === undefined) { + throw new OrderNotFound (this.id + ' could not found matching order'); + } + if (Array.isArray (id)) { + return this.parseOrders (data, market); + } + if (dataLength === 0) { + throw new OrderNotFound (this.id + ' could not found matching order'); + } + return this.parseOrder (data[0], market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'states': [ 'wait' ], + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'states': [ 'done', 'cancel' ], + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const states = this.safeValue (params, 'states', [ 'wait', 'done', 'cancel' ]); + const query = this.omit (params, 'states'); + const request = { + 'market': market['id'], + 'states': states, + 'need_price': 'True', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrdersFilter (this.extend (request, query)); + const data = this.safeValue (response, 'data', []); + let result = []; + for (let i = 0; i < data.length; i++) { + const orders = this.safeValue (data[i], 'orders', []); + const status = this.parseOrderStatus (this.safeValue (data[i], 'state')); + const parsedOrders = this.parseOrders (orders, market, since, limit, { 'status': status }); + result = this.arrayConcat (result, parsedOrders); + } + return result; + } + + parseOrder (order, market = undefined) { + // + // { + // "created_at": "2019-01-18T00:38:18Z", + // "trades_count": 0, + // "remaining_volume": "0.2", + // "price": "1001.0", + // "created_on": "1547771898", + // "side": "buy", + // "volume": "0.2", + // "state": "wait", + // "ord_type": "limit", + // "avg_price": "0.0", + // "executed_volume": "0.0", + // "id": 473797, + // "market": "veteth" + // } + // + const status = this.parseOrderStatus (this.safeValue (order, 'state')); + const marketId = this.safeString2 (order, 'market', 'market_id'); + const symbol = this.safeSymbol (marketId); + let timestamp = this.safeTimestamp (order, 'created_on'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'created_at')); + } + return { + 'info': order, + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': this.safeValue (order, 'ord_type'), + 'timeInForce': undefined, + 'side': this.safeValue (order, 'side'), + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'average': this.safeFloat (order, 'avg_price'), + 'amount': this.safeFloat (order, 'volume'), + 'remaining': this.safeFloat (order, 'remaining_volume'), + 'filled': this.safeFloat (order, 'executed_volume'), + 'status': status, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'wait': 'open', + 'done': 'closed', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async createOrders (symbol, orders, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'orders': orders, + }; + // orders: [{"side":"buy", "volume":.2, "price":1001}, {"side":"sell", "volume":0.2, "price":1002}] + const response = await this.privatePostOrdersMulti (this.extend (request, params)); + const data = response['data']; + return this.parseOrders (data); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrderDelete (this.extend ({ 'id': id }, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrderDeleteMulti (this.extend ({ 'ids': ids }, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrdersClear (params); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (path === 'tickers_multi' || path === 'order_book/multi') { + let request = '?'; + const markets = this.safeValue (params, 'markets'); + for (let i = 0; i < markets.length; i++) { + request += 'markets[]=' + markets[i] + '&'; + } + const limit = this.safeValue (params, 'limit'); + if (limit !== undefined) { + request += 'limit=' + limit; + } + url += request; + } else if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const request = { + 'uid': this.apiKey, + 'data': query, + }; + // to set the private key: + // const fs = require ('fs') + // exchange.secret = fs.readFileSync ('oceanex.pem', 'utf8') + const jwt_token = this.jwt (request, this.encode (this.secret), 'RS256'); + url += '?user_jwt=' + jwt_token; + } + headers = { 'Content-Type': 'application/json' }; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + // + // {"code":1011,"message":"This IP '5.228.233.138' is not allowed","data":{}} + // + if (response === undefined) { + return; + } + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + if ((errorCode !== undefined) && (errorCode !== '0')) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['codes'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (response); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],113:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const okex = require ('./okex.js'); + +// --------------------------------------------------------------------------- + +module.exports = class okcoin extends okex { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'okcoin', + 'name': 'OKCoin', + 'countries': [ 'CN', 'US' ], + 'hostname': 'okcoin.com', + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295551-102fbf00-c50e-11ea-90a9-462eebba5829.jpg', + 'www': 'https://www.okcoin.com', + 'doc': 'https://www.okcoin.com/docs/en/', + 'fees': 'https://www.okcoin.com/coin-fees', + 'referral': 'https://www.okcoin.com/account/register?flag=activity&channelId=600001513', + }, + 'fees': { + 'trading': { + 'taker': 0.002, + 'maker': 0.001, + }, + }, + 'options': { + 'fetchMarkets': [ 'spot' ], + }, + }); + } +}; + +},{"./okex.js":114}],114:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, OnMaintenance, ArgumentsRequired, BadRequest, AccountSuspended, InvalidAddress, PermissionDenied, DDoSProtection, InsufficientFunds, InvalidNonce, CancelPending, InvalidOrder, OrderNotFound, AuthenticationError, RequestTimeout, NotSupported, BadSymbol } = require ('./base/errors'); +const { TICK_SIZE, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class okex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'okex', + 'name': 'OKEX', + 'countries': [ 'CN', 'US' ], + 'version': 'v3', + 'rateLimit': 1000, // up to 3000 requests per 5 minutes ≈ 600 requests per minute ≈ 10 requests per second ≈ 100 ms + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, // see below + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchTime': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'futures': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '604800', + '1M': '2678400', + '3M': '8035200', + '6M': '16070400', + '1y': '31536000', + }, + 'hostname': 'okex.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg', + 'api': { + 'rest': 'https://www.{hostname}', + }, + 'www': 'https://www.okex.com', + 'doc': 'https://www.okex.com/docs/en/', + 'fees': 'https://www.okex.com/pages/products/fees.html', + 'referral': 'https://www.okex.com/join/1888677', + 'test': { + 'rest': 'https://testnet.okex.com', + }, + }, + 'api': { + 'general': { + 'get': [ + 'time', + ], + }, + 'account': { + 'get': [ + 'wallet', + 'sub-account', + 'asset-valuation', + 'wallet/{currency}', + 'withdrawal/history', + 'withdrawal/history/{currency}', + 'ledger', + 'deposit/address', + 'deposit/history', + 'deposit/history/{currency}', + 'currencies', + 'withdrawal/fee', + ], + 'post': [ + 'transfer', + 'withdrawal', + ], + }, + 'spot': { + 'get': [ + 'accounts', + 'accounts/{currency}', + 'accounts/{currency}/ledger', + 'orders', + 'orders_pending', + 'orders/{order_id}', + 'orders/{client_oid}', + 'trade_fee', + 'fills', + 'algo', + // public + 'instruments', + 'instruments/{instrument_id}/book', + 'instruments/ticker', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/candles', + 'instruments/{instrument_id}/history/candles', + ], + 'post': [ + 'order_algo', + 'orders', + 'batch_orders', + 'cancel_orders/{order_id}', + 'cancel_orders/{client_oid}', + 'cancel_batch_algos', + 'cancel_batch_orders', + ], + }, + 'margin': { + 'get': [ + 'accounts', + 'accounts/{instrument_id}', + 'accounts/{instrument_id}/ledger', + 'accounts/availability', + 'accounts/{instrument_id}/availability', + 'accounts/borrowed', + 'accounts/{instrument_id}/borrowed', + 'orders', + 'accounts/{instrument_id}/leverage', + 'orders/{order_id}', + 'orders/{client_oid}', + 'orders_pending', + 'fills', + // public + 'instruments/{instrument_id}/mark_price', + ], + 'post': [ + 'accounts/borrow', + 'accounts/repayment', + 'orders', + 'batch_orders', + 'cancel_orders', + 'cancel_orders/{order_id}', + 'cancel_orders/{client_oid}', + 'cancel_batch_orders', + 'accounts/{instrument_id}/leverage', + ], + }, + 'futures': { + 'get': [ + 'position', + '{instrument_id}/position', + 'accounts', + 'accounts/{underlying}', + 'accounts/{underlying}/leverage', + 'accounts/{underlying}/ledger', + 'order_algo/{instrument_id}', + 'orders/{instrument_id}', + 'orders/{instrument_id}/{order_id}', + 'orders/{instrument_id}/{client_oid}', + 'fills', + 'trade_fee', + 'accounts/{instrument_id}/holds', + 'order_algo/{instrument_id}', + // public + 'instruments', + 'instruments/{instrument_id}/book', + 'instruments/ticker', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/candles', + 'instruments/{instrument_id}/history/candles', + 'instruments/{instrument_id}/index', + 'rate', + 'instruments/{instrument_id}/estimated_price', + 'instruments/{instrument_id}/open_interest', + 'instruments/{instrument_id}/price_limit', + 'instruments/{instrument_id}/mark_price', + 'instruments/{instrument_id}/liquidation', + ], + 'post': [ + 'accounts/{underlying}/leverage', + 'order', + 'orders', + 'cancel_order/{instrument_id}/{order_id}', + 'cancel_order/{instrument_id}/{client_oid}', + 'cancel_batch_orders/{instrument_id}', + 'accounts/margin_mode', + 'close_position', + 'cancel_all', + 'order_algo', + 'cancel_algos', + ], + }, + 'swap': { + 'get': [ + 'position', + '{instrument_id}/position', + 'accounts', + '{instrument_id}/accounts', + 'accounts/{instrument_id}/settings', + 'accounts/{instrument_id}/ledger', + 'orders/{instrument_id}', + 'orders/{instrument_id}/{order_id}', + 'orders/{instrument_id}/{client_oid}', + 'fills', + 'accounts/{instrument_id}/holds', + 'trade_fee', + 'order_algo/{instrument_id}', + // public + 'instruments', + 'instruments/{instrument_id}/depth', + 'instruments/ticker', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/candles', + 'instruments/{instrument_id}/history/candles', + 'instruments/{instrument_id}/index', + 'rate', + 'instruments/{instrument_id}/open_interest', + 'instruments/{instrument_id}/price_limit', + 'instruments/{instrument_id}/liquidation', + 'instruments/{instrument_id}/funding_time', + 'instruments/{instrument_id}/mark_price', + 'instruments/{instrument_id}/historical_funding_rate', + ], + 'post': [ + 'accounts/{instrument_id}/leverage', + 'order', + 'orders', + 'cancel_order/{instrument_id}/{order_id}', + 'cancel_order/{instrument_id}/{client_oid}', + 'cancel_batch_orders/{instrument_id}', + 'order_algo', + 'cancel_algos', + ], + }, + 'option': { + 'get': [ + 'accounts', + 'position', + '{underlying}/position', + 'accounts/{underlying}', + 'orders/{underlying}', + 'fills/{underlying}', + 'accounts/{underlying}/ledger', + 'trade_fee', + 'orders/{underlying}/{order_id}', + 'orders/{underlying}/{client_oid}', + // public + 'underlying', + 'instruments/{underlying}', + 'instruments/{underlying}/summary', + 'instruments/{underlying}/summary/{instrument_id}', + 'instruments/{instrument_id}/book', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/candles', + ], + 'post': [ + 'order', + 'orders', + 'cancel_order/{underlying}/{order_id}', + 'cancel_order/{underlying}/{client_oid}', + 'cancel_batch_orders/{underlying}', + 'amend_order/{underlying}', + 'amend_batch_orders/{underlying}', + ], + }, + 'index': { + 'get': [ + '{instrument_id}/constituents', + ], + }, + }, + 'fees': { + 'trading': { + 'taker': 0.0015, + 'maker': 0.0010, + }, + 'spot': { + 'taker': 0.0015, + 'maker': 0.0010, + }, + 'futures': { + 'taker': 0.0005, + 'maker': 0.0002, + }, + 'swap': { + 'taker': 0.00075, + 'maker': 0.00020, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'exceptions': { + // http error codes + // 400 Bad Request — Invalid request format + // 401 Unauthorized — Invalid API Key + // 403 Forbidden — You do not have access to the requested resource + // 404 Not Found + // 429 Client Error: Too Many Requests for url + // 500 Internal Server Error — We had a problem with our server + 'exact': { + '1': ExchangeError, // { "code": 1, "message": "System error" } + // undocumented + 'failure to get a peer from the ring-balancer': ExchangeNotAvailable, // { "message": "failure to get a peer from the ring-balancer" } + 'Server is busy, please try again.': ExchangeNotAvailable, // { "message": "Server is busy, please try again." } + 'An unexpected error occurred': ExchangeError, // { "message": "An unexpected error occurred" } + 'System error': ExchangeError, // {"error_message":"System error","message":"System error"} + '4010': PermissionDenied, // { "code": 4010, "message": "For the security of your funds, withdrawals are not permitted within 24 hours after changing fund password / mobile number / Google Authenticator settings " } + // common + // '0': ExchangeError, // 200 successful,when the order placement / cancellation / operation is successful + '4001': ExchangeError, // no data received in 30s + '4002': ExchangeError, // Buffer full. cannot write data + // -------------------------------------------------------- + '30001': AuthenticationError, // { "code": 30001, "message": 'request header "OK_ACCESS_KEY" cannot be blank'} + '30002': AuthenticationError, // { "code": 30002, "message": 'request header "OK_ACCESS_SIGN" cannot be blank'} + '30003': AuthenticationError, // { "code": 30003, "message": 'request header "OK_ACCESS_TIMESTAMP" cannot be blank'} + '30004': AuthenticationError, // { "code": 30004, "message": 'request header "OK_ACCESS_PASSPHRASE" cannot be blank'} + '30005': InvalidNonce, // { "code": 30005, "message": "invalid OK_ACCESS_TIMESTAMP" } + '30006': AuthenticationError, // { "code": 30006, "message": "invalid OK_ACCESS_KEY" } + '30007': BadRequest, // { "code": 30007, "message": 'invalid Content_Type, please use "application/json" format'} + '30008': RequestTimeout, // { "code": 30008, "message": "timestamp request expired" } + '30009': ExchangeError, // { "code": 30009, "message": "system error" } + '30010': AuthenticationError, // { "code": 30010, "message": "API validation failed" } + '30011': PermissionDenied, // { "code": 30011, "message": "invalid IP" } + '30012': AuthenticationError, // { "code": 30012, "message": "invalid authorization" } + '30013': AuthenticationError, // { "code": 30013, "message": "invalid sign" } + '30014': DDoSProtection, // { "code": 30014, "message": "request too frequent" } + '30015': AuthenticationError, // { "code": 30015, "message": 'request header "OK_ACCESS_PASSPHRASE" incorrect'} + '30016': ExchangeError, // { "code": 30015, "message": "you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey" } + '30017': ExchangeError, // { "code": 30017, "message": "apikey's broker id does not match" } + '30018': ExchangeError, // { "code": 30018, "message": "apikey's domain does not match" } + '30019': ExchangeNotAvailable, // { "code": 30019, "message": "Api is offline or unavailable" } + '30020': BadRequest, // { "code": 30020, "message": "body cannot be blank" } + '30021': BadRequest, // { "code": 30021, "message": "Json data format error" }, { "code": 30021, "message": "json data format error" } + '30022': PermissionDenied, // { "code": 30022, "message": "Api has been frozen" } + '30023': BadRequest, // { "code": 30023, "message": "{0} parameter cannot be blank" } + '30024': BadSymbol, // {"code":30024,"message":"\"instrument_id\" is an invalid parameter"} + '30025': BadRequest, // { "code": 30025, "message": "{0} parameter category error" } + '30026': DDoSProtection, // { "code": 30026, "message": "requested too frequent" } + '30027': AuthenticationError, // { "code": 30027, "message": "login failure" } + '30028': PermissionDenied, // { "code": 30028, "message": "unauthorized execution" } + '30029': AccountSuspended, // { "code": 30029, "message": "account suspended" } + '30030': ExchangeNotAvailable, // { "code": 30030, "message": "endpoint request failed. Please try again" } + '30031': BadRequest, // { "code": 30031, "message": "token does not exist" } + '30032': BadSymbol, // { "code": 30032, "message": "pair does not exist" } + '30033': BadRequest, // { "code": 30033, "message": "exchange domain does not exist" } + '30034': ExchangeError, // { "code": 30034, "message": "exchange ID does not exist" } + '30035': ExchangeError, // { "code": 30035, "message": "trading is not supported in this website" } + '30036': ExchangeError, // { "code": 30036, "message": "no relevant data" } + '30037': ExchangeNotAvailable, // { "code": 30037, "message": "endpoint is offline or unavailable" } + // '30038': AuthenticationError, // { "code": 30038, "message": "user does not exist" } + '30038': OnMaintenance, // {"client_oid":"","code":"30038","error_code":"30038","error_message":"Matching engine is being upgraded. Please try in about 1 minute.","message":"Matching engine is being upgraded. Please try in about 1 minute.","order_id":"-1","result":false} + '30044': RequestTimeout, // { "code":30044, "message":"Endpoint request timeout" } + // futures + '32001': AccountSuspended, // { "code": 32001, "message": "futures account suspended" } + '32002': PermissionDenied, // { "code": 32002, "message": "futures account does not exist" } + '32003': CancelPending, // { "code": 32003, "message": "canceling, please wait" } + '32004': ExchangeError, // { "code": 32004, "message": "you have no unfilled orders" } + '32005': InvalidOrder, // { "code": 32005, "message": "max order quantity" } + '32006': InvalidOrder, // { "code": 32006, "message": "the order price or trigger price exceeds USD 1 million" } + '32007': InvalidOrder, // { "code": 32007, "message": "leverage level must be the same for orders on the same side of the contract" } + '32008': InvalidOrder, // { "code": 32008, "message": "Max. positions to open (cross margin)" } + '32009': InvalidOrder, // { "code": 32009, "message": "Max. positions to open (fixed margin)" } + '32010': ExchangeError, // { "code": 32010, "message": "leverage cannot be changed with open positions" } + '32011': ExchangeError, // { "code": 32011, "message": "futures status error" } + '32012': ExchangeError, // { "code": 32012, "message": "futures order update error" } + '32013': ExchangeError, // { "code": 32013, "message": "token type is blank" } + '32014': ExchangeError, // { "code": 32014, "message": "your number of contracts closing is larger than the number of contracts available" } + '32015': ExchangeError, // { "code": 32015, "message": "margin ratio is lower than 100% before opening positions" } + '32016': ExchangeError, // { "code": 32016, "message": "margin ratio is lower than 100% after opening position" } + '32017': ExchangeError, // { "code": 32017, "message": "no BBO" } + '32018': ExchangeError, // { "code": 32018, "message": "the order quantity is less than 1, please try again" } + '32019': ExchangeError, // { "code": 32019, "message": "the order price deviates from the price of the previous minute by more than 3%" } + '32020': ExchangeError, // { "code": 32020, "message": "the price is not in the range of the price limit" } + '32021': ExchangeError, // { "code": 32021, "message": "leverage error" } + '32022': ExchangeError, // { "code": 32022, "message": "this function is not supported in your country or region according to the regulations" } + '32023': ExchangeError, // { "code": 32023, "message": "this account has outstanding loan" } + '32024': ExchangeError, // { "code": 32024, "message": "order cannot be placed during delivery" } + '32025': ExchangeError, // { "code": 32025, "message": "order cannot be placed during settlement" } + '32026': ExchangeError, // { "code": 32026, "message": "your account is restricted from opening positions" } + '32027': ExchangeError, // { "code": 32027, "message": "cancelled over 20 orders" } + '32028': ExchangeError, // { "code": 32028, "message": "account is suspended and liquidated" } + '32029': ExchangeError, // { "code": 32029, "message": "order info does not exist" } + '32030': InvalidOrder, // The order cannot be cancelled + '32031': ArgumentsRequired, // client_oid or order_id is required. + '32038': AuthenticationError, // User does not exist + '32040': ExchangeError, // User have open contract orders or position + '32044': ExchangeError, // { "code": 32044, "message": "The margin ratio after submitting this order is lower than the minimum requirement ({0}) for your tier." } + '32045': ExchangeError, // String of commission over 1 million + '32046': ExchangeError, // Each user can hold up to 10 trade plans at the same time + '32047': ExchangeError, // system error + '32048': InvalidOrder, // Order strategy track range error + '32049': ExchangeError, // Each user can hold up to 10 track plans at the same time + '32050': InvalidOrder, // Order strategy rang error + '32051': InvalidOrder, // Order strategy ice depth error + '32052': ExchangeError, // String of commission over 100 thousand + '32053': ExchangeError, // Each user can hold up to 6 ice plans at the same time + '32057': ExchangeError, // The order price is zero. Market-close-all function cannot be executed + '32054': ExchangeError, // Trade not allow + '32055': InvalidOrder, // cancel order error + '32056': ExchangeError, // iceberg per order average should between {0}-{1} contracts + '32058': ExchangeError, // Each user can hold up to 6 initiative plans at the same time + '32059': InvalidOrder, // Total amount should exceed per order amount + '32060': InvalidOrder, // Order strategy type error + '32061': InvalidOrder, // Order strategy initiative limit error + '32062': InvalidOrder, // Order strategy initiative range error + '32063': InvalidOrder, // Order strategy initiative rate error + '32064': ExchangeError, // Time Stringerval of orders should set between 5-120s + '32065': ExchangeError, // Close amount exceeds the limit of Market-close-all (999 for BTC, and 9999 for the rest tokens) + '32066': ExchangeError, // You have open orders. Please cancel all open orders before changing your leverage level. + '32067': ExchangeError, // Account equity < required margin in this setting. Please adjust your leverage level again. + '32068': ExchangeError, // The margin for this position will fall short of the required margin in this setting. Please adjust your leverage level or increase your margin to proceed. + '32069': ExchangeError, // Target leverage level too low. Your account balance is insufficient to cover the margin required. Please adjust the leverage level again. + '32070': ExchangeError, // Please check open position or unfilled order + '32071': ExchangeError, // Your current liquidation mode does not support this action. + '32072': ExchangeError, // The highest available margin for your order’s tier is {0}. Please edit your margin and place a new order. + '32073': ExchangeError, // The action does not apply to the token + '32074': ExchangeError, // The number of contracts of your position, open orders, and the current order has exceeded the maximum order limit of this asset. + '32075': ExchangeError, // Account risk rate breach + '32076': ExchangeError, // Liquidation of the holding position(s) at market price will require cancellation of all pending close orders of the contracts. + '32077': ExchangeError, // Your margin for this asset in futures account is insufficient and the position has been taken over for liquidation. (You will not be able to place orders, close positions, transfer funds, or add margin during this period of time. Your account will be restored after the liquidation is complete.) + '32078': ExchangeError, // Please cancel all open orders before switching the liquidation mode(Please cancel all open orders before switching the liquidation mode) + '32079': ExchangeError, // Your open positions are at high risk.(Please add margin or reduce positions before switching the mode) + '32080': ExchangeError, // Funds cannot be transferred out within 30 minutes after futures settlement + '32083': ExchangeError, // The number of contracts should be a positive multiple of %%. Please place your order again + // token and margin trading + '33001': PermissionDenied, // { "code": 33001, "message": "margin account for this pair is not enabled yet" } + '33002': AccountSuspended, // { "code": 33002, "message": "margin account for this pair is suspended" } + '33003': InsufficientFunds, // { "code": 33003, "message": "no loan balance" } + '33004': ExchangeError, // { "code": 33004, "message": "loan amount cannot be smaller than the minimum limit" } + '33005': ExchangeError, // { "code": 33005, "message": "repayment amount must exceed 0" } + '33006': ExchangeError, // { "code": 33006, "message": "loan order not found" } + '33007': ExchangeError, // { "code": 33007, "message": "status not found" } + '33008': InsufficientFunds, // { "code": 33008, "message": "loan amount cannot exceed the maximum limit" } + '33009': ExchangeError, // { "code": 33009, "message": "user ID is blank" } + '33010': ExchangeError, // { "code": 33010, "message": "you cannot cancel an order during session 2 of call auction" } + '33011': ExchangeError, // { "code": 33011, "message": "no new market data" } + '33012': ExchangeError, // { "code": 33012, "message": "order cancellation failed" } + '33013': InvalidOrder, // { "code": 33013, "message": "order placement failed" } + '33014': OrderNotFound, // { "code": 33014, "message": "order does not exist" } + '33015': InvalidOrder, // { "code": 33015, "message": "exceeded maximum limit" } + '33016': ExchangeError, // { "code": 33016, "message": "margin trading is not open for this token" } + '33017': InsufficientFunds, // { "code": 33017, "message": "insufficient balance" } + '33018': ExchangeError, // { "code": 33018, "message": "this parameter must be smaller than 1" } + '33020': ExchangeError, // { "code": 33020, "message": "request not supported" } + '33021': BadRequest, // { "code": 33021, "message": "token and the pair do not match" } + '33022': InvalidOrder, // { "code": 33022, "message": "pair and the order do not match" } + '33023': ExchangeError, // { "code": 33023, "message": "you can only place market orders during call auction" } + '33024': InvalidOrder, // { "code": 33024, "message": "trading amount too small" } + '33025': InvalidOrder, // { "code": 33025, "message": "base token amount is blank" } + '33026': ExchangeError, // { "code": 33026, "message": "transaction completed" } + '33027': InvalidOrder, // { "code": 33027, "message": "cancelled order or order cancelling" } + '33028': InvalidOrder, // { "code": 33028, "message": "the decimal places of the trading price exceeded the limit" } + '33029': InvalidOrder, // { "code": 33029, "message": "the decimal places of the trading size exceeded the limit" } + '33034': ExchangeError, // { "code": 33034, "message": "You can only place limit order after Call Auction has started" } + '33035': ExchangeError, // This type of order cannot be canceled(This type of order cannot be canceled) + '33036': ExchangeError, // Exceeding the limit of entrust order + '33037': ExchangeError, // The buy order price should be lower than 130% of the trigger price + '33038': ExchangeError, // The sell order price should be higher than 70% of the trigger price + '33039': ExchangeError, // The limit of callback rate is 0 < x <= 5% + '33040': ExchangeError, // The trigger price of a buy order should be lower than the latest transaction price + '33041': ExchangeError, // The trigger price of a sell order should be higher than the latest transaction price + '33042': ExchangeError, // The limit of price variance is 0 < x <= 1% + '33043': ExchangeError, // The total amount must be larger than 0 + '33044': ExchangeError, // The average amount should be 1/1000 * total amount <= x <= total amount + '33045': ExchangeError, // The price should not be 0, including trigger price, order price, and price limit + '33046': ExchangeError, // Price variance should be 0 < x <= 1% + '33047': ExchangeError, // Sweep ratio should be 0 < x <= 100% + '33048': ExchangeError, // Per order limit: Total amount/1000 < x <= Total amount + '33049': ExchangeError, // Total amount should be X > 0 + '33050': ExchangeError, // Time interval should be 5 <= x <= 120s + '33051': ExchangeError, // cancel order number not higher limit: plan and track entrust no more than 10, ice and time entrust no more than 6 + '33059': BadRequest, // { "code": 33059, "message": "client_oid or order_id is required" } + '33060': BadRequest, // { "code": 33060, "message": "Only fill in either parameter client_oid or order_id" } + '33061': ExchangeError, // Value of a single market price order cannot exceed 100,000 USD + '33062': ExchangeError, // The leverage ratio is too high. The borrowed position has exceeded the maximum position of this leverage ratio. Please readjust the leverage ratio + '33063': ExchangeError, // Leverage multiple is too low, there is insufficient margin in the account, please readjust the leverage ratio + '33064': ExchangeError, // The setting of the leverage ratio cannot be less than 2, please readjust the leverage ratio + '33065': ExchangeError, // Leverage ratio exceeds maximum leverage ratio, please readjust leverage ratio + '33085': InvalidOrder, // The value of the position and buying order has reached the position limit, and no further buying is allowed. + // account + '21009': ExchangeError, // Funds cannot be transferred out within 30 minutes after swap settlement(Funds cannot be transferred out within 30 minutes after swap settlement) + '34001': PermissionDenied, // { "code": 34001, "message": "withdrawal suspended" } + '34002': InvalidAddress, // { "code": 34002, "message": "please add a withdrawal address" } + '34003': ExchangeError, // { "code": 34003, "message": "sorry, this token cannot be withdrawn to xx at the moment" } + '34004': ExchangeError, // { "code": 34004, "message": "withdrawal fee is smaller than minimum limit" } + '34005': ExchangeError, // { "code": 34005, "message": "withdrawal fee exceeds the maximum limit" } + '34006': ExchangeError, // { "code": 34006, "message": "withdrawal amount is lower than the minimum limit" } + '34007': ExchangeError, // { "code": 34007, "message": "withdrawal amount exceeds the maximum limit" } + '34008': InsufficientFunds, // { "code": 34008, "message": "insufficient balance" } + '34009': ExchangeError, // { "code": 34009, "message": "your withdrawal amount exceeds the daily limit" } + '34010': ExchangeError, // { "code": 34010, "message": "transfer amount must be larger than 0" } + '34011': ExchangeError, // { "code": 34011, "message": "conditions not met" } + '34012': ExchangeError, // { "code": 34012, "message": "the minimum withdrawal amount for NEO is 1, and the amount must be an integer" } + '34013': ExchangeError, // { "code": 34013, "message": "please transfer" } + '34014': ExchangeError, // { "code": 34014, "message": "transfer limited" } + '34015': ExchangeError, // { "code": 34015, "message": "subaccount does not exist" } + '34016': PermissionDenied, // { "code": 34016, "message": "transfer suspended" } + '34017': AccountSuspended, // { "code": 34017, "message": "account suspended" } + '34018': AuthenticationError, // { "code": 34018, "message": "incorrect trades password" } + '34019': PermissionDenied, // { "code": 34019, "message": "please bind your email before withdrawal" } + '34020': PermissionDenied, // { "code": 34020, "message": "please bind your funds password before withdrawal" } + '34021': InvalidAddress, // { "code": 34021, "message": "Not verified address" } + '34022': ExchangeError, // { "code": 34022, "message": "Withdrawals are not available for sub accounts" } + '34023': PermissionDenied, // { "code": 34023, "message": "Please enable futures trading before transferring your funds" } + '34026': ExchangeError, // transfer too frequently(transfer too frequently) + '34036': ExchangeError, // Parameter is incorrect, please refer to API documentation + '34037': ExchangeError, // Get the sub-account balance interface, account type is not supported + '34038': ExchangeError, // Since your C2C transaction is unusual, you are restricted from fund transfer. Please contact our customer support to cancel the restriction + '34039': ExchangeError, // You are now restricted from transferring out your funds due to abnormal trades on C2C Market. Please transfer your fund on our website or app instead to verify your identity + // swap + '35001': ExchangeError, // { "code": 35001, "message": "Contract does not exist" } + '35002': ExchangeError, // { "code": 35002, "message": "Contract settling" } + '35003': ExchangeError, // { "code": 35003, "message": "Contract paused" } + '35004': ExchangeError, // { "code": 35004, "message": "Contract pending settlement" } + '35005': AuthenticationError, // { "code": 35005, "message": "User does not exist" } + '35008': InvalidOrder, // { "code": 35008, "message": "Risk ratio too high" } + '35010': InvalidOrder, // { "code": 35010, "message": "Position closing too large" } + '35012': InvalidOrder, // { "code": 35012, "message": "Incorrect order size" } + '35014': InvalidOrder, // { "code": 35014, "message": "Order price is not within limit" } + '35015': InvalidOrder, // { "code": 35015, "message": "Invalid leverage level" } + '35017': ExchangeError, // { "code": 35017, "message": "Open orders exist" } + '35019': InvalidOrder, // { "code": 35019, "message": "Order size too large" } + '35020': InvalidOrder, // { "code": 35020, "message": "Order price too high" } + '35021': InvalidOrder, // { "code": 35021, "message": "Order size exceeded current tier limit" } + '35022': BadRequest, // { "code": 35022, "message": "Contract status error" } + '35024': BadRequest, // { "code": 35024, "message": "Contract not initialized" } + '35025': InsufficientFunds, // { "code": 35025, "message": "No account balance" } + '35026': BadRequest, // { "code": 35026, "message": "Contract settings not initialized" } + '35029': OrderNotFound, // { "code": 35029, "message": "Order does not exist" } + '35030': InvalidOrder, // { "code": 35030, "message": "Order size too large" } + '35031': InvalidOrder, // { "code": 35031, "message": "Cancel order size too large" } + '35032': ExchangeError, // { "code": 35032, "message": "Invalid user status" } + '35037': ExchangeError, // No last traded price in cache + '35039': ExchangeError, // { "code": 35039, "message": "Open order quantity exceeds limit" } + '35040': InvalidOrder, // {"error_message":"Invalid order type","result":"true","error_code":"35040","order_id":"-1"} + '35044': ExchangeError, // { "code": 35044, "message": "Invalid order status" } + '35046': InsufficientFunds, // { "code": 35046, "message": "Negative account balance" } + '35047': InsufficientFunds, // { "code": 35047, "message": "Insufficient account balance" } + '35048': ExchangeError, // { "code": 35048, "message": "User contract is frozen and liquidating" } + '35049': InvalidOrder, // { "code": 35049, "message": "Invalid order type" } + '35050': InvalidOrder, // { "code": 35050, "message": "Position settings are blank" } + '35052': InsufficientFunds, // { "code": 35052, "message": "Insufficient cross margin" } + '35053': ExchangeError, // { "code": 35053, "message": "Account risk too high" } + '35055': InsufficientFunds, // { "code": 35055, "message": "Insufficient account balance" } + '35057': ExchangeError, // { "code": 35057, "message": "No last traded price" } + '35058': ExchangeError, // { "code": 35058, "message": "No limit" } + '35059': BadRequest, // { "code": 35059, "message": "client_oid or order_id is required" } + '35060': BadRequest, // { "code": 35060, "message": "Only fill in either parameter client_oid or order_id" } + '35061': BadRequest, // { "code": 35061, "message": "Invalid instrument_id" } + '35062': InvalidOrder, // { "code": 35062, "message": "Invalid match_price" } + '35063': InvalidOrder, // { "code": 35063, "message": "Invalid order_size" } + '35064': InvalidOrder, // { "code": 35064, "message": "Invalid client_oid" } + '35066': InvalidOrder, // Order interval error + '35067': InvalidOrder, // Time-weighted order ratio error + '35068': InvalidOrder, // Time-weighted order range error + '35069': InvalidOrder, // Time-weighted single transaction limit error + '35070': InvalidOrder, // Algo order type error + '35071': InvalidOrder, // Order total must be larger than single order limit + '35072': InvalidOrder, // Maximum 6 unfulfilled time-weighted orders can be held at the same time + '35073': InvalidOrder, // Order price is 0. Market-close-all not available + '35074': InvalidOrder, // Iceberg order single transaction average error + '35075': InvalidOrder, // Failed to cancel order + '35076': InvalidOrder, // LTC 20x leverage. Not allowed to open position + '35077': InvalidOrder, // Maximum 6 unfulfilled iceberg orders can be held at the same time + '35078': InvalidOrder, // Order amount exceeded 100,000 + '35079': InvalidOrder, // Iceberg order price variance error + '35080': InvalidOrder, // Callback rate error + '35081': InvalidOrder, // Maximum 10 unfulfilled trail orders can be held at the same time + '35082': InvalidOrder, // Trail order callback rate error + '35083': InvalidOrder, // Each user can only hold a maximum of 10 unfulfilled stop-limit orders at the same time + '35084': InvalidOrder, // Order amount exceeded 1 million + '35085': InvalidOrder, // Order amount is not in the correct range + '35086': InvalidOrder, // Price exceeds 100 thousand + '35087': InvalidOrder, // Price exceeds 100 thousand + '35088': InvalidOrder, // Average amount error + '35089': InvalidOrder, // Price exceeds 100 thousand + '35090': ExchangeError, // No stop-limit orders available for cancelation + '35091': ExchangeError, // No trail orders available for cancellation + '35092': ExchangeError, // No iceberg orders available for cancellation + '35093': ExchangeError, // No trail orders available for cancellation + '35094': ExchangeError, // Stop-limit order last traded price error + '35095': BadRequest, // Instrument_id error + '35096': ExchangeError, // Algo order status error + '35097': ExchangeError, // Order status and order ID cannot exist at the same time + '35098': ExchangeError, // An order status or order ID must exist + '35099': ExchangeError, // Algo order ID error + // option + '36001': BadRequest, // Invalid underlying index. + '36002': BadRequest, // Instrument does not exist. + '36005': ExchangeError, // Instrument status is invalid. + '36101': AuthenticationError, // Account does not exist. + '36102': PermissionDenied, // Account status is invalid. + '36103': PermissionDenied, // Account is suspended due to ongoing liquidation. + '36104': PermissionDenied, // Account is not enabled for options trading. + '36105': PermissionDenied, // Please enable the account for option contract. + '36106': PermissionDenied, // Funds cannot be transferred in or out, as account is suspended. + '36107': PermissionDenied, // Funds cannot be transferred out within 30 minutes after option exercising or settlement. + '36108': InsufficientFunds, // Funds cannot be transferred in or out, as equity of the account is less than zero. + '36109': PermissionDenied, // Funds cannot be transferred in or out during option exercising or settlement. + '36201': PermissionDenied, // New order function is blocked. + '36202': PermissionDenied, // Account does not have permission to short option. + '36203': InvalidOrder, // Invalid format for client_oid. + '36204': ExchangeError, // Invalid format for request_id. + '36205': BadRequest, // Instrument id does not match underlying index. + '36206': BadRequest, // Order_id and client_oid can not be used at the same time. + '36207': InvalidOrder, // Either order price or fartouch price must be present. + '36208': InvalidOrder, // Either order price or size must be present. + '36209': InvalidOrder, // Either order_id or client_oid must be present. + '36210': InvalidOrder, // Either order_ids or client_oids must be present. + '36211': InvalidOrder, // Exceeding max batch size for order submission. + '36212': InvalidOrder, // Exceeding max batch size for oder cancellation. + '36213': InvalidOrder, // Exceeding max batch size for order amendment. + '36214': ExchangeError, // Instrument does not have valid bid/ask quote. + '36216': OrderNotFound, // Order does not exist. + '36217': InvalidOrder, // Order submission failed. + '36218': InvalidOrder, // Order cancellation failed. + '36219': InvalidOrder, // Order amendment failed. + '36220': InvalidOrder, // Order is pending cancel. + '36221': InvalidOrder, // Order qty is not valid multiple of lot size. + '36222': InvalidOrder, // Order price is breaching highest buy limit. + '36223': InvalidOrder, // Order price is breaching lowest sell limit. + '36224': InvalidOrder, // Exceeding max order size. + '36225': InvalidOrder, // Exceeding max open order count for instrument. + '36226': InvalidOrder, // Exceeding max open order count for underlying. + '36227': InvalidOrder, // Exceeding max open size across all orders for underlying + '36228': InvalidOrder, // Exceeding max available qty for instrument. + '36229': InvalidOrder, // Exceeding max available qty for underlying. + '36230': InvalidOrder, // Exceeding max position limit for underlying. + }, + 'broad': { + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'fetchOHLCV': { + 'type': 'Candles', // Candles or HistoryCandles + }, + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarkets': [ 'spot', 'futures', 'swap', 'option' ], + 'defaultType': 'spot', // 'account', 'spot', 'margin', 'futures', 'swap', 'option' + 'auth': { + 'time': 'public', + 'currencies': 'private', + 'instruments': 'public', + 'rate': 'public', + '{instrument_id}/constituents': 'public', + }, + }, + 'commonCurrencies': { + // OKEX refers to ERC20 version of Aeternity (AEToken) + 'AE': 'AET', // https://github.com/ccxt/ccxt/issues/4981 + 'BOX': 'DefiBox', + 'HOT': 'Hydro Protocol', + 'HSR': 'HC', + 'MAG': 'Maggie', + 'YOYO': 'YOYOW', + 'WIN': 'WinToken', // https://github.com/ccxt/ccxt/issues/5701 + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.generalGetTime (params); + // + // { + // "iso": "2015-01-07T23:47:25.201Z", + // "epoch": 1420674445.201 + // } + // + return this.parse8601 (this.safeString (response, 'iso')); + } + + async fetchMarkets (params = {}) { + const types = this.safeValue (this.options, 'fetchMarkets'); + let result = []; + for (let i = 0; i < types.length; i++) { + const markets = await this.fetchMarketsByType (types[i], params); + result = this.arrayConcat (result, markets); + } + return result; + } + + parseMarkets (markets) { + const result = []; + for (let i = 0; i < markets.length; i++) { + result.push (this.parseMarket (markets[i])); + } + return result; + } + + parseMarket (market) { + // + // spot markets + // + // { + // base_currency: "EOS", + // instrument_id: "EOS-OKB", + // min_size: "0.01", + // quote_currency: "OKB", + // size_increment: "0.000001", + // tick_size: "0.0001" + // } + // + // futures markets + // + // { + // instrument_id: "XRP-USD-200320", + // underlying_index: "XRP", + // quote_currency: "USD", + // tick_size: "0.0001", + // contract_val: "10", + // listing: "2020-03-06", + // delivery: "2020-03-20", + // trade_increment: "1", + // alias: "this_week", + // underlying: "XRP-USD", + // base_currency: "XRP", + // settlement_currency: "XRP", + // is_inverse: "true", + // contract_val_currency: "USD", + // } + // + // swap markets + // + // { + // instrument_id: "BSV-USD-SWAP", + // underlying_index: "BSV", + // quote_currency: "USD", + // coin: "BSV", + // contract_val: "10", + // listing: "2018-12-21T07:53:47.000Z", + // delivery: "2020-03-14T08:00:00.000Z", + // size_increment: "1", + // tick_size: "0.01", + // base_currency: "BSV", + // underlying: "BSV-USD", + // settlement_currency: "BSV", + // is_inverse: "true", + // contract_val_currency: "USD" + // } + // + // options markets + // + // { + // instrument_id: 'BTC-USD-200327-4000-C', + // underlying: 'BTC-USD', + // settlement_currency: 'BTC', + // contract_val: '0.1000', + // option_type: 'C', + // strike: '4000', + // tick_size: '0.0005', + // lot_size: '1.0000', + // listing: '2019-12-25T08:30:36.302Z', + // delivery: '2020-03-27T08:00:00.000Z', + // state: '2', + // trading_start_time: '2019-12-25T08:30:36.302Z', + // timestamp: '2020-03-13T08:05:09.456Z', + // } + // + const id = this.safeString (market, 'instrument_id'); + let marketType = 'spot'; + let spot = true; + let future = false; + let swap = false; + let option = false; + let baseId = this.safeString (market, 'base_currency'); + let quoteId = this.safeString (market, 'quote_currency'); + const contractVal = this.safeFloat (market, 'contract_val'); + if (contractVal !== undefined) { + if ('option_type' in market) { + marketType = 'option'; + spot = false; + option = true; + const underlying = this.safeString (market, 'underlying'); + const parts = underlying.split ('-'); + baseId = this.safeString (parts, 0); + quoteId = this.safeString (parts, 1); + } else { + marketType = 'swap'; + spot = false; + swap = true; + const futuresAlias = this.safeString (market, 'alias'); + if (futuresAlias !== undefined) { + swap = false; + future = true; + marketType = 'futures'; + baseId = this.safeString (market, 'underlying_index'); + } + } + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = spot ? (base + '/' + quote) : id; + const lotSize = this.safeFloat2 (market, 'lot_size', 'trade_increment'); + const precision = { + 'amount': this.safeFloat (market, 'size_increment', lotSize), + 'price': this.safeFloat (market, 'tick_size'), + }; + const minAmount = this.safeFloat2 (market, 'min_size', 'base_min_size'); + const active = true; + const fees = this.safeValue2 (this.fees, marketType, 'trading', {}); + return this.extend (fees, { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': marketType, + 'spot': spot, + 'futures': future, + 'swap': swap, + 'option': option, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': precision['price'], + 'max': undefined, + }, + 'cost': { + 'min': precision['price'], + 'max': undefined, + }, + }, + }); + } + + async fetchMarketsByType (type, params = {}) { + if (type === 'option') { + const underlying = await this.optionGetUnderlying (params); + let result = []; + for (let i = 0; i < underlying.length; i++) { + const response = await this.optionGetInstrumentsUnderlying ({ + 'underlying': underlying[i], + }); + // + // options markets + // + // [ + // { + // instrument_id: 'BTC-USD-200327-4000-C', + // underlying: 'BTC-USD', + // settlement_currency: 'BTC', + // contract_val: '0.1000', + // option_type: 'C', + // strike: '4000', + // tick_size: '0.0005', + // lot_size: '1.0000', + // listing: '2019-12-25T08:30:36.302Z', + // delivery: '2020-03-27T08:00:00.000Z', + // state: '2', + // trading_start_time: '2019-12-25T08:30:36.302Z', + // timestamp: '2020-03-13T08:05:09.456Z', + // }, + // ] + // + result = this.arrayConcat (result, response); + } + return this.parseMarkets (result); + } else if ((type === 'spot') || (type === 'futures') || (type === 'swap')) { + const method = type + 'GetInstruments'; + const response = await this[method] (params); + // + // spot markets + // + // [ + // { + // base_currency: "EOS", + // instrument_id: "EOS-OKB", + // min_size: "0.01", + // quote_currency: "OKB", + // size_increment: "0.000001", + // tick_size: "0.0001" + // } + // ] + // + // futures markets + // + // [ + // { + // instrument_id: "XRP-USD-200320", + // underlying_index: "XRP", + // quote_currency: "USD", + // tick_size: "0.0001", + // contract_val: "10", + // listing: "2020-03-06", + // delivery: "2020-03-20", + // trade_increment: "1", + // alias: "this_week", + // underlying: "XRP-USD", + // base_currency: "XRP", + // settlement_currency: "XRP", + // is_inverse: "true", + // contract_val_currency: "USD", + // } + // ] + // + // swap markets + // + // [ + // { + // instrument_id: "BSV-USD-SWAP", + // underlying_index: "BSV", + // quote_currency: "USD", + // coin: "BSV", + // contract_val: "10", + // listing: "2018-12-21T07:53:47.000Z", + // delivery: "2020-03-14T08:00:00.000Z", + // size_increment: "1", + // tick_size: "0.01", + // base_currency: "BSV", + // underlying: "BSV-USD", + // settlement_currency: "BSV", + // is_inverse: "true", + // contract_val_currency: "USD" + // } + // ] + // + return this.parseMarkets (response); + } else { + throw new NotSupported (this.id + ' fetchMarketsByType does not support market type ' + type); + } + } + + async fetchCurrencies (params = {}) { + // has['fetchCurrencies'] is currently set to false + // despite that their docs say these endpoints are public: + // https://www.okex.com/api/account/v3/withdrawal/fee + // https://www.okex.com/api/account/v3/currencies + // it will still reply with { "code":30001, "message": "OK-ACCESS-KEY header is required" } + // if you attempt to access it without authentication + const response = await this.accountGetCurrencies (params); + // + // [ + // { + // name: '', + // currency: 'BTC', + // can_withdraw: '1', + // can_deposit: '1', + // min_withdrawal: '0.0100000000000000' + // }, + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const precision = 8; // default precision, todo: fix "magic constants" + const name = this.safeString (currency, 'name'); + const canDeposit = this.safeInteger (currency, 'can_deposit'); + const canWithdraw = this.safeInteger (currency, 'can_withdraw'); + const active = (canDeposit && canWithdraw) ? true : false; + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': name, + 'active': active, + 'fee': undefined, // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { + 'min': this.safeFloat (currency, 'min_withdrawal'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = market['type'] + 'GetInstrumentsInstrumentId'; + method += (market['type'] === 'swap') ? 'Depth' : 'Book'; + const request = { + 'instrument_id': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; // max 200 + } + const response = await this[method] (this.extend (request, params)); + // + // { asks: [ ["0.02685268", "0.242571", "1"], + // ["0.02685493", "0.164085", "1"], + // ... + // ["0.02779", "1.039", "1"], + // ["0.027813", "0.0876", "1"] ], + // bids: [ ["0.02684052", "10.371849", "1"], + // ["0.02684051", "3.707", "4"], + // ... + // ["0.02634963", "0.132934", "1"], + // ["0.02634962", "0.264838", "2"] ], + // timestamp: "2018-12-17T20:24:16.159Z" } + // + const timestamp = this.parse8601 (this.safeString (response, 'timestamp')); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + // + // { best_ask: "0.02665472", + // best_bid: "0.02665221", + // instrument_id: "ETH-BTC", + // product_id: "ETH-BTC", + // last: "0.02665472", + // ask: "0.02665472", // missing in the docs + // bid: "0.02665221", // not mentioned in the docs + // open_24h: "0.02645482", + // high_24h: "0.02714633", + // low_24h: "0.02614109", + // base_volume_24h: "572298.901923", + // timestamp: "2018-12-17T21:20:07.856Z", + // quote_volume_24h: "15094.86831261" } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + let symbol = undefined; + const marketId = this.safeString (ticker, 'instrument_id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else if (marketId !== undefined) { + const parts = marketId.split ('-'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const open = this.safeFloat (ticker, 'open_24h'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_24h'), + 'low': this.safeFloat (ticker, 'low_24h'), + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': this.safeFloat (ticker, 'best_bid_size'), + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': this.safeFloat (ticker, 'best_ask_size'), + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'base_volume_24h'), + 'quoteVolume': this.safeFloat (ticker, 'quote_volume_24h'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['type'] + 'GetInstrumentsInstrumentIdTicker'; + const request = { + 'instrument_id': market['id'], + }; + const response = await this[method] (this.extend (request, params)); + // + // { best_ask: "0.02665472", + // best_bid: "0.02665221", + // instrument_id: "ETH-BTC", + // product_id: "ETH-BTC", + // last: "0.02665472", + // ask: "0.02665472", + // bid: "0.02665221", + // open_24h: "0.02645482", + // high_24h: "0.02714633", + // low_24h: "0.02614109", + // base_volume_24h: "572298.901923", + // timestamp: "2018-12-17T21:20:07.856Z", + // quote_volume_24h: "15094.86831261" } + // + return this.parseTicker (response); + } + + async fetchTickersByType (type, symbols = undefined, params = {}) { + await this.loadMarkets (); + const method = type + 'GetInstrumentsTicker'; + const response = await this[method] (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + return await this.fetchTickersByType (type, symbols, this.omit (params, 'type')); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // spot trades + // + // { + // time: "2018-12-17T23:31:08.268Z", + // timestamp: "2018-12-17T23:31:08.268Z", + // trade_id: "409687906", + // price: "0.02677805", + // size: "0.923467", + // side: "sell" + // } + // + // futures trades, swap trades + // + // { + // trade_id: "1989230840021013", + // side: "buy", + // price: "92.42", + // qty: "184", // missing in swap markets + // size: "5", // missing in futures markets + // timestamp: "2018-12-17T23:26:04.613Z" + // } + // + // fetchOrderTrades (private) + // + // spot trades, margin trades + // + // { + // "created_at":"2019-03-15T02:52:56.000Z", + // "exec_type":"T", // whether the order is taker or maker + // "fee":"0.00000082", + // "instrument_id":"BTC-USDT", + // "ledger_id":"3963052721", + // "liquidity":"T", // whether the order is taker or maker + // "order_id":"2482659399697408", + // "price":"3888.6", + // "product_id":"BTC-USDT", + // "side":"buy", + // "size":"0.00055306", + // "timestamp":"2019-03-15T02:52:56.000Z" + // }, + // + // futures trades, swap trades + // + // { + // "trade_id":"197429674631450625", + // "instrument_id":"EOS-USD-SWAP", + // "order_id":"6a-7-54d663a28-0", + // "price":"3.633", + // "order_qty":"1.0000", + // "fee":"-0.000551", + // "created_at":"2019-03-21T04:41:58.0Z", // missing in swap trades + // "timestamp":"2019-03-25T05:56:31.287Z", // missing in futures trades + // "exec_type":"M", // whether the order is taker or maker + // "side":"short", // "buy" in futures trades + // } + // + let symbol = undefined; + const marketId = this.safeString (trade, 'instrument_id'); + let base = undefined; + let quote = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } else if (marketId !== undefined) { + const parts = marketId.split ('-'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + const timestamp = this.parse8601 (this.safeString2 (trade, 'timestamp', 'created_at')); + const price = this.safeFloat (trade, 'price'); + let amount = this.safeFloat2 (trade, 'size', 'qty'); + amount = this.safeFloat (trade, 'order_qty', amount); + let takerOrMaker = this.safeString2 (trade, 'exec_type', 'liquidity'); + if (takerOrMaker === 'M') { + takerOrMaker = 'maker'; + } else if (takerOrMaker === 'T') { + takerOrMaker = 'taker'; + } + const side = this.safeString (trade, 'side'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = (side === 'buy') ? base : quote; + fee = { + // fee is either a positive number (invitation rebate) + // or a negative number (transaction fee deduction) + // therefore we need to invert the fee + // more about it https://github.com/ccxt/ccxt/issues/5909 + 'cost': -feeCost, + 'currency': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'order_id'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': this.safeString2 (trade, 'trade_id', 'ledger_id'), + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['type'] + 'GetInstrumentsInstrumentIdTrades'; + if ((limit === undefined) || (limit > 100)) { + limit = 100; // maximum = default = 100 + } + const request = { + 'instrument_id': market['id'], + 'limit': limit, + // from: 'id', + // to: 'id', + }; + const response = await this[method] (this.extend (request, params)); + // + // spot markets + // + // [ + // { + // time: "2018-12-17T23:31:08.268Z", + // timestamp: "2018-12-17T23:31:08.268Z", + // trade_id: "409687906", + // price: "0.02677805", + // size: "0.923467", + // side: "sell" + // } + // ] + // + // futures markets, swap markets + // + // [ + // { + // trade_id: "1989230840021013", + // side: "buy", + // price: "92.42", + // qty: "184", // missing in swap markets + // size: "5", // missing in futures markets + // timestamp: "2018-12-17T23:26:04.613Z" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // spot markets + // + // { + // close: "0.02684545", + // high: "0.02685084", + // low: "0.02683312", + // open: "0.02683894", + // time: "2018-12-17T20:28:00.000Z", + // volume: "101.457222" + // } + // + // futures markets + // + // [ + // 1545072720000, + // 0.3159, + // 0.3161, + // 0.3144, + // 0.3149, + // 22886, + // 725179.26172331, + // ] + // + if (Array.isArray (ohlcv)) { + const numElements = ohlcv.length; + const volumeIndex = (numElements > 6) ? 6 : 5; + let timestamp = this.safeValue (ohlcv, 0); + if (typeof timestamp === 'string') { + timestamp = this.parse8601 (timestamp); + } + return [ + timestamp, // timestamp + this.safeFloat (ohlcv, 1), // Open + this.safeFloat (ohlcv, 2), // High + this.safeFloat (ohlcv, 3), // Low + this.safeFloat (ohlcv, 4), // Close + // this.safeFloat (ohlcv, 5), // Quote Volume + // this.safeFloat (ohlcv, 6), // Base Volume + this.safeFloat (ohlcv, volumeIndex), // Volume, okex will return base volume in the 7th element for future markets + ]; + } else { + return [ + this.parse8601 (this.safeString (ohlcv, 'time')), + this.safeFloat (ohlcv, 'open'), // Open + this.safeFloat (ohlcv, 'high'), // High + this.safeFloat (ohlcv, 'low'), // Low + this.safeFloat (ohlcv, 'close'), // Close + this.safeFloat (ohlcv, 'volume'), // Base Volume + ]; + } + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const duration = this.parseTimeframe (timeframe); + const request = { + 'instrument_id': market['id'], + 'granularity': this.timeframes[timeframe], + }; + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const defaultType = this.safeString (options, 'type', 'Candles'); // Candles or HistoryCandles + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const method = market['type'] + 'GetInstrumentsInstrumentId' + type; + if (type === 'Candles') { + if (since !== undefined) { + if (limit !== undefined) { + request['end'] = this.iso8601 (this.sum (since, limit * duration * 1000)); + } + request['start'] = this.iso8601 (since); + } else { + if (limit !== undefined) { + const now = this.milliseconds (); + request['start'] = this.iso8601 (now - limit * duration * 1000); + request['end'] = this.iso8601 (now); + } + } + } else if (type === 'HistoryCandles') { + if (market['option']) { + throw new NotSupported (this.id + ' fetchOHLCV does not have ' + type + ' for ' + market['type'] + ' markets'); + } + if (since !== undefined) { + if (limit === undefined) { + limit = 300; // default + } + request['start'] = this.iso8601 (this.sum (since, limit * duration * 1000)); + request['end'] = this.iso8601 (since); + } else { + if (limit !== undefined) { + const now = this.milliseconds (); + request['end'] = this.iso8601 (now - limit * duration * 1000); + request['start'] = this.iso8601 (now); + } + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot markets + // + // [ + // { + // close: "0.02683401", + // high: "0.02683401", + // low: "0.02683401", + // open: "0.02683401", + // time: "2018-12-17T23:47:00.000Z", + // volume: "0" + // }, + // { + // close: "0.02684545", + // high: "0.02685084", + // low: "0.02683312", + // open: "0.02683894", + // time: "2018-12-17T20:28:00.000Z", + // volume: "101.457222" + // } + // ] + // + // futures + // + // [ + // [ + // 1545090660000, + // 0.3171, + // 0.3174, + // 0.3171, + // 0.3173, + // 1648, + // 51930.38579450868 + // ], + // [ + // 1545072720000, + // 0.3159, + // 0.3161, + // 0.3144, + // 0.3149, + // 22886, + // 725179.26172331 + // ] + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseAccountBalance (response) { + // + // account + // + // [ + // { + // balance: 0, + // available: 0, + // currency: "BTC", + // hold: 0 + // }, + // { + // balance: 0, + // available: 0, + // currency: "ETH", + // hold: 0 + // } + // ] + // + // spot + // + // [ + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "BTC", + // balance: "0.0000000497717339", + // available: "0.0000000497717339", + // holds: "0" + // }, + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "ICN", + // balance: "0.00000000925", + // available: "0.00000000925", + // holds: "0" + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'hold'); + account['free'] = this.safeFloat (balance, 'available'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseMarginBalance (response) { + // + // [ + // { + // "currency:BTC": { + // "available":"0", + // "balance":"0", + // "borrowed":"0", + // "can_withdraw":"0", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "currency:USDT": { + // "available":"100", + // "balance":"100", + // "borrowed":"0", + // "can_withdraw":"100", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "instrument_id":"BTC-USDT", + // "liquidation_price":"0", + // "product_id":"BTC-USDT", + // "risk_rate":"" + // }, + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const marketId = this.safeString (balance, 'instrument_id'); + const market = this.safeValue (this.markets_by_id, marketId); + let symbol = undefined; + if (market === undefined) { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = market['symbol']; + } + const omittedBalance = this.omit (balance, [ + 'instrument_id', + 'liquidation_price', + 'product_id', + 'risk_rate', + 'margin_ratio', + 'maint_margin_ratio', + 'tiers', + ]); + const keys = Object.keys (omittedBalance); + const accounts = {}; + for (let k = 0; k < keys.length; k++) { + const key = keys[k]; + const marketBalance = balance[key]; + if (key.indexOf (':') >= 0) { + const parts = key.split (':'); + const currencyId = parts[1]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (marketBalance, 'balance'); + account['used'] = this.safeFloat (marketBalance, 'hold'); + account['free'] = this.safeFloat (marketBalance, 'available'); + accounts[code] = account; + } else { + throw new NotSupported (this.id + ' margin balance response format has changed!'); + } + } + result[symbol] = this.parseBalance (accounts); + } + return result; + } + + parseFuturesBalance (response) { + // + // { + // "info":{ + // "eos":{ + // "auto_margin":"0", + // "contracts": [ + // { + // "available_qty":"40.37069445", + // "fixed_balance":"0", + // "instrument_id":"EOS-USD-190329", + // "margin_for_unfilled":"0", + // "margin_frozen":"0", + // "realized_pnl":"0", + // "unrealized_pnl":"0" + // }, + // { + // "available_qty":"40.37069445", + // "fixed_balance":"14.54895721", + // "instrument_id":"EOS-USD-190628", + // "margin_for_unfilled":"0", + // "margin_frozen":"10.64042157", + // "realized_pnl":"-3.90853564", + // "unrealized_pnl":"-0.259" + // }, + // ], + // "equity":"50.75220665", + // "margin_mode":"fixed", + // "total_avail_balance":"40.37069445" + // }, + // } + // } + // + // their root field name is "info", so our info will contain their info + const result = { 'info': response }; + const info = this.safeValue (response, 'info', {}); + const ids = Object.keys (info); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + const balance = this.safeValue (info, id, {}); + const account = this.account (); + const totalAvailBalance = this.safeFloat (balance, 'total_avail_balance'); + if (this.safeString (balance, 'margin_mode') === 'fixed') { + const contracts = this.safeValue (balance, 'contracts', []); + let free = totalAvailBalance; + for (let i = 0; i < contracts.length; i++) { + const contract = contracts[i]; + const fixedBalance = this.safeFloat (contract, 'fixed_balance'); + const realizedPnl = this.safeFloat (contract, 'realized_pnl'); + const marginFrozen = this.safeFloat (contract, 'margin_frozen'); + const marginForUnfilled = this.safeFloat (contract, 'margin_for_unfilled'); + const margin = this.sum (fixedBalance, realizedPnl) - marginFrozen - marginForUnfilled; + free = this.sum (free, margin); + } + account['free'] = free; + } else { + const realizedPnl = this.safeFloat (balance, 'realized_pnl'); + const unrealizedPnl = this.safeFloat (balance, 'unrealized_pnl'); + const marginFrozen = this.safeFloat (balance, 'margin_frozen'); + const marginForUnfilled = this.safeFloat (balance, 'margin_for_unfilled'); + account['free'] = this.sum (totalAvailBalance, realizedPnl, unrealizedPnl) - marginFrozen - marginForUnfilled; + } + // it may be incorrect to use total, free and used for swap accounts + account['total'] = this.safeFloat (balance, 'equity'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseSwapBalance (response) { + // + // { + // "info": [ + // { + // "equity":"3.0139", + // "fixed_balance":"0.0000", + // "instrument_id":"EOS-USD-SWAP", + // "margin":"0.5523", + // "margin_frozen":"0.0000", + // "margin_mode":"crossed", + // "margin_ratio":"1.0913", + // "realized_pnl":"-0.0006", + // "timestamp":"2019-03-25T03:46:10.336Z", + // "total_avail_balance":"3.0000", + // "unrealized_pnl":"0.0145" + // } + // ] + // } + // + // their root field name is "info", so our info will contain their info + const result = { 'info': response }; + const info = this.safeValue (response, 'info', []); + for (let i = 0; i < info.length; i++) { + const balance = info[i]; + const marketId = this.safeString (balance, 'instrument_id'); + let symbol = marketId; + if (marketId in this.markets_by_id) { + symbol = this.markets_by_id[marketId]['symbol']; + } + const account = this.account (); + // it may be incorrect to use total, free and used for swap accounts + account['total'] = this.safeFloat (balance, 'equity'); + account['free'] = this.safeFloat (balance, 'total_avail_balance'); + result[symbol] = account; + } + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchBalance requires a type parameter (one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + await this.loadMarkets (); + const suffix = (type === 'account') ? 'Wallet' : 'Accounts'; + const method = type + 'Get' + suffix; + const query = this.omit (params, 'type'); + const response = await this[method] (query); + // + // account + // + // [ + // { + // balance: 0, + // available: 0, + // currency: "BTC", + // hold: 0 + // }, + // { + // balance: 0, + // available: 0, + // currency: "ETH", + // hold: 0 + // } + // ] + // + // spot + // + // [ + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "BTC", + // balance: "0.0000000497717339", + // available: "0.0000000497717339", + // holds: "0" + // }, + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "ICN", + // balance: "0.00000000925", + // available: "0.00000000925", + // holds: "0" + // } + // ] + // + // margin + // + // [ + // { + // "currency:BTC": { + // "available":"0", + // "balance":"0", + // "borrowed":"0", + // "can_withdraw":"0", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "currency:USDT": { + // "available":"100", + // "balance":"100", + // "borrowed":"0", + // "can_withdraw":"100", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "instrument_id":"BTC-USDT", + // "liquidation_price":"0", + // "product_id":"BTC-USDT", + // "risk_rate":"" + // }, + // ] + // + // futures + // + // { + // "info":{ + // "eos":{ + // "auto_margin":"0", + // "contracts": [ + // { + // "available_qty":"40.37069445", + // "fixed_balance":"0", + // "instrument_id":"EOS-USD-190329", + // "margin_for_unfilled":"0", + // "margin_frozen":"0", + // "realized_pnl":"0", + // "unrealized_pnl":"0" + // }, + // { + // "available_qty":"40.37069445", + // "fixed_balance":"14.54895721", + // "instrument_id":"EOS-USD-190628", + // "margin_for_unfilled":"0", + // "margin_frozen":"10.64042157", + // "realized_pnl":"-3.90853564", + // "unrealized_pnl":"-0.259" + // }, + // ], + // "equity":"50.75220665", + // "margin_mode":"fixed", + // "total_avail_balance":"40.37069445" + // }, + // } + // } + // + // swap + // + // { + // "info": [ + // { + // "equity":"3.0139", + // "fixed_balance":"0.0000", + // "instrument_id":"EOS-USD-SWAP", + // "margin":"0.5523", + // "margin_frozen":"0.0000", + // "margin_mode":"crossed", + // "margin_ratio":"1.0913", + // "realized_pnl":"-0.0006", + // "timestamp":"2019-03-25T03:46:10.336Z", + // "total_avail_balance":"3.0000", + // "unrealized_pnl":"0.0145" + // } + // ] + // } + // + return this.parseBalanceByType (type, response); + } + + parseBalanceByType (type, response) { + if ((type === 'account') || (type === 'spot')) { + return this.parseAccountBalance (response); + } else if (type === 'margin') { + return this.parseMarginBalance (response); + } else if (type === 'futures') { + return this.parseFuturesBalance (response); + } else if (type === 'swap') { + return this.parseSwapBalance (response); + } + throw new NotSupported (this.id + " fetchBalance does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let request = { + 'instrument_id': market['id'], + // 'client_oid': 'abcdef1234567890', // [a-z0-9]{1,32} + // 'order_type': '0', // 0 = Normal limit order, 1 = Post only, 2 = Fill Or Kill, 3 = Immediatel Or Cancel, 4 = Market for futures only + }; + const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['client_oid'] = clientOrderId; + params = this.omit (params, [ 'client_oid', 'clientOrderId' ]); + } + let method = undefined; + if (market['futures'] || market['swap']) { + const size = market['futures'] ? this.numberToString (amount) : this.amountToPrecision (symbol, amount); + request = this.extend (request, { + 'type': type, // 1:open long 2:open short 3:close long 4:close short for futures + 'size': size, + // 'match_price': '0', // Order at best counter party price? (0:no 1:yes). The default is 0. If it is set as 1, the price parameter will be ignored. When posting orders at best bid price, order_type can only be 0 (regular order). + }); + const orderType = this.safeString (params, 'order_type'); + // order_type === '4' means a market order + const isMarketOrder = (type === 'market') || (orderType === '4'); + if (isMarketOrder) { + request['order_type'] = '4'; + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + if (market['futures']) { + request['leverage'] = '10'; // or '20' + } + method = market['type'] + 'PostOrder'; + } else { + const marginTrading = this.safeString (params, 'margin_trading', '1'); // 1 = spot, 2 = margin + request = this.extend (request, { + 'side': side, + 'type': type, // limit/market + 'margin_trading': marginTrading, // 1 = spot, 2 = margin + }); + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + request['size'] = this.amountToPrecision (symbol, amount); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let notional = this.safeFloat (params, 'notional'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (notional === undefined) { + notional = amount * price; + } + } else if (notional === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'notional' extra parameter (the exchange-specific behaviour)"); + } + } else { + notional = (notional === undefined) ? amount : notional; + } + const precision = market['precision']['price']; + request['notional'] = this.decimalToPrecision (notional, TRUNCATE, precision, this.precisionMode); + } else { + request['size'] = this.amountToPrecision (symbol, amount); + } + } + method = (marginTrading === '2') ? 'marginPostOrders' : 'spotPostOrders'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "client_oid":"oktspot79", + // "error_code":"", + // "error_message":"", + // "order_id":"2510789768709120", + // "result":true + // } + // + const order = this.parseOrder (response, market); + return this.extend (order, { + 'type': type, + 'side': side, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let type = undefined; + if (market['futures'] || market['swap']) { + type = market['type']; + } else { + const defaultType = this.safeString2 (this.options, 'cancelOrder', 'defaultType', market['type']); + type = this.safeString (params, 'type', defaultType); + } + if (type === undefined) { + throw new ArgumentsRequired (this.id + " cancelOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); + } + let method = type + 'PostCancelOrder'; + const request = { + 'instrument_id': market['id'], + }; + if (market['futures'] || market['swap']) { + method += 'InstrumentId'; + } else { + method += 's'; + } + const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId'); + if (clientOrderId !== undefined) { + method += 'ClientOid'; + request['client_oid'] = clientOrderId; + } else { + method += 'OrderId'; + request['order_id'] = id; + } + const query = this.omit (params, [ 'type', 'client_oid', 'clientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + const result = ('result' in response) ? response : this.safeValue (response, market['id'], {}); + // + // spot, margin + // + // { + // "btc-usdt": [ + // { + // "result":true, + // "client_oid":"a123", + // "order_id": "2510832677225473" + // } + // ] + // } + // + // futures, swap + // + // { + // "result": true, + // "client_oid": "oktfuture10", // missing if requested by order_id + // "order_id": "2517535534836736", + // "instrument_id": "EOS-USD-190628" + // } + // + return this.parseOrder (result, market); + } + + parseOrderStatus (status) { + const statuses = { + '-2': 'failed', + '-1': 'canceled', + '0': 'open', + '1': 'open', + '2': 'closed', + '3': 'open', + '4': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrderSide (side) { + const sides = { + '1': 'buy', // open long + '2': 'sell', // open short + '3': 'sell', // close long + '4': 'buy', // close short + }; + return this.safeString (sides, side, side); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "client_oid":"oktspot79", + // "error_code":"", + // "error_message":"", + // "order_id":"2510789768709120", + // "result":true + // } + // + // cancelOrder + // + // { + // "result": true, + // "client_oid": "oktfuture10", // missing if requested by order_id + // "order_id": "2517535534836736", + // // instrument_id is missing for spot/margin orders + // // available in futures and swap orders only + // "instrument_id": "EOS-USD-190628", + // } + // + // fetchOrder, fetchOrdersByState, fetchOpenOrders, fetchClosedOrders + // + // // spot and margin orders + // + // { + // "client_oid":"oktspot76", + // "created_at":"2019-03-18T07:26:49.000Z", + // "filled_notional":"3.9734", + // "filled_size":"0.001", // filled_qty in futures and swap orders + // "funds":"", // this is most likely the same as notional + // "instrument_id":"BTC-USDT", + // "notional":"", + // "order_id":"2500723297813504", + // "order_type":"0", + // "price":"4013", + // "product_id":"BTC-USDT", // missing in futures and swap orders + // "side":"buy", + // "size":"0.001", + // "status":"filled", + // "state": "2", + // "timestamp":"2019-03-18T07:26:49.000Z", + // "type":"limit" + // } + // + // // futures and swap orders + // + // { + // "instrument_id":"EOS-USD-190628", + // "size":"10", + // "timestamp":"2019-03-20T10:04:55.000Z", + // "filled_qty":"10", // filled_size in spot and margin orders + // "fee":"-0.00841043", + // "order_id":"2512669605501952", + // "price":"3.668", + // "price_avg":"3.567", // missing in spot and margin orders + // "status":"2", + // "state": "2", + // "type":"4", + // "contract_val":"10", + // "leverage":"10", // missing in swap, spot and margin orders + // "client_oid":"", + // "pnl":"1.09510794", // missing in swap, spo and margin orders + // "order_type":"0" + // } + // + const id = this.safeString (order, 'order_id'); + const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); + let side = this.safeString (order, 'side'); + const type = this.safeString (order, 'type'); + if ((side !== 'buy') && (side !== 'sell')) { + side = this.parseOrderSide (type); + } + let symbol = undefined; + const marketId = this.safeString (order, 'instrument_id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + symbol = marketId; + } + if (market !== undefined) { + if (symbol === undefined) { + symbol = market['symbol']; + } + } + let amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat2 (order, 'filled_size', 'filled_qty'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + amount = Math.max (amount, filled); + remaining = Math.max (0, amount - filled); + } + } + if (type === 'market') { + remaining = 0; + } + let cost = this.safeFloat2 (order, 'filled_notional', 'funds'); + const price = this.safeFloat (order, 'price'); + let average = this.safeFloat (order, 'price_avg'); + if (cost === undefined) { + if (filled !== undefined && average !== undefined) { + cost = average * filled; + } + } else { + if ((average === undefined) && (filled !== undefined) && (filled > 0)) { + average = cost / filled; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const feeCost = this.safeFloat (order, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + let clientOrderId = this.safeString (order, 'client_oid'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; // fix empty clientOrderId string + } + const stopPrice = this.safeFloat (order, 'trigger_price'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); + } + const instrumentId = (market['futures'] || market['swap']) ? 'InstrumentId' : ''; + let method = type + 'GetOrders' + instrumentId; + const request = { + 'instrument_id': market['id'], + // 'client_oid': 'abcdef12345', // optional, [a-z0-9]{1,32} + // 'order_id': id, + }; + const clientOid = this.safeString (params, 'client_oid'); + if (clientOid !== undefined) { + method += 'ClientOid'; + request['client_oid'] = clientOid; + } else { + method += 'OrderId'; + request['order_id'] = id; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot, margin + // + // { + // "client_oid":"oktspot70", + // "created_at":"2019-03-15T02:52:56.000Z", + // "filled_notional":"3.8886", + // "filled_size":"0.001", + // "funds":"", + // "instrument_id":"BTC-USDT", + // "notional":"", + // "order_id":"2482659399697408", + // "order_type":"0", + // "price":"3927.3", + // "product_id":"BTC-USDT", + // "side":"buy", + // "size":"0.001", + // "status":"filled", + // "state": "2", + // "timestamp":"2019-03-15T02:52:56.000Z", + // "type":"limit" + // } + // + // futures, swap + // + // { + // "instrument_id":"EOS-USD-190628", + // "size":"10", + // "timestamp":"2019-03-20T02:46:38.000Z", + // "filled_qty":"10", + // "fee":"-0.0080819", + // "order_id":"2510946213248000", + // "price":"3.712", + // "price_avg":"3.712", + // "status":"2", + // "state": "2", + // "type":"2", + // "contract_val":"10", + // "leverage":"10", + // "client_oid":"", // missing in swap orders + // "pnl":"0", // missing in swap orders + // "order_type":"0" + // } + // + return this.parseOrder (response); + } + + async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrdersByState requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let type = undefined; + if (market['futures'] || market['swap']) { + type = market['type']; + } else { + const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); + type = this.safeString (params, 'type', defaultType); + } + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); + } + const request = { + 'instrument_id': market['id'], + // '-2': failed, + // '-1': cancelled, + // '0': open , + // '1': partially filled, + // '2': fully filled, + // '3': submitting, + // '4': cancelling, + // '6': incomplete(open+partially filled), + // '7': complete(cancelled+fully filled), + 'state': state, + }; + let method = type + 'GetOrders'; + if (market['futures'] || market['swap']) { + method += 'InstrumentId'; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot, margin + // + // [ + // // in fact, this documented API response does not correspond + // // to their actual API response for spot markets + // // OKEX v3 API returns a plain array of orders (see below) + // [ + // { + // "client_oid":"oktspot76", + // "created_at":"2019-03-18T07:26:49.000Z", + // "filled_notional":"3.9734", + // "filled_size":"0.001", + // "funds":"", + // "instrument_id":"BTC-USDT", + // "notional":"", + // "order_id":"2500723297813504", + // "order_type":"0", + // "price":"4013", + // "product_id":"BTC-USDT", + // "side":"buy", + // "size":"0.001", + // "status":"filled", + // "state": "2", + // "timestamp":"2019-03-18T07:26:49.000Z", + // "type":"limit" + // }, + // ], + // { + // "before":"2500723297813504", + // "after":"2500650881647616" + // } + // ] + // + // futures, swap + // + // { + // "result":true, // missing in swap orders + // "order_info": [ + // { + // "instrument_id":"EOS-USD-190628", + // "size":"10", + // "timestamp":"2019-03-20T10:04:55.000Z", + // "filled_qty":"10", + // "fee":"-0.00841043", + // "order_id":"2512669605501952", + // "price":"3.668", + // "price_avg":"3.567", + // "status":"2", + // "state": "2", + // "type":"4", + // "contract_val":"10", + // "leverage":"10", // missing in swap orders + // "client_oid":"", + // "pnl":"1.09510794", // missing in swap orders + // "order_type":"0" + // }, + // ] + // } + // + let orders = undefined; + if (market['swap'] || market['futures']) { + orders = this.safeValue (response, 'order_info', []); + } else { + orders = response; + const responseLength = response.length; + if (responseLength < 1) { + return []; + } + // in fact, this documented API response does not correspond + // to their actual API response for spot markets + // OKEX v3 API returns a plain array of orders + if (responseLength > 1) { + const before = this.safeValue (response[1], 'before'); + if (before !== undefined) { + orders = response[0]; + } + } + } + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // '-2': failed, + // '-1': cancelled, + // '0': open , + // '1': partially filled, + // '2': fully filled, + // '3': submitting, + // '4': cancelling, + // '6': incomplete(open+partially filled), + // '7': complete(cancelled+fully filled), + return await this.fetchOrdersByState ('6', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // '-2': failed, + // '-1': cancelled, + // '0': open , + // '1': partially filled, + // '2': fully filled, + // '3': submitting, + // '4': cancelling, + // '6': incomplete(open+partially filled), + // '7': complete(cancelled+fully filled), + return await this.fetchOrdersByState ('7', symbol, since, limit, params); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // address: '0x696abb81974a8793352cbd33aadcf78eda3cfdfa', + // currency: 'eth' + // tag: 'abcde12345', // will be missing if the token does not require a deposit tag + // payment_id: 'abcde12345', // will not be returned if the token does not require a payment_id + // // can_deposit: 1, // 0 or 1, documented but missing + // // can_withdraw: 1, // 0 or 1, documented but missing + // } + // + const address = this.safeString (depositAddress, 'address'); + let tag = this.safeString2 (depositAddress, 'tag', 'payment_id'); + tag = this.safeString (depositAddress, 'memo', tag); + const currencyId = this.safeString (depositAddress, 'currency'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.accountGetDepositAddress (this.extend (request, params)); + // + // [ + // { + // address: '0x696abb81974a8793352cbd33aadcf78eda3cfdfa', + // currency: 'eth' + // } + // ] + // + const addresses = this.parseDepositAddresses (response); + const address = this.safeValue (addresses, code); + if (address === undefined) { + throw new InvalidAddress (this.id + ' fetchDepositAddress cannot return nonexistent addresses, you should create withdrawal addresses with the exchange website first'); + } + return address; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag) { + address = address + ':' + tag; + } + const fee = this.safeString (params, 'fee'); + if (fee === undefined) { + throw new ArgumentsRequired (this.id + " withdraw() requires a `fee` string parameter, network transaction fee must be ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set '0'. Withdrawing to external digital asset address requires network transaction fee."); + } + const request = { + 'currency': currency['id'], + 'to_address': address, + 'destination': '4', // 2 = OKCoin International, 3 = OKEx 4 = others + 'amount': this.numberToString (amount), + 'fee': fee, // String. Network transaction fee ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set as 0. Withdrawal to external digital asset address requires network transaction fee. + }; + if ('password' in params) { + request['trade_pwd'] = params['password']; + } else if ('trade_pwd' in params) { + request['trade_pwd'] = params['trade_pwd']; + } else if (this.password) { + request['trade_pwd'] = this.password; + } + const query = this.omit (params, [ 'fee', 'password', 'trade_pwd' ]); + if (!('trade_pwd' in request)) { + throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a password / trade_pwd parameter'); + } + const response = await this.accountPostWithdrawal (this.extend (request, query)); + // + // { + // "amount":"0.1", + // "withdrawal_id":"67485", + // "currency":"btc", + // "result":true + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'withdrawal_id'), + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'accountGetDepositHistory'; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + method += 'Currency'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'accountGetWithdrawalHistory'; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + method += 'Currency'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit, params); + } + + parseTransactionStatus (status) { + // + // deposit statuses + // + // { + // '0': 'waiting for confirmation', + // '1': 'confirmation account', + // '2': 'recharge success' + // } + // + // withdrawal statues + // + // { + // '-3': 'pending cancel', + // '-2': 'cancelled', + // '-1': 'failed', + // '0': 'pending', + // '1': 'sending', + // '2': 'sent', + // '3': 'email confirmation', + // '4': 'manual confirmation', + // '5': 'awaiting identity confirmation' + // } + // + const statuses = { + '-3': 'pending', + '-2': 'canceled', + '-1': 'failed', + '0': 'pending', + '1': 'pending', + '2': 'ok', + '3': 'pending', + '4': 'pending', + '5': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "amount":"0.1", + // "withdrawal_id":"67485", + // "currency":"btc", + // "result":true + // } + // + // fetchWithdrawals + // + // { + // amount: "4.72100000", + // withdrawal_id: "1729116", + // fee: "0.01000000eth", + // txid: "0xf653125bbf090bcfe4b5e8e7b8f586a9d87aa7de94598702758c0802b…", + // currency: "ETH", + // from: "7147338839", + // to: "0x26a3CB49578F07000575405a57888681249c35Fd", + // timestamp: "2018-08-17T07:03:42.000Z", + // status: "2" + // } + // + // fetchDeposits + // + // { + // "amount": "4.19511659", + // "txid": "14c9a8c925647cdb7e5b2937ea9aefe2b29b2c273150ad3f44b3b8a4635ed437", + // "currency": "XMR", + // "from": "", + // "to": "48PjH3ksv1fiXniKvKvyH5UtFs5WhfS2Vf7U3TwzdRJtCc7HJWvCQe56dRahyhQyTAViXZ8Nzk4gQg6o4BJBMUoxNy8y8g7", + // "deposit_id": 11571659, <-- we can use this + // "timestamp": "2019-10-01T14:54:19.000Z", + // "status": "2" + // } + // + let type = undefined; + let id = undefined; + let address = undefined; + const withdrawalId = this.safeString (transaction, 'withdrawal_id'); + const addressFrom = this.safeString (transaction, 'from'); + const addressTo = this.safeString (transaction, 'to'); + if (withdrawalId !== undefined) { + type = 'withdrawal'; + id = withdrawalId; + address = addressTo; + } else { + // the payment_id will appear on new deposits but appears to be removed from the response after 2 months + id = this.safeString2 (transaction, 'payment_id', 'deposit_id'); + type = 'deposit'; + address = addressTo; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const txid = this.safeString (transaction, 'txid'); + const timestamp = this.parse8601 (this.safeString (transaction, 'timestamp')); + let feeCost = undefined; + if (type === 'deposit') { + feeCost = 0; + } else { + if (currencyId !== undefined) { + const feeWithCurrencyId = this.safeString (transaction, 'fee'); + if (feeWithCurrencyId !== undefined) { + // https://github.com/ccxt/ccxt/pull/5748 + const lowercaseCurrencyId = currencyId.toLowerCase (); + const feeWithoutCurrencyId = feeWithCurrencyId.replace (lowercaseCurrencyId, ''); + feeCost = parseFloat (feeWithoutCurrencyId); + } + } + } + // todo parse tags + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': undefined, + 'tagTo': undefined, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseMyTrade (pair, market = undefined) { + // check that trading symbols match in both entries + const userTrade = this.safeValue (pair, 1); + const otherTrade = this.safeValue (pair, 0); + const firstMarketId = this.safeString (otherTrade, 'instrument_id'); + const secondMarketId = this.safeString (userTrade, 'instrument_id'); + if (firstMarketId !== secondMarketId) { + throw new NotSupported (this.id + ' parseMyTrade() received unrecognized response format, differing instrument_ids in one fill, the exchange API might have changed, paste your verbose output: https://github.com/ccxt/ccxt/wiki/FAQ#what-is-required-to-get-help'); + } + const marketId = firstMarketId; + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const quoteId = market['quoteId']; + let side = undefined; + let amount = undefined; + let cost = undefined; + const receivedCurrencyId = this.safeString (userTrade, 'currency'); + let feeCurrencyId = undefined; + if (receivedCurrencyId === quoteId) { + side = this.safeString (otherTrade, 'side'); + amount = this.safeFloat (otherTrade, 'size'); + cost = this.safeFloat (userTrade, 'size'); + feeCurrencyId = this.safeString (otherTrade, 'currency'); + } else { + side = this.safeString (userTrade, 'side'); + amount = this.safeFloat (userTrade, 'size'); + cost = this.safeFloat (otherTrade, 'size'); + feeCurrencyId = this.safeString (userTrade, 'currency'); + } + const id = this.safeString (userTrade, 'trade_id'); + const price = this.safeFloat (userTrade, 'price'); + const feeCostFirst = this.safeFloat (otherTrade, 'fee'); + const feeCostSecond = this.safeFloat (userTrade, 'fee'); + const feeCurrencyCodeFirst = this.safeCurrencyCode (this.safeString (otherTrade, 'currency')); + const feeCurrencyCodeSecond = this.safeCurrencyCode (this.safeString (userTrade, 'currency')); + let fee = undefined; + let fees = undefined; + // fee is either a positive number (invitation rebate) + // or a negative number (transaction fee deduction) + // therefore we need to invert the fee + // more about it https://github.com/ccxt/ccxt/issues/5909 + if ((feeCostFirst !== undefined) && (feeCostFirst !== 0)) { + if ((feeCostSecond !== undefined) && (feeCostSecond !== 0)) { + fees = [ + { + 'cost': -feeCostFirst, + 'currency': feeCurrencyCodeFirst, + }, + { + 'cost': -feeCostSecond, + 'currency': feeCurrencyCodeSecond, + }, + ]; + } else { + fee = { + 'cost': -feeCostFirst, + 'currency': feeCurrencyCodeFirst, + }; + } + } else if ((feeCostSecond !== undefined) && (feeCostSecond !== 0)) { + fee = { + 'cost': -feeCostSecond, + 'currency': feeCurrencyCodeSecond, + }; + } else { + fee = { + 'cost': 0, + 'currency': this.safeCurrencyCode (feeCurrencyId), + }; + } + // + // simplified structures to show the underlying semantics + // + // // market/limit sell + // + // { + // "currency":"USDT", + // "fee":"-0.04647925", // ←--- fee in received quote currency + // "price":"129.13", // ←------ price + // "size":"30.98616393", // ←-- cost + // }, + // { + // "currency":"ETH", + // "fee":"0", + // "price":"129.13", + // "size":"0.23996099", // ←--- amount + // }, + // + // // market/limit buy + // + // { + // "currency":"ETH", + // "fee":"-0.00036049", // ←--- fee in received base currency + // "price":"129.16", // ←------ price + // "size":"0.240322", // ←----- amount + // }, + // { + // "currency":"USDT", + // "fee":"0", + // "price":"129.16", + // "size":"31.03998952", // ←-- cost + // } + // + const timestamp = this.parse8601 (this.safeString2 (userTrade, 'timestamp', 'created_at')); + let takerOrMaker = this.safeString2 (userTrade, 'exec_type', 'liquidity'); + if (takerOrMaker === 'M') { + takerOrMaker = 'maker'; + } else if (takerOrMaker === 'T') { + takerOrMaker = 'taker'; + } + const orderId = this.safeString (userTrade, 'order_id'); + const result = { + 'info': pair, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + if (fees !== undefined) { + result['fees'] = fees; + } + return result; + } + + parseMyTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { + const grouped = this.groupBy (trades, 'trade_id'); + const tradeIds = Object.keys (grouped); + const result = []; + for (let i = 0; i < tradeIds.length; i++) { + const tradeId = tradeIds[i]; + const pair = grouped[tradeId]; + // make sure it has exactly 2 trades, no more, no less + const numTradesInPair = pair.length; + if (numTradesInPair === 2) { + const trade = this.parseMyTrade (pair); + result.push (trade); + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // okex actually returns ledger entries instead of fills here, so each fill in the order + // is represented by two trades with opposite buy/sell sides, not one :\ + // this aspect renders the 'fills' endpoint unusable for fetchOrderTrades + // until either OKEX fixes the API or we workaround this on our side somehow + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if ((limit !== undefined) && (limit > 100)) { + limit = 100; + } + const request = { + 'instrument_id': market['id'], + // 'order_id': id, // string + // 'after': '1', // pagination of data to return records earlier than the requested ledger_id + // 'before': '1', // P=pagination of data to return records newer than the requested ledger_id + // 'limit': limit, // optional, number of results per request, default = maximum = 100 + }; + const defaultType = this.safeString2 (this.options, 'fetchMyTrades', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + const method = type + 'GetFills'; + const response = await this[method] (this.extend (request, query)); + // + // [ + // // sell + // { + // "created_at":"2020-03-29T11:55:25.000Z", + // "currency":"USDT", + // "exec_type":"T", + // "fee":"-0.04647925", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562924353", + // "liquidity":"T", + // "order_id":"4636470489136128", + // "price":"129.13", + // "product_id":"ETH-USDT", + // "side":"buy", + // "size":"30.98616393", + // "timestamp":"2020-03-29T11:55:25.000Z", + // "trade_id":"18551601" + // }, + // { + // "created_at":"2020-03-29T11:55:25.000Z", + // "currency":"ETH", + // "exec_type":"T", + // "fee":"0", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562924352", + // "liquidity":"T", + // "order_id":"4636470489136128", + // "price":"129.13", + // "product_id":"ETH-USDT", + // "side":"sell", + // "size":"0.23996099", + // "timestamp":"2020-03-29T11:55:25.000Z", + // "trade_id":"18551601" + // }, + // // buy + // { + // "created_at":"2020-03-29T11:55:16.000Z", + // "currency":"ETH", + // "exec_type":"T", + // "fee":"-0.00036049", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562922669", + // "liquidity":"T", + // "order_id": "4636469894136832", + // "price":"129.16", + // "product_id":"ETH-USDT", + // "side":"buy", + // "size":"0.240322", + // "timestamp":"2020-03-29T11:55:16.000Z", + // "trade_id":"18551600" + // }, + // { + // "created_at":"2020-03-29T11:55:16.000Z", + // "currency":"USDT", + // "exec_type":"T", + // "fee":"0", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562922668", + // "liquidity":"T", + // "order_id":"4636469894136832", + // "price":"129.16", + // "product_id":"ETH-USDT", + // "side":"sell", + // "size":"31.03998952", + // "timestamp":"2020-03-29T11:55:16.000Z", + // "trade_id":"18551600" + // } + // ] + // + return this.parseMyTrades (response, market, since, limit, params); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + // 'instrument_id': market['id'], + 'order_id': id, + // 'after': '1', // return the page after the specified page number + // 'before': '1', // return the page before the specified page number + // 'limit': limit, // optional, number of results per request, default = maximum = 100 + }; + return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params)); + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + const request = { + 'instrument_id': market['id'], + // 'order_id': id, // string + // 'after': '1', // pagination of data to return records earlier than the requested ledger_id + // 'before': '1', // P=pagination of data to return records newer than the requested ledger_id + // 'limit': limit, // optional, number of results per request, default = maximum = 100 + }; + const type = market['type']; + if ((type === 'futures') || (type === 'swap')) { + method = type + 'GetInstrumentIdPosition'; + } else if (type === 'option') { + const underlying = this.safeString (params, 'underlying'); + if (underlying === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPosition() requires an underlying parameter for ' + type + ' market ' + symbol); + } + method = type + 'GetUnderlyingPosition'; + } else { + throw new NotSupported (this.id + ' fetchPosition() does not support ' + type + ' market ' + symbol + ', supported market types are futures, swap or option'); + } + const response = await this[method] (this.extend (request, params)); + // + // futures + // + // crossed margin mode + // + // { + // "result": true, + // "holding": [ + // { + // "long_qty": "2", + // "long_avail_qty": "2", + // "long_avg_cost": "8260", + // "long_settlement_price": "8260", + // "realised_pnl": "0.00020928", + // "short_qty": "2", + // "short_avail_qty": "2", + // "short_avg_cost": "8259.99", + // "short_settlement_price": "8259.99", + // "liquidation_price": "113.81", + // "instrument_id": "BTC-USD-191227", + // "leverage": "10", + // "created_at": "2019-09-25T07:58:42.129Z", + // "updated_at": "2019-10-08T14:02:51.029Z", + // "margin_mode": "crossed", + // "short_margin": "0.00242197", + // "short_pnl": "6.63E-6", + // "short_pnl_ratio": "0.002477997", + // "short_unrealised_pnl": "6.63E-6", + // "long_margin": "0.00242197", + // "long_pnl": "-6.65E-6", + // "long_pnl_ratio": "-0.002478", + // "long_unrealised_pnl": "-6.65E-6", + // "long_settled_pnl": "0", + // "short_settled_pnl": "0", + // "last": "8257.57" + // } + // ], + // "margin_mode": "crossed" + // } + // + // fixed margin mode + // + // { + // "result": true, + // "holding": [ + // { + // "long_qty": "4", + // "long_avail_qty": "4", + // "long_margin": "0.00323844", + // "long_liqui_price": "7762.09", + // "long_pnl_ratio": "0.06052306", + // "long_avg_cost": "8234.43", + // "long_settlement_price": "8234.43", + // "realised_pnl": "-0.00000296", + // "short_qty": "2", + // "short_avail_qty": "2", + // "short_margin": "0.00241105", + // "short_liqui_price": "9166.74", + // "short_pnl_ratio": "0.03318052", + // "short_avg_cost": "8295.13", + // "short_settlement_price": "8295.13", + // "instrument_id": "BTC-USD-191227", + // "long_leverage": "15", + // "short_leverage": "10", + // "created_at": "2019-09-25T07:58:42.129Z", + // "updated_at": "2019-10-08T13:12:09.438Z", + // "margin_mode": "fixed", + // "short_margin_ratio": "0.10292507", + // "short_maint_margin_ratio": "0.005", + // "short_pnl": "7.853E-5", + // "short_unrealised_pnl": "7.853E-5", + // "long_margin_ratio": "0.07103743", + // "long_maint_margin_ratio": "0.005", + // "long_pnl": "1.9841E-4", + // "long_unrealised_pnl": "1.9841E-4", + // "long_settled_pnl": "0", + // "short_settled_pnl": "0", + // "last": "8266.99" + // } + // ], + // "margin_mode": "fixed" + // } + // + // swap + // + // crossed margin mode + // + // { + // "margin_mode": "crossed", + // "timestamp": "2019-09-27T03:49:02.018Z", + // "holding": [ + // { + // "avail_position": "3", + // "avg_cost": "59.49", + // "instrument_id": "LTC-USD-SWAP", + // "last": "55.98", + // "leverage": "10.00", + // "liquidation_price": "4.37", + // "maint_margin_ratio": "0.0100", + // "margin": "0.0536", + // "position": "3", + // "realized_pnl": "0.0000", + // "unrealized_pnl": "0", + // "settled_pnl": "-0.0330", + // "settlement_price": "55.84", + // "side": "long", + // "timestamp": "2019-09-27T03:49:02.018Z" + // }, + // ] + // } + // + // fixed margin mode + // + // { + // "margin_mode": "fixed", + // "timestamp": "2019-09-27T03:47:37.230Z", + // "holding": [ + // { + // "avail_position": "20", + // "avg_cost": "8025.0", + // "instrument_id": "BTC-USD-SWAP", + // "last": "8113.1", + // "leverage": "15.00", + // "liquidation_price": "7002.6", + // "maint_margin_ratio": "0.0050", + // "margin": "0.0454", + // "position": "20", + // "realized_pnl": "-0.0001", + // "unrealized_pnl": "0", + // "settled_pnl": "0.0076", + // "settlement_price": "8279.2", + // "side": "long", + // "timestamp": "2019-09-27T03:47:37.230Z" + // } + // ] + // } + // + // option + // + // { + // "holding":[ + // { + // "instrument_id":"BTC-USD-190927-12500-C", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.017", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // }, + // { + // "instrument_id":"BTC-USD-190927-12500-P", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.019", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // } + // ] + // } + // + // todo unify parsePosition/parsePositions + return response; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let method = undefined; + const defaultType = this.safeString2 (this.options, 'fetchPositions', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if ((type === 'futures') || (type === 'swap')) { + method = type + 'GetPosition'; + } else if (type === 'option') { + const underlying = this.safeString (params, 'underlying'); + if (underlying === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPositions() requires an underlying parameter for ' + type + ' markets'); + } + method = type + 'GetUnderlyingPosition'; + } else { + throw new NotSupported (this.id + ' fetchPositions() does not support ' + type + ' markets, supported market types are futures, swap or option'); + } + params = this.omit (params, 'type'); + const response = await this[method] (params); + // + // futures + // + // ... + // + // + // swap + // + // ... + // + // option + // + // { + // "holding":[ + // { + // "instrument_id":"BTC-USD-190927-12500-C", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.017", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // }, + // { + // "instrument_id":"BTC-USD-190927-12500-P", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.019", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // } + // ] + // } + // + // todo unify parsePosition/parsePositions + return response; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchLedger', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + const suffix = (type === 'account') ? '' : 'Accounts'; + let argument = ''; + const request = { + // 'from': 'id', + // 'to': 'id', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + let currency = undefined; + if ((type === 'spot') || (type === 'futures')) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + " fetchLedger requires a currency code argument for '" + type + "' markets"); + } + argument = 'Currency'; + currency = this.currency (code); + request['currency'] = currency['id']; + } else if ((type === 'margin') || (type === 'swap')) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + " fetchLedger requires a code argument (a market symbol) for '" + type + "' markets"); + } + argument = 'InstrumentId'; + const market = this.market (code); // we intentionally put a market inside here for the margin and swap ledgers + currency = this.currency (market['base']); + request['instrument_id'] = market['id']; + // + // if (type === 'margin') { + // // + // // 3. Borrow + // // 4. Repayment + // // 5. Interest + // // 7. Buy + // // 8. Sell + // // 9. From capital account + // // 10. From C2C + // // 11. From Futures + // // 12. From Spot + // // 13. From ETT + // // 14. To capital account + // // 15. To C2C + // // 16. To Spot + // // 17. To Futures + // // 18. To ETT + // // 19. Mandatory Repayment + // // 20. From Piggybank + // // 21. To Piggybank + // // 22. From Perpetual + // // 23. To Perpetual + // // 24. Liquidation Fee + // // 54. Clawback + // // 59. Airdrop Return. + // // + // request['type'] = 'number'; // All types will be returned if this filed is left blank + // } + // + } else if (type === 'account') { + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + // + // // + // // 1. deposit + // // 2. withdrawal + // // 13. cancel withdrawal + // // 18. into futures account + // // 19. out of futures account + // // 20. into sub account + // // 21. out of sub account + // // 28. claim + // // 29. into ETT account + // // 30. out of ETT account + // // 31. into C2C account + // // 32. out of C2C account + // // 33. into margin account + // // 34. out of margin account + // // 37. into spot account + // // 38. out of spot account + // // + // request['type'] = 'number'; + // + } else { + throw new NotSupported (this.id + " fetchLedger does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + const method = type + 'Get' + suffix + argument + 'Ledger'; + const response = await this[method] (this.extend (request, query)); + // + // transfer funds transfer in/out + // trade funds moved as a result of a trade, spot and margin accounts only + // rebate fee rebate as per fee schedule, spot and margin accounts only + // match open long/open short/close long/close short (futures) or a change in the amount because of trades (swap) + // fee fee, futures only + // settlement settlement/clawback/settle long/settle short + // liquidation force close long/force close short/deliver close long/deliver close short + // funding funding fee, swap only + // margin a change in the amount after adjusting margin, swap only + // + // account + // + // [ + // { + // "amount":0.00051843, + // "balance":0.00100941, + // "currency":"BTC", + // "fee":0, + // "ledger_id":8987285, + // "timestamp":"2018-10-12T11:01:14.000Z", + // "typename":"Get from activity" + // } + // ] + // + // spot + // + // [ + // { + // "timestamp":"2019-03-18T07:08:25.000Z", + // "ledger_id":"3995334780", + // "created_at":"2019-03-18T07:08:25.000Z", + // "currency":"BTC", + // "amount":"0.0009985", + // "balance":"0.0029955", + // "type":"trade", + // "details":{ + // "instrument_id":"BTC-USDT", + // "order_id":"2500650881647616", + // "product_id":"BTC-USDT" + // } + // } + // ] + // + // margin + // + // [ + // [ + // { + // "created_at":"2019-03-20T03:45:05.000Z", + // "ledger_id":"78918186", + // "timestamp":"2019-03-20T03:45:05.000Z", + // "currency":"EOS", + // "amount":"0", // ? + // "balance":"0.59957711", + // "type":"transfer", + // "details":{ + // "instrument_id":"EOS-USDT", + // "order_id":"787057", + // "product_id":"EOS-USDT" + // } + // } + // ], + // { + // "before":"78965766", + // "after":"78918186" + // } + // ] + // + // futures + // + // [ + // { + // "ledger_id":"2508090544914461", + // "timestamp":"2019-03-19T14:40:24.000Z", + // "amount":"-0.00529521", + // "balance":"0", + // "currency":"EOS", + // "type":"fee", + // "details":{ + // "order_id":"2506982456445952", + // "instrument_id":"EOS-USD-190628" + // } + // } + // ] + // + // swap + // + // [ + // { + // "amount":"0.004742", + // "fee":"-0.000551", + // "type":"match", + // "instrument_id":"EOS-USD-SWAP", + // "ledger_id":"197429674941902848", + // "timestamp":"2019-03-25T05:56:31.286Z" + // }, + // ] + // + const responseLength = response.length; + if (responseLength < 1) { + return []; + } + const isArray = Array.isArray (response[0]); + const isMargin = (type === 'margin'); + const entries = (isMargin && isArray) ? response[0] : response; + if (type === 'swap') { + const ledgerEntries = this.parseLedger (entries); + return this.filterBySymbolSinceLimit (ledgerEntries, code, since, limit); + } + return this.parseLedger (entries, currency, since, limit); + } + + parseLedgerEntryType (type) { + const types = { + 'transfer': 'transfer', // // funds transfer in/out + 'trade': 'trade', // funds moved as a result of a trade, spot and margin accounts only + 'rebate': 'rebate', // fee rebate as per fee schedule, spot and margin accounts only + 'match': 'trade', // open long/open short/close long/close short (futures) or a change in the amount because of trades (swap) + 'fee': 'fee', // fee, futures only + 'settlement': 'trade', // settlement/clawback/settle long/settle short + 'liquidation': 'trade', // force close long/force close short/deliver close long/deliver close short + 'funding': 'fee', // funding fee, swap only + 'margin': 'margin', // a change in the amount after adjusting margin, swap only + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // + // account + // + // { + // "amount":0.00051843, + // "balance":0.00100941, + // "currency":"BTC", + // "fee":0, + // "ledger_id":8987285, + // "timestamp":"2018-10-12T11:01:14.000Z", + // "typename":"Get from activity" + // } + // + // spot + // + // { + // "timestamp":"2019-03-18T07:08:25.000Z", + // "ledger_id":"3995334780", + // "created_at":"2019-03-18T07:08:25.000Z", + // "currency":"BTC", + // "amount":"0.0009985", + // "balance":"0.0029955", + // "type":"trade", + // "details":{ + // "instrument_id":"BTC-USDT", + // "order_id":"2500650881647616", + // "product_id":"BTC-USDT" + // } + // } + // + // margin + // + // { + // "created_at":"2019-03-20T03:45:05.000Z", + // "ledger_id":"78918186", + // "timestamp":"2019-03-20T03:45:05.000Z", + // "currency":"EOS", + // "amount":"0", // ? + // "balance":"0.59957711", + // "type":"transfer", + // "details":{ + // "instrument_id":"EOS-USDT", + // "order_id":"787057", + // "product_id":"EOS-USDT" + // } + // } + // + // futures + // + // { + // "ledger_id":"2508090544914461", + // "timestamp":"2019-03-19T14:40:24.000Z", + // "amount":"-0.00529521", + // "balance":"0", + // "currency":"EOS", + // "type":"fee", + // "details":{ + // "order_id":"2506982456445952", + // "instrument_id":"EOS-USD-190628" + // } + // } + // + // swap + // + // { + // "amount":"0.004742", + // "fee":"-0.000551", + // "type":"match", + // "instrument_id":"EOS-USD-SWAP", + // "ledger_id":"197429674941902848", + // "timestamp":"2019-03-25T05:56:31.286Z" + // }, + // + const id = this.safeString (item, 'ledger_id'); + const account = undefined; + const details = this.safeValue (item, 'details', {}); + const referenceId = this.safeString (details, 'order_id'); + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const code = this.safeCurrencyCode (this.safeString (item, 'currency'), currency); + const amount = this.safeFloat (item, 'amount'); + const timestamp = this.parse8601 (this.safeString (item, 'timestamp')); + const fee = { + 'cost': this.safeFloat (item, 'fee'), + 'currency': code, + }; + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + const marketId = this.safeString (item, 'instrument_id'); + let symbol = undefined; + if (marketId in this.markets_by_id) { + const market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } + return { + 'info': item, + 'id': id, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'symbol': symbol, + 'amount': amount, + 'before': before, // balance before + 'after': after, // balance after + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const isArray = Array.isArray (params); + let request = '/api/' + api + '/' + this.version + '/'; + request += isArray ? path : this.implodeParams (path, params); + const query = isArray ? params : this.omit (params, this.extractParams (path)); + let url = this.implodeParams (this.urls['api']['rest'], { 'hostname': this.hostname }) + request; + const type = this.getPathAuthenticationType (path); + if (type === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (type === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.iso8601 (this.milliseconds ()); + headers = { + 'OK-ACCESS-KEY': this.apiKey, + 'OK-ACCESS-PASSPHRASE': this.password, + 'OK-ACCESS-TIMESTAMP': timestamp, + // 'OK-FROM': '', + // 'OK-TO': '', + // 'OK-LIMIT': '', + }; + let auth = timestamp + method + request; + if (method === 'GET') { + if (Object.keys (query).length) { + const urlencodedQuery = '?' + this.urlencode (query); + url += urlencodedQuery; + auth += urlencodedQuery; + } + } else { + if (isArray || Object.keys (query).length) { + body = this.json (query); + auth += body; + } + headers['Content-Type'] = 'application/json'; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); + headers['OK-ACCESS-SIGN'] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + getPathAuthenticationType (path) { + // https://github.com/ccxt/ccxt/issues/6651 + // a special case to handle the optionGetUnderlying interefering with + // other endpoints containing this keyword + if (path === 'underlying') { + return 'public'; + } + const auth = this.safeValue (this.options, 'auth', {}); + const key = this.findBroadlyMatchedKey (auth, path); + return this.safeString (auth, key, 'private'); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + const feedback = this.id + ' ' + body; + if (code === 503) { + // {"message":"name resolution failed"} + throw new ExchangeNotAvailable (feedback); + } + // + // {"error_message":"Order does not exist","result":"true","error_code":"35029","order_id":"-1"} + // + const message = this.safeString (response, 'message'); + const errorCode = this.safeString2 (response, 'code', 'error_code'); + const nonEmptyMessage = ((message !== undefined) && (message !== '')); + const nonZeroErrorCode = (errorCode !== undefined) && (errorCode !== '0'); + if (nonEmptyMessage) { + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + if (nonZeroErrorCode) { + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + if (nonZeroErrorCode || nonEmptyMessage) { + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],115:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class paymium extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'paymium', + 'name': 'Paymium', + 'countries': [ 'FR', 'EU' ], + 'rateLimit': 2000, + 'version': 'v1', + 'has': { + 'CORS': true, + 'fetchBalance': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchOrderBook': true, + 'createOrder': true, + 'cancelOrder': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153930-f0f02200-c2c0-11ea-9c0a-40337375ae89.jpg', + 'api': 'https://paymium.com/api', + 'www': 'https://www.paymium.com', + 'fees': 'https://www.paymium.com/page/help/fees', + 'doc': [ + 'https://github.com/Paymium/api-documentation', + 'https://www.paymium.com/page/developers', + ], + 'referral': 'https://www.paymium.com/page/sign-up?referral=eDAzPoRQFMvaAB8sf-qj', + }, + 'api': { + 'public': { + 'get': [ + 'countries', + 'data/{currency}/ticker', + 'data/{currency}/trades', + 'data/{currency}/depth', + 'bitcoin_charts/{id}/trades', + 'bitcoin_charts/{id}/depth', + ], + }, + 'private': { + 'get': [ + 'user', + 'user/addresses', + 'user/addresses/{address}', + 'user/orders', + 'user/orders/{uuid}', + 'user/price_alerts', + 'merchant/get_payment/{uuid}', + ], + 'post': [ + 'user/addresses', + 'user/orders', + 'user/withdrawals', + 'user/email_transfers', + 'user/payment_requests', + 'user/price_alerts', + 'merchant/create_payment', + ], + 'delete': [ + 'user/orders/{uuid}', + 'user/orders/{uuid}/cancel', + 'user/price_alerts/{id}', + ], + }, + }, + 'markets': { + 'BTC/EUR': { 'id': 'eur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur' }, + }, + 'fees': { + 'trading': { + 'maker': 0.002, + 'taker': 0.002, + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUser (params); + const result = { 'info': response }; + const currencies = Object.keys (this.currencies); + for (let i = 0; i < currencies.length; i++) { + const code = currencies[i]; + const currencyId = this.currencyId (code); + const free = 'balance_' + currencyId; + if (free in response) { + const account = this.account (); + const used = 'locked_' + currencyId; + account['free'] = this.safeFloat (response, free); + account['used'] = this.safeFloat (response, used); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': this.marketId (symbol), + }; + const response = await this.publicGetDataCurrencyDepth (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': this.marketId (symbol), + }; + const ticker = await this.publicGetDataCurrencyTicker (this.extend (request, params)); + const timestamp = this.safeTimestamp (ticker, 'at'); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'price'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'variation'), + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market) { + const timestamp = this.safeTimestamp (trade, 'created_at_int'); + const id = this.safeString (trade, 'uuid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amountField = 'traded_' + market['base'].toLowerCase (); + const amount = this.safeFloat (trade, amountField); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.publicGetDataCurrencyTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'type': this.capitalize (type) + 'Order', + 'currency': this.marketId (symbol), + 'direction': side, + 'amount': amount, + }; + if (type !== 'market') { + request['price'] = price; + } + const response = await this.privatePostUserOrders (this.extend (request, params)); + return { + 'info': response, + 'id': response['uuid'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'uuid': id, + }; + return await this.privateDeleteUserOrdersUuidCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let auth = nonce + url; + headers = { + 'Api-Key': this.apiKey, + 'Api-Nonce': nonce, + }; + if (method === 'POST') { + if (Object.keys (query).length) { + body = this.json (query); + auth += body; + headers['Content-Type'] = 'application/json'; + } + } else { + if (Object.keys (query).length) { + const queryString = this.urlencode (query); + auth += queryString; + url += '?' + queryString; + } + } + headers['Api-Signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('errors' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],116:[function(require,module,exports){ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, BadRequest, PermissionDenied, AccountSuspended, CancelPending, DDoSProtection, DuplicateOrderId, NotSupported } = require ('./base/errors'); +const { TICK_SIZE, ROUND, DECIMAL_PLACES } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class phemex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'phemex', + 'name': 'Phemex', + 'countries': [ 'CN' ], // China + 'rateLimit': 100, + 'version': 'v1', + 'certified': false, + 'pro': true, + 'has': { + 'cancelAllOrders': true, // swap contracts only + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg', + 'test': { + 'v1': 'https://testnet-api.phemex.com/v1', + 'public': 'https://testnet-api.phemex.com/exchange/public', + 'private': 'https://testnet-api.phemex.com', + }, + 'api': { + 'v1': 'https://api.phemex.com/v1', + 'public': 'https://api.phemex.com/exchange/public', + 'private': 'https://api.phemex.com', + }, + 'www': 'https://phemex.com', + 'doc': 'https://github.com/phemex/phemex-api-docs', + 'fees': 'https://phemex.com/fees-conditions', + 'referral': 'https://phemex.com/register?referralCode=EDNVJ', + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '3h': '10800', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '604800', + '1M': '2592000', + }, + 'api': { + 'public': { + 'get': [ + 'cfg/v2/products', // spot + contracts + 'products', // contracts only + 'nomics/trades', // ?market=&since= + 'md/kline', // ?from=1589811875&resolution=1800&symbol=sBTCUSDT&to=1592457935 + ], + }, + 'v1': { + 'get': [ + 'md/orderbook', // ?symbol=&id= + 'md/trade', // ?symbol=&id= + 'md/ticker/24hr', // ?symbol=&id= + 'md/ticker/24hr/all', // ?id= + 'md/spot/ticker/24hr', // ?symbol=&id= + 'md/spot/ticker/24hr/all', // ?symbol=&id= + 'exchange/public/products', // contracts only + ], + }, + 'private': { + 'get': [ + // spot + 'spot/orders/active', // ?symbol=&orderID= + // 'spot/orders/active', // ?symbol=&clOrDID= + 'spot/orders', // ?symbol= + 'spot/wallets', // ?currency= + 'exchange/spot/order', // ?symbol=&ordStatus=ordType=&start=&end=&limit=&offset= + 'exchange/spot/order/trades', // ?symbol=&start=&end=&limit=&offset= + // swap + 'accounts/accountPositions', // ?currency= + 'orders/activeList', // ?symbol= + 'exchange/order/list', // ?symbol=&start=&end=&offset=&limit=&ordStatus=&withCount= + 'exchange/order', // ?symbol=&orderID= + // 'exchange/order', // ?symbol=&clOrdID= + 'exchange/order/trade', // ?symbol=&start=&end=&limit=&offset=&withCount= + 'phemex-user/users/children', // ?offset=&limit=&withCount= + 'phemex-user/wallets/v2/depositAddress', // ?_t=1592722635531¤cy=USDT + 'exchange/margins/transfer', // ?start=&end=&offset=&limit=&withCount= + 'exchange/wallets/confirm/withdraw', // ?code= + 'exchange/wallets/withdrawList', // ?currency=&limit=&offset=&withCount= + 'exchange/wallets/depositList', // ?currency=&offset=&limit= + 'exchange/wallets/v2/depositAddress', // ?currency= + ], + 'post': [ + // spot + 'spot/orders', + // swap + 'orders', + 'positions/assign', // ?symbol=&posBalance=&posBalanceEv= + 'exchange/wallets/transferOut', + 'exchange/wallets/transferIn', + 'exchange/margins', + 'exchange/wallets/createWithdraw', // ?otpCode= + 'exchange/wallets/cancelWithdraw', + 'exchange/wallets/createWithdrawAddress', // ?otpCode={optCode} + ], + 'put': [ + // spot + 'spot/orders', // ?symbol=&orderID=&origClOrdID=&clOrdID=&priceEp=&baseQtyEV="eQtyEv=&stopPxEp= + // swap + 'orders/replace', // ?symbol=&orderID=&origClOrdID=&clOrdID=&price=&priceEp=&orderQty=&stopPx=&stopPxEp=&takeProfit=&takeProfitEp=&stopLoss=&stopLossEp=&pegOffsetValueEp=&pegPriceType= + 'positions/leverage', // ?symbol=&leverage=&leverageEr= + 'positions/riskLimit', // ?symbol=&riskLimit=&riskLimitEv= + ], + 'delete': [ + // spot + 'spot/orders', // ?symbol=&orderID= + // 'spot/orders', // ?symbol=&clOrdID= + // swap + 'orders/cancel', // ?symbol=&orderID= + 'orders', // ?symbol=&orderID=,, + 'orders/all', // ?symbol=&untriggered=&text= + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.1 / 100, + 'maker': 0.1 / 100, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'exceptions': { + 'exact': { + // not documented + '412': BadRequest, // {"code":412,"msg":"Missing parameter - resolution","data":null} + '6001': BadRequest, // {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null} + // documented + '19999': BadRequest, // REQUEST_IS_DUPLICATED Duplicated request ID + '10001': DuplicateOrderId, // OM_DUPLICATE_ORDERID Duplicated order ID + '10002': OrderNotFound, // OM_ORDER_NOT_FOUND Cannot find order ID + '10003': CancelPending, // OM_ORDER_PENDING_CANCEL Cannot cancel while order is already in pending cancel status + '10004': CancelPending, // OM_ORDER_PENDING_REPLACE Cannot cancel while order is already in pending cancel status + '10005': CancelPending, // OM_ORDER_PENDING Cannot cancel while order is already in pending cancel status + '11001': InsufficientFunds, // TE_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance + '11002': InvalidOrder, // TE_INVALID_RISK_LIMIT Invalid risk limit value + '11003': InsufficientFunds, // TE_NO_ENOUGH_BALANCE_FOR_NEW_RISK_LIMIT Insufficient available balance + '11004': InvalidOrder, // TE_INVALID_LEVERAGE invalid input or new leverage is over maximum allowed leverage + '11005': InsufficientFunds, // TE_NO_ENOUGH_BALANCE_FOR_NEW_LEVERAGE Insufficient available balance + '11006': ExchangeError, // TE_CANNOT_CHANGE_POSITION_MARGIN_WITHOUT_POSITION Position size is zero. Cannot change margin + '11007': ExchangeError, // TE_CANNOT_CHANGE_POSITION_MARGIN_FOR_CROSS_MARGIN Cannot change margin under CrossMargin + '11008': ExchangeError, // TE_CANNOT_REMOVE_POSITION_MARGIN_MORE_THAN_ADDED exceeds the maximum removable Margin + '11009': ExchangeError, // TE_CANNOT_REMOVE_POSITION_MARGIN_DUE_TO_UNREALIZED_PNL exceeds the maximum removable Margin + '11010': InsufficientFunds, // TE_CANNOT_ADD_POSITION_MARGIN_DUE_TO_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance + '11011': InvalidOrder, // TE_REDUCE_ONLY_ABORT Cannot accept reduce only order + '11012': InvalidOrder, // TE_REPLACE_TO_INVALID_QTY Order quantity Error + '11013': InvalidOrder, // TE_CONDITIONAL_NO_POSITION Position size is zero. Cannot determine conditional order's quantity + '11014': InvalidOrder, // TE_CONDITIONAL_CLOSE_POSITION_WRONG_SIDE Close position conditional order has the same side + '11015': InvalidOrder, // TE_CONDITIONAL_TRIGGERED_OR_CANCELED + '11016': BadRequest, // TE_ADL_NOT_TRADING_REQUESTED_ACCOUNT Request is routed to the wrong trading engine + '11017': ExchangeError, // TE_ADL_CANNOT_FIND_POSITION Cannot find requested position on current account + '11018': ExchangeError, // TE_NO_NEED_TO_SETTLE_FUNDING The current account does not need to pay a funding fee + '11019': ExchangeError, // TE_FUNDING_ALREADY_SETTLED The current account already pays the funding fee + '11020': ExchangeError, // TE_CANNOT_TRANSFER_OUT_DUE_TO_BONUS Withdraw to wallet needs to remove all remaining bonus. However if bonus is used by position or order cost, withdraw fails. + '11021': ExchangeError, // TE_INVALID_BONOUS_AMOUNT // Grpc command cannot be negative number Invalid bonus amount + '11022': AccountSuspended, // TE_REJECT_DUE_TO_BANNED Account is banned + '11023': ExchangeError, // TE_REJECT_DUE_TO_IN_PROCESS_OF_LIQ Account is in the process of liquidation + '11024': ExchangeError, // TE_REJECT_DUE_TO_IN_PROCESS_OF_ADL Account is in the process of auto-deleverage + '11025': BadRequest, // TE_ROUTE_ERROR Request is routed to the wrong trading engine + '11026': ExchangeError, // TE_UID_ACCOUNT_MISMATCH + '11027': BadSymbol, // TE_SYMBOL_INVALID Invalid number ID or name + '11028': BadSymbol, // TE_CURRENCY_INVALID Invalid currency ID or name + '11029': ExchangeError, // TE_ACTION_INVALID Unrecognized request type + '11030': ExchangeError, // TE_ACTION_BY_INVALID + '11031': DDoSProtection, // TE_SO_NUM_EXCEEDS Number of total conditional orders exceeds the max limit + '11032': DDoSProtection, // TE_AO_NUM_EXCEEDS Number of total active orders exceeds the max limit + '11033': DuplicateOrderId, // TE_ORDER_ID_DUPLICATE Duplicated order ID + '11034': InvalidOrder, // TE_SIDE_INVALID Invalid side + '11035': InvalidOrder, // TE_ORD_TYPE_INVALID Invalid OrderType + '11036': InvalidOrder, // TE_TIME_IN_FORCE_INVALID Invalid TimeInForce + '11037': InvalidOrder, // TE_EXEC_INST_INVALID Invalid ExecType + '11038': InvalidOrder, // TE_TRIGGER_INVALID Invalid trigger type + '11039': InvalidOrder, // TE_STOP_DIRECTION_INVALID Invalid stop direction type + '11040': InvalidOrder, // TE_NO_MARK_PRICE Cannot get valid mark price to create conditional order + '11041': InvalidOrder, // TE_NO_INDEX_PRICE Cannot get valid index price to create conditional order + '11042': InvalidOrder, // TE_NO_LAST_PRICE Cannot get valid last market price to create conditional order + '11043': InvalidOrder, // TE_RISING_TRIGGER_DIRECTLY Conditional order would be triggered immediately + '11044': InvalidOrder, // TE_FALLING_TRIGGER_DIRECTLY Conditional order would be triggered immediately + '11045': InvalidOrder, // TE_TRIGGER_PRICE_TOO_LARGE Conditional order trigger price is too high + '11046': InvalidOrder, // TE_TRIGGER_PRICE_TOO_SMALL Conditional order trigger price is too low + '11047': InvalidOrder, // TE_BUY_TP_SHOULD_GT_BASE TakeProfile BUY conditional order trigger price needs to be greater than reference price + '11048': InvalidOrder, // TE_BUY_SL_SHOULD_LT_BASE StopLoss BUY condition order price needs to be less than the reference price + '11049': InvalidOrder, // TE_BUY_SL_SHOULD_GT_LIQ StopLoss BUY condition order price needs to be greater than liquidation price or it will not trigger + '11050': InvalidOrder, // TE_SELL_TP_SHOULD_LT_BASE TakeProfile SELL conditional order trigger price needs to be less than reference price + '11051': InvalidOrder, // TE_SELL_SL_SHOULD_LT_LIQ StopLoss SELL condition order price needs to be less than liquidation price or it will not trigger + '11052': InvalidOrder, // TE_SELL_SL_SHOULD_GT_BASE StopLoss SELL condition order price needs to be greater than the reference price + '11053': InvalidOrder, // TE_PRICE_TOO_LARGE + '11054': InvalidOrder, // TE_PRICE_WORSE_THAN_BANKRUPT Order price cannot be more aggressive than bankrupt price if this order has instruction to close a position + '11055': InvalidOrder, // TE_PRICE_TOO_SMALL Order price is too low + '11056': InvalidOrder, // TE_QTY_TOO_LARGE Order quantity is too large + '11057': InvalidOrder, // TE_QTY_NOT_MATCH_REDUCE_ONLY Does not allow ReduceOnly order without position + '11058': InvalidOrder, // TE_QTY_TOO_SMALL Order quantity is too small + '11059': InvalidOrder, // TE_TP_SL_QTY_NOT_MATCH_POS Position size is zero. Cannot accept any TakeProfit or StopLoss order + '11060': InvalidOrder, // TE_SIDE_NOT_CLOSE_POS TakeProfit or StopLoss order has wrong side. Cannot close position + '11061': CancelPending, // TE_ORD_ALREADY_PENDING_CANCEL Repeated cancel request + '11062': InvalidOrder, // TE_ORD_ALREADY_CANCELED Order is already canceled + '11063': InvalidOrder, // TE_ORD_STATUS_CANNOT_CANCEL Order is not able to be canceled under current status + '11064': InvalidOrder, // TE_ORD_ALREADY_PENDING_REPLACE Replace request is rejected because order is already in pending replace status + '11065': InvalidOrder, // TE_ORD_REPLACE_NOT_MODIFIED Replace request does not modify any parameters of the order + '11066': InvalidOrder, // TE_ORD_STATUS_CANNOT_REPLACE Order is not able to be replaced under current status + '11067': InvalidOrder, // TE_CANNOT_REPLACE_PRICE Market conditional order cannot change price + '11068': InvalidOrder, // TE_CANNOT_REPLACE_QTY Condtional order for closing position cannot change order quantity, since the order quantity is determined by position size already + '11069': ExchangeError, // TE_ACCOUNT_NOT_IN_RANGE The account ID in the request is not valid or is not in the range of the current process + '11070': BadSymbol, // TE_SYMBOL_NOT_IN_RANGE The symbol is invalid + '11071': InvalidOrder, // TE_ORD_STATUS_CANNOT_TRIGGER + '11072': InvalidOrder, // TE_TKFR_NOT_IN_RANGE The fee value is not valid + '11073': InvalidOrder, // TE_MKFR_NOT_IN_RANGE The fee value is not valid + '11074': InvalidOrder, // TE_CANNOT_ATTACH_TP_SL Order request cannot contain TP/SL parameters when the account already has positions + '11075': InvalidOrder, // TE_TP_TOO_LARGE TakeProfit price is too large + '11076': InvalidOrder, // TE_TP_TOO_SMALL TakeProfit price is too small + '11077': InvalidOrder, // TE_TP_TRIGGER_INVALID Invalid trigger type + '11078': InvalidOrder, // TE_SL_TOO_LARGE StopLoss price is too large + '11079': InvalidOrder, // TE_SL_TOO_SMALL StopLoss price is too small + '11080': InvalidOrder, // TE_SL_TRIGGER_INVALID Invalid trigger type + '11081': InvalidOrder, // TE_RISK_LIMIT_EXCEEDS Total potential position breaches current risk limit + '11082': InsufficientFunds, // TE_CANNOT_COVER_ESTIMATE_ORDER_LOSS The remaining balance cannot cover the potential unrealized PnL for this new order + '11083': InvalidOrder, // TE_TAKE_PROFIT_ORDER_DUPLICATED TakeProfit order already exists + '11084': InvalidOrder, // TE_STOP_LOSS_ORDER_DUPLICATED StopLoss order already exists + '11085': DuplicateOrderId, // TE_CL_ORD_ID_DUPLICATE ClOrdId is duplicated + '11086': InvalidOrder, // TE_PEG_PRICE_TYPE_INVALID PegPriceType is invalid + '11087': InvalidOrder, // TE_BUY_TS_SHOULD_LT_BASE The trailing order's StopPrice should be less than the current last price + '11088': InvalidOrder, // TE_BUY_TS_SHOULD_GT_LIQ The traling order's StopPrice should be greater than the current liquidation price + '11089': InvalidOrder, // TE_SELL_TS_SHOULD_LT_LIQ The traling order's StopPrice should be greater than the current last price + '11090': InvalidOrder, // TE_SELL_TS_SHOULD_GT_BASE The traling order's StopPrice should be less than the current liquidation price + '11091': InvalidOrder, // TE_BUY_REVERT_VALUE_SHOULD_LT_ZERO The PegOffset should be less than zero + '11092': InvalidOrder, // TE_SELL_REVERT_VALUE_SHOULD_GT_ZERO The PegOffset should be greater than zero + '11093': InvalidOrder, // TE_BUY_TTP_SHOULD_ACTIVATE_ABOVE_BASE The activation price should be greater than the current last price + '11094': InvalidOrder, // TE_SELL_TTP_SHOULD_ACTIVATE_BELOW_BASE The activation price should be less than the current last price + '11095': InvalidOrder, // TE_TRAILING_ORDER_DUPLICATED A trailing order exists already + '11096': InvalidOrder, // TE_CLOSE_ORDER_CANNOT_ATTACH_TP_SL An order to close position cannot have trailing instruction + '11097': BadRequest, // TE_CANNOT_FIND_WALLET_OF_THIS_CURRENCY This crypto is not supported + '11098': BadRequest, // TE_WALLET_INVALID_ACTION Invalid action on wallet + '11099': ExchangeError, // TE_WALLET_VID_UNMATCHED Wallet operation request has a wrong wallet vid + '11100': InsufficientFunds, // TE_WALLET_INSUFFICIENT_BALANCE Wallet has insufficient balance + '11101': InsufficientFunds, // TE_WALLET_INSUFFICIENT_LOCKED_BALANCE Locked balance in wallet is not enough for unlock/withdraw request + '11102': BadRequest, // TE_WALLET_INVALID_DEPOSIT_AMOUNT Deposit amount must be greater than zero + '11103': BadRequest, // TE_WALLET_INVALID_WITHDRAW_AMOUNT Withdraw amount must be less than zero + '11104': BadRequest, // TE_WALLET_REACHED_MAX_AMOUNT Deposit makes wallet exceed max amount allowed + '11105': InsufficientFunds, // TE_PLACE_ORDER_INSUFFICIENT_BASE_BALANCE Insufficient funds in base wallet + '11106': InsufficientFunds, // TE_PLACE_ORDER_INSUFFICIENT_QUOTE_BALANCE Insufficient funds in quote wallet + '11107': ExchangeError, // TE_CANNOT_CONNECT_TO_REQUEST_SEQ TradingEngine failed to connect with CrossEngine + '11108': InvalidOrder, // TE_CANNOT_REPLACE_OR_CANCEL_MARKET_ORDER Cannot replace/amend market order + '11109': InvalidOrder, // TE_CANNOT_REPLACE_OR_CANCEL_IOC_ORDER Cannot replace/amend ImmediateOrCancel order + '11110': InvalidOrder, // TE_CANNOT_REPLACE_OR_CANCEL_FOK_ORDER Cannot replace/amend FillOrKill order + '11111': InvalidOrder, // TE_MISSING_ORDER_ID OrderId is missing + '11112': InvalidOrder, // TE_QTY_TYPE_INVALID QtyType is invalid + '11113': BadRequest, // TE_USER_ID_INVALID UserId is invalid + '11114': InvalidOrder, // TE_ORDER_VALUE_TOO_LARGE Order value is too large + '11115': InvalidOrder, // TE_ORDER_VALUE_TOO_SMALL Order value is too small + // not documented + '30018': BadRequest, // {"code":30018,"msg":"phemex.data.size.uplimt","data":null} + '39996': PermissionDenied, // {"code": "39996","msg": "Access denied."} + }, + 'broad': { + 'Failed to find api-key': AuthenticationError, // {"msg":"Failed to find api-key 1c5ec63fd-660d-43ea-847a-0d3ba69e106e","code":10500} + 'Missing required parameter': BadRequest, // {"msg":"Missing required parameter","code":10500} + 'API Signature verification failed': AuthenticationError, // {"msg":"API Signature verification failed.","code":10500} + }, + }, + 'options': { + 'x-phemex-request-expiry': 60, // in seconds + 'createOrderByQuoteRequiresPrice': true, + }, + }); + } + + parseSafeFloat (value = undefined) { + if (value === undefined) { + return value; + } + value = value.replace (',', ''); + const parts = value.split (' '); + return this.safeFloat (parts, 0); + } + + parseSwapMarket (market) { + // + // { + // "symbol":"BTCUSD", + // "displaySymbol":"BTC / USD", + // "indexSymbol":".BTC", + // "markSymbol":".MBTC", + // "fundingRateSymbol":".BTCFR", + // "fundingRate8hSymbol":".BTCFR8H", + // "contractUnderlyingAssets":"USD", + // "settleCurrency":"BTC", + // "quoteCurrency":"USD", + // "contractSize":"1 USD", + // "lotSize":1, + // "tickSize":0.5, + // "priceScale":4, + // "ratioScale":8, + // "pricePrecision":1, + // "minPriceEp":5000, + // "maxPriceEp":10000000000, + // "maxOrderQty":1000000, + // "type":"Perpetual" + // "steps":"50", + // "riskLimits":[ + // {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000}, + // {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000}, + // {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000}, + // ], + // "underlyingSymbol":".BTC", + // "baseCurrency":"BTC", + // "settlementCurrency":"BTC", + // "valueScale":8, + // "defaultLeverage":0, + // "maxLeverage":100, + // "initMarginEr":"1000000", + // "maintMarginEr":"500000", + // "defaultRiskLimitEv":10000000000, + // "deleverage":true, + // "makerFeeRateEr":-250000, + // "takerFeeRateEr":750000, + // "fundingInterval":8, + // "marketUrl":"https://phemex.com/trade/BTCUSD", + // "description":"BTCUSD is a BTC/USD perpetual contract priced on the .BTC Index. Each contract is worth 1 USD of Bitcoin. Funding is paid and received every 8 hours. At UTC time: 00:00, 08:00, 16:00.", + // } + // + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseCurrency', 'contractUnderlyingAssets'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const type = this.safeStringLower (market, 'type'); + let taker = undefined; + let maker = undefined; + let inverse = false; + const spot = false; + const swap = true; + const settlementCurrencyId = this.safeString (market, 'settlementCurrency'); + if (settlementCurrencyId !== quoteId) { + inverse = true; + } + const linear = !inverse; + const precision = { + 'amount': this.safeFloat (market, 'lotSize'), + 'price': this.safeFloat (market, 'tickSize'), + }; + const priceScale = this.safeInteger (market, 'priceScale'); + const ratioScale = this.safeInteger (market, 'ratioScale'); + const valueScale = this.safeInteger (market, 'valueScale'); + const minPriceEp = this.safeFloat (market, 'minPriceEp'); + const maxPriceEp = this.safeFloat (market, 'maxPriceEp'); + const makerFeeRateEr = this.safeFloat (market, 'makerFeeRateEr'); + const takerFeeRateEr = this.safeFloat (market, 'takerFeeRateEr'); + if (makerFeeRateEr !== undefined) { + maker = this.fromEn (makerFeeRateEr, ratioScale, 0.00000001); + } + if (takerFeeRateEr !== undefined) { + taker = this.fromEn (takerFeeRateEr, ratioScale, 0.00000001); + } + const limits = { + 'amount': { + 'min': precision['amount'], + 'max': undefined, + }, + 'price': { + 'min': this.fromEn (minPriceEp, priceScale, precision['price']), + 'max': this.fromEn (maxPriceEp, priceScale, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': this.parseSafeFloat (this.safeString (market, 'maxOrderQty')), + }, + }; + const active = undefined; + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'swap': swap, + 'linear': linear, + 'inverse': inverse, + 'active': active, + 'taker': taker, + 'maker': maker, + 'priceScale': priceScale, + 'valueScale': valueScale, + 'ratioScale': ratioScale, + 'precision': precision, + 'limits': limits, + }; + } + + parseSpotMarket (market) { + // + // { + // "symbol":"sBTCUSDT", + // "displaySymbol":"BTC / USDT", + // "quoteCurrency":"USDT", + // "pricePrecision":2, + // "type":"Spot", + // "baseCurrency":"BTC", + // "baseTickSize":"0.000001 BTC", + // "baseTickSizeEv":100, + // "quoteTickSize":"0.01 USDT", + // "quoteTickSizeEv":1000000, + // "minOrderValue":"10 USDT", + // "minOrderValueEv":1000000000, + // "maxBaseOrderSize":"1000 BTC", + // "maxBaseOrderSizeEv":100000000000, + // "maxOrderValue":"5,000,000 USDT", + // "maxOrderValueEv":500000000000000, + // "defaultTakerFee":"0.001", + // "defaultTakerFeeEr":100000, + // "defaultMakerFee":"0.001", + // "defaultMakerFeeEr":100000, + // "baseQtyPrecision":6, + // "quoteQtyPrecision":2 + // } + // + const type = this.safeStringLower (market, 'type'); + const id = this.safeString (market, 'symbol'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const baseId = this.safeString (market, 'baseCurrency'); + const linear = undefined; + const inverse = undefined; + const spot = true; + const swap = false; + const taker = this.safeFloat (market, 'defaultTakerFee'); + const maker = this.safeFloat (market, 'defaultMakerFee'); + const precision = { + 'amount': this.parseSafeFloat (this.safeString (market, 'baseTickSize')), + 'price': this.parseSafeFloat (this.safeString (market, 'quoteTickSize')), + }; + const limits = { + 'amount': { + 'min': precision['amount'], + 'max': this.parseSafeFloat (this.safeString (market, 'maxBaseOrderSize')), + }, + 'price': { + 'min': precision['price'], + 'max': undefined, + }, + 'cost': { + 'min': this.parseSafeFloat (this.safeString (market, 'minOrderValue')), + 'max': this.parseSafeFloat (this.safeString (market, 'maxOrderValue')), + }, + }; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = undefined; + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'swap': swap, + 'linear': linear, + 'inverse': inverse, + 'active': active, + 'taker': taker, + 'maker': maker, + 'precision': precision, + 'priceScale': 8, + 'valueScale': 8, + 'ratioScale': 8, + 'limits': limits, + }; + } + + async fetchMarkets (params = {}) { + const v2Products = await this.publicGetCfgV2Products (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "ratioScale":8, + // "currencies":[ + // {"currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"}, + // {"currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"}, + // {"currency":"USDT","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"TetherUS"}, + // ], + // "products":[ + // { + // "symbol":"BTCUSD", + // "displaySymbol":"BTC / USD", + // "indexSymbol":".BTC", + // "markSymbol":".MBTC", + // "fundingRateSymbol":".BTCFR", + // "fundingRate8hSymbol":".BTCFR8H", + // "contractUnderlyingAssets":"USD", + // "settleCurrency":"BTC", + // "quoteCurrency":"USD", + // "contractSize":1.0, + // "lotSize":1, + // "tickSize":0.5, + // "priceScale":4, + // "ratioScale":8, + // "pricePrecision":1, + // "minPriceEp":5000, + // "maxPriceEp":10000000000, + // "maxOrderQty":1000000, + // "type":"Perpetual" + // }, + // { + // "symbol":"sBTCUSDT", + // "displaySymbol":"BTC / USDT", + // "quoteCurrency":"USDT", + // "pricePrecision":2, + // "type":"Spot", + // "baseCurrency":"BTC", + // "baseTickSize":"0.000001 BTC", + // "baseTickSizeEv":100, + // "quoteTickSize":"0.01 USDT", + // "quoteTickSizeEv":1000000, + // "minOrderValue":"10 USDT", + // "minOrderValueEv":1000000000, + // "maxBaseOrderSize":"1000 BTC", + // "maxBaseOrderSizeEv":100000000000, + // "maxOrderValue":"5,000,000 USDT", + // "maxOrderValueEv":500000000000000, + // "defaultTakerFee":"0.001", + // "defaultTakerFeeEr":100000, + // "defaultMakerFee":"0.001", + // "defaultMakerFeeEr":100000, + // "baseQtyPrecision":6, + // "quoteQtyPrecision":2 + // }, + // ], + // "riskLimits":[ + // { + // "symbol":"BTCUSD", + // "steps":"50", + // "riskLimits":[ + // {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000}, + // {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000}, + // {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000}, + // ] + // }, + // ], + // "leverages":[ + // {"initialMargin":"1.0%","initialMarginEr":1000000,"options":[1,2,3,5,10,25,50,100]}, + // {"initialMargin":"1.5%","initialMarginEr":1500000,"options":[1,2,3,5,10,25,50,66]}, + // {"initialMargin":"2.0%","initialMarginEr":2000000,"options":[1,2,3,5,10,25,33,50]}, + // ] + // } + // } + // + const v1Products = await this.v1GetExchangePublicProducts (params); + const v1ProductsData = this.safeValue (v1Products, 'data', []); + // + // { + // "code":0, + // "msg":"OK", + // "data":[ + // { + // "symbol":"BTCUSD", + // "underlyingSymbol":".BTC", + // "quoteCurrency":"USD", + // "baseCurrency":"BTC", + // "settlementCurrency":"BTC", + // "maxOrderQty":1000000, + // "maxPriceEp":100000000000000, + // "lotSize":1, + // "tickSize":"0.5", + // "contractSize":"1 USD", + // "priceScale":4, + // "ratioScale":8, + // "valueScale":8, + // "defaultLeverage":0, + // "maxLeverage":100, + // "initMarginEr":"1000000", + // "maintMarginEr":"500000", + // "defaultRiskLimitEv":10000000000, + // "deleverage":true, + // "makerFeeRateEr":-250000, + // "takerFeeRateEr":750000, + // "fundingInterval":8, + // "marketUrl":"https://phemex.com/trade/BTCUSD", + // "description":"BTCUSD is a BTC/USD perpetual contract priced on the .BTC Index. Each contract is worth 1 USD of Bitcoin. Funding is paid and received every 8 hours. At UTC time: 00:00, 08:00, 16:00.", + // "type":"Perpetual" + // }, + // ] + // } + // + const v2ProductsData = this.safeValue (v2Products, 'data', {}); + const products = this.safeValue (v2ProductsData, 'products', []); + const riskLimits = this.safeValue (v2ProductsData, 'riskLimits', []); + const riskLimitsById = this.indexBy (riskLimits, 'symbol'); + const v1ProductsById = this.indexBy (v1ProductsData, 'symbol'); + const result = []; + for (let i = 0; i < products.length; i++) { + let market = products[i]; + const type = this.safeStringLower (market, 'type'); + if (type === 'perpetual') { + const id = this.safeString (market, 'symbol'); + const riskLimitValues = this.safeValue (riskLimitsById, id, {}); + market = this.extend (market, riskLimitValues); + const v1ProductsValues = this.safeValue (v1ProductsById, id, {}); + market = this.extend (market, v1ProductsValues); + market = this.parseSwapMarket (market); + } else { + market = this.parseSpotMarket (market); + } + result.push (market); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCfgV2Products (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // ..., + // "currencies":[ + // {"currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"}, + // {"currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"}, + // {"currency":"USDT","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"TetherUS"}, + // ], + // ... + // } + // } + const data = this.safeValue (response, 'data', {}); + const currencies = this.safeValue (data, 'currencies', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currency'); + const name = this.safeString (currency, 'name'); + const code = this.safeCurrencyCode (id); + const valueScale = this.safeInteger (currency, 'valueScale'); + const minValueEv = this.safeFloat (currency, 'minValueEv'); + const maxValueEv = this.safeFloat (currency, 'maxValueEv'); + let minAmount = undefined; + let maxAmount = undefined; + let precision = undefined; + if (valueScale !== undefined) { + precision = Math.pow (10, -valueScale); + precision = parseFloat (this.decimalToPrecision (precision, ROUND, 0.00000001, this.precisionMode)); + if (minValueEv !== undefined) { + minAmount = parseFloat (this.decimalToPrecision (minValueEv * precision, ROUND, 0.00000001, this.precisionMode)); + } + if (maxValueEv !== undefined) { + maxAmount = parseFloat (this.decimalToPrecision (maxValueEv * precision, ROUND, 0.00000001, this.precisionMode)); + } + } + result[code] = { + 'id': id, + 'info': currency, + 'code': code, + 'name': name, + 'active': undefined, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + 'valueScale': valueScale, + }; + } + return result; + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1, market = undefined) { + if (market === undefined) { + throw new ArgumentsRequired (this.id + ' parseBidAsk requires a market argument'); + } + let amount = this.safeFloat (bidask, amountKey); + if (market['spot']) { + amount = this.fromEv (amount, market); + } + return [ + this.fromEp (this.safeFloat (bidask, priceKey), market), + amount, + ]; + } + + parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) { + const result = { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + const sides = [ bidsKey, asksKey ]; + for (let i = 0; i < sides.length; i++) { + const side = sides[i]; + const orders = []; + const bidasks = this.safeValue (orderbook, side); + for (let k = 0; k < bidasks.length; k++) { + orders.push (this.parseBidAsk (bidasks[k], priceKey, amountKey, market)); + } + result[side] = orders; + } + result[bidsKey] = this.sortBy (result[bidsKey], 0, true); + result[asksKey] = this.sortBy (result[asksKey], 0); + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'id': 123456789, // optional request id + }; + const response = await this.v1GetMdOrderbook (this.extend (request, params)); + // + // { + // "error": null, + // "id": 0, + // "result": { + // "book": { + // "asks": [ + // [ 23415000000, 105262000 ], + // [ 23416000000, 147914000 ], + // [ 23419000000, 160914000 ], + // ], + // "bids": [ + // [ 23360000000, 32995000 ], + // [ 23359000000, 221887000 ], + // [ 23356000000, 284599000 ], + // ], + // }, + // "depth": 30, + // "sequence": 1592059928, + // "symbol": "sETHUSDT", + // "timestamp": 1592387340020000955, + // "type": "snapshot" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const book = this.safeValue (result, 'book', {}); + const timestamp = this.safeIntegerProduct (result, 'timestamp', 0.000001); + const orderbook = this.parseOrderBook (book, timestamp, 'bids', 'asks', 0, 1, market); + orderbook['nonce'] = this.safeInteger (result, 'sequence'); + return orderbook; + } + + toEn (n, scale, precision) { + return parseInt (this.decimalToPrecision (n * Math.pow (10, scale), ROUND, precision, DECIMAL_PLACES)); + } + + toEv (amount, market = undefined) { + if ((amount === undefined) || (market === undefined)) { + return amount; + } + return this.toEn (amount, market['valueScale'], 0); + } + + toEp (price, market = undefined) { + if ((price === undefined) || (market === undefined)) { + return price; + } + return this.toEn (price, market['priceScale'], 0); + } + + fromEn (en, scale, precision, precisionMode = undefined) { + precisionMode = (precisionMode === undefined) ? this.precisionMode : precisionMode; + return parseFloat (this.decimalToPrecision (en * Math.pow (10, -scale), ROUND, precision, precisionMode)); + } + + fromEp (ep, market = undefined) { + if ((ep === undefined) || (market === undefined)) { + return ep; + } + return this.fromEn (ep, market['priceScale'], market['precision']['price']); + } + + fromEv (ev, market = undefined) { + if ((ev === undefined) || (market === undefined)) { + return ev; + } + if (market['spot']) { + return this.fromEn (ev, market['valueScale'], market['precision']['amount']); + } else { + return this.fromEn (ev, market['valueScale'], 1 / Math.pow (10, market['valueScale'])); + } + } + + fromEr (er, market = undefined) { + if ((er === undefined) || (market === undefined)) { + return er; + } + return this.fromEn (er, market['ratioScale'], 0.00000001); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1592467200, // timestamp + // 300, // interval + // 23376000000, // last + // 23322000000, // open + // 23381000000, // high + // 23315000000, // low + // 23367000000, // close + // 208671000, // base volume + // 48759063370, // quote volume + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.fromEp (this.safeFloat (ohlcv, 3), market), + this.fromEp (this.safeFloat (ohlcv, 4), market), + this.fromEp (this.safeFloat (ohlcv, 5), market), + this.fromEp (this.safeFloat (ohlcv, 6), market), + this.fromEv (this.safeFloat (ohlcv, 7), market), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + const request = { + // 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + // 'from': 1588830682, // seconds + // 'to': this.seconds (), + }; + const duration = this.parseTimeframe (timeframe); + const now = this.seconds (); + if (since !== undefined) { + if (limit === undefined) { + limit = 2000; // max 2000 + } + since = parseInt (since / 1000); + request['from'] = since; + // time ranges ending in the future are not accepted + // https://github.com/ccxt/ccxt/issues/8050 + request['to'] = Math.min (now, this.sum (since, duration * limit)); + } else if (limit !== undefined) { + limit = Math.min (limit, 2000); + request['from'] = now - duration * this.sum (limit, 1); + request['to'] = now; + } else { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument, or a limit argument, or both'); + } + await this.loadMarkets (); + const market = this.market (symbol); + request['symbol'] = market['id']; + const response = await this.publicGetMdKline (this.extend (request, params)); + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "total":-1, + // "rows":[ + // [1592467200,300,23376000000,23322000000,23381000000,23315000000,23367000000,208671000,48759063370], + // [1592467500,300,23367000000,23314000000,23390000000,23311000000,23331000000,234820000,54848948710], + // [1592467800,300,23331000000,23385000000,23391000000,23326000000,23387000000,152931000,35747882250], + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const rows = this.safeValue (data, 'rows', []); + return this.parseOHLCVs (rows, market, timeframe, since, limit); + } + + parseTicker (ticker, market = undefined) { + // + // spot + // + // { + // "askEp": 943836000000, + // "bidEp": 943601000000, + // "highEp": 955946000000, + // "lastEp": 943803000000, + // "lowEp": 924973000000, + // "openEp": 948693000000, + // "symbol": "sBTCUSDT", + // "timestamp": 1592471203505728630, + // "turnoverEv": 111822826123103, + // "volumeEv": 11880532281 + // } + // + // swap + // + // { + // "askEp": 2332500, + // "bidEp": 2331000, + // "fundingRateEr": 10000, + // "highEp": 2380000, + // "indexEp": 2329057, + // "lastEp": 2331500, + // "lowEp": 2274000, + // "markEp": 2329232, + // "openEp": 2337500, + // "openInterest": 1298050, + // "predFundingRateEr": 19921, + // "symbol": "ETHUSD", + // "timestamp": 1592474241582701416, + // "turnoverEv": 47228362330, + // "volume": 4053863 + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeIntegerProduct (ticker, 'timestamp', 0.000001); + const last = this.fromEp (this.safeFloat (ticker, 'lastEp'), market); + const quoteVolume = this.fromEp (this.safeFloat (ticker, 'turnoverEv'), market); + let baseVolume = this.safeFloat (ticker, 'volume'); + if (baseVolume === undefined) { + baseVolume = this.fromEv (this.safeFloat (ticker, 'volumeEv')); + } + let vwap = undefined; + if ((market !== undefined) && (market['spot'])) { + vwap = this.vwap (baseVolume, quoteVolume); + } + let change = undefined; + let percentage = undefined; + let average = undefined; + const open = this.fromEp (this.safeFloat (ticker, 'openEp'), market); + if ((open !== undefined) && (last !== undefined)) { + change = last - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, last) / 2; + } + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.fromEp (this.safeFloat (ticker, 'highEp'), market), + 'low': this.fromEp (this.safeFloat (ticker, 'lowEp'), market), + 'bid': this.fromEp (this.safeFloat (ticker, 'bidEp'), market), + 'bidVolume': undefined, + 'ask': this.fromEp (this.safeFloat (ticker, 'askEp'), market), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'id': 123456789, // optional request id + }; + const method = market['spot'] ? 'v1GetMdSpotTicker24hr' : 'v1GetMdTicker24hr'; + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "error": null, + // "id": 0, + // "result": { + // "askEp": 943836000000, + // "bidEp": 943601000000, + // "highEp": 955946000000, + // "lastEp": 943803000000, + // "lowEp": 924973000000, + // "openEp": 948693000000, + // "symbol": "sBTCUSDT", + // "timestamp": 1592471203505728630, + // "turnoverEv": 111822826123103, + // "volumeEv": 11880532281 + // } + // } + // + // swap + // + // { + // "error": null, + // "id": 0, + // "result": { + // "askEp": 2332500, + // "bidEp": 2331000, + // "fundingRateEr": 10000, + // "highEp": 2380000, + // "indexEp": 2329057, + // "lastEp": 2331500, + // "lowEp": 2274000, + // "markEp": 2329232, + // "openEp": 2337500, + // "openInterest": 1298050, + // "predFundingRateEr": 19921, + // "symbol": "ETHUSD", + // "timestamp": 1592474241582701416, + // "turnoverEv": 47228362330, + // "volume": 4053863 + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTicker (result, market); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'id': 123456789, // optional request id + }; + const response = await this.v1GetMdTrade (this.extend (request, params)); + // + // { + // "error": null, + // "id": 0, + // "result": { + // "sequence": 1315644947, + // "symbol": "BTCUSD", + // "trades": [ + // [ 1592541746712239749, 13156448570000, "Buy", 93070000, 40173 ], + // [ 1592541740434625085, 13156447110000, "Sell", 93065000, 5000 ], + // [ 1592541732958241616, 13156441390000, "Buy", 93070000, 3460 ], + // ], + // "type": "snapshot" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // 1592541746712239749, + // 13156448570000, + // "Buy", + // 93070000, + // 40173 + // ] + // + // fetchMyTrades (private) + // + // spot + // + // { + // "qtyType": "ByQuote", + // "transactTimeNs": 1589450974800550100, + // "clOrdID": "8ba59d40-df25-d4b0-14cf-0703f44e9690", + // "orderID": "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83", + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "priceEP": 970056000000, + // "baseQtyEv": 0, + // "quoteQtyEv": 1000000000, + // "action": "New", + // "execStatus": "MakerFill", + // "ordStatus": "Filled", + // "ordType": "Limit", + // "execInst": "None", + // "timeInForce": "GoodTillCancel", + // "stopDirection": "UNSPECIFIED", + // "tradeType": "Trade", + // "stopPxEp": 0, + // "execId": "c6bd8979-07ba-5946-b07e-f8b65135dbb1", + // "execPriceEp": 970056000000, + // "execBaseQtyEv": 103000, + // "execQuoteQtyEv": 999157680, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "execFeeEv": 0, + // "feeRateEr": 0 + // } + // + // swap + // + // { + // "transactTimeNs": 1578026629824704800, + // "symbol": "BTCUSD", + // "currency": "BTC", + // "action": "Replace", + // "side": "Sell", + // "tradeType": "Trade", + // "execQty": 700, + // "execPriceEp": 71500000, + // "orderQty": 700, + // "priceEp": 71500000, + // "execValueEv": 9790209, + // "feeRateEr": -25000, + // "execFeeEv": -2447, + // "ordType": "Limit", + // "execID": "b01671a1-5ddc-5def-b80a-5311522fd4bf", + // "orderID": "b63bc982-be3a-45e0-8974-43d6375fb626", + // "clOrdID": "uuid-1577463487504", + // "execStatus": "MakerFill" + // } + // + let price = undefined; + let amount = undefined; + let timestamp = undefined; + let id = undefined; + let side = undefined; + let cost = undefined; + let type = undefined; + let fee = undefined; + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + let orderId = undefined; + let takerOrMaker = undefined; + if (Array.isArray (trade)) { + const tradeLength = trade.length; + timestamp = this.safeIntegerProduct (trade, 0, 0.000001); + if (tradeLength > 4) { + id = this.safeString (trade, tradeLength - 4); + } + side = this.safeStringLower (trade, tradeLength - 3); + price = this.fromEp (this.safeFloat (trade, tradeLength - 2), market); + amount = this.fromEv (this.safeFloat (trade, tradeLength - 1), market); + if (market['spot']) { + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + } + } else { + timestamp = this.safeIntegerProduct (trade, 'transactTimeNs', 0.000001); + id = this.safeString2 (trade, 'execId', 'execID'); + orderId = this.safeString (trade, 'orderID'); + side = this.safeStringLower (trade, 'side'); + type = this.parseOrderType (this.safeString (trade, 'ordType')); + const execStatus = this.safeString (trade, 'execStatus'); + if (execStatus === 'MakerFill') { + takerOrMaker = 'maker'; + } + price = this.fromEp (this.safeFloat (trade, 'execPriceEp'), market); + amount = this.fromEv (this.safeFloat (trade, 'execBaseQtyEv'), market); + amount = this.safeFloat (trade, 'execQty', amount); + cost = this.fromEv (this.safeFloat2 (trade, 'execQuoteQtyEv', 'execValueEv'), market); + const feeCost = this.fromEv (this.safeFloat (trade, 'execFeeEv'), market); + if (feeCost !== undefined) { + let feeRate = undefined; + let feeRateEr = this.safeFloat (trade, 'feeRateEr'); + if (feeRateEr < 0) { + feeRateEr = Math.abs (feeRateEr); + feeRate = this.fromEr (feeRateEr, market); + feeRate = -feeRate; + } else { + feeRate = this.fromEr (feeRateEr, market); + } + let feeCurrencyCode = undefined; + if (market['spot']) { + feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + } else { + const info = this.safeValue (market, 'info'); + if (info !== undefined) { + const settlementCurrencyId = this.safeString (info, 'settlementCurrency'); + feeCurrencyCode = this.safeCurrencyCode (settlementCurrencyId); + } + } + fee = { + 'cost': feeCost, + 'rate': feeRate, + 'currency': feeCurrencyCode, + }; + } + } + return { + 'info': trade, + 'id': id, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseSpotBalance (response) { + // + // { + // "code":0, + // "msg":"", + // "data":[ + // { + // "currency":"USDT", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // }, + // { + // "currency":"ETH", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // } + // ] + // } + // + const result = { 'info': response }; + const data = this.safeValue (response, 'data', []); + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const currency = this.safeValue (this.currencies, code, {}); + const scale = this.safeInteger (currency, 'valueScale', 8); + const account = this.account (); + const balanceEv = this.safeFloat (balance, 'balanceEv'); + const lockedTradingBalanceEv = this.safeFloat (balance, 'lockedTradingBalanceEv'); + const lockedWithdrawEv = this.safeFloat (balance, 'lockedWithdrawEv'); + const total = this.fromEn (balanceEv, scale, scale, DECIMAL_PLACES); + const lockedTradingBalance = this.fromEn (lockedTradingBalanceEv, scale, scale, DECIMAL_PLACES); + const lockedWithdraw = this.fromEn (lockedWithdrawEv, scale, scale, DECIMAL_PLACES); + const used = this.sum (lockedTradingBalance, lockedWithdraw); + account['total'] = total; + account['used'] = used; + result[code] = account; + } + return this.parseBalance (result); + } + + parseSwapBalance (response) { + // + // { + // "code":0, + // "msg":"", + // "data":{ + // "account":{ + // "accountId":6192120001, + // "currency":"BTC", + // "accountBalanceEv":1254744, + // "totalUsedBalanceEv":0, + // "bonusBalanceEv":1254744 + // }, + // "positions":[ + // { + // "accountID":6192120001, + // "symbol":"BTCUSD", + // "currency":"BTC", + // "side":"None", + // "positionStatus":"Normal", + // "crossMargin":false, + // "leverageEr":0, + // "leverage":0E-8, + // "initMarginReqEr":1000000, + // "initMarginReq":0.01000000, + // "maintMarginReqEr":500000, + // "maintMarginReq":0.00500000, + // "riskLimitEv":10000000000, + // "riskLimit":100.00000000, + // "size":0, + // "value":0E-8, + // "valueEv":0, + // "avgEntryPriceEp":0, + // "avgEntryPrice":0E-8, + // "posCostEv":0, + // "posCost":0E-8, + // "assignedPosBalanceEv":0, + // "assignedPosBalance":0E-8, + // "bankruptCommEv":0, + // "bankruptComm":0E-8, + // "bankruptPriceEp":0, + // "bankruptPrice":0E-8, + // "positionMarginEv":0, + // "positionMargin":0E-8, + // "liquidationPriceEp":0, + // "liquidationPrice":0E-8, + // "deleveragePercentileEr":0, + // "deleveragePercentile":0E-8, + // "buyValueToCostEr":1150750, + // "buyValueToCost":0.01150750, + // "sellValueToCostEr":1149250, + // "sellValueToCost":0.01149250, + // "markPriceEp":96359083, + // "markPrice":9635.90830000, + // "markValueEv":0, + // "markValue":null, + // "unRealisedPosLossEv":0, + // "unRealisedPosLoss":null, + // "estimatedOrdLossEv":0, + // "estimatedOrdLoss":0E-8, + // "usedBalanceEv":0, + // "usedBalance":0E-8, + // "takeProfitEp":0, + // "takeProfit":null, + // "stopLossEp":0, + // "stopLoss":null, + // "realisedPnlEv":0, + // "realisedPnl":null, + // "cumRealisedPnlEv":0, + // "cumRealisedPnl":null + // } + // ] + // } + // } + // + const result = { 'info': response }; + const data = this.safeValue (response, 'data', {}); + const balance = this.safeValue (data, 'account', {}); + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const currency = this.currency (code); + const account = this.account (); + const accountBalanceEv = this.safeFloat (balance, 'accountBalanceEv'); + const totalUsedBalanceEv = this.safeFloat (balance, 'totalUsedBalanceEv'); + const valueScale = this.safeInteger (currency, 'valueScale', 8); + account['total'] = this.fromEn (accountBalanceEv, valueScale, valueScale, DECIMAL_PLACES); + account['used'] = this.fromEn (totalUsedBalanceEv, valueScale, valueScale, DECIMAL_PLACES); + result[code] = account; + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'defaultType', 'fetchBalance', 'spot'); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetSpotWallets'; + const request = {}; + if (type === 'swap') { + const code = this.safeString (params, 'code'); + if (code !== undefined) { + const currency = this.currency (code); + request['currency'] = currency['id']; + params = this.omit (params, 'code'); + } else { + const currency = this.safeString (params, 'currency'); + if (currency === undefined) { + throw new ArgumentsRequired (this.id + ' fetchBalance requires a code parameter or a currency parameter for ' + type + ' type'); + } + } + method = 'privateGetAccountsAccountPositions'; + } + params = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code":0, + // "msg":"", + // "data":[ + // { + // "currency":"USDT", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // }, + // { + // "currency":"ETH", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // } + // ] + // } + // + // swap + // + // { + // "code":0, + // "msg":"", + // "data":{ + // "account":{ + // "accountId":6192120001, + // "currency":"BTC", + // "accountBalanceEv":1254744, + // "totalUsedBalanceEv":0, + // "bonusBalanceEv":1254744 + // }, + // "positions":[ + // { + // "accountID":6192120001, + // "symbol":"BTCUSD", + // "currency":"BTC", + // "side":"None", + // "positionStatus":"Normal", + // "crossMargin":false, + // "leverageEr":0, + // "leverage":0E-8, + // "initMarginReqEr":1000000, + // "initMarginReq":0.01000000, + // "maintMarginReqEr":500000, + // "maintMarginReq":0.00500000, + // "riskLimitEv":10000000000, + // "riskLimit":100.00000000, + // "size":0, + // "value":0E-8, + // "valueEv":0, + // "avgEntryPriceEp":0, + // "avgEntryPrice":0E-8, + // "posCostEv":0, + // "posCost":0E-8, + // "assignedPosBalanceEv":0, + // "assignedPosBalance":0E-8, + // "bankruptCommEv":0, + // "bankruptComm":0E-8, + // "bankruptPriceEp":0, + // "bankruptPrice":0E-8, + // "positionMarginEv":0, + // "positionMargin":0E-8, + // "liquidationPriceEp":0, + // "liquidationPrice":0E-8, + // "deleveragePercentileEr":0, + // "deleveragePercentile":0E-8, + // "buyValueToCostEr":1150750, + // "buyValueToCost":0.01150750, + // "sellValueToCostEr":1149250, + // "sellValueToCost":0.01149250, + // "markPriceEp":96359083, + // "markPrice":9635.90830000, + // "markValueEv":0, + // "markValue":null, + // "unRealisedPosLossEv":0, + // "unRealisedPosLoss":null, + // "estimatedOrdLossEv":0, + // "estimatedOrdLoss":0E-8, + // "usedBalanceEv":0, + // "usedBalance":0E-8, + // "takeProfitEp":0, + // "takeProfit":null, + // "stopLossEp":0, + // "stopLoss":null, + // "realisedPnlEv":0, + // "realisedPnl":null, + // "cumRealisedPnlEv":0, + // "cumRealisedPnl":null + // } + // ] + // } + // } + // + const result = (type === 'swap') ? this.parseSwapBalance (response) : this.parseSpotBalance (response); + return result; + } + + parseOrderStatus (status) { + const statuses = { + 'Created': 'open', + 'Untriggered': 'open', + 'Deactivated': 'closed', + 'Triggered': 'open', + 'Rejected': 'rejected', + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'Canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrderType (type) { + const types = { + 'Limit': 'limit', + 'Market': 'market', + }; + return this.safeString (types, type, type); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GoodTillCancel': 'GTC', + 'PostOnly': 'PO', + 'ImmediateOrCancel': 'IOC', + 'FillOrKill': 'FOK', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseSpotOrder (order, market = undefined) { + // + // spot + // + // { + // "orderID": "d1d09454-cabc-4a23-89a7-59d43363f16d", + // "clOrdID": "309bcd5c-9f6e-4a68-b775-4494542eb5cb", + // "priceEp": 0, + // "action": "New", + // "trigger": "UNSPECIFIED", + // "pegPriceType": "UNSPECIFIED", + // "stopDirection": "UNSPECIFIED", + // "bizError": 0, + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "baseQtyEv": 0, + // "ordType": "Limit", + // "timeInForce": "GoodTillCancel", + // "ordStatus": "Created", + // "cumFeeEv": 0, + // "cumBaseQtyEv": 0, + // "cumQuoteQtyEv": 0, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "avgPriceEp": 0, + // "cumBaseAmountEv": 0, + // "cumQuoteAmountEv": 0, + // "quoteQtyEv": 0, + // "qtyType": "ByBase", + // "stopPxEp": 0, + // "pegOffsetValueEp": 0 + // } + // + // { + // "orderID":"99232c3e-3d6a-455f-98cc-2061cdfe91bc", + // "stopPxEp":0, + // "avgPriceEp":0, + // "qtyType":"ByBase", + // "leavesBaseQtyEv":0, + // "leavesQuoteQtyEv":0, + // "baseQtyEv":"1000000000", + // "feeCurrency":"4", + // "stopDirection":"UNSPECIFIED", + // "symbol":"sETHUSDT", + // "side":"Buy", + // "quoteQtyEv":250000000000, + // "priceEp":25000000000, + // "ordType":"Limit", + // "timeInForce":"GoodTillCancel", + // "ordStatus":"Rejected", + // "execStatus":"NewRejected", + // "createTimeNs":1592675305266037130, + // "cumFeeEv":0, + // "cumBaseValueEv":0, + // "cumQuoteValueEv":0 + // } + // + const id = this.safeString (order, 'orderID'); + let clientOrderId = this.safeString (order, 'clOrdID'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let price = this.fromEp (this.safeFloat (order, 'priceEp'), market); + if (price === 0) { + price = undefined; + } + const amount = this.fromEv (this.safeFloat (order, 'baseQtyEv'), market); + const remaining = this.fromEv (this.safeFloat (order, 'leavesBaseQtyEv'), market); + let filled = this.fromEv (this.safeFloat (order, 'cumBaseQtyEv'), market); + const cost = this.fromEv (this.safeFloat (order, 'quoteQtyEv'), market); + const average = this.fromEp (this.safeFloat (order, 'avgPriceEp'), market); + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const side = this.safeStringLower (order, 'side'); + const type = this.parseOrderType (this.safeString (order, 'ordType')); + const timestamp = this.safeIntegerProduct2 (order, 'actionTimeNs', 'createTimeNs', 0.000001); + let fee = undefined; + const feeCost = this.fromEv (this.safeFloat (order, 'cumFeeEv'), market); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + if (filled === undefined) { + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.min (0, amount - remaining); + } + } + const timeInForce = this.parseTimeInForce (this.safeStirng (order, 'timeInForce')); + const stopPrice = this.fromEp (this.safeFloat (order, 'stopPxEp', market)); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + parseSwapOrder (order, market = undefined) { + // + // { + // "bizError":0, + // "orderID":"7a1ad384-44a3-4e54-a102-de4195a29e32", + // "clOrdID":"", + // "symbol":"ETHUSD", + // "side":"Buy", + // "actionTimeNs":1592668973945065381, + // "transactTimeNs":0, + // "orderType":"Market", + // "priceEp":2267500, + // "price":226.75000000, + // "orderQty":1, + // "displayQty":0, + // "timeInForce":"ImmediateOrCancel", + // "reduceOnly":false, + // "closedPnlEv":0, + // "closedPnl":0E-8, + // "closedSize":0, + // "cumQty":0, + // "cumValueEv":0, + // "cumValue":0E-8, + // "leavesQty":1, + // "leavesValueEv":11337, + // "leavesValue":1.13370000, + // "stopDirection":"UNSPECIFIED", + // "stopPxEp":0, + // "stopPx":0E-8, + // "trigger":"UNSPECIFIED", + // "pegOffsetValueEp":0, + // "execStatus":"PendingNew", + // "pegPriceType":"UNSPECIFIED", + // "ordStatus":"Created" + // } + // + const id = this.safeString (order, 'orderID'); + let clientOrderId = this.safeString (order, 'clOrdID'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const side = this.safeStringLower (order, 'side'); + const type = this.parseOrderType (this.safeString (order, 'orderType')); + const price = this.fromEp (this.safeFloat (order, 'priceEp'), market); + const amount = this.safeFloat (order, 'orderQty'); + const filled = this.safeFloat (order, 'cumQty'); + const remaining = this.safeFloat (order, 'leavesQty'); + const timestamp = this.safeIntegerProduct (order, 'actionTimeNs', 0.000001); + const cost = this.safeFloat (order, 'cumValue'); + let lastTradeTimestamp = this.safeIntegerProduct (order, 'transactTimeNs', 0.000001); + if (lastTradeTimestamp === 0) { + lastTradeTimestamp = undefined; + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); + const stopPrice = this.safeFloat (order, 'stopPx'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'average': undefined, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrder (order, market = undefined) { + if ('closedPnl' in order) { + return this.parseSwapOrder (order, market); + } + return this.parseSpotOrder (order, market); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + side = this.capitalize (side); + type = this.capitalize (type); + const request = { + // common + 'symbol': market['id'], + 'side': side, // Sell, Buy + 'ordType': type, // Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched or Pegged for swap orders + // 'stopPxEp': this.toEp (stopPx, market), // for conditional orders + // 'priceEp': this.toEp (price, market), // required for limit orders + // 'timeInForce': 'GoodTillCancel', // GoodTillCancel, PostOnly, ImmediateOrCancel, FillOrKill + // ---------------------------------------------------------------- + // spot + // 'qtyType': 'ByBase', // ByBase, ByQuote + // 'quoteQtyEv': this.toEp (cost, market), + // 'baseQtyEv': this.toEv (amount, market), + // 'trigger': 'ByLastPrice', // required for conditional orders + // ---------------------------------------------------------------- + // swap + // 'clOrdID': this.uuid (), // max length 40 + // 'orderQty': this.amountToPrecision (amount, symbol), + // 'reduceOnly': false, + // 'closeOnTrigger': false, // implicit reduceOnly and cancel other orders in the same direction + // 'takeProfitEp': this.toEp (takeProfit, market), + // 'stopLossEp': this.toEp (stopLossEp, market), + // 'triggerType': 'ByMarkPrice', // ByMarkPrice, ByLastPrice + // 'pegOffsetValueEp': integer, // Trailing offset from current price. Negative value when position is long, positive when position is short + // 'pegPriceType': 'TrailingStopPeg', // TrailingTakeProfitPeg + // 'text': 'comment', + }; + if (market['spot']) { + let qtyType = this.safeValue (params, 'qtyType', 'ByBase'); + if (price !== undefined) { + qtyType = 'ByQuote'; + } + request['qtyType'] = qtyType; + if (qtyType === 'ByQuote') { + let cost = this.safeFloat (params, 'cost'); + params = this.omit (params, 'cost'); + if (this.options['createOrderByQuoteRequiresPrice']) { + if (price !== undefined) { + cost = amount * price; + } else if (cost === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder ' + qtyType + ' requires a price argument or a cost parameter'); + } + } + cost = (cost === undefined) ? amount : cost; + request['quoteQtyEv'] = this.toEp (cost, market); + } else { + request['baseQtyEv'] = this.toEv (amount, market); + } + } else if (market['swap']) { + request['orderQty'] = parseInt (amount); + } + if (type === 'Limit') { + request['priceEp'] = this.toEp (price, market); + } + const stopPrice = this.safeFloat2 (params, 'stopPx', 'stopPrice'); + if (stopPrice !== undefined) { + request['stopPxEp'] = this.toEp (stopPrice, market); + } + params = this.omit (params, [ 'stopPx', 'stopPrice' ]); + const method = market['spot'] ? 'privatePostSpotOrders' : 'privatePostOrders'; + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code": 0, + // "msg": "", + // "data": { + // "orderID": "d1d09454-cabc-4a23-89a7-59d43363f16d", + // "clOrdID": "309bcd5c-9f6e-4a68-b775-4494542eb5cb", + // "priceEp": 0, + // "action": "New", + // "trigger": "UNSPECIFIED", + // "pegPriceType": "UNSPECIFIED", + // "stopDirection": "UNSPECIFIED", + // "bizError": 0, + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "baseQtyEv": 0, + // "ordType": "Limit", + // "timeInForce": "GoodTillCancel", + // "ordStatus": "Created", + // "cumFeeEv": 0, + // "cumBaseQtyEv": 0, + // "cumQuoteQtyEv": 0, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "avgPriceEp": 0, + // "cumBaseAmountEv": 0, + // "cumQuoteAmountEv": 0, + // "quoteQtyEv": 0, + // "qtyType": "ByBase", + // "stopPxEp": 0, + // "pegOffsetValueEp": 0 + // } + // } + // + // swap + // + // { + // "code":0, + // "msg":"", + // "data":{ + // "bizError":0, + // "orderID":"7a1ad384-44a3-4e54-a102-de4195a29e32", + // "clOrdID":"", + // "symbol":"ETHUSD", + // "side":"Buy", + // "actionTimeNs":1592668973945065381, + // "transactTimeNs":0, + // "orderType":"Market", + // "priceEp":2267500, + // "price":226.75000000, + // "orderQty":1, + // "displayQty":0, + // "timeInForce":"ImmediateOrCancel", + // "reduceOnly":false, + // "closedPnlEv":0, + // "closedPnl":0E-8, + // "closedSize":0, + // "cumQty":0, + // "cumValueEv":0, + // "cumValue":0E-8, + // "leavesQty":1, + // "leavesValueEv":11337, + // "leavesValue":1.13370000, + // "stopDirection":"UNSPECIFIED", + // "stopPxEp":0, + // "stopPx":0E-8, + // "trigger":"UNSPECIFIED", + // "pegOffsetValueEp":0, + // "execStatus":"PendingNew", + // "pegPriceType":"UNSPECIFIED", + // "ordStatus":"Created" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdID'); + params = this.omit (params, [ 'clientOrderId', 'clOrdID' ]); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + } else { + request['orderID'] = id; + } + const method = market['spot'] ? 'privateDeleteSpotOrders' : 'privateDeleteOrdersCancel'; + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': market['id'], + // 'untriggerred': false, // false to cancel non-conditional orders, true to cancel conditional orders + // 'text': 'up to 40 characters max', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + if (!market['swap']) { + throw new NotSupported (this.id + ' cancelAllOrders() supports swap market type orders only'); + } + request['symbol'] = market['id']; + } + return await this.privateDeleteOrdersAll (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetSpotOrdersActive' : 'privateGetExchangeOrder'; + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdID'); + params = this.omit (params, [ 'clientOrderId', 'clOrdID' ]); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + } else { + request['orderID'] = id; + } + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + let order = data; + if (Array.isArray (data)) { + const numOrders = data.length; + if (numOrders < 1) { + if (clientOrderId !== undefined) { + throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with clientOrderId ' + clientOrderId + ' not found'); + } else { + throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with id ' + id + ' not found'); + } + } + order = this.safeValue (data, 0, {}); + } + return this.parseOrder (order, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetExchangeOrderList'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const rows = this.safeValue (data, 'rows', []); + return this.parseOrders (rows, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetOrdersActiveList'; + const request = { + 'symbol': market['id'], + }; + try { + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + if (Array.isArray (data)) { + return this.parseOrders (data, market, since, limit); + } else { + const rows = this.safeValue (data, 'rows', []); + return this.parseOrders (rows, market, since, limit); + } + } catch (e) { + return []; + } + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetExchangeSpotOrder' : 'privateGetExchangeOrderList'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "total":8, + // "rows":[ + // { + // "orderID":"99232c3e-3d6a-455f-98cc-2061cdfe91bc", + // "stopPxEp":0, + // "avgPriceEp":0, + // "qtyType":"ByBase", + // "leavesBaseQtyEv":0, + // "leavesQuoteQtyEv":0, + // "baseQtyEv":"1000000000", + // "feeCurrency":"4", + // "stopDirection":"UNSPECIFIED", + // "symbol":"sETHUSDT", + // "side":"Buy", + // "quoteQtyEv":250000000000, + // "priceEp":25000000000, + // "ordType":"Limit", + // "timeInForce":"GoodTillCancel", + // "ordStatus":"Rejected", + // "execStatus":"NewRejected", + // "createTimeNs":1592675305266037130, + // "cumFeeEv":0, + // "cumBaseValueEv":0, + // "cumQuoteValueEv":0 + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + if (Array.isArray (data)) { + return this.parseOrders (data, market, since, limit); + } else { + const rows = this.safeValue (data, 'rows', []); + return this.parseOrders (rows, market, since, limit); + } + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetExchangeSpotOrderTrades' : 'privateGetExchangeOrderTrade'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (market['swap'] && (limit !== undefined)) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code": 0, + // "msg": "OK", + // "data": { + // "total": 1, + // "rows": [ + // { + // "qtyType": "ByQuote", + // "transactTimeNs": 1589450974800550100, + // "clOrdID": "8ba59d40-df25-d4b0-14cf-0703f44e9690", + // "orderID": "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83", + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "priceEP": 970056000000, + // "baseQtyEv": 0, + // "quoteQtyEv": 1000000000, + // "action": "New", + // "execStatus": "MakerFill", + // "ordStatus": "Filled", + // "ordType": "Limit", + // "execInst": "None", + // "timeInForce": "GoodTillCancel", + // "stopDirection": "UNSPECIFIED", + // "tradeType": "Trade", + // "stopPxEp": 0, + // "execId": "c6bd8979-07ba-5946-b07e-f8b65135dbb1", + // "execPriceEp": 970056000000, + // "execBaseQtyEv": 103000, + // "execQuoteQtyEv": 999157680, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "execFeeEv": 0, + // "feeRateEr": 0 + // } + // ] + // } + // } + // + // + // swap + // + // { + // "code": 0, + // "msg": "OK", + // "data": { + // "total": 79, + // "rows": [ + // { + // "transactTimeNs": 1606054879331565300, + // "symbol": "BTCUSD", + // "currency": "BTC", + // "action": "New", + // "side": "Buy", + // "tradeType": "Trade", + // "execQty": 5, + // "execPriceEp": 182990000, + // "orderQty": 5, + // "priceEp": 183870000, + // "execValueEv": 27323, + // "feeRateEr": 75000, + // "execFeeEv": 21, + // "ordType": "Market", + // "execID": "5eee56a4-04a9-5677-8eb0-c2fe22ae3645", + // "orderID": "ee0acb82-f712-4543-a11d-d23efca73197", + // "clOrdID": "", + // "execStatus": "TakerFill" + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const rows = this.safeValue (data, 'rows', []); + return this.parseTrades (rows, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetPhemexUserWalletsV2DepositAddress (this.extend (request, params)); + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad", + // "tag":null + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const address = this.safeString (data, 'address'); + const tag = this.safeString (data, 'tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetExchangeWalletsDepositList (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":[ + // { + // "id":29200, + // "currency":"USDT", + // "currencyCode":3, + // "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d", + // "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad", + // "amountEv":3000000000, + // "confirmations":13, + // "type":"Deposit", + // "status":"Success", + // "createdAt":1592722565000 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransactions (data, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetExchangeWalletsWithdrawList (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":[ + // { + // "address": "1Lxxxxxxxxxxx" + // "amountEv": 200000 + // "currency": "BTC" + // "currencyCode": 1 + // "expiredTime": 0 + // "feeEv": 50000 + // "rejectReason": null + // "status": "Succeed" + // "txHash": "44exxxxxxxxxxxxxxxxxxxxxx" + // "withdrawStatus: "" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransactions (data, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'Success': 'ok', + 'Succeed': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // ... + // + // fetchDeposits + // + // { + // "id":29200, + // "currency":"USDT", + // "currencyCode":3, + // "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d", + // "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad", + // "amountEv":3000000000, + // "confirmations":13, + // "type":"Deposit", + // "status":"Success", + // "createdAt":1592722565000 + // } + // + // fetchWithdrawals + // + // { + // "address": "1Lxxxxxxxxxxx" + // "amountEv": 200000 + // "currency": "BTC" + // "currencyCode": 1 + // "expiredTime": 0 + // "feeEv": 50000 + // "rejectReason": null + // "status": "Succeed" + // "txHash": "44exxxxxxxxxxxxxxxxxxxxxx" + // "withdrawStatus: "" + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = undefined; + const txid = this.safeString (transaction, 'txHash'); + const currencyId = this.safeString (transaction, 'currency'); + currency = this.safeCurrency (currencyId, currency); + const code = currency['code']; + const timestamp = this.safeInteger (transaction, 'createdAt'); + let type = this.safeStringLower (transaction, 'type'); + const feeCost = this.fromEn (this.safeFloat (transaction, 'feeEv'), currency['valueScale'], currency['precision']); + let fee = undefined; + if (feeCost !== undefined) { + type = 'withdrawal'; + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.fromEn (this.safeFloat (transaction, 'amountEv'), currency['valueScale'], currency['precision']); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const code = this.safeString (params, 'code'); + const request = {}; + if (code === undefined) { + const currencyId = this.safeString (params, 'currency'); + if (currencyId === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPositions() requires a currency parameter or a code parameter'); + } + } else { + const currency = this.currency (code); + params = this.omit (params, 'code'); + request['currency'] = currency['id']; + } + const response = await this.privateGetAccountsAccountPositions (this.extend (request, params)); + // + // { + // "code":0,"msg":"", + // "data":{ + // "account":{ + // "accountId":6192120001, + // "currency":"BTC", + // "accountBalanceEv":1254744, + // "totalUsedBalanceEv":0, + // "bonusBalanceEv":1254744 + // }, + // "positions":[ + // { + // "accountID":6192120001, + // "symbol":"BTCUSD", + // "currency":"BTC", + // "side":"None", + // "positionStatus":"Normal", + // "crossMargin":false, + // "leverageEr":100000000, + // "leverage":1.00000000, + // "initMarginReqEr":100000000, + // "initMarginReq":1.00000000, + // "maintMarginReqEr":500000, + // "maintMarginReq":0.00500000, + // "riskLimitEv":10000000000, + // "riskLimit":100.00000000, + // "size":0, + // "value":0E-8, + // "valueEv":0, + // "avgEntryPriceEp":0, + // "avgEntryPrice":0E-8, + // "posCostEv":0, + // "posCost":0E-8, + // "assignedPosBalanceEv":0, + // "assignedPosBalance":0E-8, + // "bankruptCommEv":0, + // "bankruptComm":0E-8, + // "bankruptPriceEp":0, + // "bankruptPrice":0E-8, + // "positionMarginEv":0, + // "positionMargin":0E-8, + // "liquidationPriceEp":0, + // "liquidationPrice":0E-8, + // "deleveragePercentileEr":0, + // "deleveragePercentile":0E-8, + // "buyValueToCostEr":100225000, + // "buyValueToCost":1.00225000, + // "sellValueToCostEr":100075000, + // "sellValueToCost":1.00075000, + // "markPriceEp":135736070, + // "markPrice":13573.60700000, + // "markValueEv":0, + // "markValue":null, + // "unRealisedPosLossEv":0, + // "unRealisedPosLoss":null, + // "estimatedOrdLossEv":0, + // "estimatedOrdLoss":0E-8, + // "usedBalanceEv":0, + // "usedBalance":0E-8, + // "takeProfitEp":0, + // "takeProfit":null, + // "stopLossEp":0, + // "stopLoss":null, + // "cumClosedPnlEv":0, + // "cumFundingFeeEv":0, + // "cumTransactFeeEv":0, + // "realisedPnlEv":0, + // "realisedPnl":null, + // "cumRealisedPnlEv":0, + // "cumRealisedPnl":null + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const positions = this.safeValue (data, 'positions', []); + // todo unify parsePosition/parsePositions + return positions; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + const requestPath = '/' + this.implodeParams (path, params); + let url = requestPath; + let queryString = ''; + if ((method === 'GET') || (method === 'DELETE') || (method === 'PUT')) { + if (Object.keys (query).length) { + queryString = this.urlencodeWithArrayRepeat (query); + url += '?' + queryString; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.seconds (); + const xPhemexRequestExpiry = this.safeInteger (this.options, 'x-phemex-request-expiry', 60); + const expiry = this.sum (timestamp, xPhemexRequestExpiry); + const expiryString = expiry.toString (); + headers = { + 'x-phemex-access-token': this.apiKey, + 'x-phemex-request-expiry': expiryString, + }; + let payload = ''; + if (method === 'POST') { + payload = this.json (params); + body = payload; + headers['Content-Type'] = 'application/json'; + } + const auth = requestPath + queryString + expiryString + payload; + headers['x-phemex-request-signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"code":30018,"msg":"phemex.data.size.uplimt","data":null} + // {"code":412,"msg":"Missing parameter - resolution","data":null} + // {"code":412,"msg":"Missing parameter - to","data":null} + // {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null} + // + const error = this.safeValue (response, 'error', response); + const errorCode = this.safeString (error, 'code'); + const message = this.safeString (error, 'msg'); + if ((errorCode !== undefined) && (errorCode !== '0')) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],117:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, RequestTimeout, AuthenticationError, PermissionDenied, DDoSProtection, InsufficientFunds, OrderNotFound, InvalidOrder, AccountSuspended, CancelPending, InvalidNonce, OnMaintenance, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class poloniex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'poloniex', + 'name': 'Poloniex', + 'countries': [ 'US' ], + 'rateLimit': 1000, // up to 6 calls per second + 'certified': false, + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrder': true, // true endpoint for a single open order + 'fetchOpenOrders': true, // true endpoint for open orders + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrderTrades': true, // true endpoint for trades of a single open or closed order + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'cancelAllOrders': true, + 'withdraw': true, + }, + 'timeframes': { + '5m': 300, + '15m': 900, + '30m': 1800, + '2h': 7200, + '4h': 14400, + '1d': 86400, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766817-e9456312-5ee6-11e7-9b3c-b628ca5626a5.jpg', + 'api': { + 'public': 'https://poloniex.com/public', + 'private': 'https://poloniex.com/tradingApi', + }, + 'www': 'https://www.poloniex.com', + 'doc': 'https://docs.poloniex.com', + 'fees': 'https://poloniex.com/fees', + 'referral': 'https://poloniex.com/signup?c=UBFZJRPJ', + }, + 'api': { + 'public': { + 'get': [ + 'return24hVolume', + 'returnChartData', + 'returnCurrencies', + 'returnLoanOrders', + 'returnOrderBook', + 'returnTicker', + 'returnTradeHistory', + ], + }, + 'private': { + 'post': [ + 'buy', + 'cancelLoanOffer', + 'cancelOrder', + 'cancelAllOrders', + 'closeMarginPosition', + 'createLoanOffer', + 'generateNewAddress', + 'getMarginPosition', + 'marginBuy', + 'marginSell', + 'moveOrder', + 'returnActiveLoans', + 'returnAvailableAccountBalances', + 'returnBalances', + 'returnCompleteBalances', + 'returnDepositAddresses', + 'returnDepositsWithdrawals', + 'returnFeeInfo', + 'returnLendingHistory', + 'returnMarginAccountSummary', + 'returnOpenLoanOffers', + 'returnOpenOrders', + 'returnOrderTrades', + 'returnOrderStatus', + 'returnTradableBalances', + 'returnTradeHistory', + 'sell', + 'toggleAutoRenew', + 'transferBalance', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + // starting from Jan 8 2020 + 'maker': 0.0009, + 'taker': 0.0009, + }, + 'funding': {}, + }, + 'limits': { + 'amount': { + 'min': 0.000001, + 'max': 1000000000, + }, + 'price': { + 'min': 0.00000001, + 'max': 1000000000, + }, + 'cost': { + 'min': 0.00000000, + 'max': 1000000000, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'commonCurrencies': { + 'AIR': 'AirCoin', + 'APH': 'AphroditeCoin', + 'BCC': 'BTCtalkcoin', + 'BDG': 'Badgercoin', + 'BTM': 'Bitmark', + 'CON': 'Coino', + 'GOLD': 'GoldEagles', + 'GPUC': 'GPU', + 'HOT': 'Hotcoin', + 'ITC': 'Information Coin', + 'KEY': 'KEYCoin', + 'PLX': 'ParallaxCoin', + 'REPV2': 'REP', + 'STR': 'XLM', + 'SOC': 'SOCC', + 'XAP': 'API Coin', + // this is not documented in the API docs for Poloniex + // https://github.com/ccxt/ccxt/issues/7084 + // when the user calls withdraw ('USDT', amount, address, tag, params) + // with params = { 'currencyToWithdrawAs': 'USDTTRON' } + // or params = { 'currencyToWithdrawAs': 'USDTETH' } + // fetchWithdrawals ('USDT') returns the corresponding withdrawals + // with a USDTTRON or a USDTETH currency id, respectfully + // therefore we have map them back to the original code USDT + // otherwise the returned withdrawals are filtered out + 'USDTTRON': 'USDT', + 'USDTETH': 'USDT', + }, + 'options': { + 'limits': { + 'cost': { + 'min': { + 'BTC': 0.0001, + 'ETH': 0.0001, + 'XMR': 0.0001, + 'USDT': 1.0, + }, + }, + }, + }, + 'exceptions': { + 'exact': { + 'You may only place orders that reduce your position.': InvalidOrder, + 'Invalid order number, or you are not the person who placed the order.': OrderNotFound, + 'Permission denied': PermissionDenied, + 'Connection timed out. Please try again.': RequestTimeout, + 'Internal error. Please try again.': ExchangeNotAvailable, + 'Currently in maintenance mode.': OnMaintenance, + 'Order not found, or you are not the person who placed it.': OrderNotFound, + 'Invalid API key/secret pair.': AuthenticationError, + 'Please do not make more than 8 API calls per second.': DDoSProtection, + 'Rate must be greater than zero.': InvalidOrder, // {"error":"Rate must be greater than zero."} + 'Invalid currency pair.': BadSymbol, // {"error":"Invalid currency pair."} + 'Invalid currencyPair parameter.': BadSymbol, // {"error":"Invalid currencyPair parameter."} + }, + 'broad': { + 'Total must be at least': InvalidOrder, // {"error":"Total must be at least 0.0001."} + 'This account is frozen.': AccountSuspended, + 'Not enough': InsufficientFunds, + 'Nonce must be greater': InvalidNonce, + 'You have already called cancelOrder or moveOrder on this order.': CancelPending, + 'Amount must be at least': InvalidOrder, // {"error":"Amount must be at least 0.000001."} + 'is either completed or does not exist': InvalidOrder, // {"error":"Order 587957810791 is either completed or does not exist."} + 'Error pulling ': ExchangeError, // {"error":"Error pulling order book"} + }, + }, + 'orders': {}, // orders cache / emulation + }); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, cost)), + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "date":1590913773, + // "high":0.02491611, + // "low":0.02491611, + // "open":0.02491611, + // "close":0.02491611, + // "volume":0, + // "quoteVolume":0, + // "weightedAverage":0.02491611 + // } + // + return [ + this.safeTimestamp (ohlcv, 'date'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'quoteVolume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'period': this.timeframes[timeframe], + }; + if (since === undefined) { + request['end'] = this.seconds (); + if (limit === undefined) { + request['start'] = request['end'] - this.parseTimeframe ('1w'); // max range = 1 week + } else { + request['start'] = request['end'] - limit * this.parseTimeframe (timeframe); + } + } else { + request['start'] = parseInt (since / 1000); + if (limit !== undefined) { + const end = this.sum (request['start'], limit * this.parseTimeframe (timeframe)); + request['end'] = end; + } + } + const response = await this.publicGetReturnChartData (this.extend (request, params)); + // + // [ + // {"date":1590913773,"high":0.02491611,"low":0.02491611,"open":0.02491611,"close":0.02491611,"volume":0,"quoteVolume":0,"weightedAverage":0.02491611}, + // {"date":1590913800,"high":0.02495324,"low":0.02489501,"open":0.02491797,"close":0.02493693,"volume":0.0927415,"quoteVolume":3.7227869,"weightedAverage":0.02491185}, + // {"date":1590914100,"high":0.02498596,"low":0.02488503,"open":0.02493033,"close":0.02497896,"volume":0.21196348,"quoteVolume":8.50291888,"weightedAverage":0.02492832}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async loadMarkets (reload = false, params = {}) { + const markets = await super.loadMarkets (reload, params); + const currenciesByNumericId = this.safeValue (this.options, 'currenciesByNumericId'); + if ((currenciesByNumericId === undefined) || reload) { + this.options['currenciesByNumericId'] = this.indexBy (this.currencies, 'numericId'); + } + return markets; + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetReturnTicker (params); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ quoteId, baseId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const limits = this.extend (this.limits, { + 'cost': { + 'min': this.safeValue (this.options['limits']['cost']['min'], quote), + }, + }); + const isFrozen = this.safeString (market, 'isFrozen'); + const active = (isFrozen !== '1'); + const numericId = this.safeInteger (market, 'id'); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': active, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'account': 'all', + }; + const response = await this.privatePostReturnCompleteBalances (this.extend (request, params)); + const result = { 'info': response }; + const currencyIds = Object.keys (response); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = this.safeValue (response, currencyId, {}); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'onOrders'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const fees = await this.privatePostReturnFeeInfo (params); + // + // { + // makerFee: '0.00100000', + // takerFee: '0.00200000', + // marginMakerFee: '0.00100000', + // marginTakerFee: '0.00200000', + // thirtyDayVolume: '106.08463302', + // nextTier: 500000, + // } + // + return { + 'info': fees, + 'maker': this.safeFloat (fees, 'makerFee'), + 'taker': this.safeFloat (fees, 'takerFee'), + 'withdraw': {}, + 'deposit': {}, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; // 100 + } + const response = await this.publicGetReturnOrderBook (this.extend (request, params)); + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'seq'); + return orderbook; + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': 'all', + }; + if (limit !== undefined) { + request['depth'] = limit; // 100 + } + const response = await this.publicGetReturnOrderBook (this.extend (request, params)); + const marketIds = Object.keys (response); + const result = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let symbol = undefined; + if (marketId in this.markets_by_id) { + symbol = this.markets_by_id[marketId]['symbol']; + } else { + const [ quoteId, baseId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const orderbook = this.parseOrderBook (response[marketId]); + orderbook['nonce'] = this.safeInteger (response[marketId], 'seq'); + result[symbol] = orderbook; + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + let open = undefined; + let change = undefined; + let average = undefined; + const last = this.safeFloat (ticker, 'last'); + const relativeChange = this.safeFloat (ticker, 'percentChange'); + if (relativeChange !== -1) { + open = last / this.sum (1, relativeChange); + change = last - open; + average = this.sum (last, open) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24hr'), + 'low': this.safeFloat (ticker, 'low24hr'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': relativeChange * 100, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'quoteVolume'), + 'quoteVolume': this.safeFloat (ticker, 'baseVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetReturnTicker (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = undefined; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } else { + const [ quoteId, baseId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + market = { 'symbol': symbol }; + } + const ticker = response[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetReturnCurrencies (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = response[id]; + const precision = 8; // default precision, todo: fix "magic constants" + const code = this.safeCurrencyCode (id); + const active = (currency['delisted'] === 0) && !currency['disabled']; + const numericId = this.safeInteger (currency, 'id'); + const fee = this.safeFloat (currency, 'txFee'); + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'name': currency['name'], + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': fee, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.publicGetReturnTicker (params); + const ticker = response[market['id']]; + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchMyTrades + // + // { + // globalTradeID: 471030550, + // tradeID: '42582', + // date: '2020-06-16 09:47:50', + // rate: '0.000079980000', + // amount: '75215.00000000', + // total: '6.01569570', + // fee: '0.00095000', + // feeDisplay: '0.26636100 TRX (0.07125%)', + // orderNumber: '5963454848', + // type: 'sell', + // category: 'exchange' + // } + // + // createOrder (taker trades) + // + // { + // 'amount': '200.00000000', + // 'date': '2019-12-15 16:04:10', + // 'rate': '0.00000355', + // 'total': '0.00071000', + // 'tradeID': '119871', + // 'type': 'buy', + // 'takerAdjustment': '200.00000000' + // } + // + const id = this.safeString2 (trade, 'globalTradeID', 'tradeID'); + const orderId = this.safeString (trade, 'orderNumber'); + const timestamp = this.parse8601 (this.safeString (trade, 'date')); + let symbol = undefined; + if ((!market) && ('currencyPair' in trade)) { + const marketId = this.safeString (trade, 'currencyPair'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ quoteId, baseId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'type'); + let fee = undefined; + const price = this.safeFloat (trade, 'rate'); + const cost = this.safeFloat (trade, 'total'); + const amount = this.safeFloat (trade, 'amount'); + const feeDisplay = this.safeString (trade, 'feeDisplay'); + if (feeDisplay !== undefined) { + const parts = feeDisplay.split (' '); + const feeCost = this.safeFloat (parts, 0); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (parts, 1); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + let feeRate = this.safeString (parts, 2); + if (feeRate !== undefined) { + feeRate = feeRate.replace ('(', ''); + const feeRateParts = feeRate.split ('%'); + feeRate = this.safeString (feeRateParts, 0); + feeRate = parseFloat (feeRate) / 100; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + } + } + let takerOrMaker = undefined; + const takerAdjustment = this.safeFloat (trade, 'takerAdjustment'); + if (takerAdjustment !== undefined) { + takerOrMaker = 'taker'; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + request['end'] = this.seconds (); // last 50000 trades by default + } + const trades = await this.publicGetReturnTradeHistory (this.extend (request, params)); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const pair = market ? market['id'] : 'all'; + const request = { 'currencyPair': pair }; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + request['end'] = this.sum (this.seconds (), 1); // adding 1 is a fix for #3411 + } + // limit is disabled (does not really work as expected) + if (limit !== undefined) { + request['limit'] = parseInt (limit); + } + const response = await this.privatePostReturnTradeHistory (this.extend (request, params)); + // + // specific market (symbol defined) + // + // [ + // { + // globalTradeID: 470912587, + // tradeID: '42543', + // date: '2020-06-15 17:31:22', + // rate: '0.000083840000', + // amount: '95237.60321429', + // total: '7.98472065', + // fee: '0.00095000', + // feeDisplay: '0.36137761 TRX (0.07125%)', + // orderNumber: '5926344995', + // type: 'sell', + // category: 'exchange' + // }, + // { + // globalTradeID: 470974497, + // tradeID: '42560', + // date: '2020-06-16 00:41:23', + // rate: '0.000078220000', + // amount: '1000000.00000000', + // total: '78.22000000', + // fee: '0.00095000', + // feeDisplay: '3.48189819 TRX (0.07125%)', + // orderNumber: '5945490830', + // type: 'sell', + // category: 'exchange' + // } + // ] + // + // all markets (symbol undefined) + // + // { + // BTC_GNT: [{ + // globalTradeID: 470839947, + // tradeID: '4322347', + // date: '2020-06-15 12:25:24', + // rate: '0.000005810000', + // amount: '1702.04429303', + // total: '0.00988887', + // fee: '0.00095000', + // feeDisplay: '4.18235294 TRX (0.07125%)', + // orderNumber: '102290272520', + // type: 'buy', + // category: 'exchange' + // }, { + // globalTradeID: 470895902, + // tradeID: '4322413', + // date: '2020-06-15 16:19:00', + // rate: '0.000005980000', + // amount: '18.66879219', + // total: '0.00011163', + // fee: '0.00095000', + // feeDisplay: '0.04733727 TRX (0.07125%)', + // orderNumber: '102298304480', + // type: 'buy', + // category: 'exchange' + // }], + // } + // + let result = []; + if (market !== undefined) { + result = this.parseTrades (response, market); + } else { + if (response) { + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + const trades = this.parseTrades (response[id], market); + for (let j = 0; j < trades.length; j++) { + result.push (trades[j]); + } + } else { + const [ quoteId, baseId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const trades = response[id]; + for (let j = 0; j < trades.length; j++) { + const market = { + 'symbol': symbol, + 'base': base, + 'quote': quote, + }; + result.push (this.parseTrade (trades[j], market)); + } + } + } + } + } + return this.filterBySinceLimit (result, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'Open': 'open', + 'Partially filled': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrder + // + // { + // status: 'Open', + // rate: '0.40000000', + // amount: '1.00000000', + // currencyPair: 'BTC_ETH', + // date: '2018-10-17 17:04:50', + // total: '0.40000000', + // type: 'buy', + // startingAmount: '1.00000', + // } + // + // fetchOpenOrders + // + // { + // orderNumber: '514514894224', + // type: 'buy', + // rate: '0.00001000', + // startingAmount: '100.00000000', + // amount: '100.00000000', + // total: '0.00100000', + // date: '2018-10-23 17:38:53', + // margin: 0, + // } + // + // createOrder + // + // { + // 'orderNumber': '9805453960', + // 'resultingTrades': [ + // { + // 'amount': '200.00000000', + // 'date': '2019-12-15 16:04:10', + // 'rate': '0.00000355', + // 'total': '0.00071000', + // 'tradeID': '119871', + // 'type': 'buy', + // 'takerAdjustment': '200.00000000', + // }, + // ], + // 'fee': '0.00000000', + // 'clientOrderId': '12345', + // 'currencyPair': 'BTC_MANA', + // // --------------------------------------------------------- + // // the following fields are injected by createOrder + // 'timestamp': timestamp, + // 'status': 'open', + // 'type': type, + // 'side': side, + // 'price': price, + // 'amount': amount, + // // --------------------------------------------------------- + // // 'resultingTrades' in editOrder + // 'resultingTrades': { + // 'BTC_MANA': [], + // } + // } + // + let timestamp = this.safeInteger (order, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'date')); + } + let symbol = undefined; + const marketId = this.safeString (order, 'currencyPair'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ quoteId, baseId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let trades = undefined; + let resultingTrades = this.safeValue (order, 'resultingTrades'); + if (!Array.isArray (resultingTrades)) { + resultingTrades = this.safeValue (resultingTrades, this.safeString (market, 'id', marketId)); + } + if (resultingTrades !== undefined) { + trades = this.parseTrades (resultingTrades, market); + } + const price = this.safeFloat2 (order, 'price', 'rate'); + let remaining = this.safeFloat (order, 'amount'); + let amount = this.safeFloat (order, 'startingAmount'); + let filled = undefined; + let cost = 0; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + if (price !== undefined) { + cost = filled * price; + } + } + } else { + amount = remaining; + } + let status = this.parseOrderStatus (this.safeString (order, 'status')); + let average = undefined; + let lastTradeTimestamp = undefined; + if (filled === undefined) { + if (trades !== undefined) { + filled = 0; + cost = 0; + const tradesLength = trades.length; + if (tradesLength > 0) { + lastTradeTimestamp = trades[0]['timestamp']; + for (let i = 0; i < tradesLength; i++) { + const trade = trades[i]; + const tradeAmount = trade['amount']; + const tradePrice = trade['price']; + filled = this.sum (filled, tradeAmount); + cost = this.sum (cost, tradePrice * tradeAmount); + lastTradeTimestamp = Math.max (lastTradeTimestamp, trade['timestamp']); + } + } + if (amount !== undefined) { + remaining = Math.max (amount - filled, 0); + if (filled >= amount) { + status = 'closed'; + } + } + } + } + if ((filled !== undefined) && (cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + let type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side', type); + if (type === side) { + type = undefined; + } + const id = this.safeString (order, 'orderNumber'); + let fee = undefined; + const feeCost = this.safeFloat (order, 'fee'); + if (feeCost !== undefined) { + let feeCurrencyCode = undefined; + if (market !== undefined) { + feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const clientOrderId = this.safeString (order, 'clientOrderId'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + }; + } + + parseOpenOrders (orders, market, result) { + for (let i = 0; i < orders.length; i++) { + const order = orders[i]; + const extended = this.extend (order, { + 'status': 'open', + 'type': 'limit', + 'side': order['type'], + 'price': order['rate'], + }); + result.push (this.parseOrder (extended, market)); + } + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const pair = market ? market['id'] : 'all'; + const request = { + 'currencyPair': pair, + }; + const response = await this.privatePostReturnOpenOrders (this.extend (request, params)); + const extension = { 'status': 'open' }; + if (market === undefined) { + const marketIds = Object.keys (response); + let openOrders = []; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const orders = response[marketId]; + const m = this.markets_by_id[marketId]; + openOrders = this.arrayConcat (openOrders, this.parseOrders (orders, m, undefined, undefined, extension)); + } + return this.filterBySinceLimit (openOrders, since, limit); + } else { + return this.parseOrders (response, market, since, limit, extension); + } + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' createOrder() does not accept market orders'); + } + await this.loadMarkets (); + const method = 'privatePost' + this.capitalize (side); + const market = this.market (symbol); + amount = this.amountToPrecision (symbol, amount); + const request = { + 'currencyPair': market['id'], + 'rate': this.priceToPrecision (symbol, price), + 'amount': amount, + }; + const clientOrderId = this.safeString (params, 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clientOrderId'] = clientOrderId; + params = this.omit (params, 'clientOrderId'); + } + // remember the timestamp before issuing the request + const timestamp = this.milliseconds (); + const response = await this[method] (this.extend (request, params)); + // + // { + // 'orderNumber': '9805453960', + // 'resultingTrades': [ + // { + // 'amount': '200.00000000', + // 'date': '2019-12-15 16:04:10', + // 'rate': '0.00000355', + // 'total': '0.00071000', + // 'tradeID': '119871', + // 'type': 'buy', + // 'takerAdjustment': '200.00000000', + // }, + // ], + // 'fee': '0.00000000', + // 'currencyPair': 'BTC_MANA', + // } + // + return this.parseOrder (this.extend ({ + 'timestamp': timestamp, + 'status': 'open', + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + }, response), market); + } + + async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + price = parseFloat (price); + const request = { + 'orderNumber': id, + 'rate': this.priceToPrecision (symbol, price), + }; + if (amount !== undefined) { + request['amount'] = this.amountToPrecision (symbol, amount); + } + const response = await this.privatePostMoveOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const clientOrderId = this.safeValue (params, 'clientOrderId'); + if (clientOrderId === undefined) { + request['orderNumber'] = id; + } else { + request['clientOrderId'] = clientOrderId; + } + params = this.omit (params, 'clientOrderId'); + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['currencyPair'] = market['id']; + } + const response = await this.privatePostCancelAllOrders (this.extend (request, params)); + // + // { + // "success": 1, + // "message": "Orders canceled", + // "orderNumbers": [ + // 503749, + // 888321, + // 7315825, + // 7316824 + // ] + // } + // + return response; + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + id = id.toString (); + const request = { + 'orderNumber': id, + }; + const response = await this.privatePostReturnOrderStatus (this.extend (request, params)); + // + // { + // success: 1, + // result: { + // '6071071': { + // status: 'Open', + // rate: '0.40000000', + // amount: '1.00000000', + // currencyPair: 'BTC_ETH', + // date: '2018-10-17 17:04:50', + // total: '0.40000000', + // type: 'buy', + // startingAmount: '1.00000', + // }, + // }, + // } + // + const result = this.safeValue (response['result'], id); + if (result === undefined) { + throw new OrderNotFound (this.id + ' order id ' + id + ' not found'); + } + return this.parseOrder (result); + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const orders = await this.fetchOpenOrders (symbol, undefined, undefined, params); + const indexed = this.indexBy (orders, 'id'); + return (id in indexed) ? 'open' : 'closed'; + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderNumber': id, + }; + const trades = await this.privatePostReturnOrderTrades (this.extend (request, params)); + return this.parseTrades (trades); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + // USDT, USDTETH, USDTTRON + let currencyId = undefined; + let currency = undefined; + if (code in this.currencies) { + currency = this.currency (code); + currencyId = currency['id']; + } else { + currencyId = code; + } + const request = { + 'currency': currencyId, + }; + const response = await this.privatePostGenerateNewAddress (this.extend (request, params)); + let address = undefined; + let tag = undefined; + if (response['success'] === 1) { + address = this.safeString (response, 'response'); + } + this.checkAddress (address); + if (currency !== undefined) { + const depositAddress = this.safeString (currency['info'], 'depositAddress'); + if (depositAddress !== undefined) { + tag = address; + address = depositAddress; + } + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostReturnDepositAddresses (params); + // USDT, USDTETH, USDTTRON + let currencyId = undefined; + let currency = undefined; + if (code in this.currencies) { + currency = this.currency (code); + currencyId = currency['id']; + } else { + currencyId = code; + } + let address = this.safeString (response, currencyId); + let tag = undefined; + this.checkAddress (address); + if (currency !== undefined) { + const depositAddress = this.safeString (currency['info'], 'depositAddress'); + if (depositAddress !== undefined) { + tag = address; + address = depositAddress; + } + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + }; + if (tag !== undefined) { + request['paymentId'] = tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + // + // { + // response: 'Withdrew 1.00000000 USDT.', + // email2FA: false, + // withdrawalNumber: 13449869 + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'withdrawalNumber'), + }; + } + + async fetchTransactionsHelper (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const year = 31104000; // 60 * 60 * 24 * 30 * 12 = one year of history, why not + const now = this.seconds (); + const start = (since !== undefined) ? parseInt (since / 1000) : now - 10 * year; + const request = { + 'start': start, // UNIX timestamp, required + 'end': now, // UNIX timestamp, required + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostReturnDepositsWithdrawals (this.extend (request, params)); + // + // { + // "adjustments":[], + // "deposits":[ + // { + // currency: "BTC", + // address: "1MEtiqJWru53FhhHrfJPPvd2tC3TPDVcmW", + // amount: "0.01063000", + // confirmations: 1, + // txid: "952b0e1888d6d491591facc0d37b5ebec540ac1efb241fdbc22bcc20d1822fb6", + // timestamp: 1507916888, + // status: "COMPLETE" + // }, + // { + // currency: "ETH", + // address: "0x20108ba20b65c04d82909e91df06618107460197", + // amount: "4.00000000", + // confirmations: 38, + // txid: "0x4be260073491fe63935e9e0da42bd71138fdeb803732f41501015a2d46eb479d", + // timestamp: 1525060430, + // status: "COMPLETE" + // } + // ], + // "withdrawals":[ + // { + // "withdrawalNumber":13449869, + // "currency":"USDTTRON", // not documented in API docs, see commonCurrencies in describe() + // "address":"TXGaqPW23JdRWhsVwS2mRsGsegbdnAd3Rw", + // "amount":"1.00000000", + // "fee":"0.00000000", + // "timestamp":1591573420, + // "status":"COMPLETE: dadf427224b3d44b38a2c13caa4395e4666152556ca0b2f67dbd86a95655150f", + // "ipAddress":"74.116.3.247", + // "canCancel":0, + // "canResendEmail":0, + // "paymentID":null, + // "scope":"crypto" + // }, + // { + // withdrawalNumber: 8224394, + // currency: "EMC2", + // address: "EYEKyCrqTNmVCpdDV8w49XvSKRP9N3EUyF", + // amount: "63.10796020", + // fee: "0.01000000", + // timestamp: 1510819838, + // status: "COMPLETE: d37354f9d02cb24d98c8c4fc17aa42f475530b5727effdf668ee5a43ce667fd6", + // ipAddress: "5.220.220.200" + // }, + // { + // withdrawalNumber: 9290444, + // currency: "ETH", + // address: "0x191015ff2e75261d50433fbd05bd57e942336149", + // amount: "0.15500000", + // fee: "0.00500000", + // timestamp: 1514099289, + // status: "COMPLETE: 0x12d444493b4bca668992021fd9e54b5292b8e71d9927af1f076f554e4bea5b2d", + // ipAddress: "5.228.227.214" + // }, + // { + // withdrawalNumber: 11518260, + // currency: "BTC", + // address: "8JoDXAmE1GY2LRK8jD1gmAmgRPq54kXJ4t", + // amount: "0.20000000", + // fee: "0.00050000", + // timestamp: 1527918155, + // status: "COMPLETE: 1864f4ebb277d90b0b1ff53259b36b97fa1990edc7ad2be47c5e0ab41916b5ff", + // ipAddress: "211.8.195.26" + // } + // ] + // } + // + return response; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.fetchTransactionsHelper (code, since, limit, params); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const withdrawals = this.safeValue (response, 'withdrawals', []); + const deposits = this.safeValue (response, 'deposits', []); + const withdrawalTransactions = this.parseTransactions (withdrawals, currency, since, limit); + const depositTransactions = this.parseTransactions (deposits, currency, since, limit); + const transactions = this.arrayConcat (depositTransactions, withdrawalTransactions); + return this.filterByCurrencySinceLimit (this.sortBy (transactions, 'timestamp'), code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.fetchTransactionsHelper (code, since, limit, params); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const withdrawals = this.safeValue (response, 'withdrawals', []); + const transactions = this.parseTransactions (withdrawals, currency, since, limit); + return this.filterByCurrencySinceLimit (transactions, code, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.fetchTransactionsHelper (code, since, limit, params); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const deposits = this.safeValue (response, 'deposits', []); + const transactions = this.parseTransactions (deposits, currency, since, limit); + return this.filterByCurrencySinceLimit (transactions, code, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'COMPLETE': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // deposits + // + // { + // "txid": "f49d489616911db44b740612d19464521179c76ebe9021af85b6de1e2f8d68cd", + // "type": "deposit", + // "amount": "49798.01987021", + // "status": "COMPLETE", + // "address": "DJVJZ58tJC8UeUv9Tqcdtn6uhWobouxFLT", + // "currency": "DOGE", + // "timestamp": 1524321838, + // "confirmations": 3371, + // "depositNumber": 134587098 + // } + // + // withdrawals + // + // { + // "fee": "0.00050000", + // "type": "withdrawal", + // "amount": "0.40234387", + // "status": "COMPLETE: fbabb2bf7d81c076f396f3441166d5f60f6cea5fdfe69e02adcc3b27af8c2746", + // "address": "1EdAqY4cqHoJGAgNfUFER7yZpg1Jc9DUa3", + // "currency": "BTC", + // "canCancel": 0, + // "ipAddress": "185.230.101.31", + // "paymentID": null, + // "timestamp": 1523834337, + // "canResendEmail": 0, + // "withdrawalNumber": 11162900 + // } + // + const timestamp = this.safeTimestamp (transaction, 'timestamp'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let status = this.safeString (transaction, 'status', 'pending'); + let txid = this.safeString (transaction, 'txid'); + if (status !== undefined) { + const parts = status.split (': '); + const numParts = parts.length; + status = parts[0]; + if ((numParts > 1) && (txid === undefined)) { + txid = parts[1]; + } + status = this.parseTransactionStatus (status); + } + const defaultType = ('withdrawalNumber' in transaction) ? 'withdrawal' : 'deposit'; + const type = this.safeString (transaction, 'type', defaultType); + const id = this.safeString2 (transaction, 'withdrawalNumber', 'depositNumber'); + let amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'paymentID'); + // according to https://poloniex.com/fees/ + const feeCost = this.safeFloat (transaction, 'fee', 0); + if (type === 'withdrawal') { + // poloniex withdrawal amount includes the fee + amount = amount - feeCost; + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': tag, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const response = await this.privatePostGetMarginPosition (this.extend (request, params)); + // + // { + // type: "none", + // amount: "0.00000000", + // total: "0.00000000", + // basePrice: "0.00000000", + // liquidationPrice: -1, + // pl: "0.00000000", + // lendingFees: "0.00000000" + // } + // + // todo unify parsePosition/parsePositions + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.extend ({ 'command': path }, params); + if (api === 'public') { + url += '?' + this.urlencode (query); + } else { + this.checkRequiredCredentials (); + query['nonce'] = this.nonce (); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // {"error":"Permission denied."} + if ('error' in response) { + const message = response['error']; + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],118:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, BadResponse, BadRequest, InvalidOrder, InsufficientFunds, AuthenticationError, ArgumentsRequired, InvalidAddress, RateLimitExceeded, DDoSProtection, BadSymbol } = require ('./base/errors'); +const { TRUNCATE, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class probit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'probit', + 'name': 'ProBit', + 'countries': [ 'SC', 'KR' ], // Seychelles, South Korea + 'rateLimit': 250, // ms + 'has': { + 'CORS': true, + 'fetchTime': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchTickers': true, + 'fetchTicker': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchTrades': true, + 'fetchBalance': true, + 'createOrder': true, + 'createMarketOrder': true, + 'cancelOrder': true, + 'fetchOrder': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchMyTrades': true, + 'fetchDepositAddress': true, + 'withdraw': true, + 'signIn': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '10m': '10m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '6h': '6h', + '12h': '12h', + '1d': '1D', + '1w': '1W', + '1M': '1M', + }, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/79268032-c4379480-7ea2-11ea-80b3-dd96bb29fd0d.jpg', + 'api': { + 'accounts': 'https://accounts.probit.com', + 'public': 'https://api.probit.com/api/exchange', + 'private': 'https://api.probit.com/api/exchange', + }, + 'www': 'https://www.probit.com', + 'doc': [ + 'https://docs-en.probit.com', + 'https://docs-ko.probit.com', + ], + 'fees': 'https://support.probit.com/hc/en-us/articles/360020968611-Trading-Fees', + 'referral': 'https://www.probit.com/r/34608773', + }, + 'api': { + 'public': { + 'get': [ + 'market', + 'currency', + 'currency_with_platform', + 'time', + 'ticker', + 'order_book', + 'trade', + 'candle', + ], + }, + 'private': { + 'post': [ + 'new_order', + 'cancel_order', + 'withdrawal', + ], + 'get': [ + 'balance', + 'order', + 'open_order', + 'order_history', + 'trade_history', + 'deposit_address', + ], + }, + 'accounts': { + 'post': [ + 'token', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + }, + 'exceptions': { + 'exact': { + 'UNAUTHORIZED': AuthenticationError, + 'INVALID_ARGUMENT': BadRequest, // Parameters are not a valid format, parameters are empty, or out of range, or a parameter was sent when not required. + 'TRADING_UNAVAILABLE': ExchangeNotAvailable, + 'NOT_ENOUGH_BALANCE': InsufficientFunds, + 'NOT_ALLOWED_COMBINATION': BadRequest, + 'INVALID_ORDER': InvalidOrder, // Requested order does not exist, or it is not your order + 'RATE_LIMIT_EXCEEDED': RateLimitExceeded, // You are sending requests too frequently. Please try it later. + 'MARKET_UNAVAILABLE': ExchangeNotAvailable, // Market is closed today + 'INVALID_MARKET': BadSymbol, // Requested market is not exist + 'INVALID_CURRENCY': BadRequest, // Requested currency is not exist on ProBit system + 'TOO_MANY_OPEN_ORDERS': DDoSProtection, // Too many open orders + 'DUPLICATE_ADDRESS': InvalidAddress, // Address already exists in withdrawal address list + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'timeInForce': { + 'limit': 'gtc', + 'market': 'ioc', + }, + }, + 'commonCurrencies': { + 'BTCBEAR': 'BEAR', + 'BTCBULL': 'BULL', + 'CBC': 'CryptoBharatCoin', + 'UNI': 'UNICORN Token', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarket (params); + // + // { + // "data":[ + // { + // "id":"MONA-USDT", + // "base_currency_id":"MONA", + // "quote_currency_id":"USDT", + // "min_price":"0.001", + // "max_price":"9999999999999999", + // "price_increment":"0.001", + // "min_quantity":"0.0001", + // "max_quantity":"9999999999999999", + // "quantity_precision":4, + // "min_cost":"1", + // "max_cost":"9999999999999999", + // "cost_precision":8, + // "taker_fee_rate":"0.2", + // "maker_fee_rate":"0.2", + // "show_in_ui":true, + // "closed":false + // }, + // ] + // } + // + const markets = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency_id'); + const quoteId = this.safeString (market, 'quote_currency_id'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const closed = this.safeValue (market, 'closed', false); + const active = !closed; + const amountPrecision = this.safeInteger (market, 'quantity_precision'); + const costPrecision = this.safeInteger (market, 'cost_precision'); + const precision = { + 'amount': 1 / Math.pow (10, amountPrecision), + 'price': this.safeFloat (market, 'price_increment'), + 'cost': 1 / Math.pow (10, costPrecision), + }; + const takerFeeRate = this.safeFloat (market, 'taker_fee_rate'); + const makerFeeRate = this.safeFloat (market, 'maker_fee_rate'); + result.push ({ + 'id': id, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'taker': takerFeeRate / 100, + 'maker': makerFeeRate / 100, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_quantity'), + 'max': this.safeFloat (market, 'max_quantity'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'min_cost'), + 'max': this.safeFloat (market, 'max_cost'), + }, + }, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencyWithPlatform (params); + // + // { + // "data":[ + // { + // "id":"USDT", + // "display_name":{"ko-kr":"테더","en-us":"Tether"}, + // "show_in_ui":true, + // "platform":[ + // { + // "id":"ETH", + // "priority":1, + // "deposit":true, + // "withdrawal":true, + // "currency_id":"USDT", + // "precision":6, + // "min_confirmation_count":15, + // "require_destination_tag":false, + // "display_name":{"name":{"ko-kr":"ERC-20","en-us":"ERC-20"}}, + // "min_deposit_amount":"0", + // "min_withdrawal_amount":"1", + // "withdrawal_fee":[ + // {"amount":"0.01","priority":2,"currency_id":"ETH"}, + // {"amount":"1.5","priority":1,"currency_id":"USDT"}, + // ], + // "deposit_fee":{}, + // "suspended_reason":"", + // "deposit_suspended":false, + // "withdrawal_suspended":false + // }, + // { + // "id":"OMNI", + // "priority":2, + // "deposit":true, + // "withdrawal":true, + // "currency_id":"USDT", + // "precision":6, + // "min_confirmation_count":3, + // "require_destination_tag":false, + // "display_name":{"name":{"ko-kr":"OMNI","en-us":"OMNI"}}, + // "min_deposit_amount":"0", + // "min_withdrawal_amount":"5", + // "withdrawal_fee":[{"amount":"5","priority":1,"currency_id":"USDT"}], + // "deposit_fee":{}, + // "suspended_reason":"wallet_maintenance", + // "deposit_suspended":false, + // "withdrawal_suspended":false + // } + // ], + // "stakeable":false, + // "unstakeable":false, + // "auto_stake":false, + // "auto_stake_amount":"0" + // } + // ] + // } + // + const currencies = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'id'); + const code = this.safeCurrencyCode (id); + const displayName = this.safeValue (currency, 'display_name'); + const name = this.safeString (displayName, 'en-us'); + const platforms = this.safeValue (currency, 'platform', []); + const platformsByPriority = this.sortBy (platforms, 'priority'); + const platform = this.safeValue (platformsByPriority, 0, {}); + const precision = this.safeInteger (platform, 'precision'); + const depositSuspended = this.safeValue (platform, 'deposit_suspended'); + const withdrawalSuspended = this.safeValue (platform, 'withdrawal_suspended'); + const active = !(depositSuspended && withdrawalSuspended); + const withdrawalFees = this.safeValue (platform, 'withdrawal_fee', {}); + const withdrawalFeesByPriority = this.sortBy (withdrawalFees, 'priority'); + const withdrawalFee = this.safeValue (withdrawalFeesByPriority, 0, {}); + const fee = this.safeFloat (withdrawalFee, 'amount'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'deposit': { + 'min': this.safeFloat (platform, 'min_deposit_amount'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (platform, 'min_withdrawal_amount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + // + // { + // data: [ + // { + // "currency_id":"XRP", + // "total":"100", + // "available":"0", + // } + // ] + // } + // + const data = this.safeValue (response, 'data'); + const result = { 'info': data }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'total'); + account['free'] = this.safeFloat (balance, 'available'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + }; + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { + // data: [ + // { side: 'buy', price: '0.000031', quantity: '10' }, + // { side: 'buy', price: '0.00356007', quantity: '4.92156877' }, + // { side: 'sell', price: '0.1857', quantity: '0.17' }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const dataBySide = this.groupBy (data, 'side'); + return this.parseOrderBook (dataBySide, undefined, 'buy', 'sell', 'price', 'quantity'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const marketIds = this.marketIds (symbols); + request['market_ids'] = marketIds.join (','); + } + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "data":[ + // { + // "last":"0.022902", + // "low":"0.021693", + // "high":"0.024093", + // "change":"-0.000047", + // "base_volume":"15681.986", + // "quote_volume":"360.514403624", + // "market_id":"ETH-BTC", + // "time":"2020-04-12T18:43:38.000Z" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTickers (data, symbols); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_ids': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "data":[ + // { + // "last":"0.022902", + // "low":"0.021693", + // "high":"0.024093", + // "change":"-0.000047", + // "base_volume":"15681.986", + // "quote_volume":"360.514403624", + // "market_id":"ETH-BTC", + // "time":"2020-04-12T18:43:38.000Z" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const ticker = this.safeValue (data, 0); + if (ticker === undefined) { + throw new BadResponse (this.id + ' fetchTicker() returned an empty response'); + } + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "last":"0.022902", + // "low":"0.021693", + // "high":"0.024093", + // "change":"-0.000047", + // "base_volume":"15681.986", + // "quote_volume":"360.514403624", + // "market_id":"ETH-BTC", + // "time":"2020-04-12T18:43:38.000Z" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'time')); + const marketId = this.safeString (ticker, 'market_id'); + const symbol = this.safeSymbol (marketId, market, '-'); + const close = this.safeFloat (ticker, 'last'); + const change = this.safeFloat (ticker, 'change'); + let percentage = undefined; + let open = undefined; + if (change !== undefined) { + if (close !== undefined) { + open = close - change; + percentage = (change / open) * 100; + } + } + const baseVolume = this.safeFloat (ticker, 'base_volume'); + const quoteVolume = this.safeFloat (ticker, 'quote_volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + 'limit': 100, + 'start_time': this.iso8601 (0), + 'end_time': this.iso8601 (this.milliseconds ()), + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['market_id'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradeHistory (this.extend (request, params)); + // + // { + // data: [ + // { + // "id":"BTC-USDT:183566", + // "order_id":"17209376", + // "side":"sell", + // "fee_amount":"0.657396569175", + // "fee_currency_id":"USDT", + // "status":"settled", + // "price":"6573.96569175", + // "quantity":"0.1", + // "cost":"657.396569175", + // "time":"2018-08-10T06:06:46.000Z", + // "market_id":"BTC-USDT" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + 'limit': 100, + 'start_time': '1970-01-01T00:00:00.000Z', + 'end_time': this.iso8601 (this.milliseconds ()), + }; + if (since !== undefined) { + request['start_time'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTrade (this.extend (request, params)); + // + // { + // "data":[ + // { + // "id":"ETH-BTC:3331886", + // "price":"0.022981", + // "quantity":"12.337", + // "time":"2020-04-12T20:55:42.371Z", + // "side":"sell", + // "tick_direction":"down" + // }, + // { + // "id":"ETH-BTC:3331885", + // "price":"0.022982", + // "quantity":"6.472", + // "time":"2020-04-12T20:55:39.652Z", + // "side":"sell", + // "tick_direction":"down" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":"ETH-BTC:3331886", + // "price":"0.022981", + // "quantity":"12.337", + // "time":"2020-04-12T20:55:42.371Z", + // "side":"sell", + // "tick_direction":"down" + // } + // + // fetchMyTrades (private) + // + // { + // "id":"BTC-USDT:183566", + // "order_id":"17209376", + // "side":"sell", + // "fee_amount":"0.657396569175", + // "fee_currency_id":"USDT", + // "status":"settled", + // "price":"6573.96569175", + // "quantity":"0.1", + // "cost":"657.396569175", + // "time":"2018-08-10T06:06:46.000Z", + // "market_id":"BTC-USDT" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'time')); + const id = this.safeString (trade, 'id'); + let marketId = undefined; + if (id !== undefined) { + const parts = id.split (':'); + marketId = this.safeString (parts, 0); + } + marketId = this.safeString (trade, 'market_id', marketId); + const symbol = this.safeSymbol (marketId, market, '-'); + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const orderId = this.safeString (trade, 'order_id'); + const feeCost = this.safeFloat (trade, 'fee_amount'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency_id'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { "data":"2020-04-12T18:54:25.390Z" } + // + const timestamp = this.parse8601 (this.safeString (response, 'data')); + return timestamp; + } + + normalizeOHLCVTimestamp (timestamp, timeframe, after = false) { + const duration = this.parseTimeframe (timeframe); + if (timeframe === '1M') { + const iso8601 = this.iso8601 (timestamp); + const parts = iso8601.split ('-'); + const year = this.safeString (parts, 0); + let month = this.safeInteger (parts, 1); + if (after) { + month = this.sum (month, 1); + } + if (month < 10) { + month = '0' + month.toString (); + } else { + month = month.toString (); + } + return year + '-' + month + '-01T00:00:00.000Z'; + } else if (timeframe === '1w') { + timestamp = parseInt (timestamp / 1000); + const firstSunday = 259200; // 1970-01-04T00:00:00.000Z + const difference = timestamp - firstSunday; + const numWeeks = this.integerDivide (difference, duration); + let previousSunday = this.sum (firstSunday, numWeeks * duration); + if (after) { + previousSunday = this.sum (previousSunday, duration); + } + return this.iso8601 (previousSunday * 1000); + } else { + timestamp = parseInt (timestamp / 1000); + timestamp = duration * parseInt (timestamp / duration); + if (after) { + timestamp = this.sum (timestamp, duration); + } + return this.iso8601 (timestamp * 1000); + } + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const interval = this.timeframes[timeframe]; + limit = (limit === undefined) ? 100 : limit; + let requestLimit = this.sum (limit, 1); + requestLimit = Math.min (1000, requestLimit); // max 1000 + const request = { + 'market_ids': market['id'], + 'interval': interval, + 'sort': 'asc', // 'asc' will always include the start_time, 'desc' will always include end_time + 'limit': requestLimit, // max 1000 + }; + const now = this.milliseconds (); + const duration = this.parseTimeframe (timeframe); + let startTime = since; + let endTime = now; + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires either a since argument or a limit argument'); + } else { + startTime = now - limit * duration * 1000; + } + } else { + if (limit === undefined) { + endTime = now; + } else { + endTime = this.sum (since, this.sum (limit, 1) * duration * 1000); + } + } + const startTimeNormalized = this.normalizeOHLCVTimestamp (startTime, timeframe); + const endTimeNormalized = this.normalizeOHLCVTimestamp (endTime, timeframe, true); + request['start_time'] = startTimeNormalized; + request['end_time'] = endTimeNormalized; + const response = await this.publicGetCandle (this.extend (request, params)); + // + // { + // "data":[ + // { + // "market_id":"ETH-BTC", + // "open":"0.02811", + // "close":"0.02811", + // "low":"0.02811", + // "high":"0.02811", + // "base_volume":"0.0005", + // "quote_volume":"0.000014055", + // "start_time":"2018-11-30T18:19:00.000Z", + // "end_time":"2018-11-30T18:20:00.000Z" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "market_id":"ETH-BTC", + // "open":"0.02811", + // "close":"0.02811", + // "low":"0.02811", + // "high":"0.02811", + // "base_volume":"0.0005", + // "quote_volume":"0.000014055", + // "start_time":"2018-11-30T18:19:00.000Z", + // "end_time":"2018-11-30T18:20:00.000Z" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'start_time')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'base_volume'), + ]; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + since = this.parse8601 (since); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market_id'] = market['id']; + } + const response = await this.privateGetOpenOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'start_time': this.iso8601 (0), + 'end_time': this.iso8601 (this.milliseconds ()), + 'limit': 100, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market_id'] = market['id']; + } + if (since) { + request['start_time'] = this.iso8601 (since); + } + if (limit) { + request['limit'] = limit; + } + const response = await this.privateGetOrderHistory (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_order_id'); + if (clientOrderId !== undefined) { + request['client_order_id'] = clientOrderId; + } else { + request['order_id'] = id; + } + const query = this.omit (params, [ 'clientOrderId', 'client_order_id' ]); + const response = await this.privateGetOrder (this.extend (request, query)); + const data = this.safeValue (response, 'data', []); + const order = this.safeValue (data, 0); + return this.parseOrder (order, market); + } + + parseOrderStatus (status) { + const statuses = { + 'open': 'open', + 'cancelled': 'canceled', + 'filled': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // id: string, + // user_id: string, + // market_id: string, + // type: 'orderType', + // side: 'side', + // quantity: string, + // limit_price: string, + // time_in_force: 'timeInForce', + // filled_cost: string, + // filled_quantity: string, + // open_quantity: string, + // cancelled_quantity: string, + // status: 'orderStatus', + // time: 'date', + // client_order_id: string, + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const marketId = this.safeString (order, 'market_id'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'time')); + let price = this.safeFloat (order, 'limit_price'); + const filled = this.safeFloat (order, 'filled_quantity'); + let remaining = this.safeFloat (order, 'open_quantity'); + const canceledAmount = this.safeFloat (order, 'cancelled_quantity'); + if (canceledAmount !== undefined) { + remaining = this.sum (remaining, canceledAmount); + } + const amount = this.safeFloat (order, 'quantity', this.sum (filled, remaining)); + let cost = this.safeFloat2 (order, 'filled_cost', 'cost'); + if (type === 'market') { + price = undefined; + } + let average = undefined; + if (filled !== undefined) { + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } + if (cost !== undefined) { + if (filled > 0) { + average = cost / filled; + } + } + } + let clientOrderId = this.safeString (order, 'client_order_id'); + if (clientOrderId === '') { + clientOrderId = undefined; + } + const timeInForce = this.safeStringUpper (order, 'time_in_force'); + return { + 'id': id, + 'info': order, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'status': status, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': average, + 'cost': cost, + 'fee': undefined, + 'trades': undefined, + }; + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['cost'], this.precisionMode); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const options = this.safeValue (this.options, 'timeInForce'); + const defaultTimeInForce = this.safeValue (options, type); + const timeInForce = this.safeString2 (params, 'timeInForce', 'time_in_force', defaultTimeInForce); + const request = { + 'market_id': market['id'], + 'type': type, + 'side': side, + 'time_in_force': timeInForce, + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_order_id'); + if (clientOrderId !== undefined) { + request['client_order_id'] = clientOrderId; + } + let costToPrecision = undefined; + if (type === 'limit') { + request['limit_price'] = this.priceToPrecision (symbol, price); + request['quantity'] = this.amountToPrecision (symbol, amount); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let cost = this.safeFloat (params, 'cost'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (cost === undefined) { + cost = amount * price; + } + } else if (cost === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument for market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'cost' extra parameter (the exchange-specific behaviour)"); + } + } else { + cost = (cost === undefined) ? amount : cost; + } + costToPrecision = this.costToPrecision (symbol, cost); + request['cost'] = costToPrecision; + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + } + const query = this.omit (params, [ 'timeInForce', 'time_in_force', 'clientOrderId', 'client_order_id' ]); + const response = await this.privatePostNewOrder (this.extend (request, query)); + // + // { + // data: { + // id: string, + // user_id: string, + // market_id: string, + // type: 'orderType', + // side: 'side', + // quantity: string, + // limit_price: string, + // time_in_force: 'timeInForce', + // filled_cost: string, + // filled_quantity: string, + // open_quantity: string, + // cancelled_quantity: string, + // status: 'orderStatus', + // time: 'date', + // client_order_id: string, + // } + // } + // + const data = this.safeValue (response, 'data'); + const order = this.parseOrder (data, market); + // a workaround for incorrect huge amounts + // returned by the exchange on market buys + if ((type === 'market') && (side === 'buy')) { + order['amount'] = undefined; + order['cost'] = parseFloat (costToPrecision); + order['remaining'] = undefined; + } + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + 'order_id': id, + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data); + } + + parseDepositAddress (depositAddress, currency = undefined) { + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'destination_tag'); + const currencyId = this.safeString (depositAddress, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_id': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + // + // { + // "data":[ + // { + // "currency_id":"ETH", + // "address":"0x12e2caf3c4051ba1146e612f532901a423a9898a", + // "destination_tag":null + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const firstAddress = this.safeValue (data, 0); + if (firstAddress === undefined) { + throw new InvalidAddress (this.id + ' fetchDepositAddress returned an empty response'); + } + return this.parseDepositAddress (firstAddress, currency); + } + + async fetchDepositAddresses (codes = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (codes) { + const currencyIds = []; + for (let i = 0; i < codes.length; i++) { + const currency = this.currency (codes[i]); + currencyIds.push (currency['id']); + } + request['currency_id'] = codes.join (','); + } + const response = await this.privateGetDepositAddress (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseDepositAddresses (data); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // In order to use this method + // you need to allow API withdrawal from the API Settings Page, and + // and register the list of withdrawal addresses and destination tags on the API Settings page + // you can only withdraw to the registered addresses using the API + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag === undefined) { + tag = ''; + } + const request = { + 'currency_id': currency['id'], + // 'platform_id': 'ETH', // if omitted it will use the default platform for the currency + 'address': address, + 'destination_tag': tag, + 'amount': this.currencyToPrecision (code, amount), + // which currency to pay the withdrawal fees + // only applicable for currencies that accepts multiple withdrawal fee options + // 'fee_currency_id': 'ETH', // if omitted it will use the default fee policy for each currency + // whether the amount field includes fees + // 'include_fee': false, // makes sense only when fee_currency_id is equal to currency_id + }; + const response = await this.privatePostWithdrawal (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseTransaction (data, currency); + } + + parseTransaction (transaction, currency = undefined) { + const id = this.safeString (transaction, 'id'); + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'destination_tag'); + const txid = this.safeString (transaction, 'hash'); + const timestamp = this.parse8601 (this.safeString (transaction, 'time')); + const type = this.safeString (transaction, 'type'); + const currencyId = this.safeString (transaction, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined && feeCost !== 0) { + fee = { + 'currency': code, + 'cost': feeCost, + }; + } + return { + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'status': status, + 'type': type, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + 'info': transaction, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'requested': 'pending', + 'pending': 'pending', + 'confirming': 'pending', + 'confirmed': 'pending', + 'applying': 'pending', + 'done': 'ok', + 'cancelled': 'canceled', + 'cancelling': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + const query = this.omit (params, this.extractParams (path)); + if (api === 'accounts') { + this.checkRequiredCredentials (); + url += this.implodeParams (path, params); + const auth = this.apiKey + ':' + this.secret; + const auth64 = this.stringToBase64 (auth); + headers = { + 'Authorization': 'Basic ' + this.decode (auth64), + 'Content-Type': 'application/json', + }; + if (Object.keys (query).length) { + body = this.json (query); + } + } else { + url += this.version + '/'; + if (api === 'public') { + url += this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + const now = this.milliseconds (); + this.checkRequiredCredentials (); + const expires = this.safeInteger (this.options, 'expires'); + if ((expires === undefined) || (expires < now)) { + throw new AuthenticationError (this.id + ' access token expired, call signIn() method'); + } + const accessToken = this.safeString (this.options, 'accessToken'); + headers = { + 'Authorization': 'Bearer ' + accessToken, + }; + url += this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length) { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async signIn (params = {}) { + this.checkRequiredCredentials (); + const request = { + 'grant_type': 'client_credentials', // the only supported value + }; + const response = await this.accountsPostToken (this.extend (request, params)); + // + // { + // access_token: '0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=', + // token_type: 'bearer', + // expires_in: 900 + // } + // + const expiresIn = this.safeInteger (response, 'expires_in'); + const accessToken = this.safeString (response, 'access_token'); + this.options['accessToken'] = accessToken; + this.options['expires'] = this.sum (this.milliseconds (), expiresIn * 1000); + return response; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('errorCode' in response) { + const errorCode = this.safeString (response, 'errorCode'); + const message = this.safeString (response, 'message'); + if (errorCode !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],119:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidOrder, InsufficientFunds, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class qtrade extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'qtrade', + 'name': 'qTrade', + 'countries': [ 'US' ], + 'rateLimit': 1000, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg', + 'api': 'https://api.qtrade.io', + 'www': 'https://qtrade.io', + 'doc': 'https://qtrade-exchange.github.io/qtrade-docs', + 'referral': 'https://qtrade.io/?ref=BKOQWVFGRH2C', + }, + 'has': { + 'CORS': false, + 'fetchTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchMyTrades': true, + 'fetchClosedOrders': true, + 'fetchOpenOrders': true, + 'fetchOHLCV': true, + 'createOrder': true, + 'cancelOrder': true, + 'createMarketOrder': false, + 'withdraw': true, + 'fetchDepositAddress': true, + 'fetchTransactions': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchDeposit': true, + 'fetchWithdrawal': true, + }, + 'timeframes': { + '5m': 'fivemin', + '15m': 'fifteenmin', + '30m': 'thirtymin', + '1h': 'onehour', + '2h': 'twohour', + '4h': 'fourhour', + '1d': 'oneday', + }, + 'api': { + 'public': { + 'get': [ + 'ticker/{market_string}', + 'tickers', + 'currency/{code}', + 'currencies', + 'common', + 'market/{market_string}', + 'markets', + 'market/{market_string}/trades', + 'orderbook/{market_string}', + 'market/{market_string}/ohlcv/{interval}', + ], + }, + 'private': { + 'get': [ + 'me', + 'balances', + 'balances_all', // undocumented + 'market/{market_string}', + 'orders', + 'order/{order_id}', + 'trades', + 'withdraw/{withdraw_id}', + 'withdraws', + 'deposit/{deposit_id}', + 'deposits', + 'transfers', + ], + 'post': [ + 'cancel_order', + 'withdraw', + 'deposit_address/{currency}', + 'sell_limit', + 'buy_limit', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.0025, + 'maker': 0.0, + }, + 'funding': { + 'withdraw': {}, + }, + }, + 'exceptions': { + 'exact': { + 'invalid_auth': AuthenticationError, + 'insuff_funds': InsufficientFunds, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // { + // "data":{ + // "markets":[ + // { + // "id":5, + // "market_currency":"BAC", + // "base_currency":"BTC", + // "maker_fee":"0.0025", + // "taker_fee":"0.0025", + // "metadata":{ + // "delisting_date":"7/15/2018", + // "market_notices":[ + // { + // "message":"Delisting Notice: This market has been delisted due to low volume. Please cancel your orders and withdraw your funds by 7/15/2018.", + // "type":"warning" + // } + // ] + // }, + // "can_trade":false, + // "can_cancel":true, + // "can_view":false, + // "market_string":"BAC_BTC", + // "minimum_sell_amount":"0.0001", + // "minimum_buy_value":"0.0001", + // "market_precision":8, + // "base_precision":8 + // }, + // ], + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const markets = this.safeValue (data, 'markets', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const marketId = this.safeString (market, 'market_string'); + const numericId = this.safeInteger (market, 'id'); + const baseId = this.safeString (market, 'market_currency'); + const quoteId = this.safeString (market, 'base_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'market_precision'), + 'price': this.safeInteger (market, 'base_precision'), + }; + const canView = this.safeValue (market, 'can_view', false); + const canTrade = this.safeValue (market, 'can_trade', false); + const active = canTrade && canView; + result.push ({ + 'symbol': symbol, + 'id': marketId, + 'numericId': numericId, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'taker': this.safeFloat (market, 'taker_fee'), + 'maker': this.safeFloat (market, 'maker_fee'), + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minimum_buy_value'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "data":{ + // "currencies":[ + // { + // "code":"DGB", + // "long_name":"Digibyte", + // "type":"bitcoin_like", + // "precision":8, + // "config":{ + // "price":0.0035, + // "withdraw_fee":"10", + // "deposit_types":[ + // { + // "label":"Address", + // "lookup_mode":"address", + // "render_type":"address", + // "deposit_type":"address", + // "lookup_config":{} + // } + // ], + // "default_signer":103, + // "address_version":30, + // "satoshi_per_byte":300, + // "required_confirmations":200, + // "required_generate_confirmations":300 + // }, + // "metadata":{}, + // "minimum_order":"0.0001", + // "status":"ok", + // "can_withdraw":true, + // "delisted":false, + // "deposit_disabled":false, + // "withdraw_disabled":false, + // "deposit_warn_codes":[], + // "withdraw_warn_codes":[] + // }, + // ], + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const currencies = this.safeValue (data, 'currencies', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'long_name'); + const type = this.safeString (currency, 'type'); + const canWithdraw = this.safeValue (currency, 'can_withdraw', true); + const depositDisabled = this.safeValue (currency, 'deposit_disabled', false); + const config = this.safeValue (currency, 'config', {}); + const status = this.safeString (currency, 'status'); + const active = canWithdraw && (status === 'ok') && !depositDisabled; + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': type, + 'name': name, + 'fee': this.safeFloat (config, 'withdraw_fee'), + 'precision': this.safeInteger (currency, 'precision'), + 'active': active, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'minimum_order'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":"2019-12-07T22:55:00Z", + // "open":"0.00197", + // "high":"0.00197", + // "low":"0.00197", + // "close":"0.00197", + // "volume":"0.00016676", + // "market_volume":"0.08465047" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'time')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'market_volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_string': market['id'], + 'interval': this.timeframes[timeframe], + }; + const response = await this.publicGetMarketMarketStringOhlcvInterval (this.extend (request, params)); + // + // { + // "data":{ + // "slices":[ + // {"time":"2019-12-07T22:55:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0.00016676","market_volume":"0.08465047"}, + // {"time":"2019-12-07T23:00:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"}, + // {"time":"2019-12-07T23:05:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const ohlcvs = this.safeValue (data, 'slices', []); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { 'market_string': marketId }; + const response = await this.publicGetOrderbookMarketString (this.extend (request, params)); + // + // { + // "data":{ + // "buy":{ + // "0.00700015":"4.76196367", + // "0.00700017":"1.89755391", + // "0.00700018":"2.13214088", + // }, + // "last_change":1588539869958811, + // "sell":{ + // "0.02418662":"0.19513696", + // "0.02465627":"0.2439212", + // "0.02530277":"0.663475931274359255", + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orderbook = {}; + const sides = { 'buy': 'bids', 'sell': 'asks' }; + const keys = Object.keys (sides); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const side = sides[key]; + const bidasks = this.safeValue (data, key, {}); + const prices = Object.keys (bidasks); + const result = []; + for (let j = 0; j < prices.length; j++) { + const priceAsString = prices[j]; + const price = this.safeFloat (prices, j); + const amount = this.safeFloat (bidasks, priceAsString); + result.push ([ price, amount ]); + } + orderbook[side] = result; + } + const timestamp = this.safeIntegerProduct (data, 'last_change', 0.001); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "ask":"0.02423119", + // "bid":"0.0230939", + // "day_avg_price":"0.0247031874349301", + // "day_change":"-0.0237543162270376", + // "day_high":"0.02470552", + // "day_low":"0.02470172", + // "day_open":"0.02530277", + // "day_volume_base":"0.00268074", + // "day_volume_market":"0.10851798", + // "id":41, + // "id_hr":"ETH_BTC", + // "last":"0.02470172", + // "last_change":1588533365354609 + // } + // + const marketId = this.safeString (ticker, 'id_hr'); + const symbol = this.safeSymbol (marketId, market, '_'); + const timestamp = this.safeIntegerProduct (ticker, 'last_change', 0.001); + const previous = this.safeFloat (ticker, 'day_open'); + const last = this.safeFloat (ticker, 'last'); + const day_change = this.safeFloat (ticker, 'day_change'); + let percentage = undefined; + let change = undefined; + let average = this.safeFloat (ticker, 'day_avg_price'); + if (day_change !== undefined) { + percentage = day_change * 100; + if (previous !== undefined) { + change = day_change * previous; + } + } + if ((average === undefined) && (last !== undefined) && (previous !== undefined)) { + average = this.sum (last, previous) / 2; + } + const baseVolume = this.safeFloat (ticker, 'day_volume_market'); + const quoteVolume = this.safeFloat (ticker, 'day_volume_base'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'day_high'), + 'low': this.safeFloat (ticker, 'day_low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': previous, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + // + // { + // "data":{ + // "markets":[ + // { + // "ask":"0.0000003", + // "bid":"0.00000029", + // "day_avg_price":"0.0000002999979728", + // "day_change":"0.0344827586206897", + // "day_high":"0.0000003", + // "day_low":"0.0000003", + // "day_open":"0.00000029", + // "day_volume_base":"0.00591958", + // "day_volume_market":"19732.06666665", + // "id":36, + // "id_hr":"DOGE_BTC", + // "last":"0.0000003", + // "last_change":1588534202130778 + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'markets', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_string': market['id'], + }; + const response = await this.publicGetTickerMarketString (this.extend (request, params)); + // + // { + // "data":{ + // "ask":"0.02423119", + // "bid":"0.0230939", + // "day_avg_price":"0.0247031874349301", + // "day_change":"-0.0237543162270376", + // "day_high":"0.02470552", + // "day_low":"0.02470172", + // "day_open":"0.02530277", + // "day_volume_base":"0.00268074", + // "day_volume_market":"0.10851798", + // "id":41, + // "id_hr":"ETH_BTC", + // "last":"0.02470172", + // "last_change":1588533365354609 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_string': market['id'], + // 'older_than': 123, // returns trades with id < older_than + // 'newer_than': 123, // returns trades with id > newer_than + }; + const response = await this.publicGetMarketMarketStringTrades (this.extend (request, params)); + // + // { + // "data":{ + // "trades":[ + // { + // "id":85507, + // "amount":"0.09390502", + // "price":"0.02556325", + // "base_volume":"0.00240051", + // "seller_taker":true, + // "side":"sell", + // "created_at":"0001-01-01T00:00:00Z", + // "created_at_ts":1581560391338718 + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'desc': true, // Returns newest trades first when true + // 'older_than': 123, // returns trades with id < older_than + // 'newer_than': 123, // returns trades with id > newer_than + }; + let market = undefined; + const numericId = this.safeValue (params, 'market_id'); + if (numericId !== undefined) { + request['market_id'] = numericId; // mutually exclusive with market_string + } else if (symbol !== undefined) { + market = this.market (symbol); + request['market_string'] = market['id']; + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // { + // "data":{ + // "trades":[ + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "order_id":13790596, + // "market_id":41, + // "market_string":"ETH_BTC", + // "taker":true, + // "base_fee":"0.00001249", + // "side":"sell", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":85507, + // "amount":"0.09390502", + // "price":"0.02556325", + // "base_volume":"0.00240051", + // "seller_taker":true, + // "side":"sell", + // "created_at":"0001-01-01T00:00:00Z", + // "created_at_ts":1581560391338718 + // } + // + // fetchMyTrades (private) + // + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "order_id":13790596, + // "market_id":41, + // "market_string":"ETH_BTC", + // "taker":true, + // "base_fee":"0.00001249", + // "side":"sell", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // + // createOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "base_amount": "9.58970687", + // "base_fee": "0.02397426", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.97179355", + // "price": "9.86804952", + // "taker": true + // } + // + const id = this.safeString (trade, 'id'); + let timestamp = this.safeIntegerProduct (trade, 'created_at_ts', 0.001); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + const side = this.safeString (trade, 'side'); + const marketId = this.safeString (trade, 'market_string'); + const symbol = this.safeSymbol (marketId, market, '_'); + let cost = this.safeFloat2 (trade, 'base_volume', 'base_amount'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'market_amount', 'amount'); + if ((cost === undefined) && (amount !== undefined) && (price !== undefined)) { + if (price !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'base_fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = (market === undefined) ? undefined : market['quote']; + fee = { + 'currency': feeCurrencyCode, + 'cost': feeCost, + }; + } + const taker = this.safeValue (trade, 'taker', true); + const takerOrMaker = taker ? 'taker' : 'maker'; + const orderId = this.safeString (trade, 'order_id'); + const result = { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalancesAll (params); + // + // { + // "data":{ + // "balances": [ + // { "balance": "100000000", "currency": "BCH" }, + // { "balance": "99992435.78253015", "currency": "LTC" }, + // { "balance": "99927153.76074182", "currency": "BTC" }, + // ], + // "order_balances":[], + // "limit_used":0, + // "limit_remaining":4000, + // "limit":4000 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let balances = this.safeValue (data, 'balances', []); + const result = { + 'info': response, + }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = (code in result) ? result[code] : this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = 0; + result[code] = account; + } + balances = this.safeValue (data, 'order_balances', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = (code in result) ? result[code] : this.account (); + account['used'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new InvalidOrder (this.id + ' createOrder() allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amount': this.amountToPrecision (symbol, amount), + 'market_id': market['numericId'], + 'price': this.priceToPrecision (symbol, price), + }; + const method = (side === 'sell') ? 'privatePostSellLimit' : 'privatePostBuyLimit'; + const response = await this[method] (this.extend (request, params)); + // + // { + // "data": { + // "order": { + // "created_at": "2018-04-06T20:46:52.899248Z", + // "id": 13253, + // "market_amount": "1", + // "market_amount_remaining": "0", + // "market_id": 1, + // "open": false, + // "order_type": "sell_limit", + // "price": "0.01", + // "trades": [ + // { + // "base_amount": "0.27834267", + // "base_fee": "0.00069585", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.02820645", + // "price": "9.86805058", + // "taker": true + // }, + // { + // "base_amount": "9.58970687", + // "base_fee": "0.02397426", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.97179355", + // "price": "9.86804952", + // "taker": true + // } + // ] + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const order = this.safeValue (data, 'order', {}); + return this.parseOrder (order, market); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "created_at": "2018-04-06T20:46:52.899248Z", + // "id": 13253, + // "market_amount": "1", + // "market_amount_remaining": "0", + // "market_id": 1, + // "open": false, + // "order_type": "sell_limit", + // "price": "0.01", + // "trades": [ + // { + // "base_amount": "0.27834267", + // "base_fee": "0.00069585", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.02820645", + // "price": "9.86805058", + // "taker": true + // }, + // { + // "base_amount": "9.58970687", + // "base_fee": "0.02397426", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.97179355", + // "price": "9.86804952", + // "taker": true + // } + // ] + // } + // + // fetchOrder + // + // { + // id: 13790596, + // market_amount: "0.15", + // market_amount_remaining: "0", + // created_at: "2020-05-04T06:08:18.513413Z", + // price: "0.0230939", + // base_amount: "0", + // order_type: "sell_limit", + // market_id: 41, + // market_string: "ETH_BTC", + // open: false, + // trades: [ + // { + // id: 107331, + // market_amount: "0.1082536946986", + // price: "0.0230939", + // base_amount: "0.00249999", + // taker: true, + // base_fee: "0.00001249", + // created_at: "2020-05-04T06:08:18.513413Z", + // } + // ], + // close_reason: "canceled" + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const sideType = this.safeString (order, 'order_type'); + let orderType = undefined; + let side = undefined; + if (sideType !== undefined) { + const parts = sideType.split ('_'); + side = this.safeString (parts, 0); + orderType = this.safeString (parts, 1); + } + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'market_amount'); + let remaining = this.safeFloat (order, 'market_amount_remaining'); + let filled = undefined; + const open = this.safeValue (order, 'open', false); + const closeReason = this.safeString (order, 'close_reason'); + let status = undefined; + if (open) { + status = 'open'; + } else if (closeReason === 'canceled') { + status = 'canceled'; + } else { + status = 'closed'; + } + const marketId = this.safeString (order, 'market_string'); + const symbol = this.safeSymbol (marketId, market, '_'); + const rawTrades = this.safeValue (order, 'trades', []); + const parsedTrades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'order': id, + 'side': side, + 'type': orderType, + }); + const numTrades = parsedTrades.length; + let lastTradeTimestamp = undefined; + let feeCost = undefined; + let cost = undefined; + if (numTrades > 0) { + feeCost = 0; + cost = 0; + filled = 0; + remaining = amount; + for (let i = 0; i < parsedTrades.length; i++) { + const trade = parsedTrades[i]; + feeCost = this.sum (trade['fee']['cost'], feeCost); + lastTradeTimestamp = this.safeInteger (trade, 'timestamp'); + cost = this.sum (trade['cost'], cost); + filled = this.sum (trade['amount'], filled); + remaining = Math.max (0, remaining - trade['amount']); + } + } + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyCode = (market === undefined) ? undefined : market['quote']; + fee = { + 'currency': feeCurrencyCode, + 'cost': feeCost, + }; + } + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + let average = undefined; + if (filled !== undefined) { + if ((price !== undefined) && (cost === undefined)) { + cost = filled * price; + } + if ((cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'cost': cost, + 'trades': parsedTrades, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': parseInt (id), + }; + // successful cancellation returns 200 with no payload + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'order_id': id }; + const response = await this.privateGetOrderOrderId (this.extend (request, params)); + // + // { + // "data":{ + // "order":{ + // "id":13790596, + // "market_amount":"0.15", + // "market_amount_remaining":"0.0417463053014", + // "created_at":"2020-05-04T06:08:18.513413Z", + // "price":"0.0230939", + // "order_type":"sell_limit", + // "market_id":41, + // "market_string":"ETH_BTC", + // "open":true, + // "trades":[ + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "taker":true, + // "base_fee":"0.00001249", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // ] + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const order = this.safeValue (data, 'order', {}); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'open': true, + // 'older_than': 123, // returns orders with id < older_than + // 'newer_than': 123, // returns orders with id > newer_than + }; + let market = undefined; + const numericId = this.safeValue (params, 'market_id'); + if (numericId !== undefined) { + request['market_id'] = numericId; // mutually exclusive with market_string + } else if (symbol !== undefined) { + market = this.market (symbol); + request['market_string'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // "data":{ + // "orders":[ + // { + // "id":13790596, + // "market_amount":"0.15", + // "market_amount_remaining":"0.0417463053014", + // "created_at":"2020-05-04T06:08:18.513413Z", + // "price":"0.0230939", + // "order_type":"sell_limit", + // "market_id":41, + // "market_string":"ETH_BTC", + // "open":true, + // "trades":[ + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "taker":true, + // "base_fee":"0.00001249", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // ] + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orders = this.safeValue (data, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'open': true }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'open': false }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "currency_status":"ok", + // "deposit_methods":{ + // "address":{ + // "deposit_type":"address", + // "render_type":"address", + // "label":"Address", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // }, + // }, + // } + // + const code = (currency === undefined) ? undefined : currency['code']; + let address = this.safeString (depositAddress, 'address'); + let tag = undefined; + if (address !== undefined) { + const parts = address.split (':'); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostDepositAddressCurrency (this.extend (request, params)); + // + // { + // "data":{ + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "currency_status":"ok", + // "deposit_methods":{ + // "address":{ + // "deposit_type":"address", + // "render_type":"address", + // "label":"Address", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // }, + // }, + // }, + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseDepositAddress (data, currency); + } + + async fetchDeposit (id, code = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'deposit_id': id, + }; + const response = await this.privateGetDepositDepositId (this.extend (request, params)); + // + // { + // "data":{ + // "deposit":{ + // "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "amount":"0.13", + // "currency":"ETH", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"credited", + // "relay_status":"", + // "network_data":{ + // "confirms":87, + // "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "sweep_balance":"0.150000000000000000", + // "confirms_required":80, + // "unsigned_sweep_tx":{ + // "chainId":1, + // "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "gas":"0x5208", + // "gasPrice":"0x19b45a500", + // "nonce":"0x0", + // "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "value":"0x214646b6347d800" + // }, + // "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "tx_index":"0x6f", + // "tx_value":"0.130000000000000000", + // "key_index":311, + // "blockheight":9877869, + // "signed_sweep_tx":{ + // "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9" + // }, + // "estimated_sweep_tx_fee":144900000000000 + // }, + // "created_at":"2020-05-04T05:38:42.145162Z" + // } + // } + // } + const data = this.safeValue (response, 'data', {}); + const deposit = this.safeValue (data, 'deposit', {}); + return this.parseTransaction (deposit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetDeposits (params); + // + // { + // "data":{ + // "deposits":[ + // { + // "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "amount":"0.13", + // "currency":"ETH", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"credited", + // "relay_status":"", + // "network_data":{ + // "confirms":87, + // "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "sweep_balance":"0.150000000000000000", + // "confirms_required":80, + // "unsigned_sweep_tx":{ + // "chainId":1, + // "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "gas":"0x5208", + // "gasPrice":"0x19b45a500", + // "nonce":"0x0", + // "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "value":"0x214646b6347d800" + // }, + // "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "tx_index":"0x6f", + // "tx_value":"0.130000000000000000", + // "key_index":311, + // "blockheight":9877869, + // "signed_sweep_tx":{ + // "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9" + // }, + // "estimated_sweep_tx_fee":144900000000000 + // }, + // "created_at":"2020-05-04T05:38:42.145162Z" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const deposits = this.safeValue (data, 'deposits', []); + return this.parseTransactions (deposits, currency, since, limit); + } + + async fetchWithdrawal (id, code = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'withdraw_id': id, + }; + const response = await this.privateGetWithdrawWithdrawId (this.extend (request, params)); + // + // { + // data: { + // withdraw: { + // "id":25524, + // "amount":"0.0417463053014", + // "user_id":0, + // "currency":"ETH", + // "network_data":{ + // "unsigned_tx":{ + // "chainId":1, + // "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "gas":"0x5208", + // "gasPrice":"0x20c8558e9", + // "nonce":"0xf3", + // "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "value":"0x71712bcd113308" + // }, + // "estimated_tx_fee":184800004893000, + // "confirms_required":80, + // "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "confirms":83, + // "signed_tx":{ + // "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0" + // } + // }, + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"confirmed", + // "relay_status":"", + // "created_at":"2020-05-05T06:32:19.907061Z", + // "cancel_requested":false + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const withdrawal = this.safeValue (data, 'withdraw', {}); + return this.parseTransaction (withdrawal); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetWithdraws (params); + // { + // "data":{ + // "withdraws":[ + // { + // "id":25524, + // "amount":"0.0417463053014", + // "user_id":0, + // "currency":"ETH", + // "network_data":{ + // "unsigned_tx":{ + // "chainId":1, + // "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "gas":"0x5208", + // "gasPrice":"0x20c8558e9", + // "nonce":"0xf3", + // "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "value":"0x71712bcd113308" + // }, + // "estimated_tx_fee":184800004893000, + // "confirms_required":80, + // "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "confirms":83, + // "signed_tx":{ + // "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0" + // } + // }, + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"confirmed", + // "relay_status":"", + // "created_at":"2020-05-05T06:32:19.907061Z", + // "cancel_requested":false + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const withdrawals = this.safeValue (data, 'withdraws', []); + return this.parseTransactions (withdrawals, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchDeposit + // + // { + // "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "amount":"0.13", + // "currency":"ETH", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"credited", + // "relay_status":"", + // "network_data":{ + // "confirms":87, + // "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "sweep_balance":"0.150000000000000000", + // "confirms_required":80, + // "unsigned_sweep_tx":{ + // "chainId":1, + // "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "gas":"0x5208", + // "gasPrice":"0x19b45a500", + // "nonce":"0x0", + // "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "value":"0x214646b6347d800" + // }, + // "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "tx_index":"0x6f", + // "tx_value":"0.130000000000000000", + // "key_index":311, + // "blockheight":9877869, + // "signed_sweep_tx":{ + // "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9" + // }, + // "estimated_sweep_tx_fee":144900000000000 + // }, + // "created_at":"2020-05-04T05:38:42.145162Z" + // } + // + // fetchWithdrawals, fetchWithdrawal + // + // { + // "id":25524, + // "amount":"0.0417463053014", + // "user_id":0, + // "currency":"ETH", + // "network_data":{ + // "unsigned_tx":{ + // "chainId":1, + // "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "gas":"0x5208", + // "gasPrice":"0x20c8558e9", + // "nonce":"0xf3", + // "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "value":"0x71712bcd113308" + // }, + // "estimated_tx_fee":184800004893000, + // "confirms_required":80, + // "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "confirms":83, + // "signed_tx":{ + // "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0" + // } + // }, + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"confirmed", + // "relay_status":"", + // "created_at":"2020-05-05T06:32:19.907061Z", + // "cancel_requested":false + // } + // + // withdraw + // + // { + // "code": "initiated", + // "id": 3, + // "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process." + // } + // + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const id = this.safeString (transaction, 'id'); + const networkData = this.safeValue (transaction, 'network_data', {}); + const unsignedTx = this.safeValue (networkData, 'unsigned_tx', {}); + const addressFrom = this.safeString (unsignedTx, 'from'); + const txid = this.safeString (networkData, 'txid'); + let address = this.safeString (transaction, 'address'); + let tag = undefined; + if (address !== undefined) { + const parts = address.split (':'); + const numParts = parts.length; + if (numParts > 1) { + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + } + const addressTo = address; + const tagFrom = undefined; + const tagTo = tag; + const cancelRequested = this.safeValue (transaction, 'cancel_requested'); + const type = (cancelRequested === undefined) ? 'deposit' : 'withdrawal'; + const amount = this.safeFloat (transaction, 'amount'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const statusCode = this.safeString (transaction, 'code'); + if (cancelRequested) { + status = 'canceled'; + } else if (status === undefined) { + status = this.parseTransactionStatus (statusCode); + } + const fee = undefined; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': tagFrom, + 'tagTo': tagTo, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'initiated': 'pending', + 'needs_create': 'pending', + 'credited': 'ok', + 'confirmed': 'ok', + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'address': address, + 'amount': amount, + 'currency': currency['id'], + }; + if (tag !== undefined) { + request['address'] += ':' + tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + // + // { + // "data": { + // "code": "initiated", + // "id": 3, + // "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process." + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const result = this.parseTransaction (data); + return this.extend (result, { + 'currency': code, + 'address': address, + 'addressTo': address, + 'tag': tag, + 'tagTo': tag, + 'amount': amount, + }); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.version + '/'; + if (api === 'private') { + url += 'user/'; + } + url += this.implodeParams (path, params); + const request = this.omit (params, this.extractParams (path)); + if (method === 'POST') { + body = this.json (request); + } else { + if (Object.keys (request).length) { + url += '?' + this.urlencode (request); + } + } + if (api === 'private') { + const timestamp = this.milliseconds ().toString (); + const bodyAsString = (method === 'POST') ? body : ''; + const auth = [ + method, + url, + timestamp, + bodyAsString, + this.secret, + ].join ("\n"); // eslint-disable-line quotes + const hash = this.hash (this.encode (auth), 'sha256', 'base64'); + let key = this.apiKey; + if (typeof key !== 'string') { + key = key.toString (); + } + const signature = 'HMAC-SHA256 ' + key + ':' + hash; + headers = { + 'Authorization': signature, + 'HMAC-Timestamp': timestamp, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + } + } + url = this.urls['api'] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + // + // {"errors":[{"code":"insuff_funds","title":"Your available balance is too low for that action"}]} + // {"errors":[{"code": "invalid_auth","title": "Invalid HMAC signature"}]} + // + if (response === undefined) { + return; + } + const errors = this.safeValue (response, 'errors', []); + const numErrors = errors.length; + if (numErrors < 1) { + return; + } + const feedback = this.id + ' ' + body; + for (let i = 0; i < errors.length; i++) { + const error = errors[i]; + const errorCode = this.safeString (error, 'code'); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + throw new ExchangeError (feedback); // unknown message + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],120:[function(require,module,exports){ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ROUND } = require ('./base/functions/number'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, OrderNotFound } = require ('./base/errors'); + +module.exports = class rightbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'rightbtc', + 'name': 'RightBTC', + 'countries': [ 'AE' ], + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'privateAPI': false, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': 'emulated', + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': 'min1', + '5m': 'min5', + '15m': 'min15', + '30m': 'min30', + '1h': 'hr1', + '1d': 'day1', + '1w': 'week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182092-1f372700-c2ec-11ea-8f9e-01b4d3ff8941.jpg', + 'api': 'https://www.rightbtc.com/api', + 'www': 'https://www.rightbtc.com', + 'doc': [ + 'https://docs.rightbtc.com/api/', + ], + // eslint-disable-next-line no-useless-escape + // 'fees': 'https://www.rightbtc.com/\#\!/support/fee', + }, + 'api': { + 'public': { + 'get': [ + // 'getAssetsTradingPairs/zh', // 404 + 'trading_pairs', + 'ticker/{trading_pair}', + 'tickers', + 'depth/{trading_pair}', + 'depth/{trading_pair}/{count}', + 'trades/{trading_pair}', + 'trades/{trading_pair}/{count}', + 'candlestick/latest/{trading_pair}', + 'candlestick/{timeSymbol}/{trading_pair}', + 'candlestick/{timeSymbol}/{trading_pair}/{count}', + ], + }, + 'trader': { + 'get': [ + 'balance/{symbol}', + 'balances', + 'deposits/{asset}/{page}', + 'withdrawals/{asset}/{page}', + 'orderpage/{trading_pair}/{cursor}', + 'orders/{trading_pair}/{ids}', // ids are a slash-separated list of {id}/{id}/{id}/... + 'history/{trading_pair}/{ids}', + 'historys/{trading_pair}/{page}', + 'trading_pairs', + ], + 'post': [ + 'order', + ], + 'delete': [ + 'order/{trading_pair}/{ids}', + ], + }, + }, + // HARDCODING IS DEPRECATED, THE FEES BELOW SHOULD BE REWRITTEN + 'fees': { + 'trading': { + // min trading fees + // 0.0001 BTC + // 0.01 ETP + // 0.001 ETH + // 0.1 BITCNY + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'withdraw': { + // 'BTM': n => 3 + n * (1 / 100), + // 'ZDC': n => 1 + n * (0.5 / 100), + // 'ZGC': n => 0.5 + n * (0.5 / 100), + // 'BTS': n => 1 + n * (1 / 100), + // 'DLT': n => 3 + n * (1 / 100), + // 'SNT': n => 10 + n * (1 / 100), + // 'XNC': n => 1 + n * (1 / 100), + // 'ICO': n => 3 + n * (1 / 100), + // 'CMC': n => 1 + n * (0.5 / 100), + // 'GXS': n => 0.2 + n * (1 / 100), + // 'OBITS': n => 0.3 + n * (1 / 100), + // 'ICS': n => 2 + n * (1 / 100), + // 'TIC': n => 2 + n * (1 / 100), + // 'IND': n => 20 + n * (1 / 100), + // 'MVC': n => 20 + n * (1 / 100), + // 'BitCNY': n => 0.1 + n * (1 / 100), + // 'MTX': n => 1 + n * (1 / 100), + 'ETP': 0.01, + 'BTC': 0.0005, + 'ETH': 0.005, + 'ETC': 0.01, + 'STORJ': 3, + 'LTC': 0.01, + 'ZEC': 0.001, + 'BCC': 0.001, + 'XRB': 0, + 'NXS': 0.1, + }, + }, + }, + 'commonCurrencies': { + 'XRB': 'NANO', + }, + 'exceptions': { + 'ERR_USERTOKEN_NOT_FOUND': AuthenticationError, + 'ERR_ASSET_NOT_EXISTS': ExchangeError, + 'ERR_ASSET_NOT_AVAILABLE': ExchangeError, + 'ERR_BALANCE_NOT_ENOUGH': InsufficientFunds, + 'ERR_CREATE_ORDER': InvalidOrder, + 'ERR_CANDLESTICK_DATA': ExchangeError, + }, + }); + } + + async fetchMarkets (params = {}) { + // let zh = await this.publicGetGetAssetsTradingPairsZh (); + const markets = await this.publicGetTradingPairs (params); + const marketIds = Object.keys (markets); + const result = []; + for (let i = 0; i < marketIds.length; i++) { + const id = marketIds[i]; + const market = markets[id]; + const baseId = this.safeString (market, 'bid_asset_symbol'); + const quoteId = this.safeString (market, 'ask_asset_symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'bid_asset_decimals'), + 'price': this.safeInteger (market, 'ask_asset_decimals'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['price']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + divideSafeFloat (x, key, divisor) { + const value = this.safeFloat (x, key); + if (value !== undefined) { + return value / divisor; + } + return value; + } + + parseTicker (ticker, market = undefined) { + const symbol = market['symbol']; + const timestamp = this.safeInteger (ticker, 'date'); + const last = this.divideSafeFloat (ticker, 'last', 1e8); + const high = this.divideSafeFloat (ticker, 'high', 1e8); + const low = this.divideSafeFloat (ticker, 'low', 1e8); + const bid = this.divideSafeFloat (ticker, 'buy', 1e8); + const ask = this.divideSafeFloat (ticker, 'sell', 1e8); + const baseVolume = this.divideSafeFloat (ticker, 'vol24h', 1e8); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + }; + const response = await this.publicGetTickerTradingPair (this.extend (request, params)); + const result = this.safeValue (response, 'result'); + if (result === undefined) { + throw new ExchangeError (this.id + ' fetchTicker returned an empty response for symbol ' + symbol); + } + return this.parseTicker (result, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const tickers = response['result']; + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const id = ticker['market']; + if (!(id in this.marketsById)) { + continue; + } + const market = this.marketsById[id]; + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'trading_pair': this.marketId (symbol), + }; + let method = 'publicGetDepthTradingPair'; + if (limit !== undefined) { + method += 'Count'; + request['count'] = limit; + } + const response = await this[method] (this.extend (request, params)); + const bidsasks = {}; + const types = ['bid', 'ask']; + for (let ti = 0; ti < types.length; ti++) { + const type = types[ti]; + bidsasks[type] = []; + for (let i = 0; i < response['result'][type].length; i++) { + const [ price, amount, total ] = response['result'][type][i]; + bidsasks[type].push ([ + price / 1e8, + amount / 1e8, + total / 1e8, + ]); + } + } + return this.parseOrderBook (bidsasks, undefined, 'bid', 'ask'); + } + + parseTrade (trade, market = undefined) { + // + // { + // "order_id": 118735, + // "trade_id": 7, + // "trading_pair": "BTCCNY", + // "side": "B", + // "quantity": 1000000000, + // "price": 900000000, + // "created_at": "2017-06-06T20:45:27.000Z" + // } + // + let timestamp = this.safeInteger (trade, 'date'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + let id = this.safeString (trade, 'tid'); + id = this.safeString (trade, 'trade_id', id); + const orderId = this.safeString (trade, 'order_id'); + const price = this.divideSafeFloat (trade, 'price', 1e8); + let amount = this.safeFloat (trade, 'amount'); + amount = this.safeFloat (trade, 'quantity', amount); + if (amount !== undefined) { + amount = amount / 1e8; + } + const marketId = this.safeString (trade, 'trading_pair'); + const symbol = this.safeSymbol (marketId, market); + let cost = this.costToPrecision (symbol, price * amount); + cost = parseFloat (cost); + let side = this.safeStringLower (trade, 'side'); + if (side === 'b') { + side = 'buy'; + } else if (side === 's') { + side = 'sell'; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + }; + const response = await this.publicGetTradesTradingPair (this.extend (request, params)); + return this.parseTrades (response['result'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeInteger (ohlcv, 0), + parseFloat (ohlcv[2]) / 1e8, + parseFloat (ohlcv[3]) / 1e8, + parseFloat (ohlcv[4]) / 1e8, + parseFloat (ohlcv[5]) / 1e8, + parseFloat (ohlcv[1]) / 1e8, + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'timeSymbol': this.timeframes[timeframe], + }; + const response = await this.publicGetCandlestickTimeSymbolTradingPair (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.traderGetBalances (params); + // + // { + // "status": { + // "success": 1, + // "message": "GET_BALANCES" + // }, + // "result": [ + // { + // "asset": "ETP", + // "balance": "5000000000000", + // "frozen": "0", + // "state": "1" + // }, + // { + // "asset": "CNY", + // "balance": "10000000000000", + // "frozen": "240790000", + // "state": "1" + // } + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'result', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + // https://github.com/ccxt/ccxt/issues/3873 + account['free'] = this.divideSafeFloat (balance, 'balance', 1e8); + account['used'] = this.divideSafeFloat (balance, 'frozen', 1e8); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const order = { + 'trading_pair': market['id'], + // We need to use decimalToPrecision here, since + // 0.036*1e8 === 3599999.9999999995 + // which would get truncated to 3599999 after parseInt + // which would then be rejected by rightBtc because it's too precise + 'quantity': parseInt (this.decimalToPrecision (amount * 1e8, ROUND, 0, this.precisionMode)), + 'limit': parseInt (this.decimalToPrecision (price * 1e8, ROUND, 0, this.precisionMode)), + 'type': type.toUpperCase (), + 'side': side.toUpperCase (), + }; + const response = await this.traderPostOrder (this.extend (order, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'ids': id, + }; + const response = await this.traderDeleteOrderTradingPairIds (this.extend (request, params)); + return response; + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'TRADE': 'closed', // TRADE means filled or partially filled orders + 'CANCEL': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder / fetchOpenOrders + // + // { + // "id": 4180528, + // "quantity": 20000000, + // "rest": 20000000, + // "limit": 1000000, + // "price": null, + // "side": "BUY", + // "created": 1496005693738 + // } + // + // fetchOrders + // + // { + // "trading_pair": "ETPCNY", + // "status": "TRADE", + // "fee": 0.23, + // "min_fee": 10000000, + // "created_at": "2017-05-25T00:12:27.000Z", + // "cost": 1152468000000, + // "limit": 3600000000, + // "id": 11060, + // "quantity": 32013000000, + // "filled_quantity": 32013000000 + // } + // + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'trading_pair'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = this.safeInteger (order, 'created'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'created_at')); + } + if ('time' in order) { + timestamp = order['time']; + } else if ('transactTime' in order) { + timestamp = order['transactTime']; + } + let price = this.safeFloat2 (order, 'limit', 'price'); + if (price !== undefined) { + price = price / 1e8; + } + const amount = this.divideSafeFloat (order, 'quantity', 1e8); + let filled = this.divideSafeFloat (order, 'filled_quantity', 1e8); + let remaining = this.divideSafeFloat (order, 'rest', 1e8); + const cost = this.divideSafeFloat (order, 'cost', 1e8); + // lines 483-494 should be generalized into a base class method + if (amount !== undefined) { + if (remaining === undefined) { + if (filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + if (filled === undefined) { + if (remaining !== undefined) { + filled = Math.max (0, amount - remaining); + } + } + } + const type = 'limit'; + const side = this.safeStringLower (order, 'side'); + const feeCost = this.divideSafeFloat (order, 'min_fee', 1e8); + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = market['quote']; + } + fee = { + 'rate': this.safeFloat (order, 'fee'), + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const trades = undefined; + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'ids': id, + }; + const response = await this.traderGetOrdersTradingPairIds (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": "SUC_LIST_AVTICE_ORDERS" + // }, + // "result": [ + // { + // "id": 4180528, + // "quantity": 20000000, + // "rest": 20000000, + // "limit": 1000000, + // "price": null, + // "side": "BUY", + // "created": 1496005693738 + // } + // ] + // } + // + const orders = this.parseOrders (response['result'], market); + const ordersById = this.indexBy (orders, 'id'); + if (!(id in ordersById)) { + throw new OrderNotFound (this.id + ' fetchOrder could not find order ' + id.toString () + ' in open orders.'); + } + return ordersById[id]; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'cursor': 0, + }; + const response = await this.traderGetOrderpageTradingPairCursor (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": "SUC_LIST_AVTICE_ORDERS_PAGE" + // }, + // "result": { + // "cursor": "0", + // "orders": [ + // { + // "id": 4180528, + // "quantity": 20000000, + // "rest": 20000000, + // "limit": 1000000, + // "price": null, + // "side": "BUY", + // "created": 1496005693738 + // } + // ] + // } + // } + // + return this.parseOrders (response['result']['orders'], market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + let ids = this.safeString (params, 'ids'); + if ((symbol === undefined) || (ids === undefined)) { + throw new ArgumentsRequired (this.id + " fetchOrders requires a 'symbol' argument and an extra 'ids' parameter. The 'ids' should be an array or a string of one or more order ids separated with slashes."); // eslint-disable-line quotes + } + if (Array.isArray (ids)) { + ids = ids.join ('/'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'ids': ids, + }; + const response = await this.traderGetHistoryTradingPairIds (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": null + // }, + // "result": [ + // { + // "trading_pair": "ETPCNY", + // "status": "TRADE", + // "fee": 0.23, + // "min_fee": 10000000, + // "created_at": "2017-05-25T00:12:27.000Z", + // "cost": 1152468000000, + // "limit": 3600000000, + // "id": 11060, + // "quantity": 32013000000, + // "filled_quantity": 32013000000 + // } + // ] + // } + // + return this.parseOrders (response['result'], undefined, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'page': 0, + }; + const response = await this.traderGetHistorysTradingPairPage (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": null + // }, + // "result": [ + // { + // "order_id": 118735, + // "trade_id": 7, + // "trading_pair": "BTCCNY", + // "side": "B", + // "quantity": 1000000000, + // "price": 900000000, + // "created_at": "2017-06-06T20:45:27.000Z" + // }, + // { + // "order_id": 118734, + // "trade_id": 7, + // "trading_pair": "BTCCNY", + // "side": "S", + // "quantity": 1000000000, + // "price": 900000000, + // "created_at": "2017-06-06T20:45:27.000Z" + // } + // ] + // } + // + return this.parseTrades (response['result'], undefined, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + '/' + api + '/' + this.implodeParams (path, params); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + headers = { + 'apikey': this.apiKey, + 'signature': this.secret, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const status = this.safeValue (response, 'status'); + if (status !== undefined) { + // + // {"status":{"success":0,"message":"ERR_USERTOKEN_NOT_FOUND"}} + // + const success = this.safeString (status, 'success'); + if (success !== '1') { + const message = this.safeString (status, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],121:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, BadSymbol, BadRequest, InvalidOrder, ArgumentsRequired, OrderNotFound, InsufficientFunds, DDoSProtection } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class ripio extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'ripio', + 'name': 'Ripio', + 'countries': [ 'AR', 'BR' ], // Argentina + 'rateLimit': 50, + 'version': 'v1', + 'pro': true, + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/94507548-a83d6a80-0218-11eb-9998-28b9cec54165.jpg', + 'api': { + 'public': 'https://api.exchange.ripio.com/api', + 'private': 'https://api.exchange.ripio.com/api', + }, + 'www': 'https://exchange.ripio.com', + 'doc': [ + 'https://exchange.ripio.com/en/api/', + ], + 'fees': 'https://exchange.ripio.com/en/fee', + }, + 'api': { + 'public': { + 'get': [ + 'rate/all/', + 'rate/{pair}/', + 'orderbook/{pair}/', + 'tradehistory/{pair}/', + 'pair/', + 'currency/', + 'orderbook/{pair}/depth/', + ], + }, + 'private': { + 'get': [ + 'balances/exchange_balances/', + 'order/{pair}/{order_id}/', + 'order/{pair}/', + 'trade/{pair}/', + ], + 'post': [ + 'order/{pair}/', + 'order/{pair}/{order_id}/cancel/', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.0 / 100, + 'maker': 0.0 / 100, + }, + }, + 'precisionMode': TICK_SIZE, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + }, + 'exceptions': { + 'exact': { + }, + 'broad': { + 'Authentication credentials were not provided': AuthenticationError, // {"detail":"Authentication credentials were not provided."} + 'Disabled pair': BadSymbol, // {"status_code":400,"errors":{"pair":["Invalid/Disabled pair BTC_ARS"]},"message":"An error has occurred, please check the form."} + 'Invalid order type': InvalidOrder, // {"status_code":400,"errors":{"order_type":["Invalid order type. Valid options: ['MARKET', 'LIMIT']"]},"message":"An error has occurred, please check the form."} + 'Your balance is not enough': InsufficientFunds, // {"status_code":400,"errors":{"non_field_errors":["Your balance is not enough for this order: You have 0 BTC but you need 1 BTC"]},"message":"An error has occurred, please check the form."} + "Order couldn't be created": ExchangeError, // {'status_code': 400,'errors': {'non_field_errors': _("Order couldn't be created")}, 'message': _('Seems like an unexpected error occurred. Please try again later or write us to support@ripio.com if the problem persists.') } + // {"status_code":404,"errors":{"order":["Order 286e560e-b8a2-464b-8b84-15a7e2a67eab not found."]},"message":"An error has occurred, please check the form."} + // {"status_code":404,"errors":{"trade":["Trade not found."]},"message":"An error has occurred, please check the form."} + 'not found': OrderNotFound, + 'Invalid pair': BadSymbol, // {"status_code":400,"errors":{"pair":["Invalid pair FOOBAR"]},"message":"An error has occurred, please check the form."} + 'amount must be a number': BadRequest, // {"status_code":400,"errors":{"amount":["amount must be a number"]},"message":"An error has occurred, please check the form."} + 'Total must be at least': InvalidOrder, // {"status_code":400,"errors":{"non_field_errors":["Total must be at least 10."]},"message":"An error has occurred, please check the form."} + 'Account not found': BadRequest, // {"error_description": "Account not found."}, "status": 404 + 'Wrong password provided': AuthenticationError, // {'error': "Wrong password provided."}, “status_code”: 400 + 'User tokens limit': DDoSProtection, // {'error': "User tokens limit. Can't create more than 10 tokens."}, “status_code”: 400 + 'Something unexpected ocurred': ExchangeError, // {'status_code': 400, 'errors': {'non_field_errors': 'Something unexpected ocurred!'}, 'message': 'Seems like an unexpected error occurred. Please try again later or write us to support@ripio.com if the problem persists.'} + // {'status_code': 404, 'errors': {'account_balance': ['Exchange balance not found.']},'message': 'An error has occurred, please check the form.'} + // {'status_code': 404, 'errors': {'account_balance': ['Account balance not found.']},'message': 'An error has occurred, please check the form.'} + 'account_balance': BadRequest, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPair (params); + // + // { + // "next":null, + // "previous":null, + // "results":[ + // { + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "symbol":"BTC_USDC", + // "fees":[ + // {"traded_volume":0.0,"maker_fee":0.0,"taker_fee":0.0,"cancellation_fee":0.0} + // ], + // "country":"ZZ", + // "enabled":true, + // "priority":10, + // "min_amount":"0.00001", + // "price_tick":"0.000001", + // "min_value":"10", + // "limit_price_threshold":"25.00" + // }, + // ] + // } + // + const result = []; + const results = this.safeValue (response, 'results', []); + for (let i = 0; i < results.length; i++) { + const market = results[i]; + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const id = this.safeString (market, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeFloat (market, 'min_amount'), + 'price': this.safeFloat (market, 'price_tick'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'min_value'), + 'max': undefined, + }, + }; + const active = this.safeValue (market, 'enabled', true); + const fees = this.safeValue (market, 'fees', []); + const firstFee = this.safeValue (fees, 0, {}); + const maker = this.safeFloat (firstFee, 'maker_fee', 0.0); + const taker = this.safeFloat (firstFee, 'taker_fee', 0.0); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'maker': maker, + 'taker': taker, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrency (params); + // + // { + // "next":null, + // "previous":null, + // "results":[ + // { + // "name":"Argentine Peso", + // "symbol":"$", + // "currency":"ARS", + // "country":"AR", + // "decimal_places":"2", + // "enabled":true + // }, + // { + // "name":"Bitcoin Cash", + // "symbol":"BCH", + // "currency":"BCH", + // "country":"AR", + // "decimal_places":"8", + // "enabled":true + // }, + // { + // "name":"Bitcoin", + // "symbol":"BTC", + // "currency":"BTC", + // "country":"AR", + // "decimal_places":"8", + // "enabled":true + // } + // ] + // } + // + const results = this.safeValue (response, 'results', []); + const result = {}; + for (let i = 0; i < results.length; i++) { + const currency = results[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + const active = this.safeValue (currency, 'enabled', true); + const precision = this.safeInteger (currency, 'decimal_places'); + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'info': currency, // the original payload + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "pair":"BTC_USDC", + // "last_price":"10850.02", + // "low":"10720.03", + // "high":"10909.99", + // "variation":"1.21", + // "volume":"0.83868", + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "bid":"10811.00", + // "ask":"10720.03", + // "avg":"10851.47", + // "ask_volume":"0.00140", + // "bid_volume":"0.00185", + // "created_at":"2020-09-28 21:44:51.228920+00:00" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'created_at')); + const marketId = this.safeString (ticker, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'last_price'); + const average = this.safeFloat (ticker, 'avg'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': this.safeFloat (ticker, 'bid_volume'), + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': this.safeFloat (ticker, 'ask_volume'), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': average, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetRatePair (this.extend (request, params)); + // + // { + // "pair":"BTC_USDC", + // "last_price":"10850.02", + // "low":"10720.03", + // "high":"10909.99", + // "variation":"1.21", + // "volume":"0.83868", + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "bid":"10811.00", + // "ask":"10720.03", + // "avg":"10851.47", + // "ask_volume":"0.00140", + // "bid_volume":"0.00185", + // "created_at":"2020-09-28 21:44:51.228920+00:00" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetRateAll (params); + // + // [ + // { + // "pair":"BTC_USDC", + // "last_price":"10850.02", + // "low":"10720.03", + // "high":"10909.99", + // "variation":"1.21", + // "volume":"0.83868", + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "bid":"10811.00", + // "ask":"10720.03", + // "avg":"10851.47", + // "ask_volume":"0.00140", + // "bid_volume":"0.00185", + // "created_at":"2020-09-28 21:44:51.228920+00:00" + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetOrderbookPair (this.extend (request, params)); + // + // { + // "buy":[ + // {"amount":"0.00230","total":"24.95","price":"10850.02"}, + // {"amount":"0.07920","total":"858.52","price":"10840.00"}, + // {"amount":"0.00277","total":"30.00","price":"10833.03"}, + // ], + // "sell":[ + // {"amount":"0.03193","total":"348.16","price":"10904.00"}, + // {"amount":"0.00210","total":"22.90","price":"10905.70"}, + // {"amount":"0.00300","total":"32.72","price":"10907.98"}, + // ], + // "updated_id":47225 + // } + // + const orderbook = this.parseOrderBook (response, undefined, 'buy', 'sell', 'price', 'amount'); + orderbook['nonce'] = this.safeInteger (response, 'updated_id'); + return orderbook; + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades, private fetchMyTrades + // + // { + // "created_at":1601322501, + // "amount":"0.00276", + // "price":"10850.020000", + // "side":"SELL", + // "pair":"BTC_USDC", + // "taker_fee":"0", + // "taker_side":"SELL", + // "maker_fee":"0", + // "taker":2577953, + // "maker":2577937 + // } + // + // createOrder fills + // + // { + // "pair":"BTC_USDC", + // "exchanged":0.002, + // "match_price":10593.99, + // "maker_fee":0.0, + // "taker_fee":0.0, + // "timestamp":1601730306942 + // } + // + const id = this.safeString (trade, 'id'); + let timestamp = this.safeInteger (trade, 'timestamp'); + timestamp = this.safeTimestamp (trade, 'created_at', timestamp); + let side = this.safeString (trade, 'side'); + const takerSide = this.safeString (trade, 'taker_side'); + const takerOrMaker = (takerSide === side) ? 'taker' : 'maker'; + if (side !== undefined) { + side = side.toLowerCase (); + } + const price = this.safeFloat2 (trade, 'price', 'match_price'); + const amount = this.safeFloat2 (trade, 'amount', 'exchanged'); + let cost = undefined; + if ((amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const marketId = this.safeString (trade, 'pair'); + market = this.safeMarket (marketId, market); + const feeCost = this.safeFloat (trade, takerOrMaker + '_fee'); + const orderId = this.safeString (trade, takerOrMaker); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': (side === 'buy') ? market['base'] : market['quote'], + }; + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTradehistoryPair (this.extend (request, params)); + // + // [ + // { + // "created_at":1601322501, + // "amount":"0.00276", + // "price":"10850.020000", + // "side":"SELL", + // "pair":"BTC_USDC", + // "taker_fee":"0", + // "taker_side":"SELL", + // "maker_fee":"0", + // "taker":2577953, + // "maker":2577937 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalancesExchangeBalances (params); + // + // [ + // { + // "id":603794, + // "currency":"USD Coin", + // "symbol":"USDC", + // "available":"0", + // "locked":"0", + // "code":"exchange", + // "balance_type":"crypto" + // }, + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'symbol'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const uppercaseSide = side.toUpperCase (); + const request = { + 'pair': market['id'], + 'order_type': uppercaseType, // LIMIT, MARKET + 'side': uppercaseSide, // BUY or SELL + 'amount': this.amountToPrecision (symbol, amount), + }; + if (uppercaseType === 'LIMIT') { + request['limit_price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrderPair (this.extend (request, params)); + // + // { + // "order_id": "160f523c-f6ef-4cd1-a7c9-1a8ede1468d8", + // "pair": "BTC_ARS", + // "side": "BUY", + // "amount": "0.00400", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "OPEN", + // "created_at": 1578413945, + // "filled": "0.00000", + // "limit_price": "10.00", + // "stop_price": null, + // "distance": null + // } + // + // createOrder market type + // + // { + // "order_id":"d6b60c01-8624-44f2-9e6c-9e8cd677ea5c", + // "pair":"BTC_USDC", + // "side":"BUY", + // "amount":"0.00200", + // "notional":"50", + // "fill_or_kill":false, + // "all_or_none":false, + // "order_type":"MARKET", + // "status":"OPEN", + // "created_at":1601730306, + // "filled":"0.00000", + // "fill_price":10593.99, + // "fee":0.0, + // "fills":[ + // { + // "pair":"BTC_USDC", + // "exchanged":0.002, + // "match_price":10593.99, + // "maker_fee":0.0, + // "taker_fee":0.0, + // "timestamp":1601730306942 + // } + // ], + // "filled_at":"2020-10-03T13:05:06.942186Z", + // "limit_price":"0.000000", + // "stop_price":null, + // "distance":null + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'order_id': id, + }; + const response = await this.privatePostOrderPairOrderIdCancel (this.extend (request, params)); + // + // { + // "order_id": "286e560e-b8a2-464b-8b84-15a7e2a67eab", + // "pair": "BTC_ARS", + // "side": "SELL", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "CANC", + // "created_at": 1575472707, + // "filled": "0.00000", + // "limit_price": "681000.00", + // "stop_price": null, + // "distance": null + // } + // + return this.parseOrder (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'order_id': id, + }; + const response = await this.privateGetOrderPairOrderId (this.extend (request, params)); + // + // { + // "order_id": "0b4ff48e-cfd6-42db-8d8c-3b536da447af", + // "pair": "BTC_ARS", + // "side": "BUY", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "OPEN", + // "created_at": 1575472944, + // "filled": "0.00000", + // "limit_price": "661000.00", + // "stop_price": null, + // "distance": null + // } + // + return this.parseOrder (response, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + // 'status': 'OPEN,PART,CLOS,CANC,COMP', + // 'offset': 0, + // 'limit': limit, + }; + if (limit !== undefined) { + request['offset'] = limit; + } + const response = await this.privateGetOrderPair (this.extend (request, params)); + // + // { + // "next": "https://api.exchange.ripio.com/api/v1/order/BTC_ARS/?limit=20&offset=20&page=1&page_size=25&status=OPEN%2CPART", + // "previous": null, + // "results": { + // "data": [ + // { + // "order_id": "ca74280b-6966-4b73-a720-68709078922b", + // "pair": "BTC_ARS", + // "side": "SELL", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "OPEN", + // "created_at": 1578340134, + // "filled": "0.00000", + // "limit_price": "665000.00", + // "stop_price": null, + // "distance": null + // }, + // ] + // } + // } + // + const results = this.safeValue (response, 'results', {}); + const data = this.safeValue (results, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'OPEN,PART', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'CLOS,CANC,COMP', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'OPEN': 'open', + 'PART': 'open', + 'CLOS': 'canceled', + 'CANC': 'canceled', + 'COMP': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder, cancelOrder, fetchOpenOrders, fetchClosedOrders, fetchOrders, fetchOrder + // + // { + // "order_id": "286e560e-b8a2-464b-8b84-15a7e2a67eab", + // "pair": "BTC_ARS", + // "side": "SELL", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "CANC", + // "created_at": 1575472707, + // "filled": "0.00000", + // "limit_price": "681000.00", + // "stop_price": null, + // "distance": null + // } + // + // { + // "order_id":"d6b60c01-8624-44f2-9e6c-9e8cd677ea5c", + // "pair":"BTC_USDC", + // "side":"BUY", + // "amount":"0.00200", + // "notional":"50", + // "fill_or_kill":false, + // "all_or_none":false, + // "order_type":"MARKET", + // "status":"OPEN", + // "created_at":1601730306, + // "filled":"0.00000", + // "fill_price":10593.99, + // "fee":0.0, + // "fills":[ + // { + // "pair":"BTC_USDC", + // "exchanged":0.002, + // "match_price":10593.99, + // "maker_fee":0.0, + // "taker_fee":0.0, + // "timestamp":1601730306942 + // } + // ], + // "filled_at":"2020-10-03T13:05:06.942186Z", + // "limit_price":"0.000000", + // "stop_price":null, + // "distance":null + // } + // + const id = this.safeString (order, 'order_id'); + const amount = this.safeFloat (order, 'amount'); + let cost = this.safeFloat (order, 'notional'); + const type = this.safeStringLower (order, 'order_type'); + const priceField = (type === 'market') ? 'fill_price' : 'limit_price'; + const price = this.safeFloat (order, priceField); + const side = this.safeStringLower (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'created_at'); + let average = this.safeValue (order, 'fill_price'); + let filled = this.safeFloat (order, 'filled'); + let remaining = undefined; + const fills = this.safeValue (order, 'fills'); + let trades = undefined; + let lastTradeTimestamp = undefined; + if (fills !== undefined) { + const numFills = fills.length; + if (numFills > 0) { + filled = 0; + cost = 0; + trades = this.parseTrades (fills, market, undefined, undefined, { + 'order': id, + 'side': side, + }); + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + filled = this.sum (trade['amount'], filled); + cost = this.sum (trade['cost'], cost); + lastTradeTimestamp = trade['timestamp']; + } + if ((average === undefined) && (filled > 0)) { + average = cost / filled; + } + } + } + if (filled !== undefined) { + if ((cost === undefined) && (price !== undefined)) { + cost = price * filled; + } + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const stopPrice = this.safeFloat (order, 'stop_price'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': trades, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + // 'offset': 0, + // 'limit': limit, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradePair (this.extend (request, params)); + // + // { + // "next": "https://api.exchange.ripio.com/api/v1/trade//?limit=20&offset=20", + // "previous": null, + // "results": { + // "data": [ + // { + // "created_at": 1578414028, + // "amount": "0.00100", + // "price": "665000.00", + // "side": "BUY", + // "taker_fee": "0", + // "taker_side": "BUY", + // "match_price": "66500000", + // "maker_fee": "0", + // "taker": 4892, + // "maker": 4889 + // }, + // ] + // } + // } + // + const results = this.safeValue (response, 'results', {}); + const data = this.safeValue (results, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = '/' + this.version + '/' + this.implodeParams (path, params); + let url = this.urls['api'][api] + request; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + if (method === 'POST') { + body = this.json (query); + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + this.apiKey, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"detail":"Authentication credentials were not provided."} + // {"status_code":400,"errors":{"pair":["Invalid pair FOOBAR"]},"message":"An error has occurred, please check the form."} + // {"status_code":400,"errors":{"order_type":["Invalid order type. Valid options: ['MARKET', 'LIMIT']"]},"message":"An error has occurred, please check the form."} + // {"status_code":400,"errors":{"non_field_errors":"Something unexpected ocurred!"},"message":"Seems like an unexpected error occurred. Please try again later or write us to support@ripio.com if the problem persists."} + // {"status_code":400,"errors":{"pair":["Invalid/Disabled pair BTC_ARS"]},"message":"An error has occurred, please check the form."} + // + const detail = this.safeString (response, 'detail'); + if (detail !== undefined) { + const feedback = this.id + ' ' + body; + // this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], detail, feedback); + } + const errors = this.safeValue (response, 'errors'); + if (errors !== undefined) { + const feedback = this.id + ' ' + body; + const keys = Object.keys (errors); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const error = this.safeValue (errors, key, []); + const message = this.safeString (error, 0); + // this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],122:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class southxchange extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'southxchange', + 'name': 'SouthXchange', + 'countries': [ 'AR' ], // Argentina + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27838912-4f94ec8a-60f6-11e7-9e5d-bbf9bd50a559.jpg', + 'api': 'https://www.southxchange.com/api', + 'www': 'https://www.southxchange.com', + 'doc': 'https://www.southxchange.com/Home/Api', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'price/{symbol}', + 'prices', + 'book/{symbol}', + 'trades/{symbol}', + ], + }, + 'private': { + 'post': [ + 'cancelMarketOrders', + 'cancelOrder', + 'getOrder', + 'generatenewaddress', + 'listOrders', + 'listBalances', + 'listTransactions', + 'placeOrder', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.3 / 100, + }, + }, + 'commonCurrencies': { + 'BHD': 'Bithold', + 'GHOST': 'GHOSTPRISM', + 'MTC': 'Marinecoin', + 'SMT': 'SmartNode', + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const baseId = market[0]; + const quoteId = market[1]; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = baseId + '/' + quoteId; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'info': market, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostListBalances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'Currency'); + const code = this.safeCurrencyCode (currencyId); + const deposited = this.safeFloat (balance, 'Deposited'); + const unconfirmed = this.safeFloat (balance, 'Unconfirmed'); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'Available'); + account['total'] = this.sum (deposited, unconfirmed); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetBookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'BuyOrders', 'SellOrders', 'Price', 'Amount'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'Last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'Bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'Ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'Variation24Hr'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'Volume24Hr'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetPrices (params); + const tickers = this.indexBy (response, 'Market'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetPriceSymbol (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market) { + const timestamp = this.safeTimestamp (trade, 'At'); + const price = this.safeFloat (trade, 'Price'); + const amount = this.safeFloat (trade, 'Amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const side = this.safeString (trade, 'Type'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': undefined, + 'order': undefined, + 'type': undefined, + 'side': side, + 'price': price, + 'takerOrMaker': undefined, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOrder (order, market = undefined) { + const status = 'open'; + const baseId = this.safeString (order, 'ListingCurrency'); + const quoteId = this.safeString (order, 'ReferenceCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const timestamp = undefined; + const price = this.safeFloat (order, 'LimitPrice'); + const amount = this.safeFloat (order, 'OriginalAmount'); + const remaining = this.safeFloat (order, 'Amount'); + let filled = undefined; + let cost = undefined; + if (amount !== undefined) { + cost = price * amount; + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const type = 'limit'; + const side = this.safeStringLower (order, 'Type'); + const id = this.safeString (order, 'Code'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'average': undefined, + 'trades': undefined, + }; + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privatePostListOrders (params); + return this.parseOrders (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'listingCurrency': market['base'], + 'referenceCurrency': market['quote'], + 'type': side, + 'amount': amount, + }; + if (type === 'limit') { + request['limitPrice'] = price; + } + const response = await this.privatePostPlaceOrder (this.extend (request, params)); + const id = JSON.parse (response); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderCode': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostGeneratenewaddress (this.extend (request, params)); + // + // the exchange API returns a quoted-quoted-string + // + // "\"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93\"" + // + let address = response; + if (address[0] === '"') { + address = JSON.parse (address); + if (address[0] === '"') { + address = JSON.parse (address); + } + } + const parts = address.split ('|'); + const numParts = parts.length; + address = parts[0]; + this.checkAddress (address); + let tag = undefined; + if (numParts > 1) { + tag = parts[1]; + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'address': address, + 'amount': amount, + }; + if (tag !== undefined) { + request['address'] = address + '|' + tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'trade': 'trade', + 'tradefee': 'fee', + 'withdraw': 'transaction', + 'deposit': 'transaction', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // + const id = this.safeString (item, 'MovementId'); + let direction = undefined; + const account = undefined; + let referenceId = this.safeString2 (item, 'TradeId', 'OrderCode'); + referenceId = this.safeString (item, 'Hash', referenceId); + const referenceAccount = this.safeString (item, 'Address'); + const type = this.safeString (item, 'Type'); + const ledgerEntryType = this.parseLedgerEntryType (type); + const code = this.safeCurrencyCode (this.safeString (item, 'CurrencyCode'), currency); + let amount = this.safeFloat (item, 'Amount'); + const after = this.safeFloat (item, 'TotalBalance'); + let before = undefined; + if (amount !== undefined) { + if (after !== undefined) { + before = after - amount; + } + if (type === 'withdrawal') { + direction = 'out'; + } else if (type === 'deposit') { + direction = 'in'; + } else if ((type === 'trade') || (type === 'tradefee')) { + direction = (amount < 0) ? 'out' : 'in'; + amount = Math.abs (amount); + } + } + const timestamp = this.parse8601 (this.safeString (item, 'Date')); + const fee = undefined; + const status = this.safeString (item, 'Status'); + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': ledgerEntryType, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchLedger() requires a code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + limit = (limit === undefined) ? 50 : limit; + const request = { + 'Currency': currency['id'], + // 'TransactionType': 'transactions', // deposits, withdrawals, depositswithdrawals, transactions + // 'PageIndex': 0, + 'PageSize': limit, // max 50 + 'SortField': 'Date', + // 'Descending': true, + }; + const pageIndex = this.safeInteger (params, 'PageIndex'); + if (pageIndex === undefined) { + request['Descending'] = true; + } + const response = await this.privatePostListTransactions (this.extend (request, params)); + // + // fetchLedger ('BTC') + // + // { + // "TotalElements":2, + // "Result":[ + // { + // "Date":"2020-08-07T13:06:22.117", + // "CurrencyCode":"BTC", + // "Amount":-0.000000301000000000, + // "TotalBalance":0.000100099000000000, + // "Type":"tradefee", + // "Status":"confirmed", + // "Address":null, + // "Hash":null, + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":5298215, + // "MovementId":null + // }, + // { + // "Date":"2020-08-07T13:06:22.117", + // "CurrencyCode":"BTC", + // "Amount":0.000100400000000000, + // "TotalBalance":0.000100400000000000, + // "Type":"trade", + // "Status":"confirmed", + // "Address":null, + // "Hash":null, + // "Price":11811.474849000000000000, + // "OtherAmount":1.185872, + // "OtherCurrency":"USDT", + // "OrderCode":"78389610", + // "TradeId":5298215, + // "MovementId":null + // } + // ] + // } + // + // fetchLedger ('BTC'), same trade, other side + // + // { + // "TotalElements":2, + // "Result":[ + // { + // "Date":"2020-08-07T13:06:22.133", + // "CurrencyCode":"USDT", + // "Amount":-1.185872000000000000, + // "TotalBalance":26.428806000000000000, + // "Type":"trade", + // "Status":"confirmed", + // "Address":null, + // "Hash":null, + // "Price":11811.474849000000000000, + // "OtherAmount":0.000100400, + // "OtherCurrency":"BTC", + // "OrderCode":"78389610", + // "TradeId":5298215, + // "MovementId":null + // }, + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // ] + // } + // + const result = this.safeValue (response, 'Result', []); + return this.parseLedger (result, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'processed': 'pending', + 'confirmed': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // + const id = this.safeString (transaction, 'MovementId'); + const amount = this.safeFloat (transaction, 'Amount'); + const address = this.safeString (transaction, 'Address'); + const addressTo = address; + const addressFrom = undefined; + const tag = undefined; + const tagTo = tag; + const tagFrom = undefined; + const txid = this.safeString (transaction, 'Hash'); + const type = this.safeString (transaction, 'Type'); + const timestamp = this.parse8601 (this.safeString (transaction, 'Date')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'Status')); + const currencyId = this.safeString (transaction, 'CurrencyCode'); + const code = this.safeCurrencyCode (currencyId, currency); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressTo': addressTo, + 'addressFrom': addressFrom, + 'tag': tag, + 'tagTo': tagTo, + 'tagFrom': tagFrom, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + limit = (limit === undefined) ? 50 : limit; + const request = { + 'Currency': currency['id'], + 'TransactionType': 'depositswithdrawals', // deposits, withdrawals, depositswithdrawals, transactions + // 'PageIndex': 0, + 'PageSize': limit, // max 50 + 'SortField': 'Date', + // 'Descending': true, + }; + const pageIndex = this.safeInteger (params, 'PageIndex'); + if (pageIndex === undefined) { + request['Descending'] = true; + } + const response = await this.privatePostListTransactions (this.extend (request, params)); + // + // { + // "TotalElements":2, + // "Result":[ + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // ] + // } + // + const result = this.safeValue (response, 'Result', []); + return this.parseTransactions (result, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'TransactionType': 'deposits', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'TransactionType': 'withdrawals', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const url = this.urls['api'] + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + query = this.extend ({ + 'key': this.apiKey, + 'nonce': nonce, + }, query); + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + 'Hash': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],123:[function(require,module,exports){ +(function (module, exports) { + 'use strict'; + + // Utils + function assert (val, msg) { + if (!val) throw new Error(msg || 'Assertion failed'); + } + + // Could use `inherits` module, but don't want to move from single file + // architecture yet. + function inherits (ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + + // BN + + function BN (number, base, endian) { + if (BN.isBN(number)) { + return number; + } + + this.negative = 0; + this.words = null; + this.length = 0; + + // Reduction context + this.red = null; + + if (number !== null) { + if (base === 'le' || base === 'be') { + endian = base; + base = 10; + } + + this._init(number || 0, base || 10, endian || 'be'); + } + } + if (typeof module === 'object') { + module.exports = BN; + } else { + exports.BN = BN; + } + + BN.BN = BN; + BN.wordSize = 26; + + var Buffer; + try { + Buffer = require('buffer').Buffer; + } catch (e) { + } + + BN.isBN = function isBN (num) { + if (num instanceof BN) { + return true; + } + + return num !== null && typeof num === 'object' && + num.constructor.wordSize === BN.wordSize && Array.isArray(num.words); + }; + + BN.max = function max (left, right) { + if (left.cmp(right) > 0) return left; + return right; + }; + + BN.min = function min (left, right) { + if (left.cmp(right) < 0) return left; + return right; + }; + + BN.prototype._init = function init (number, base, endian) { + if (typeof number === 'number') { + return this._initNumber(number, base, endian); + } + + if (typeof number === 'object') { + return this._initArray(number, base, endian); + } + + if (base === 'hex') { + base = 16; + } + assert(base === (base | 0) && base >= 2 && base <= 36); + + number = number.toString().replace(/\s+/g, ''); + var start = 0; + if (number[0] === '-') { + start++; + } + + if (base === 16) { + this._parseHex(number, start); + } else { + this._parseBase(number, base, start); + } + + if (number[0] === '-') { + this.negative = 1; + } + + this._strip(); + + if (endian !== 'le') return; + + this._initArray(this.toArray(), base, endian); + }; + + BN.prototype._initNumber = function _initNumber (number, base, endian) { + if (number < 0) { + this.negative = 1; + number = -number; + } + if (number < 0x4000000) { + this.words = [number & 0x3ffffff]; + this.length = 1; + } else if (number < 0x10000000000000) { + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff + ]; + this.length = 2; + } else { + assert(number < 0x20000000000000); // 2 ^ 53 (unsafe) + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff, + 1 + ]; + this.length = 3; + } + + if (endian !== 'le') return; + + // Reverse the bytes + this._initArray(this.toArray(), base, endian); + }; + + BN.prototype._initArray = function _initArray (number, base, endian) { + // Perhaps a Uint8Array + assert(typeof number.length === 'number'); + if (number.length <= 0) { + this.words = [0]; + this.length = 1; + return this; + } + + this.length = Math.ceil(number.length / 3); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + var j, w; + var off = 0; + if (endian === 'be') { + for (i = number.length - 1, j = 0; i >= 0; i -= 3) { + w = number[i] | (number[i - 1] << 8) | (number[i - 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } else if (endian === 'le') { + for (i = 0, j = 0; i < number.length; i += 3) { + w = number[i] | (number[i + 1] << 8) | (number[i + 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } + return this._strip(); + }; + + function parseHex (str, start, end) { + var r = 0; + var len = Math.min(str.length, end); + var z = 0; + for (var i = start; i < len; i++) { + var c = str.charCodeAt(i) - 48; + + r <<= 4; + + var b; + + // 'a' - 'f' + if (c >= 49 && c <= 54) { + b = c - 49 + 0xa; + + // 'A' - 'F' + } else if (c >= 17 && c <= 22) { + b = c - 17 + 0xa; + + // '0' - '9' + } else { + b = c; + } + + r |= b; + z |= b; + } + + assert(!(z & 0xf0), 'Invalid character in ' + str); + return r; + } + + BN.prototype._parseHex = function _parseHex (number, start) { + // Create possibly bigger array to ensure that it fits the number + this.length = Math.ceil((number.length - start) / 6); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + var j, w; + // Scan 24-bit chunks and add them to the number + var off = 0; + for (i = number.length - 6, j = 0; i >= start; i -= 6) { + w = parseHex(number, i, i + 6); + this.words[j] |= (w << off) & 0x3ffffff; + // NOTE: `0x3fffff` is intentional here, 26bits max shift + 24bit hex limb + this.words[j + 1] |= w >>> (26 - off) & 0x3fffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + if (i + 6 !== start) { + w = parseHex(number, start, i + 6); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] |= w >>> (26 - off) & 0x3fffff; + } + this._strip(); + }; + + function parseBase (str, start, end, mul) { + var r = 0; + var b = 0; + var len = Math.min(str.length, end); + for (var i = start; i < len; i++) { + var c = str.charCodeAt(i) - 48; + + r *= mul; + + // 'a' + if (c >= 49) { + b = c - 49 + 0xa; + + // 'A' + } else if (c >= 17) { + b = c - 17 + 0xa; + + // '0' - '9' + } else { + b = c; + } + assert(c >= 0 && b < mul, 'Invalid character'); + r += b; + } + return r; + } + + BN.prototype._parseBase = function _parseBase (number, base, start) { + // Initialize as zero + this.words = [0]; + this.length = 1; + + // Find length of limb in base + for (var limbLen = 0, limbPow = 1; limbPow <= 0x3ffffff; limbPow *= base) { + limbLen++; + } + limbLen--; + limbPow = (limbPow / base) | 0; + + var total = number.length - start; + var mod = total % limbLen; + var end = Math.min(total, total - mod) + start; + + var word = 0; + for (var i = start; i < end; i += limbLen) { + word = parseBase(number, i, i + limbLen, base); + + this.imuln(limbPow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + + if (mod !== 0) { + var pow = 1; + word = parseBase(number, i, number.length, base); + + for (i = 0; i < mod; i++) { + pow *= base; + } + + this.imuln(pow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + }; + + BN.prototype.copy = function copy (dest) { + dest.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + dest.words[i] = this.words[i]; + } + dest.length = this.length; + dest.negative = this.negative; + dest.red = this.red; + }; + + BN.prototype._move = function _move (dest) { + dest.words = this.words; + dest.length = this.length; + dest.negative = this.negative; + dest.red = this.red; + }; + + BN.prototype.clone = function clone () { + var r = new BN(null); + this.copy(r); + return r; + }; + + BN.prototype._expand = function _expand (size) { + while (this.length < size) { + this.words[this.length++] = 0; + } + return this; + }; + + // Remove leading `0` from `this` + BN.prototype._strip = function strip () { + while (this.length > 1 && this.words[this.length - 1] === 0) { + this.length--; + } + return this._normSign(); + }; + + BN.prototype._normSign = function _normSign () { + // -0 = 0 + if (this.length === 1 && this.words[0] === 0) { + this.negative = 0; + } + return this; + }; + + // Check Symbol.for because not everywhere where Symbol defined + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Browser_compatibility + if (typeof Symbol !== 'undefined' && typeof Symbol.for === 'function') { + BN.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspect; + } else { + BN.prototype.inspect = inspect; + } + + function inspect () { + return (this.red ? ''; + } + + /* + + var zeros = []; + var groupSizes = []; + var groupBases = []; + + var s = ''; + var i = -1; + while (++i < BN.wordSize) { + zeros[i] = s; + s += '0'; + } + groupSizes[0] = 0; + groupSizes[1] = 0; + groupBases[0] = 0; + groupBases[1] = 0; + var base = 2 - 1; + while (++base < 36 + 1) { + var groupSize = 0; + var groupBase = 1; + while (groupBase < (1 << BN.wordSize) / base) { + groupBase *= base; + groupSize += 1; + } + groupSizes[base] = groupSize; + groupBases[base] = groupBase; + } + + */ + + var zeros = [ + '', + '0', + '00', + '000', + '0000', + '00000', + '000000', + '0000000', + '00000000', + '000000000', + '0000000000', + '00000000000', + '000000000000', + '0000000000000', + '00000000000000', + '000000000000000', + '0000000000000000', + '00000000000000000', + '000000000000000000', + '0000000000000000000', + '00000000000000000000', + '000000000000000000000', + '0000000000000000000000', + '00000000000000000000000', + '000000000000000000000000', + '0000000000000000000000000' + ]; + + var groupSizes = [ + 0, 0, + 25, 16, 12, 11, 10, 9, 8, + 8, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5 + ]; + + var groupBases = [ + 0, 0, + 33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216, + 43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625, + 16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632, + 6436343, 7962624, 9765625, 11881376, 14348907, 17210368, 20511149, + 24300000, 28629151, 33554432, 39135393, 45435424, 52521875, 60466176 + ]; + + BN.prototype.toString = function toString (base, padding) { + base = base || 10; + padding = padding | 0 || 1; + + var out; + if (base === 16 || base === 'hex') { + out = ''; + var off = 0; + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = this.words[i]; + var word = (((w << off) | carry) & 0xffffff).toString(16); + carry = (w >>> (24 - off)) & 0xffffff; + if (carry !== 0 || i !== this.length - 1) { + out = zeros[6 - word.length] + word + out; + } else { + out = word + out; + } + off += 2; + if (off >= 26) { + off -= 26; + i--; + } + } + if (carry !== 0) { + out = carry.toString(16) + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + if (base === (base | 0) && base >= 2 && base <= 36) { + // var groupSize = Math.floor(BN.wordSize * Math.LN2 / Math.log(base)); + var groupSize = groupSizes[base]; + // var groupBase = Math.pow(base, groupSize); + var groupBase = groupBases[base]; + out = ''; + var c = this.clone(); + c.negative = 0; + while (!c.isZero()) { + var r = c.modrn(groupBase).toString(base); + c = c.idivn(groupBase); + + if (!c.isZero()) { + out = zeros[groupSize - r.length] + r + out; + } else { + out = r + out; + } + } + if (this.isZero()) { + out = '0' + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + assert(false, 'Base should be between 2 and 36'); + }; + + BN.prototype.toNumber = function toNumber () { + var ret = this.words[0]; + if (this.length === 2) { + ret += this.words[1] * 0x4000000; + } else if (this.length === 3 && this.words[2] === 0x01) { + // NOTE: at this stage it is known that the top bit is set + ret += 0x10000000000000 + (this.words[1] * 0x4000000); + } else if (this.length > 2) { + assert(false, 'Number can only safely store up to 53 bits'); + } + return (this.negative !== 0) ? -ret : ret; + }; + + BN.prototype.toJSON = function toJSON () { + return this.toString(16, 2); + }; + + if (Buffer) { + BN.prototype.toBuffer = function toBuffer (endian, length) { + return this.toArrayLike(Buffer, endian, length); + }; + } + + BN.prototype.toArray = function toArray (endian, length) { + return this.toArrayLike(Array, endian, length); + }; + + var allocate = function allocate (ArrayType, size) { + if (ArrayType.allocUnsafe) { + return ArrayType.allocUnsafe(size); + } + return new ArrayType(size); + }; + + BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) { + this._strip(); + + var byteLength = this.byteLength(); + var reqLength = length || Math.max(1, byteLength); + assert(byteLength <= reqLength, 'byte array longer than desired length'); + assert(reqLength > 0, 'Requested array length <= 0'); + + var res = allocate(ArrayType, reqLength); + var postfix = endian === 'le' ? 'LE' : 'BE'; + this['_toArrayLike' + postfix](res, byteLength); + return res; + }; + + BN.prototype._toArrayLikeLE = function _toArrayLikeLE (res, byteLength) { + var position = 0; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position++] = word & 0xff; + if (position < res.length) { + res[position++] = (word >> 8) & 0xff; + } + if (position < res.length) { + res[position++] = (word >> 16) & 0xff; + } + + if (shift === 6) { + if (position < res.length) { + res[position++] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position < res.length) { + res[position++] = carry; + + while (position < res.length) { + res[position++] = 0; + } + } + }; + + BN.prototype._toArrayLikeBE = function _toArrayLikeBE (res, byteLength) { + var position = res.length - 1; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position--] = word & 0xff; + if (position >= 0) { + res[position--] = (word >> 8) & 0xff; + } + if (position >= 0) { + res[position--] = (word >> 16) & 0xff; + } + + if (shift === 6) { + if (position >= 0) { + res[position--] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position >= 0) { + res[position--] = carry; + + while (position >= 0) { + res[position--] = 0; + } + } + }; + + if (Math.clz32) { + BN.prototype._countBits = function _countBits (w) { + return 32 - Math.clz32(w); + }; + } else { + BN.prototype._countBits = function _countBits (w) { + var t = w; + var r = 0; + if (t >= 0x1000) { + r += 13; + t >>>= 13; + } + if (t >= 0x40) { + r += 7; + t >>>= 7; + } + if (t >= 0x8) { + r += 4; + t >>>= 4; + } + if (t >= 0x02) { + r += 2; + t >>>= 2; + } + return r + t; + }; + } + + BN.prototype._zeroBits = function _zeroBits (w) { + // Short-cut + if (w === 0) return 26; + + var t = w; + var r = 0; + if ((t & 0x1fff) === 0) { + r += 13; + t >>>= 13; + } + if ((t & 0x7f) === 0) { + r += 7; + t >>>= 7; + } + if ((t & 0xf) === 0) { + r += 4; + t >>>= 4; + } + if ((t & 0x3) === 0) { + r += 2; + t >>>= 2; + } + if ((t & 0x1) === 0) { + r++; + } + return r; + }; + + // Return number of used bits in a BN + BN.prototype.bitLength = function bitLength () { + var w = this.words[this.length - 1]; + var hi = this._countBits(w); + return (this.length - 1) * 26 + hi; + }; + + function toBitArray (num) { + var w = new Array(num.bitLength()); + + for (var bit = 0; bit < w.length; bit++) { + var off = (bit / 26) | 0; + var wbit = bit % 26; + + w[bit] = (num.words[off] >>> wbit) & 0x01; + } + + return w; + } + + // Number of trailing zero bits + BN.prototype.zeroBits = function zeroBits () { + if (this.isZero()) return 0; + + var r = 0; + for (var i = 0; i < this.length; i++) { + var b = this._zeroBits(this.words[i]); + r += b; + if (b !== 26) break; + } + return r; + }; + + BN.prototype.byteLength = function byteLength () { + return Math.ceil(this.bitLength() / 8); + }; + + BN.prototype.toTwos = function toTwos (width) { + if (this.negative !== 0) { + return this.abs().inotn(width).iaddn(1); + } + return this.clone(); + }; + + BN.prototype.fromTwos = function fromTwos (width) { + if (this.testn(width - 1)) { + return this.notn(width).iaddn(1).ineg(); + } + return this.clone(); + }; + + BN.prototype.isNeg = function isNeg () { + return this.negative !== 0; + }; + + // Return negative clone of `this` + BN.prototype.neg = function neg () { + return this.clone().ineg(); + }; + + BN.prototype.ineg = function ineg () { + if (!this.isZero()) { + this.negative ^= 1; + } + + return this; + }; + + // Or `num` with `this` in-place + BN.prototype.iuor = function iuor (num) { + while (this.length < num.length) { + this.words[this.length++] = 0; + } + + for (var i = 0; i < num.length; i++) { + this.words[i] = this.words[i] | num.words[i]; + } + + return this._strip(); + }; + + BN.prototype.ior = function ior (num) { + assert((this.negative | num.negative) === 0); + return this.iuor(num); + }; + + // Or `num` with `this` + BN.prototype.or = function or (num) { + if (this.length > num.length) return this.clone().ior(num); + return num.clone().ior(this); + }; + + BN.prototype.uor = function uor (num) { + if (this.length > num.length) return this.clone().iuor(num); + return num.clone().iuor(this); + }; + + // And `num` with `this` in-place + BN.prototype.iuand = function iuand (num) { + // b = min-length(num, this) + var b; + if (this.length > num.length) { + b = num; + } else { + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = this.words[i] & num.words[i]; + } + + this.length = b.length; + + return this._strip(); + }; + + BN.prototype.iand = function iand (num) { + assert((this.negative | num.negative) === 0); + return this.iuand(num); + }; + + // And `num` with `this` + BN.prototype.and = function and (num) { + if (this.length > num.length) return this.clone().iand(num); + return num.clone().iand(this); + }; + + BN.prototype.uand = function uand (num) { + if (this.length > num.length) return this.clone().iuand(num); + return num.clone().iuand(this); + }; + + // Xor `num` with `this` in-place + BN.prototype.iuxor = function iuxor (num) { + // a.length > b.length + var a; + var b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = a.words[i] ^ b.words[i]; + } + + if (this !== a) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = a.length; + + return this._strip(); + }; + + BN.prototype.ixor = function ixor (num) { + assert((this.negative | num.negative) === 0); + return this.iuxor(num); + }; + + // Xor `num` with `this` + BN.prototype.xor = function xor (num) { + if (this.length > num.length) return this.clone().ixor(num); + return num.clone().ixor(this); + }; + + BN.prototype.uxor = function uxor (num) { + if (this.length > num.length) return this.clone().iuxor(num); + return num.clone().iuxor(this); + }; + + // Not ``this`` with ``width`` bitwidth + BN.prototype.inotn = function inotn (width) { + assert(typeof width === 'number' && width >= 0); + + var bytesNeeded = Math.ceil(width / 26) | 0; + var bitsLeft = width % 26; + + // Extend the buffer with leading zeroes + this._expand(bytesNeeded); + + if (bitsLeft > 0) { + bytesNeeded--; + } + + // Handle complete words + for (var i = 0; i < bytesNeeded; i++) { + this.words[i] = ~this.words[i] & 0x3ffffff; + } + + // Handle the residue + if (bitsLeft > 0) { + this.words[i] = ~this.words[i] & (0x3ffffff >> (26 - bitsLeft)); + } + + // And remove leading zeroes + return this._strip(); + }; + + BN.prototype.notn = function notn (width) { + return this.clone().inotn(width); + }; + + // Set `bit` of `this` + BN.prototype.setn = function setn (bit, val) { + assert(typeof bit === 'number' && bit >= 0); + + var off = (bit / 26) | 0; + var wbit = bit % 26; + + this._expand(off + 1); + + if (val) { + this.words[off] = this.words[off] | (1 << wbit); + } else { + this.words[off] = this.words[off] & ~(1 << wbit); + } + + return this._strip(); + }; + + // Add `num` to `this` in-place + BN.prototype.iadd = function iadd (num) { + var r; + + // negative + positive + if (this.negative !== 0 && num.negative === 0) { + this.negative = 0; + r = this.isub(num); + this.negative ^= 1; + return this._normSign(); + + // positive + negative + } else if (this.negative === 0 && num.negative !== 0) { + num.negative = 0; + r = this.isub(num); + num.negative = 1; + return r._normSign(); + } + + // a.length > b.length + var a, b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) + (b.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + + this.length = a.length; + if (carry !== 0) { + this.words[this.length] = carry; + this.length++; + // Copy the rest of the words + } else if (a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + return this; + }; + + // Add `num` to `this` + BN.prototype.add = function add (num) { + var res; + if (num.negative !== 0 && this.negative === 0) { + num.negative = 0; + res = this.sub(num); + num.negative ^= 1; + return res; + } else if (num.negative === 0 && this.negative !== 0) { + this.negative = 0; + res = num.sub(this); + this.negative = 1; + return res; + } + + if (this.length > num.length) return this.clone().iadd(num); + + return num.clone().iadd(this); + }; + + // Subtract `num` from `this` in-place + BN.prototype.isub = function isub (num) { + // this - (-num) = this + num + if (num.negative !== 0) { + num.negative = 0; + var r = this.iadd(num); + num.negative = 1; + return r._normSign(); + + // -this - num = -(this + num) + } else if (this.negative !== 0) { + this.negative = 0; + this.iadd(num); + this.negative = 1; + return this._normSign(); + } + + // At this point both numbers are positive + var cmp = this.cmp(num); + + // Optimization - zeroify + if (cmp === 0) { + this.negative = 0; + this.length = 1; + this.words[0] = 0; + return this; + } + + // a > b + var a, b; + if (cmp > 0) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) - (b.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + + // Copy rest of the words + if (carry === 0 && i < a.length && a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = Math.max(this.length, i); + + if (a !== this) { + this.negative = 1; + } + + return this._strip(); + }; + + // Subtract `num` from `this` + BN.prototype.sub = function sub (num) { + return this.clone().isub(num); + }; + + function smallMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + var len = (self.length + num.length) | 0; + out.length = len; + len = (len - 1) | 0; + + // Peel one iteration (compiler can't do it, because of code complexity) + var a = self.words[0] | 0; + var b = num.words[0] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + var carry = (r / 0x4000000) | 0; + out.words[0] = lo; + + for (var k = 1; k < len; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = carry >>> 26; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = (k - j) | 0; + a = self.words[i] | 0; + b = num.words[j] | 0; + r = a * b + rword; + ncarry += (r / 0x4000000) | 0; + rword = r & 0x3ffffff; + } + out.words[k] = rword | 0; + carry = ncarry | 0; + } + if (carry !== 0) { + out.words[k] = carry | 0; + } else { + out.length--; + } + + return out._strip(); + } + + // TODO(indutny): it may be reasonable to omit it for users who don't need + // to work with 256-bit numbers, otherwise it gives 20% improvement for 256-bit + // multiplication (like elliptic secp256k1). + var comb10MulTo = function comb10MulTo (self, num, out) { + var a = self.words; + var b = num.words; + var o = out.words; + var c = 0; + var lo; + var mid; + var hi; + var a0 = a[0] | 0; + var al0 = a0 & 0x1fff; + var ah0 = a0 >>> 13; + var a1 = a[1] | 0; + var al1 = a1 & 0x1fff; + var ah1 = a1 >>> 13; + var a2 = a[2] | 0; + var al2 = a2 & 0x1fff; + var ah2 = a2 >>> 13; + var a3 = a[3] | 0; + var al3 = a3 & 0x1fff; + var ah3 = a3 >>> 13; + var a4 = a[4] | 0; + var al4 = a4 & 0x1fff; + var ah4 = a4 >>> 13; + var a5 = a[5] | 0; + var al5 = a5 & 0x1fff; + var ah5 = a5 >>> 13; + var a6 = a[6] | 0; + var al6 = a6 & 0x1fff; + var ah6 = a6 >>> 13; + var a7 = a[7] | 0; + var al7 = a7 & 0x1fff; + var ah7 = a7 >>> 13; + var a8 = a[8] | 0; + var al8 = a8 & 0x1fff; + var ah8 = a8 >>> 13; + var a9 = a[9] | 0; + var al9 = a9 & 0x1fff; + var ah9 = a9 >>> 13; + var b0 = b[0] | 0; + var bl0 = b0 & 0x1fff; + var bh0 = b0 >>> 13; + var b1 = b[1] | 0; + var bl1 = b1 & 0x1fff; + var bh1 = b1 >>> 13; + var b2 = b[2] | 0; + var bl2 = b2 & 0x1fff; + var bh2 = b2 >>> 13; + var b3 = b[3] | 0; + var bl3 = b3 & 0x1fff; + var bh3 = b3 >>> 13; + var b4 = b[4] | 0; + var bl4 = b4 & 0x1fff; + var bh4 = b4 >>> 13; + var b5 = b[5] | 0; + var bl5 = b5 & 0x1fff; + var bh5 = b5 >>> 13; + var b6 = b[6] | 0; + var bl6 = b6 & 0x1fff; + var bh6 = b6 >>> 13; + var b7 = b[7] | 0; + var bl7 = b7 & 0x1fff; + var bh7 = b7 >>> 13; + var b8 = b[8] | 0; + var bl8 = b8 & 0x1fff; + var bh8 = b8 >>> 13; + var b9 = b[9] | 0; + var bl9 = b9 & 0x1fff; + var bh9 = b9 >>> 13; + + out.negative = self.negative ^ num.negative; + out.length = 19; + /* k = 0 */ + lo = Math.imul(al0, bl0); + mid = Math.imul(al0, bh0); + mid = (mid + Math.imul(ah0, bl0)) | 0; + hi = Math.imul(ah0, bh0); + var w0 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w0 >>> 26)) | 0; + w0 &= 0x3ffffff; + /* k = 1 */ + lo = Math.imul(al1, bl0); + mid = Math.imul(al1, bh0); + mid = (mid + Math.imul(ah1, bl0)) | 0; + hi = Math.imul(ah1, bh0); + lo = (lo + Math.imul(al0, bl1)) | 0; + mid = (mid + Math.imul(al0, bh1)) | 0; + mid = (mid + Math.imul(ah0, bl1)) | 0; + hi = (hi + Math.imul(ah0, bh1)) | 0; + var w1 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w1 >>> 26)) | 0; + w1 &= 0x3ffffff; + /* k = 2 */ + lo = Math.imul(al2, bl0); + mid = Math.imul(al2, bh0); + mid = (mid + Math.imul(ah2, bl0)) | 0; + hi = Math.imul(ah2, bh0); + lo = (lo + Math.imul(al1, bl1)) | 0; + mid = (mid + Math.imul(al1, bh1)) | 0; + mid = (mid + Math.imul(ah1, bl1)) | 0; + hi = (hi + Math.imul(ah1, bh1)) | 0; + lo = (lo + Math.imul(al0, bl2)) | 0; + mid = (mid + Math.imul(al0, bh2)) | 0; + mid = (mid + Math.imul(ah0, bl2)) | 0; + hi = (hi + Math.imul(ah0, bh2)) | 0; + var w2 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w2 >>> 26)) | 0; + w2 &= 0x3ffffff; + /* k = 3 */ + lo = Math.imul(al3, bl0); + mid = Math.imul(al3, bh0); + mid = (mid + Math.imul(ah3, bl0)) | 0; + hi = Math.imul(ah3, bh0); + lo = (lo + Math.imul(al2, bl1)) | 0; + mid = (mid + Math.imul(al2, bh1)) | 0; + mid = (mid + Math.imul(ah2, bl1)) | 0; + hi = (hi + Math.imul(ah2, bh1)) | 0; + lo = (lo + Math.imul(al1, bl2)) | 0; + mid = (mid + Math.imul(al1, bh2)) | 0; + mid = (mid + Math.imul(ah1, bl2)) | 0; + hi = (hi + Math.imul(ah1, bh2)) | 0; + lo = (lo + Math.imul(al0, bl3)) | 0; + mid = (mid + Math.imul(al0, bh3)) | 0; + mid = (mid + Math.imul(ah0, bl3)) | 0; + hi = (hi + Math.imul(ah0, bh3)) | 0; + var w3 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w3 >>> 26)) | 0; + w3 &= 0x3ffffff; + /* k = 4 */ + lo = Math.imul(al4, bl0); + mid = Math.imul(al4, bh0); + mid = (mid + Math.imul(ah4, bl0)) | 0; + hi = Math.imul(ah4, bh0); + lo = (lo + Math.imul(al3, bl1)) | 0; + mid = (mid + Math.imul(al3, bh1)) | 0; + mid = (mid + Math.imul(ah3, bl1)) | 0; + hi = (hi + Math.imul(ah3, bh1)) | 0; + lo = (lo + Math.imul(al2, bl2)) | 0; + mid = (mid + Math.imul(al2, bh2)) | 0; + mid = (mid + Math.imul(ah2, bl2)) | 0; + hi = (hi + Math.imul(ah2, bh2)) | 0; + lo = (lo + Math.imul(al1, bl3)) | 0; + mid = (mid + Math.imul(al1, bh3)) | 0; + mid = (mid + Math.imul(ah1, bl3)) | 0; + hi = (hi + Math.imul(ah1, bh3)) | 0; + lo = (lo + Math.imul(al0, bl4)) | 0; + mid = (mid + Math.imul(al0, bh4)) | 0; + mid = (mid + Math.imul(ah0, bl4)) | 0; + hi = (hi + Math.imul(ah0, bh4)) | 0; + var w4 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w4 >>> 26)) | 0; + w4 &= 0x3ffffff; + /* k = 5 */ + lo = Math.imul(al5, bl0); + mid = Math.imul(al5, bh0); + mid = (mid + Math.imul(ah5, bl0)) | 0; + hi = Math.imul(ah5, bh0); + lo = (lo + Math.imul(al4, bl1)) | 0; + mid = (mid + Math.imul(al4, bh1)) | 0; + mid = (mid + Math.imul(ah4, bl1)) | 0; + hi = (hi + Math.imul(ah4, bh1)) | 0; + lo = (lo + Math.imul(al3, bl2)) | 0; + mid = (mid + Math.imul(al3, bh2)) | 0; + mid = (mid + Math.imul(ah3, bl2)) | 0; + hi = (hi + Math.imul(ah3, bh2)) | 0; + lo = (lo + Math.imul(al2, bl3)) | 0; + mid = (mid + Math.imul(al2, bh3)) | 0; + mid = (mid + Math.imul(ah2, bl3)) | 0; + hi = (hi + Math.imul(ah2, bh3)) | 0; + lo = (lo + Math.imul(al1, bl4)) | 0; + mid = (mid + Math.imul(al1, bh4)) | 0; + mid = (mid + Math.imul(ah1, bl4)) | 0; + hi = (hi + Math.imul(ah1, bh4)) | 0; + lo = (lo + Math.imul(al0, bl5)) | 0; + mid = (mid + Math.imul(al0, bh5)) | 0; + mid = (mid + Math.imul(ah0, bl5)) | 0; + hi = (hi + Math.imul(ah0, bh5)) | 0; + var w5 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w5 >>> 26)) | 0; + w5 &= 0x3ffffff; + /* k = 6 */ + lo = Math.imul(al6, bl0); + mid = Math.imul(al6, bh0); + mid = (mid + Math.imul(ah6, bl0)) | 0; + hi = Math.imul(ah6, bh0); + lo = (lo + Math.imul(al5, bl1)) | 0; + mid = (mid + Math.imul(al5, bh1)) | 0; + mid = (mid + Math.imul(ah5, bl1)) | 0; + hi = (hi + Math.imul(ah5, bh1)) | 0; + lo = (lo + Math.imul(al4, bl2)) | 0; + mid = (mid + Math.imul(al4, bh2)) | 0; + mid = (mid + Math.imul(ah4, bl2)) | 0; + hi = (hi + Math.imul(ah4, bh2)) | 0; + lo = (lo + Math.imul(al3, bl3)) | 0; + mid = (mid + Math.imul(al3, bh3)) | 0; + mid = (mid + Math.imul(ah3, bl3)) | 0; + hi = (hi + Math.imul(ah3, bh3)) | 0; + lo = (lo + Math.imul(al2, bl4)) | 0; + mid = (mid + Math.imul(al2, bh4)) | 0; + mid = (mid + Math.imul(ah2, bl4)) | 0; + hi = (hi + Math.imul(ah2, bh4)) | 0; + lo = (lo + Math.imul(al1, bl5)) | 0; + mid = (mid + Math.imul(al1, bh5)) | 0; + mid = (mid + Math.imul(ah1, bl5)) | 0; + hi = (hi + Math.imul(ah1, bh5)) | 0; + lo = (lo + Math.imul(al0, bl6)) | 0; + mid = (mid + Math.imul(al0, bh6)) | 0; + mid = (mid + Math.imul(ah0, bl6)) | 0; + hi = (hi + Math.imul(ah0, bh6)) | 0; + var w6 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w6 >>> 26)) | 0; + w6 &= 0x3ffffff; + /* k = 7 */ + lo = Math.imul(al7, bl0); + mid = Math.imul(al7, bh0); + mid = (mid + Math.imul(ah7, bl0)) | 0; + hi = Math.imul(ah7, bh0); + lo = (lo + Math.imul(al6, bl1)) | 0; + mid = (mid + Math.imul(al6, bh1)) | 0; + mid = (mid + Math.imul(ah6, bl1)) | 0; + hi = (hi + Math.imul(ah6, bh1)) | 0; + lo = (lo + Math.imul(al5, bl2)) | 0; + mid = (mid + Math.imul(al5, bh2)) | 0; + mid = (mid + Math.imul(ah5, bl2)) | 0; + hi = (hi + Math.imul(ah5, bh2)) | 0; + lo = (lo + Math.imul(al4, bl3)) | 0; + mid = (mid + Math.imul(al4, bh3)) | 0; + mid = (mid + Math.imul(ah4, bl3)) | 0; + hi = (hi + Math.imul(ah4, bh3)) | 0; + lo = (lo + Math.imul(al3, bl4)) | 0; + mid = (mid + Math.imul(al3, bh4)) | 0; + mid = (mid + Math.imul(ah3, bl4)) | 0; + hi = (hi + Math.imul(ah3, bh4)) | 0; + lo = (lo + Math.imul(al2, bl5)) | 0; + mid = (mid + Math.imul(al2, bh5)) | 0; + mid = (mid + Math.imul(ah2, bl5)) | 0; + hi = (hi + Math.imul(ah2, bh5)) | 0; + lo = (lo + Math.imul(al1, bl6)) | 0; + mid = (mid + Math.imul(al1, bh6)) | 0; + mid = (mid + Math.imul(ah1, bl6)) | 0; + hi = (hi + Math.imul(ah1, bh6)) | 0; + lo = (lo + Math.imul(al0, bl7)) | 0; + mid = (mid + Math.imul(al0, bh7)) | 0; + mid = (mid + Math.imul(ah0, bl7)) | 0; + hi = (hi + Math.imul(ah0, bh7)) | 0; + var w7 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w7 >>> 26)) | 0; + w7 &= 0x3ffffff; + /* k = 8 */ + lo = Math.imul(al8, bl0); + mid = Math.imul(al8, bh0); + mid = (mid + Math.imul(ah8, bl0)) | 0; + hi = Math.imul(ah8, bh0); + lo = (lo + Math.imul(al7, bl1)) | 0; + mid = (mid + Math.imul(al7, bh1)) | 0; + mid = (mid + Math.imul(ah7, bl1)) | 0; + hi = (hi + Math.imul(ah7, bh1)) | 0; + lo = (lo + Math.imul(al6, bl2)) | 0; + mid = (mid + Math.imul(al6, bh2)) | 0; + mid = (mid + Math.imul(ah6, bl2)) | 0; + hi = (hi + Math.imul(ah6, bh2)) | 0; + lo = (lo + Math.imul(al5, bl3)) | 0; + mid = (mid + Math.imul(al5, bh3)) | 0; + mid = (mid + Math.imul(ah5, bl3)) | 0; + hi = (hi + Math.imul(ah5, bh3)) | 0; + lo = (lo + Math.imul(al4, bl4)) | 0; + mid = (mid + Math.imul(al4, bh4)) | 0; + mid = (mid + Math.imul(ah4, bl4)) | 0; + hi = (hi + Math.imul(ah4, bh4)) | 0; + lo = (lo + Math.imul(al3, bl5)) | 0; + mid = (mid + Math.imul(al3, bh5)) | 0; + mid = (mid + Math.imul(ah3, bl5)) | 0; + hi = (hi + Math.imul(ah3, bh5)) | 0; + lo = (lo + Math.imul(al2, bl6)) | 0; + mid = (mid + Math.imul(al2, bh6)) | 0; + mid = (mid + Math.imul(ah2, bl6)) | 0; + hi = (hi + Math.imul(ah2, bh6)) | 0; + lo = (lo + Math.imul(al1, bl7)) | 0; + mid = (mid + Math.imul(al1, bh7)) | 0; + mid = (mid + Math.imul(ah1, bl7)) | 0; + hi = (hi + Math.imul(ah1, bh7)) | 0; + lo = (lo + Math.imul(al0, bl8)) | 0; + mid = (mid + Math.imul(al0, bh8)) | 0; + mid = (mid + Math.imul(ah0, bl8)) | 0; + hi = (hi + Math.imul(ah0, bh8)) | 0; + var w8 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w8 >>> 26)) | 0; + w8 &= 0x3ffffff; + /* k = 9 */ + lo = Math.imul(al9, bl0); + mid = Math.imul(al9, bh0); + mid = (mid + Math.imul(ah9, bl0)) | 0; + hi = Math.imul(ah9, bh0); + lo = (lo + Math.imul(al8, bl1)) | 0; + mid = (mid + Math.imul(al8, bh1)) | 0; + mid = (mid + Math.imul(ah8, bl1)) | 0; + hi = (hi + Math.imul(ah8, bh1)) | 0; + lo = (lo + Math.imul(al7, bl2)) | 0; + mid = (mid + Math.imul(al7, bh2)) | 0; + mid = (mid + Math.imul(ah7, bl2)) | 0; + hi = (hi + Math.imul(ah7, bh2)) | 0; + lo = (lo + Math.imul(al6, bl3)) | 0; + mid = (mid + Math.imul(al6, bh3)) | 0; + mid = (mid + Math.imul(ah6, bl3)) | 0; + hi = (hi + Math.imul(ah6, bh3)) | 0; + lo = (lo + Math.imul(al5, bl4)) | 0; + mid = (mid + Math.imul(al5, bh4)) | 0; + mid = (mid + Math.imul(ah5, bl4)) | 0; + hi = (hi + Math.imul(ah5, bh4)) | 0; + lo = (lo + Math.imul(al4, bl5)) | 0; + mid = (mid + Math.imul(al4, bh5)) | 0; + mid = (mid + Math.imul(ah4, bl5)) | 0; + hi = (hi + Math.imul(ah4, bh5)) | 0; + lo = (lo + Math.imul(al3, bl6)) | 0; + mid = (mid + Math.imul(al3, bh6)) | 0; + mid = (mid + Math.imul(ah3, bl6)) | 0; + hi = (hi + Math.imul(ah3, bh6)) | 0; + lo = (lo + Math.imul(al2, bl7)) | 0; + mid = (mid + Math.imul(al2, bh7)) | 0; + mid = (mid + Math.imul(ah2, bl7)) | 0; + hi = (hi + Math.imul(ah2, bh7)) | 0; + lo = (lo + Math.imul(al1, bl8)) | 0; + mid = (mid + Math.imul(al1, bh8)) | 0; + mid = (mid + Math.imul(ah1, bl8)) | 0; + hi = (hi + Math.imul(ah1, bh8)) | 0; + lo = (lo + Math.imul(al0, bl9)) | 0; + mid = (mid + Math.imul(al0, bh9)) | 0; + mid = (mid + Math.imul(ah0, bl9)) | 0; + hi = (hi + Math.imul(ah0, bh9)) | 0; + var w9 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w9 >>> 26)) | 0; + w9 &= 0x3ffffff; + /* k = 10 */ + lo = Math.imul(al9, bl1); + mid = Math.imul(al9, bh1); + mid = (mid + Math.imul(ah9, bl1)) | 0; + hi = Math.imul(ah9, bh1); + lo = (lo + Math.imul(al8, bl2)) | 0; + mid = (mid + Math.imul(al8, bh2)) | 0; + mid = (mid + Math.imul(ah8, bl2)) | 0; + hi = (hi + Math.imul(ah8, bh2)) | 0; + lo = (lo + Math.imul(al7, bl3)) | 0; + mid = (mid + Math.imul(al7, bh3)) | 0; + mid = (mid + Math.imul(ah7, bl3)) | 0; + hi = (hi + Math.imul(ah7, bh3)) | 0; + lo = (lo + Math.imul(al6, bl4)) | 0; + mid = (mid + Math.imul(al6, bh4)) | 0; + mid = (mid + Math.imul(ah6, bl4)) | 0; + hi = (hi + Math.imul(ah6, bh4)) | 0; + lo = (lo + Math.imul(al5, bl5)) | 0; + mid = (mid + Math.imul(al5, bh5)) | 0; + mid = (mid + Math.imul(ah5, bl5)) | 0; + hi = (hi + Math.imul(ah5, bh5)) | 0; + lo = (lo + Math.imul(al4, bl6)) | 0; + mid = (mid + Math.imul(al4, bh6)) | 0; + mid = (mid + Math.imul(ah4, bl6)) | 0; + hi = (hi + Math.imul(ah4, bh6)) | 0; + lo = (lo + Math.imul(al3, bl7)) | 0; + mid = (mid + Math.imul(al3, bh7)) | 0; + mid = (mid + Math.imul(ah3, bl7)) | 0; + hi = (hi + Math.imul(ah3, bh7)) | 0; + lo = (lo + Math.imul(al2, bl8)) | 0; + mid = (mid + Math.imul(al2, bh8)) | 0; + mid = (mid + Math.imul(ah2, bl8)) | 0; + hi = (hi + Math.imul(ah2, bh8)) | 0; + lo = (lo + Math.imul(al1, bl9)) | 0; + mid = (mid + Math.imul(al1, bh9)) | 0; + mid = (mid + Math.imul(ah1, bl9)) | 0; + hi = (hi + Math.imul(ah1, bh9)) | 0; + var w10 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w10 >>> 26)) | 0; + w10 &= 0x3ffffff; + /* k = 11 */ + lo = Math.imul(al9, bl2); + mid = Math.imul(al9, bh2); + mid = (mid + Math.imul(ah9, bl2)) | 0; + hi = Math.imul(ah9, bh2); + lo = (lo + Math.imul(al8, bl3)) | 0; + mid = (mid + Math.imul(al8, bh3)) | 0; + mid = (mid + Math.imul(ah8, bl3)) | 0; + hi = (hi + Math.imul(ah8, bh3)) | 0; + lo = (lo + Math.imul(al7, bl4)) | 0; + mid = (mid + Math.imul(al7, bh4)) | 0; + mid = (mid + Math.imul(ah7, bl4)) | 0; + hi = (hi + Math.imul(ah7, bh4)) | 0; + lo = (lo + Math.imul(al6, bl5)) | 0; + mid = (mid + Math.imul(al6, bh5)) | 0; + mid = (mid + Math.imul(ah6, bl5)) | 0; + hi = (hi + Math.imul(ah6, bh5)) | 0; + lo = (lo + Math.imul(al5, bl6)) | 0; + mid = (mid + Math.imul(al5, bh6)) | 0; + mid = (mid + Math.imul(ah5, bl6)) | 0; + hi = (hi + Math.imul(ah5, bh6)) | 0; + lo = (lo + Math.imul(al4, bl7)) | 0; + mid = (mid + Math.imul(al4, bh7)) | 0; + mid = (mid + Math.imul(ah4, bl7)) | 0; + hi = (hi + Math.imul(ah4, bh7)) | 0; + lo = (lo + Math.imul(al3, bl8)) | 0; + mid = (mid + Math.imul(al3, bh8)) | 0; + mid = (mid + Math.imul(ah3, bl8)) | 0; + hi = (hi + Math.imul(ah3, bh8)) | 0; + lo = (lo + Math.imul(al2, bl9)) | 0; + mid = (mid + Math.imul(al2, bh9)) | 0; + mid = (mid + Math.imul(ah2, bl9)) | 0; + hi = (hi + Math.imul(ah2, bh9)) | 0; + var w11 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w11 >>> 26)) | 0; + w11 &= 0x3ffffff; + /* k = 12 */ + lo = Math.imul(al9, bl3); + mid = Math.imul(al9, bh3); + mid = (mid + Math.imul(ah9, bl3)) | 0; + hi = Math.imul(ah9, bh3); + lo = (lo + Math.imul(al8, bl4)) | 0; + mid = (mid + Math.imul(al8, bh4)) | 0; + mid = (mid + Math.imul(ah8, bl4)) | 0; + hi = (hi + Math.imul(ah8, bh4)) | 0; + lo = (lo + Math.imul(al7, bl5)) | 0; + mid = (mid + Math.imul(al7, bh5)) | 0; + mid = (mid + Math.imul(ah7, bl5)) | 0; + hi = (hi + Math.imul(ah7, bh5)) | 0; + lo = (lo + Math.imul(al6, bl6)) | 0; + mid = (mid + Math.imul(al6, bh6)) | 0; + mid = (mid + Math.imul(ah6, bl6)) | 0; + hi = (hi + Math.imul(ah6, bh6)) | 0; + lo = (lo + Math.imul(al5, bl7)) | 0; + mid = (mid + Math.imul(al5, bh7)) | 0; + mid = (mid + Math.imul(ah5, bl7)) | 0; + hi = (hi + Math.imul(ah5, bh7)) | 0; + lo = (lo + Math.imul(al4, bl8)) | 0; + mid = (mid + Math.imul(al4, bh8)) | 0; + mid = (mid + Math.imul(ah4, bl8)) | 0; + hi = (hi + Math.imul(ah4, bh8)) | 0; + lo = (lo + Math.imul(al3, bl9)) | 0; + mid = (mid + Math.imul(al3, bh9)) | 0; + mid = (mid + Math.imul(ah3, bl9)) | 0; + hi = (hi + Math.imul(ah3, bh9)) | 0; + var w12 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w12 >>> 26)) | 0; + w12 &= 0x3ffffff; + /* k = 13 */ + lo = Math.imul(al9, bl4); + mid = Math.imul(al9, bh4); + mid = (mid + Math.imul(ah9, bl4)) | 0; + hi = Math.imul(ah9, bh4); + lo = (lo + Math.imul(al8, bl5)) | 0; + mid = (mid + Math.imul(al8, bh5)) | 0; + mid = (mid + Math.imul(ah8, bl5)) | 0; + hi = (hi + Math.imul(ah8, bh5)) | 0; + lo = (lo + Math.imul(al7, bl6)) | 0; + mid = (mid + Math.imul(al7, bh6)) | 0; + mid = (mid + Math.imul(ah7, bl6)) | 0; + hi = (hi + Math.imul(ah7, bh6)) | 0; + lo = (lo + Math.imul(al6, bl7)) | 0; + mid = (mid + Math.imul(al6, bh7)) | 0; + mid = (mid + Math.imul(ah6, bl7)) | 0; + hi = (hi + Math.imul(ah6, bh7)) | 0; + lo = (lo + Math.imul(al5, bl8)) | 0; + mid = (mid + Math.imul(al5, bh8)) | 0; + mid = (mid + Math.imul(ah5, bl8)) | 0; + hi = (hi + Math.imul(ah5, bh8)) | 0; + lo = (lo + Math.imul(al4, bl9)) | 0; + mid = (mid + Math.imul(al4, bh9)) | 0; + mid = (mid + Math.imul(ah4, bl9)) | 0; + hi = (hi + Math.imul(ah4, bh9)) | 0; + var w13 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w13 >>> 26)) | 0; + w13 &= 0x3ffffff; + /* k = 14 */ + lo = Math.imul(al9, bl5); + mid = Math.imul(al9, bh5); + mid = (mid + Math.imul(ah9, bl5)) | 0; + hi = Math.imul(ah9, bh5); + lo = (lo + Math.imul(al8, bl6)) | 0; + mid = (mid + Math.imul(al8, bh6)) | 0; + mid = (mid + Math.imul(ah8, bl6)) | 0; + hi = (hi + Math.imul(ah8, bh6)) | 0; + lo = (lo + Math.imul(al7, bl7)) | 0; + mid = (mid + Math.imul(al7, bh7)) | 0; + mid = (mid + Math.imul(ah7, bl7)) | 0; + hi = (hi + Math.imul(ah7, bh7)) | 0; + lo = (lo + Math.imul(al6, bl8)) | 0; + mid = (mid + Math.imul(al6, bh8)) | 0; + mid = (mid + Math.imul(ah6, bl8)) | 0; + hi = (hi + Math.imul(ah6, bh8)) | 0; + lo = (lo + Math.imul(al5, bl9)) | 0; + mid = (mid + Math.imul(al5, bh9)) | 0; + mid = (mid + Math.imul(ah5, bl9)) | 0; + hi = (hi + Math.imul(ah5, bh9)) | 0; + var w14 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w14 >>> 26)) | 0; + w14 &= 0x3ffffff; + /* k = 15 */ + lo = Math.imul(al9, bl6); + mid = Math.imul(al9, bh6); + mid = (mid + Math.imul(ah9, bl6)) | 0; + hi = Math.imul(ah9, bh6); + lo = (lo + Math.imul(al8, bl7)) | 0; + mid = (mid + Math.imul(al8, bh7)) | 0; + mid = (mid + Math.imul(ah8, bl7)) | 0; + hi = (hi + Math.imul(ah8, bh7)) | 0; + lo = (lo + Math.imul(al7, bl8)) | 0; + mid = (mid + Math.imul(al7, bh8)) | 0; + mid = (mid + Math.imul(ah7, bl8)) | 0; + hi = (hi + Math.imul(ah7, bh8)) | 0; + lo = (lo + Math.imul(al6, bl9)) | 0; + mid = (mid + Math.imul(al6, bh9)) | 0; + mid = (mid + Math.imul(ah6, bl9)) | 0; + hi = (hi + Math.imul(ah6, bh9)) | 0; + var w15 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w15 >>> 26)) | 0; + w15 &= 0x3ffffff; + /* k = 16 */ + lo = Math.imul(al9, bl7); + mid = Math.imul(al9, bh7); + mid = (mid + Math.imul(ah9, bl7)) | 0; + hi = Math.imul(ah9, bh7); + lo = (lo + Math.imul(al8, bl8)) | 0; + mid = (mid + Math.imul(al8, bh8)) | 0; + mid = (mid + Math.imul(ah8, bl8)) | 0; + hi = (hi + Math.imul(ah8, bh8)) | 0; + lo = (lo + Math.imul(al7, bl9)) | 0; + mid = (mid + Math.imul(al7, bh9)) | 0; + mid = (mid + Math.imul(ah7, bl9)) | 0; + hi = (hi + Math.imul(ah7, bh9)) | 0; + var w16 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w16 >>> 26)) | 0; + w16 &= 0x3ffffff; + /* k = 17 */ + lo = Math.imul(al9, bl8); + mid = Math.imul(al9, bh8); + mid = (mid + Math.imul(ah9, bl8)) | 0; + hi = Math.imul(ah9, bh8); + lo = (lo + Math.imul(al8, bl9)) | 0; + mid = (mid + Math.imul(al8, bh9)) | 0; + mid = (mid + Math.imul(ah8, bl9)) | 0; + hi = (hi + Math.imul(ah8, bh9)) | 0; + var w17 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w17 >>> 26)) | 0; + w17 &= 0x3ffffff; + /* k = 18 */ + lo = Math.imul(al9, bl9); + mid = Math.imul(al9, bh9); + mid = (mid + Math.imul(ah9, bl9)) | 0; + hi = Math.imul(ah9, bh9); + var w18 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w18 >>> 26)) | 0; + w18 &= 0x3ffffff; + o[0] = w0; + o[1] = w1; + o[2] = w2; + o[3] = w3; + o[4] = w4; + o[5] = w5; + o[6] = w6; + o[7] = w7; + o[8] = w8; + o[9] = w9; + o[10] = w10; + o[11] = w11; + o[12] = w12; + o[13] = w13; + o[14] = w14; + o[15] = w15; + o[16] = w16; + o[17] = w17; + o[18] = w18; + if (c !== 0) { + o[19] = c; + out.length++; + } + return out; + }; + + // Polyfill comb + if (!Math.imul) { + comb10MulTo = smallMulTo; + } + + function bigMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + out.length = self.length + num.length; + + var carry = 0; + var hncarry = 0; + for (var k = 0; k < out.length - 1; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = hncarry; + hncarry = 0; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = k - j; + var a = self.words[i] | 0; + var b = num.words[j] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + ncarry = (ncarry + ((r / 0x4000000) | 0)) | 0; + lo = (lo + rword) | 0; + rword = lo & 0x3ffffff; + ncarry = (ncarry + (lo >>> 26)) | 0; + + hncarry += ncarry >>> 26; + ncarry &= 0x3ffffff; + } + out.words[k] = rword; + carry = ncarry; + ncarry = hncarry; + } + if (carry !== 0) { + out.words[k] = carry; + } else { + out.length--; + } + + return out._strip(); + } + + function jumboMulTo (self, num, out) { + // Temporary disable, see https://github.com/indutny/bn.js/issues/211 + // var fftm = new FFTM(); + // return fftm.mulp(self, num, out); + return bigMulTo(self, num, out); + } + + BN.prototype.mulTo = function mulTo (num, out) { + var res; + var len = this.length + num.length; + if (this.length === 10 && num.length === 10) { + res = comb10MulTo(this, num, out); + } else if (len < 63) { + res = smallMulTo(this, num, out); + } else if (len < 1024) { + res = bigMulTo(this, num, out); + } else { + res = jumboMulTo(this, num, out); + } + + return res; + }; + + // Cooley-Tukey algorithm for FFT + // slightly revisited to rely on looping instead of recursion + + function FFTM (x, y) { + this.x = x; + this.y = y; + } + + FFTM.prototype.makeRBT = function makeRBT (N) { + var t = new Array(N); + var l = BN.prototype._countBits(N) - 1; + for (var i = 0; i < N; i++) { + t[i] = this.revBin(i, l, N); + } + + return t; + }; + + // Returns binary-reversed representation of `x` + FFTM.prototype.revBin = function revBin (x, l, N) { + if (x === 0 || x === N - 1) return x; + + var rb = 0; + for (var i = 0; i < l; i++) { + rb |= (x & 1) << (l - i - 1); + x >>= 1; + } + + return rb; + }; + + // Performs "tweedling" phase, therefore 'emulating' + // behaviour of the recursive algorithm + FFTM.prototype.permute = function permute (rbt, rws, iws, rtws, itws, N) { + for (var i = 0; i < N; i++) { + rtws[i] = rws[rbt[i]]; + itws[i] = iws[rbt[i]]; + } + }; + + FFTM.prototype.transform = function transform (rws, iws, rtws, itws, N, rbt) { + this.permute(rbt, rws, iws, rtws, itws, N); + + for (var s = 1; s < N; s <<= 1) { + var l = s << 1; + + var rtwdf = Math.cos(2 * Math.PI / l); + var itwdf = Math.sin(2 * Math.PI / l); + + for (var p = 0; p < N; p += l) { + var rtwdf_ = rtwdf; + var itwdf_ = itwdf; + + for (var j = 0; j < s; j++) { + var re = rtws[p + j]; + var ie = itws[p + j]; + + var ro = rtws[p + j + s]; + var io = itws[p + j + s]; + + var rx = rtwdf_ * ro - itwdf_ * io; + + io = rtwdf_ * io + itwdf_ * ro; + ro = rx; + + rtws[p + j] = re + ro; + itws[p + j] = ie + io; + + rtws[p + j + s] = re - ro; + itws[p + j + s] = ie - io; + + /* jshint maxdepth : false */ + if (j !== l) { + rx = rtwdf * rtwdf_ - itwdf * itwdf_; + + itwdf_ = rtwdf * itwdf_ + itwdf * rtwdf_; + rtwdf_ = rx; + } + } + } + } + }; + + FFTM.prototype.guessLen13b = function guessLen13b (n, m) { + var N = Math.max(m, n) | 1; + var odd = N & 1; + var i = 0; + for (N = N / 2 | 0; N; N = N >>> 1) { + i++; + } + + return 1 << i + 1 + odd; + }; + + FFTM.prototype.conjugate = function conjugate (rws, iws, N) { + if (N <= 1) return; + + for (var i = 0; i < N / 2; i++) { + var t = rws[i]; + + rws[i] = rws[N - i - 1]; + rws[N - i - 1] = t; + + t = iws[i]; + + iws[i] = -iws[N - i - 1]; + iws[N - i - 1] = -t; + } + }; + + FFTM.prototype.normalize13b = function normalize13b (ws, N) { + var carry = 0; + for (var i = 0; i < N / 2; i++) { + var w = Math.round(ws[2 * i + 1] / N) * 0x2000 + + Math.round(ws[2 * i] / N) + + carry; + + ws[i] = w & 0x3ffffff; + + if (w < 0x4000000) { + carry = 0; + } else { + carry = w / 0x4000000 | 0; + } + } + + return ws; + }; + + FFTM.prototype.convert13b = function convert13b (ws, len, rws, N) { + var carry = 0; + for (var i = 0; i < len; i++) { + carry = carry + (ws[i] | 0); + + rws[2 * i] = carry & 0x1fff; carry = carry >>> 13; + rws[2 * i + 1] = carry & 0x1fff; carry = carry >>> 13; + } + + // Pad with zeroes + for (i = 2 * len; i < N; ++i) { + rws[i] = 0; + } + + assert(carry === 0); + assert((carry & ~0x1fff) === 0); + }; + + FFTM.prototype.stub = function stub (N) { + var ph = new Array(N); + for (var i = 0; i < N; i++) { + ph[i] = 0; + } + + return ph; + }; + + FFTM.prototype.mulp = function mulp (x, y, out) { + var N = 2 * this.guessLen13b(x.length, y.length); + + var rbt = this.makeRBT(N); + + var _ = this.stub(N); + + var rws = new Array(N); + var rwst = new Array(N); + var iwst = new Array(N); + + var nrws = new Array(N); + var nrwst = new Array(N); + var niwst = new Array(N); + + var rmws = out.words; + rmws.length = N; + + this.convert13b(x.words, x.length, rws, N); + this.convert13b(y.words, y.length, nrws, N); + + this.transform(rws, _, rwst, iwst, N, rbt); + this.transform(nrws, _, nrwst, niwst, N, rbt); + + for (var i = 0; i < N; i++) { + var rx = rwst[i] * nrwst[i] - iwst[i] * niwst[i]; + iwst[i] = rwst[i] * niwst[i] + iwst[i] * nrwst[i]; + rwst[i] = rx; + } + + this.conjugate(rwst, iwst, N); + this.transform(rwst, iwst, rmws, _, N, rbt); + this.conjugate(rmws, _, N); + this.normalize13b(rmws, N); + + out.negative = x.negative ^ y.negative; + out.length = x.length + y.length; + return out._strip(); + }; + + // Multiply `this` by `num` + BN.prototype.mul = function mul (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return this.mulTo(num, out); + }; + + // Multiply employing FFT + BN.prototype.mulf = function mulf (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return jumboMulTo(this, num, out); + }; + + // In-place Multiplication + BN.prototype.imul = function imul (num) { + return this.clone().mulTo(num, this); + }; + + BN.prototype.imuln = function imuln (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(typeof num === 'number'); + assert(num < 0x4000000); + + // Carry + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = (this.words[i] | 0) * num; + var lo = (w & 0x3ffffff) + (carry & 0x3ffffff); + carry >>= 26; + carry += (w / 0x4000000) | 0; + // NOTE: lo is 27bit maximum + carry += lo >>> 26; + this.words[i] = lo & 0x3ffffff; + } + + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + + return isNegNum ? this.ineg() : this; + }; + + BN.prototype.muln = function muln (num) { + return this.clone().imuln(num); + }; + + // `this` * `this` + BN.prototype.sqr = function sqr () { + return this.mul(this); + }; + + // `this` * `this` in-place + BN.prototype.isqr = function isqr () { + return this.imul(this.clone()); + }; + + // Math.pow(`this`, `num`) + BN.prototype.pow = function pow (num) { + var w = toBitArray(num); + if (w.length === 0) return new BN(1); + + // Skip leading zeroes + var res = this; + for (var i = 0; i < w.length; i++, res = res.sqr()) { + if (w[i] !== 0) break; + } + + if (++i < w.length) { + for (var q = res.sqr(); i < w.length; i++, q = q.sqr()) { + if (w[i] === 0) continue; + + res = res.mul(q); + } + } + + return res; + }; + + // Shift-left in-place + BN.prototype.iushln = function iushln (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + var carryMask = (0x3ffffff >>> (26 - r)) << (26 - r); + var i; + + if (r !== 0) { + var carry = 0; + + for (i = 0; i < this.length; i++) { + var newCarry = this.words[i] & carryMask; + var c = ((this.words[i] | 0) - newCarry) << r; + this.words[i] = c | carry; + carry = newCarry >>> (26 - r); + } + + if (carry) { + this.words[i] = carry; + this.length++; + } + } + + if (s !== 0) { + for (i = this.length - 1; i >= 0; i--) { + this.words[i + s] = this.words[i]; + } + + for (i = 0; i < s; i++) { + this.words[i] = 0; + } + + this.length += s; + } + + return this._strip(); + }; + + BN.prototype.ishln = function ishln (bits) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushln(bits); + }; + + // Shift-right in-place + // NOTE: `hint` is a lowest bit before trailing zeroes + // NOTE: if `extended` is present - it will be filled with destroyed bits + BN.prototype.iushrn = function iushrn (bits, hint, extended) { + assert(typeof bits === 'number' && bits >= 0); + var h; + if (hint) { + h = (hint - (hint % 26)) / 26; + } else { + h = 0; + } + + var r = bits % 26; + var s = Math.min((bits - r) / 26, this.length); + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + var maskedWords = extended; + + h -= s; + h = Math.max(0, h); + + // Extended mode, copy masked part + if (maskedWords) { + for (var i = 0; i < s; i++) { + maskedWords.words[i] = this.words[i]; + } + maskedWords.length = s; + } + + if (s === 0) { + // No-op, we should not move anything at all + } else if (this.length > s) { + this.length -= s; + for (i = 0; i < this.length; i++) { + this.words[i] = this.words[i + s]; + } + } else { + this.words[0] = 0; + this.length = 1; + } + + var carry = 0; + for (i = this.length - 1; i >= 0 && (carry !== 0 || i >= h); i--) { + var word = this.words[i] | 0; + this.words[i] = (carry << (26 - r)) | (word >>> r); + carry = word & mask; + } + + // Push carried bits as a mask + if (maskedWords && carry !== 0) { + maskedWords.words[maskedWords.length++] = carry; + } + + if (this.length === 0) { + this.words[0] = 0; + this.length = 1; + } + + return this._strip(); + }; + + BN.prototype.ishrn = function ishrn (bits, hint, extended) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushrn(bits, hint, extended); + }; + + // Shift-left + BN.prototype.shln = function shln (bits) { + return this.clone().ishln(bits); + }; + + BN.prototype.ushln = function ushln (bits) { + return this.clone().iushln(bits); + }; + + // Shift-right + BN.prototype.shrn = function shrn (bits) { + return this.clone().ishrn(bits); + }; + + BN.prototype.ushrn = function ushrn (bits) { + return this.clone().iushrn(bits); + }; + + // Test if n bit is set + BN.prototype.testn = function testn (bit) { + assert(typeof bit === 'number' && bit >= 0); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) return false; + + // Check bit and return + var w = this.words[s]; + + return !!(w & q); + }; + + // Return only lowers bits of number (in-place) + BN.prototype.imaskn = function imaskn (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + + assert(this.negative === 0, 'imaskn works only with positive numbers'); + + if (this.length <= s) { + return this; + } + + if (r !== 0) { + s++; + } + this.length = Math.min(s, this.length); + + if (r !== 0) { + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + this.words[this.length - 1] &= mask; + } + + return this._strip(); + }; + + // Return only lowers bits of number + BN.prototype.maskn = function maskn (bits) { + return this.clone().imaskn(bits); + }; + + // Add plain number `num` to `this` + BN.prototype.iaddn = function iaddn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.isubn(-num); + + // Possible sign change + if (this.negative !== 0) { + if (this.length === 1 && (this.words[0] | 0) <= num) { + this.words[0] = num - (this.words[0] | 0); + this.negative = 0; + return this; + } + + this.negative = 0; + this.isubn(num); + this.negative = 1; + return this; + } + + // Add without checks + return this._iaddn(num); + }; + + BN.prototype._iaddn = function _iaddn (num) { + this.words[0] += num; + + // Carry + for (var i = 0; i < this.length && this.words[i] >= 0x4000000; i++) { + this.words[i] -= 0x4000000; + if (i === this.length - 1) { + this.words[i + 1] = 1; + } else { + this.words[i + 1]++; + } + } + this.length = Math.max(this.length, i + 1); + + return this; + }; + + // Subtract plain number `num` from `this` + BN.prototype.isubn = function isubn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.iaddn(-num); + + if (this.negative !== 0) { + this.negative = 0; + this.iaddn(num); + this.negative = 1; + return this; + } + + this.words[0] -= num; + + if (this.length === 1 && this.words[0] < 0) { + this.words[0] = -this.words[0]; + this.negative = 1; + } else { + // Carry + for (var i = 0; i < this.length && this.words[i] < 0; i++) { + this.words[i] += 0x4000000; + this.words[i + 1] -= 1; + } + } + + return this._strip(); + }; + + BN.prototype.addn = function addn (num) { + return this.clone().iaddn(num); + }; + + BN.prototype.subn = function subn (num) { + return this.clone().isubn(num); + }; + + BN.prototype.iabs = function iabs () { + this.negative = 0; + + return this; + }; + + BN.prototype.abs = function abs () { + return this.clone().iabs(); + }; + + BN.prototype._ishlnsubmul = function _ishlnsubmul (num, mul, shift) { + var len = num.length + shift; + var i; + + this._expand(len); + + var w; + var carry = 0; + for (i = 0; i < num.length; i++) { + w = (this.words[i + shift] | 0) + carry; + var right = (num.words[i] | 0) * mul; + w -= right & 0x3ffffff; + carry = (w >> 26) - ((right / 0x4000000) | 0); + this.words[i + shift] = w & 0x3ffffff; + } + for (; i < this.length - shift; i++) { + w = (this.words[i + shift] | 0) + carry; + carry = w >> 26; + this.words[i + shift] = w & 0x3ffffff; + } + + if (carry === 0) return this._strip(); + + // Subtraction overflow + assert(carry === -1); + carry = 0; + for (i = 0; i < this.length; i++) { + w = -(this.words[i] | 0) + carry; + carry = w >> 26; + this.words[i] = w & 0x3ffffff; + } + this.negative = 1; + + return this._strip(); + }; + + BN.prototype._wordDiv = function _wordDiv (num, mode) { + var shift = this.length - num.length; + + var a = this.clone(); + var b = num; + + // Normalize + var bhi = b.words[b.length - 1] | 0; + var bhiBits = this._countBits(bhi); + shift = 26 - bhiBits; + if (shift !== 0) { + b = b.ushln(shift); + a.iushln(shift); + bhi = b.words[b.length - 1] | 0; + } + + // Initialize quotient + var m = a.length - b.length; + var q; + + if (mode !== 'mod') { + q = new BN(null); + q.length = m + 1; + q.words = new Array(q.length); + for (var i = 0; i < q.length; i++) { + q.words[i] = 0; + } + } + + var diff = a.clone()._ishlnsubmul(b, 1, m); + if (diff.negative === 0) { + a = diff; + if (q) { + q.words[m] = 1; + } + } + + for (var j = m - 1; j >= 0; j--) { + var qj = (a.words[b.length + j] | 0) * 0x4000000 + + (a.words[b.length + j - 1] | 0); + + // NOTE: (qj / bhi) is (0x3ffffff * 0x4000000 + 0x3ffffff) / 0x2000000 max + // (0x7ffffff) + qj = Math.min((qj / bhi) | 0, 0x3ffffff); + + a._ishlnsubmul(b, qj, j); + while (a.negative !== 0) { + qj--; + a.negative = 0; + a._ishlnsubmul(b, 1, j); + if (!a.isZero()) { + a.negative ^= 1; + } + } + if (q) { + q.words[j] = qj; + } + } + if (q) { + q._strip(); + } + a._strip(); + + // Denormalize + if (mode !== 'div' && shift !== 0) { + a.iushrn(shift); + } + + return { + div: q || null, + mod: a + }; + }; + + // NOTE: 1) `mode` can be set to `mod` to request mod only, + // to `div` to request div only, or be absent to + // request both div & mod + // 2) `positive` is true if unsigned mod is requested + BN.prototype.divmod = function divmod (num, mode, positive) { + assert(!num.isZero()); + + if (this.isZero()) { + return { + div: new BN(0), + mod: new BN(0) + }; + } + + var div, mod, res; + if (this.negative !== 0 && num.negative === 0) { + res = this.neg().divmod(num, mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.iadd(num); + } + } + + return { + div: div, + mod: mod + }; + } + + if (this.negative === 0 && num.negative !== 0) { + res = this.divmod(num.neg(), mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + return { + div: div, + mod: res.mod + }; + } + + if ((this.negative & num.negative) !== 0) { + res = this.neg().divmod(num.neg(), mode); + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.isub(num); + } + } + + return { + div: res.div, + mod: mod + }; + } + + // Both numbers are positive at this point + + // Strip both numbers to approximate shift value + if (num.length > this.length || this.cmp(num) < 0) { + return { + div: new BN(0), + mod: this + }; + } + + // Very short reduction + if (num.length === 1) { + if (mode === 'div') { + return { + div: this.divn(num.words[0]), + mod: null + }; + } + + if (mode === 'mod') { + return { + div: null, + mod: new BN(this.modrn(num.words[0])) + }; + } + + return { + div: this.divn(num.words[0]), + mod: new BN(this.modrn(num.words[0])) + }; + } + + return this._wordDiv(num, mode); + }; + + // Find `this` / `num` + BN.prototype.div = function div (num) { + return this.divmod(num, 'div', false).div; + }; + + // Find `this` % `num` + BN.prototype.mod = function mod (num) { + return this.divmod(num, 'mod', false).mod; + }; + + BN.prototype.umod = function umod (num) { + return this.divmod(num, 'mod', true).mod; + }; + + // Find Round(`this` / `num`) + BN.prototype.divRound = function divRound (num) { + var dm = this.divmod(num); + + // Fast case - exact division + if (dm.mod.isZero()) return dm.div; + + var mod = dm.div.negative !== 0 ? dm.mod.isub(num) : dm.mod; + + var half = num.ushrn(1); + var r2 = num.andln(1); + var cmp = mod.cmp(half); + + // Round down + if (cmp < 0 || (r2 === 1 && cmp === 0)) return dm.div; + + // Round up + return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1); + }; + + BN.prototype.modrn = function modrn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(num <= 0x3ffffff); + var p = (1 << 26) % num; + + var acc = 0; + for (var i = this.length - 1; i >= 0; i--) { + acc = (p * acc + (this.words[i] | 0)) % num; + } + + return isNegNum ? -acc : acc; + }; + + // WARNING: DEPRECATED + BN.prototype.modn = function modn (num) { + return this.modrn(num); + }; + + // In-place division by number + BN.prototype.idivn = function idivn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(num <= 0x3ffffff); + + var carry = 0; + for (var i = this.length - 1; i >= 0; i--) { + var w = (this.words[i] | 0) + carry * 0x4000000; + this.words[i] = (w / num) | 0; + carry = w % num; + } + + this._strip(); + return isNegNum ? this.ineg() : this; + }; + + BN.prototype.divn = function divn (num) { + return this.clone().idivn(num); + }; + + BN.prototype.egcd = function egcd (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var x = this; + var y = p.clone(); + + if (x.negative !== 0) { + x = x.umod(p); + } else { + x = x.clone(); + } + + // A * x + B * y = x + var A = new BN(1); + var B = new BN(0); + + // C * x + D * y = y + var C = new BN(0); + var D = new BN(1); + + var g = 0; + + while (x.isEven() && y.isEven()) { + x.iushrn(1); + y.iushrn(1); + ++g; + } + + var yp = y.clone(); + var xp = x.clone(); + + while (!x.isZero()) { + for (var i = 0, im = 1; (x.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + x.iushrn(i); + while (i-- > 0) { + if (A.isOdd() || B.isOdd()) { + A.iadd(yp); + B.isub(xp); + } + + A.iushrn(1); + B.iushrn(1); + } + } + + for (var j = 0, jm = 1; (y.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + y.iushrn(j); + while (j-- > 0) { + if (C.isOdd() || D.isOdd()) { + C.iadd(yp); + D.isub(xp); + } + + C.iushrn(1); + D.iushrn(1); + } + } + + if (x.cmp(y) >= 0) { + x.isub(y); + A.isub(C); + B.isub(D); + } else { + y.isub(x); + C.isub(A); + D.isub(B); + } + } + + return { + a: C, + b: D, + gcd: y.iushln(g) + }; + }; + + // This is reduced incarnation of the binary EEA + // above, designated to invert members of the + // _prime_ fields F(p) at a maximal speed + BN.prototype._invmp = function _invmp (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var a = this; + var b = p.clone(); + + if (a.negative !== 0) { + a = a.umod(p); + } else { + a = a.clone(); + } + + var x1 = new BN(1); + var x2 = new BN(0); + + var delta = b.clone(); + + while (a.cmpn(1) > 0 && b.cmpn(1) > 0) { + for (var i = 0, im = 1; (a.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + a.iushrn(i); + while (i-- > 0) { + if (x1.isOdd()) { + x1.iadd(delta); + } + + x1.iushrn(1); + } + } + + for (var j = 0, jm = 1; (b.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + b.iushrn(j); + while (j-- > 0) { + if (x2.isOdd()) { + x2.iadd(delta); + } + + x2.iushrn(1); + } + } + + if (a.cmp(b) >= 0) { + a.isub(b); + x1.isub(x2); + } else { + b.isub(a); + x2.isub(x1); + } + } + + var res; + if (a.cmpn(1) === 0) { + res = x1; + } else { + res = x2; + } + + if (res.cmpn(0) < 0) { + res.iadd(p); + } + + return res; + }; + + BN.prototype.gcd = function gcd (num) { + if (this.isZero()) return num.abs(); + if (num.isZero()) return this.abs(); + + var a = this.clone(); + var b = num.clone(); + a.negative = 0; + b.negative = 0; + + // Remove common factor of two + for (var shift = 0; a.isEven() && b.isEven(); shift++) { + a.iushrn(1); + b.iushrn(1); + } + + do { + while (a.isEven()) { + a.iushrn(1); + } + while (b.isEven()) { + b.iushrn(1); + } + + var r = a.cmp(b); + if (r < 0) { + // Swap `a` and `b` to make `a` always bigger than `b` + var t = a; + a = b; + b = t; + } else if (r === 0 || b.cmpn(1) === 0) { + break; + } + + a.isub(b); + } while (true); + + return b.iushln(shift); + }; + + // Invert number in the field F(num) + BN.prototype.invm = function invm (num) { + return this.egcd(num).a.umod(num); + }; + + BN.prototype.isEven = function isEven () { + return (this.words[0] & 1) === 0; + }; + + BN.prototype.isOdd = function isOdd () { + return (this.words[0] & 1) === 1; + }; + + // And first word and num + BN.prototype.andln = function andln (num) { + return this.words[0] & num; + }; + + // Increment at the bit position in-line + BN.prototype.bincn = function bincn (bit) { + assert(typeof bit === 'number'); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) { + this._expand(s + 1); + this.words[s] |= q; + return this; + } + + // Add bit and propagate, if needed + var carry = q; + for (var i = s; carry !== 0 && i < this.length; i++) { + var w = this.words[i] | 0; + w += carry; + carry = w >>> 26; + w &= 0x3ffffff; + this.words[i] = w; + } + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + return this; + }; + + BN.prototype.isZero = function isZero () { + return this.length === 1 && this.words[0] === 0; + }; + + BN.prototype.cmpn = function cmpn (num) { + var negative = num < 0; + + if (this.negative !== 0 && !negative) return -1; + if (this.negative === 0 && negative) return 1; + + this._strip(); + + var res; + if (this.length > 1) { + res = 1; + } else { + if (negative) { + num = -num; + } + + assert(num <= 0x3ffffff, 'Number is too big'); + + var w = this.words[0] | 0; + res = w === num ? 0 : w < num ? -1 : 1; + } + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Compare two numbers and return: + // 1 - if `this` > `num` + // 0 - if `this` == `num` + // -1 - if `this` < `num` + BN.prototype.cmp = function cmp (num) { + if (this.negative !== 0 && num.negative === 0) return -1; + if (this.negative === 0 && num.negative !== 0) return 1; + + var res = this.ucmp(num); + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Unsigned comparison + BN.prototype.ucmp = function ucmp (num) { + // At this point both numbers have the same sign + if (this.length > num.length) return 1; + if (this.length < num.length) return -1; + + var res = 0; + for (var i = this.length - 1; i >= 0; i--) { + var a = this.words[i] | 0; + var b = num.words[i] | 0; + + if (a === b) continue; + if (a < b) { + res = -1; + } else if (a > b) { + res = 1; + } + break; + } + return res; + }; + + BN.prototype.gtn = function gtn (num) { + return this.cmpn(num) === 1; + }; + + BN.prototype.gt = function gt (num) { + return this.cmp(num) === 1; + }; + + BN.prototype.gten = function gten (num) { + return this.cmpn(num) >= 0; + }; + + BN.prototype.gte = function gte (num) { + return this.cmp(num) >= 0; + }; + + BN.prototype.ltn = function ltn (num) { + return this.cmpn(num) === -1; + }; + + BN.prototype.lt = function lt (num) { + return this.cmp(num) === -1; + }; + + BN.prototype.lten = function lten (num) { + return this.cmpn(num) <= 0; + }; + + BN.prototype.lte = function lte (num) { + return this.cmp(num) <= 0; + }; + + BN.prototype.eqn = function eqn (num) { + return this.cmpn(num) === 0; + }; + + BN.prototype.eq = function eq (num) { + return this.cmp(num) === 0; + }; + + // + // A reduce context, could be using montgomery or something better, depending + // on the `m` itself. + // + BN.red = function red (num) { + return new Red(num); + }; + + BN.prototype.toRed = function toRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + assert(this.negative === 0, 'red works only with positives'); + return ctx.convertTo(this)._forceRed(ctx); + }; + + BN.prototype.fromRed = function fromRed () { + assert(this.red, 'fromRed works only with numbers in reduction context'); + return this.red.convertFrom(this); + }; + + BN.prototype._forceRed = function _forceRed (ctx) { + this.red = ctx; + return this; + }; + + BN.prototype.forceRed = function forceRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + return this._forceRed(ctx); + }; + + BN.prototype.redAdd = function redAdd (num) { + assert(this.red, 'redAdd works only with red numbers'); + return this.red.add(this, num); + }; + + BN.prototype.redIAdd = function redIAdd (num) { + assert(this.red, 'redIAdd works only with red numbers'); + return this.red.iadd(this, num); + }; + + BN.prototype.redSub = function redSub (num) { + assert(this.red, 'redSub works only with red numbers'); + return this.red.sub(this, num); + }; + + BN.prototype.redISub = function redISub (num) { + assert(this.red, 'redISub works only with red numbers'); + return this.red.isub(this, num); + }; + + BN.prototype.redShl = function redShl (num) { + assert(this.red, 'redShl works only with red numbers'); + return this.red.shl(this, num); + }; + + BN.prototype.redMul = function redMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.mul(this, num); + }; + + BN.prototype.redIMul = function redIMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.imul(this, num); + }; + + BN.prototype.redSqr = function redSqr () { + assert(this.red, 'redSqr works only with red numbers'); + this.red._verify1(this); + return this.red.sqr(this); + }; + + BN.prototype.redISqr = function redISqr () { + assert(this.red, 'redISqr works only with red numbers'); + this.red._verify1(this); + return this.red.isqr(this); + }; + + // Square root over p + BN.prototype.redSqrt = function redSqrt () { + assert(this.red, 'redSqrt works only with red numbers'); + this.red._verify1(this); + return this.red.sqrt(this); + }; + + BN.prototype.redInvm = function redInvm () { + assert(this.red, 'redInvm works only with red numbers'); + this.red._verify1(this); + return this.red.invm(this); + }; + + // Return negative clone of `this` % `red modulo` + BN.prototype.redNeg = function redNeg () { + assert(this.red, 'redNeg works only with red numbers'); + this.red._verify1(this); + return this.red.neg(this); + }; + + BN.prototype.redPow = function redPow (num) { + assert(this.red && !num.red, 'redPow(normalNum)'); + this.red._verify1(this); + return this.red.pow(this, num); + }; + + // Prime numbers with efficient reduction + var primes = { + k256: null, + p224: null, + p192: null, + p25519: null + }; + + // Pseudo-Mersenne prime + function MPrime (name, p) { + // P = 2 ^ N - K + this.name = name; + this.p = new BN(p, 16); + this.n = this.p.bitLength(); + this.k = new BN(1).iushln(this.n).isub(this.p); + + this.tmp = this._tmp(); + } + + MPrime.prototype._tmp = function _tmp () { + var tmp = new BN(null); + tmp.words = new Array(Math.ceil(this.n / 13)); + return tmp; + }; + + MPrime.prototype.ireduce = function ireduce (num) { + // Assumes that `num` is less than `P^2` + // num = HI * (2 ^ N - K) + HI * K + LO = HI * K + LO (mod P) + var r = num; + var rlen; + + do { + this.split(r, this.tmp); + r = this.imulK(r); + r = r.iadd(this.tmp); + rlen = r.bitLength(); + } while (rlen > this.n); + + var cmp = rlen < this.n ? -1 : r.ucmp(this.p); + if (cmp === 0) { + r.words[0] = 0; + r.length = 1; + } else if (cmp > 0) { + r.isub(this.p); + } else { + r._strip(); + } + + return r; + }; + + MPrime.prototype.split = function split (input, out) { + input.iushrn(this.n, 0, out); + }; + + MPrime.prototype.imulK = function imulK (num) { + return num.imul(this.k); + }; + + function K256 () { + MPrime.call( + this, + 'k256', + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f'); + } + inherits(K256, MPrime); + + K256.prototype.split = function split (input, output) { + // 256 = 9 * 26 + 22 + var mask = 0x3fffff; + + var outLen = Math.min(input.length, 9); + for (var i = 0; i < outLen; i++) { + output.words[i] = input.words[i]; + } + output.length = outLen; + + if (input.length <= 9) { + input.words[0] = 0; + input.length = 1; + return; + } + + // Shift by 9 limbs + var prev = input.words[9]; + output.words[output.length++] = prev & mask; + + for (i = 10; i < input.length; i++) { + var next = input.words[i] | 0; + input.words[i - 10] = ((next & mask) << 4) | (prev >>> 22); + prev = next; + } + prev >>>= 22; + input.words[i - 10] = prev; + if (prev === 0 && input.length > 10) { + input.length -= 10; + } else { + input.length -= 9; + } + }; + + K256.prototype.imulK = function imulK (num) { + // K = 0x1000003d1 = [ 0x40, 0x3d1 ] + num.words[num.length] = 0; + num.words[num.length + 1] = 0; + num.length += 2; + + // bounded at: 0x40 * 0x3ffffff + 0x3d0 = 0x100000390 + var lo = 0; + for (var i = 0; i < num.length; i++) { + var w = num.words[i] | 0; + lo += w * 0x3d1; + num.words[i] = lo & 0x3ffffff; + lo = w * 0x40 + ((lo / 0x4000000) | 0); + } + + // Fast length reduction + if (num.words[num.length - 1] === 0) { + num.length--; + if (num.words[num.length - 1] === 0) { + num.length--; + } + } + return num; + }; + + function P224 () { + MPrime.call( + this, + 'p224', + 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001'); + } + inherits(P224, MPrime); + + function P192 () { + MPrime.call( + this, + 'p192', + 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff'); + } + inherits(P192, MPrime); + + function P25519 () { + // 2 ^ 255 - 19 + MPrime.call( + this, + '25519', + '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed'); + } + inherits(P25519, MPrime); + + P25519.prototype.imulK = function imulK (num) { + // K = 0x13 + var carry = 0; + for (var i = 0; i < num.length; i++) { + var hi = (num.words[i] | 0) * 0x13 + carry; + var lo = hi & 0x3ffffff; + hi >>>= 26; + + num.words[i] = lo; + carry = hi; + } + if (carry !== 0) { + num.words[num.length++] = carry; + } + return num; + }; + + // Exported mostly for testing purposes, use plain name instead + BN._prime = function prime (name) { + // Cached version of prime + if (primes[name]) return primes[name]; + + var prime; + if (name === 'k256') { + prime = new K256(); + } else if (name === 'p224') { + prime = new P224(); + } else if (name === 'p192') { + prime = new P192(); + } else if (name === 'p25519') { + prime = new P25519(); + } else { + throw new Error('Unknown prime ' + name); + } + primes[name] = prime; + + return prime; + }; + + // + // Base reduction engine + // + function Red (m) { + if (typeof m === 'string') { + var prime = BN._prime(m); + this.m = prime.p; + this.prime = prime; + } else { + assert(m.gtn(1), 'modulus must be greater than 1'); + this.m = m; + this.prime = null; + } + } + + Red.prototype._verify1 = function _verify1 (a) { + assert(a.negative === 0, 'red works only with positives'); + assert(a.red, 'red works only with red numbers'); + }; + + Red.prototype._verify2 = function _verify2 (a, b) { + assert((a.negative | b.negative) === 0, 'red works only with positives'); + assert(a.red && a.red === b.red, + 'red works only with red numbers'); + }; + + Red.prototype.imod = function imod (a) { + if (this.prime) return this.prime.ireduce(a)._forceRed(this); + + a.umod(this.m)._forceRed(this)._move(a); + return a; + }; + + Red.prototype.neg = function neg (a) { + if (a.isZero()) { + return a.clone(); + } + + return this.m.sub(a)._forceRed(this); + }; + + Red.prototype.add = function add (a, b) { + this._verify2(a, b); + + var res = a.add(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.iadd = function iadd (a, b) { + this._verify2(a, b); + + var res = a.iadd(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res; + }; + + Red.prototype.sub = function sub (a, b) { + this._verify2(a, b); + + var res = a.sub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.isub = function isub (a, b) { + this._verify2(a, b); + + var res = a.isub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res; + }; + + Red.prototype.shl = function shl (a, num) { + this._verify1(a); + return this.imod(a.ushln(num)); + }; + + Red.prototype.imul = function imul (a, b) { + this._verify2(a, b); + return this.imod(a.imul(b)); + }; + + Red.prototype.mul = function mul (a, b) { + this._verify2(a, b); + return this.imod(a.mul(b)); + }; + + Red.prototype.isqr = function isqr (a) { + return this.imul(a, a.clone()); + }; + + Red.prototype.sqr = function sqr (a) { + return this.mul(a, a); + }; + + Red.prototype.sqrt = function sqrt (a) { + if (a.isZero()) return a.clone(); + + var mod3 = this.m.andln(3); + assert(mod3 % 2 === 1); + + // Fast case + if (mod3 === 3) { + var pow = this.m.add(new BN(1)).iushrn(2); + return this.pow(a, pow); + } + + // Tonelli-Shanks algorithm (Totally unoptimized and slow) + // + // Find Q and S, that Q * 2 ^ S = (P - 1) + var q = this.m.subn(1); + var s = 0; + while (!q.isZero() && q.andln(1) === 0) { + s++; + q.iushrn(1); + } + assert(!q.isZero()); + + var one = new BN(1).toRed(this); + var nOne = one.redNeg(); + + // Find quadratic non-residue + // NOTE: Max is such because of generalized Riemann hypothesis. + var lpow = this.m.subn(1).iushrn(1); + var z = this.m.bitLength(); + z = new BN(2 * z * z).toRed(this); + + while (this.pow(z, lpow).cmp(nOne) !== 0) { + z.redIAdd(nOne); + } + + var c = this.pow(z, q); + var r = this.pow(a, q.addn(1).iushrn(1)); + var t = this.pow(a, q); + var m = s; + while (t.cmp(one) !== 0) { + var tmp = t; + for (var i = 0; tmp.cmp(one) !== 0; i++) { + tmp = tmp.redSqr(); + } + assert(i < m); + var b = this.pow(c, new BN(1).iushln(m - i - 1)); + + r = r.redMul(b); + c = b.redSqr(); + t = t.redMul(c); + m = i; + } + + return r; + }; + + Red.prototype.invm = function invm (a) { + var inv = a._invmp(this.m); + if (inv.negative !== 0) { + inv.negative = 0; + return this.imod(inv).redNeg(); + } else { + return this.imod(inv); + } + }; + + Red.prototype.pow = function pow (a, num) { + if (num.isZero()) return new BN(1).toRed(this); + if (num.cmpn(1) === 0) return a.clone(); + + var windowSize = 4; + var wnd = new Array(1 << windowSize); + wnd[0] = new BN(1).toRed(this); + wnd[1] = a; + for (var i = 2; i < wnd.length; i++) { + wnd[i] = this.mul(wnd[i - 1], a); + } + + var res = wnd[0]; + var current = 0; + var currentLen = 0; + var start = num.bitLength() % 26; + if (start === 0) { + start = 26; + } + + for (i = num.length - 1; i >= 0; i--) { + var word = num.words[i]; + for (var j = start - 1; j >= 0; j--) { + var bit = (word >> j) & 1; + if (res !== wnd[0]) { + res = this.sqr(res); + } + + if (bit === 0 && current === 0) { + currentLen = 0; + continue; + } + + current <<= 1; + current |= bit; + currentLen++; + if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue; + + res = this.mul(res, wnd[current]); + currentLen = 0; + current = 0; + } + start = 26; + } + + return res; + }; + + Red.prototype.convertTo = function convertTo (num) { + var r = num.umod(this.m); + + return r === num ? r.clone() : r; + }; + + Red.prototype.convertFrom = function convertFrom (num) { + var res = num.clone(); + res.red = null; + return res; + }; + + // + // Montgomery method engine + // + + BN.mont = function mont (num) { + return new Mont(num); + }; + + function Mont (m) { + Red.call(this, m); + + this.shift = this.m.bitLength(); + if (this.shift % 26 !== 0) { + this.shift += 26 - (this.shift % 26); + } + + this.r = new BN(1).iushln(this.shift); + this.r2 = this.imod(this.r.sqr()); + this.rinv = this.r._invmp(this.m); + + this.minv = this.rinv.mul(this.r).isubn(1).div(this.m); + this.minv = this.minv.umod(this.r); + this.minv = this.r.sub(this.minv); + } + inherits(Mont, Red); + + Mont.prototype.convertTo = function convertTo (num) { + return this.imod(num.ushln(this.shift)); + }; + + Mont.prototype.convertFrom = function convertFrom (num) { + var r = this.imod(num.mul(this.rinv)); + r.red = null; + return r; + }; + + Mont.prototype.imul = function imul (a, b) { + if (a.isZero() || b.isZero()) { + a.words[0] = 0; + a.length = 1; + return a; + } + + var t = a.imul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.mul = function mul (a, b) { + if (a.isZero() || b.isZero()) return new BN(0)._forceRed(this); + + var t = a.mul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.invm = function invm (a) { + // (AR)^-1 * R^2 = (A^-1 * R^-1) * R^2 = A^-1 * R + var res = this.imod(a._invmp(this.m).mul(this.r2)); + return res._forceRed(this); + }; +})(typeof module === 'undefined' || module, this); + +},{"buffer":185}],124:[function(require,module,exports){ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(); + } + else if (typeof define === "function" && define.amd) { + // AMD + define([], factory); + } + else { + // Global (browser) + root.CryptoJS = factory(); + } +}(this, function () { + + /** + * CryptoJS core components. + */ + var CryptoJS = CryptoJS || (function (Math, undefined) { + /* + * Local polyfil of Object.create + */ + var create = Object.create || (function () { + function F() {}; + + return function (obj) { + var subtype; + + F.prototype = obj; + + subtype = new F(); + + F.prototype = null; + + return subtype; + }; + }()) + + /** + * CryptoJS namespace. + */ + var C = {}; + + /** + * Library namespace. + */ + var C_lib = C.lib = {}; + + /** + * Base object for prototypal inheritance. + */ + var Base = C_lib.Base = (function () { + + + return { + /** + * Creates a new object that inherits from this object. + * + * @param {Object} overrides Properties to copy into the new object. + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * field: 'value', + * + * method: function () { + * } + * }); + */ + extend: function (overrides) { + // Spawn + var subtype = create(this); + + // Augment + if (overrides) { + subtype.mixIn(overrides); + } + + // Create default initializer + if (!subtype.hasOwnProperty('init') || this.init === subtype.init) { + subtype.init = function () { + subtype.$super.init.apply(this, arguments); + }; + } + + // Initializer's prototype is the subtype object + subtype.init.prototype = subtype; + + // Reference supertype + subtype.$super = this; + + return subtype; + }, + + /** + * Extends this object and runs the init method. + * Arguments to create() will be passed to init(). + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var instance = MyType.create(); + */ + create: function () { + var instance = this.extend(); + instance.init.apply(instance, arguments); + + return instance; + }, + + /** + * Initializes a newly created object. + * Override this method to add some logic when your objects are created. + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * init: function () { + * // ... + * } + * }); + */ + init: function () { + }, + + /** + * Copies properties into this object. + * + * @param {Object} properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn: function (properties) { + for (var propertyName in properties) { + if (properties.hasOwnProperty(propertyName)) { + this[propertyName] = properties[propertyName]; + } + } + + // IE won't copy toString using the loop above + if (properties.hasOwnProperty('toString')) { + this.toString = properties.toString; + } + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = instance.clone(); + */ + clone: function () { + return this.init.prototype.extend(this); + } + }; + }()); + + /** + * An array of 32-bit words. + * + * @property {Array} words The array of 32-bit words. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var WordArray = C_lib.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of 32-bit words. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.create(); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 4; + } + }, + + /** + * Converts this word array to a string. + * + * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return {string} The stringified word array. + * + * @example + * + * var string = wordArray + ''; + * var string = wordArray.toString(); + * var string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString: function (encoder) { + return (encoder || Hex).stringify(this); + }, + + /** + * Concatenates a word array to this word array. + * + * @param {WordArray} wordArray The word array to append. + * + * @return {WordArray} This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat: function (wordArray) { + // Shortcuts + var thisWords = this.words; + var thatWords = wordArray.words; + var thisSigBytes = this.sigBytes; + var thatSigBytes = wordArray.sigBytes; + + // Clamp excess bits + this.clamp(); + + // Concat + if (thisSigBytes % 4) { + // Copy one byte at a time + for (var i = 0; i < thatSigBytes; i++) { + var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var i = 0; i < thatSigBytes; i += 4) { + thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + var r = (function (m_w) { + var m_w = m_w; + var m_z = 0x3ade68b1; + var mask = 0xffffffff; + + return function () { + m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; + m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; + var result = ((m_z << 0x10) + m_w) & mask; + result /= 0x100000000; + result += 0.5; + return result * (Math.random() > .5 ? 1 : -1); + } + }); + + for (var i = 0, rcache; i < nBytes; i += 4) { + var _r = r((rcache || Math.random()) * 0x100000000); + + rcache = _r() * 0x3ade67b7; + words.push((_r() * 0x100000000) | 0); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + var processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Reusable object + var W = []; + + /** + * SHA-1 hash algorithm. + */ + var SHA1 = C_algo.SHA1 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476, + 0xc3d2e1f0 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + + // Computation + for (var i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + var t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + C.SHA1 = Hasher._createHelper(SHA1); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + C.HmacSHA1 = Hasher._createHmacHelper(SHA1); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Initialization and round constants tables + var H = []; + var K = []; + + // Compute constants + (function () { + function isPrime(n) { + var sqrtN = Math.sqrt(n); + for (var factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } + + return true; + } + + function getFractionalBits(n) { + return ((n - (n | 0)) * 0x100000000) | 0; + } + + var n = 2; + var nPrime = 0; + while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; + } + }()); + + // Reusable object + var W = []; + + /** + * SHA-256 hash algorithm. + */ + var SHA256 = C_algo.SHA256 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init(H.slice(0)); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + var f = H[5]; + var g = H[6]; + var h = H[7]; + + // Computation + for (var i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var gamma0x = W[i - 15]; + var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3); + + var gamma1x = W[i - 2]; + var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + var ch = (e & f) ^ (~e & g); + var maj = (a & b) ^ (a & c) ^ (b & c); + + var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + var t1 = h + sigma1 + ch + K[i] + W[i]; + var t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + C.SHA256 = Hasher._createHelper(SHA256); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + C.HmacSHA256 = Hasher._createHmacHelper(SHA256); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * UTF-16 BE encoding strategy. + */ + var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + /** + * UTF-16 LE encoding strategy. + */ + C_enc.Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + function swapEndian(word) { + return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + } + }()); + + + (function () { + // Check if typed arrays are supported + if (typeof ArrayBuffer != 'function') { + return; + } + + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + + // Reference original init + var superInit = WordArray.init; + + // Augment WordArray.init to handle typed arrays + var subInit = WordArray.init = function (typedArray) { + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array || + (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || + typedArray instanceof Int16Array || + typedArray instanceof Uint16Array || + typedArray instanceof Int32Array || + typedArray instanceof Uint32Array || + typedArray instanceof Float32Array || + typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + var typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + var words = []; + for (var i = 0; i < typedArrayByteLength; i++) { + words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + superInit.call(this, words, typedArrayByteLength); + } else { + // Else call normal init + superInit.apply(this, arguments); + } + }; + + subInit.prototype = WordArray; + }()); + + + /** @preserve + (c) 2012 by Cédric Mesnil. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var _zl = WordArray.create([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); + var _zr = WordArray.create([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); + var _sl = WordArray.create([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); + var _sr = WordArray.create([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); + + var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); + var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + + /** + * RIPEMD160 hash algorithm. + */ + var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ + _doReset: function () { + this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + }, + + _doProcessBlock: function (M, offset) { + + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + // Swap + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + var H = this._hash.words; + var hl = _hl.words; + var hr = _hr.words; + var zl = _zl.words; + var zr = _zr.words; + var sl = _sl.words; + var sr = _sr.words; + + // Working variables + var al, bl, cl, dl, el; + var ar, br, cr, dr, er; + + ar = al = H[0]; + br = bl = H[1]; + cr = cl = H[2]; + dr = dl = H[3]; + er = el = H[4]; + // Computation + var t; + for (var i = 0; i < 80; i += 1) { + t = (al + M[offset+zl[i]])|0; + if (i<16){ + t += f1(bl,cl,dl) + hl[0]; + } else if (i<32) { + t += f2(bl,cl,dl) + hl[1]; + } else if (i<48) { + t += f3(bl,cl,dl) + hl[2]; + } else if (i<64) { + t += f4(bl,cl,dl) + hl[3]; + } else {// if (i<80) { + t += f5(bl,cl,dl) + hl[4]; + } + t = t|0; + t = rotl(t,sl[i]); + t = (t+el)|0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + M[offset+zr[i]])|0; + if (i<16){ + t += f5(br,cr,dr) + hr[0]; + } else if (i<32) { + t += f4(br,cr,dr) + hr[1]; + } else if (i<48) { + t += f3(br,cr,dr) + hr[2]; + } else if (i<64) { + t += f2(br,cr,dr) + hr[3]; + } else {// if (i<80) { + t += f1(br,cr,dr) + hr[4]; + } + t = t|0; + t = rotl(t,sr[i]) ; + t = (t+er)|0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr)|0; + H[1] = (H[2] + dl + er)|0; + H[2] = (H[3] + el + ar)|0; + H[3] = (H[4] + al + br)|0; + H[4] = (H[0] + bl + cr)|0; + H[0] = t; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | + (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 5; i++) { + // Shortcut + var H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + + function f1(x, y, z) { + return ((x) ^ (y) ^ (z)); + + } + + function f2(x, y, z) { + return (((x)&(y)) | ((~x)&(z))); + } + + function f3(x, y, z) { + return (((x) | (~(y))) ^ (z)); + } + + function f4(x, y, z) { + return (((x) & (z)) | ((y)&(~(z)))); + } + + function f5(x, y, z) { + return ((x) ^ ((y) |(~(z)))); + + } + + function rotl(x,n) { + return (x<>>(32-n)); + } + + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + C.RIPEMD160 = Hasher._createHelper(RIPEMD160); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA1 = C_algo.SHA1; + var HMAC = C_algo.HMAC; + + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + var PBKDF2 = C_algo.PBKDF2 = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: SHA1, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init HMAC + var hmac = HMAC.create(cfg.hasher, password); + + // Initial values + var derivedKey = WordArray.create(); + var blockIndex = WordArray.create([0x00000001]); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var blockIndexWords = blockIndex.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + var block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + var blockWords = block.words; + var blockWordsLength = blockWords.length; + + // Iterations + var intermediate = block; + for (var i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + var intermediateWords = intermediate.words; + + // XOR intermediate with block + for (var j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } + + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.PBKDF2 = function (password, salt, cfg) { + return PBKDF2.create(cfg).compute(password, salt); + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + var block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA256 = C_algo.SHA256; + + /** + * SHA-224 hash algorithm. + */ + var SHA224 = C_algo.SHA224 = SHA256.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + ]); + }, + + _doFinalize: function () { + var hash = SHA256._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + C.SHA224 = SHA256._createHelper(SHA224); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + C.HmacSHA224 = SHA256._createHmacHelper(SHA224); + }()); + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var X32WordArray = C_lib.WordArray; + + /** + * x64 namespace. + */ + var C_x64 = C.x64 = {}; + + /** + * A 64-bit word. + */ + var X64Word = C_x64.Word = Base.extend({ + /** + * Initializes a newly created 64-bit word. + * + * @param {number} high The high 32 bits. + * @param {number} low The low 32 bits. + * + * @example + * + * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); + */ + init: function (high, low) { + this.high = high; + this.low = low; + } + + /** + * Bitwise NOTs this word. + * + * @return {X64Word} A new x64-Word object after negating. + * + * @example + * + * var negated = x64Word.not(); + */ + // not: function () { + // var high = ~this.high; + // var low = ~this.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ANDs this word with the passed word. + * + * @param {X64Word} word The x64-Word to AND with this word. + * + * @return {X64Word} A new x64-Word object after ANDing. + * + * @example + * + * var anded = x64Word.and(anotherX64Word); + */ + // and: function (word) { + // var high = this.high & word.high; + // var low = this.low & word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to OR with this word. + * + * @return {X64Word} A new x64-Word object after ORing. + * + * @example + * + * var ored = x64Word.or(anotherX64Word); + */ + // or: function (word) { + // var high = this.high | word.high; + // var low = this.low | word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise XORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to XOR with this word. + * + * @return {X64Word} A new x64-Word object after XORing. + * + * @example + * + * var xored = x64Word.xor(anotherX64Word); + */ + // xor: function (word) { + // var high = this.high ^ word.high; + // var low = this.low ^ word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the left. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftL(25); + */ + // shiftL: function (n) { + // if (n < 32) { + // var high = (this.high << n) | (this.low >>> (32 - n)); + // var low = this.low << n; + // } else { + // var high = this.low << (n - 32); + // var low = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the right. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftR(7); + */ + // shiftR: function (n) { + // if (n < 32) { + // var low = (this.low >>> n) | (this.high << (32 - n)); + // var high = this.high >>> n; + // } else { + // var low = this.high >>> (n - 32); + // var high = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Rotates this word n bits to the left. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotL(25); + */ + // rotL: function (n) { + // return this.shiftL(n).or(this.shiftR(64 - n)); + // }, + + /** + * Rotates this word n bits to the right. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotR(7); + */ + // rotR: function (n) { + // return this.shiftR(n).or(this.shiftL(64 - n)); + // }, + + /** + * Adds this word with the passed word. + * + * @param {X64Word} word The x64-Word to add with this word. + * + * @return {X64Word} A new x64-Word object after adding. + * + * @example + * + * var added = x64Word.add(anotherX64Word); + */ + // add: function (word) { + // var low = (this.low + word.low) | 0; + // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; + // var high = (this.high + word.high + carry) | 0; + + // return X64Word.create(high, low); + // } + }); + + /** + * An array of 64-bit words. + * + * @property {Array} words The array of CryptoJS.x64.Word objects. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var X64WordArray = C_x64.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.x64.WordArray.create(); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ]); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ], 10); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 8; + } + }, + + /** + * Converts this 64-bit word array to a 32-bit word array. + * + * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. + * + * @example + * + * var x32WordArray = x64WordArray.toX32(); + */ + toX32: function () { + // Shortcuts + var x64Words = this.words; + var x64WordsLength = x64Words.length; + + // Convert + var x32Words = []; + for (var i = 0; i < x64WordsLength; i++) { + var x64Word = x64Words[i]; + x32Words.push(x64Word.high); + x32Words.push(x64Word.low); + } + + return X32WordArray.create(x32Words, this.sigBytes); + }, + + /** + * Creates a copy of this word array. + * + * @return {X64WordArray} The clone. + * + * @example + * + * var clone = x64WordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + + // Clone "words" array + var words = clone.words = this.words.slice(0); + + // Clone each X64Word object + var wordsLength = words.length; + for (var i = 0; i < wordsLength; i++) { + words[i] = words[i].clone(); + } + + return clone; + } + }); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var C_algo = C.algo; + + // Constants tables + var RHO_OFFSETS = []; + var PI_INDEXES = []; + var ROUND_CONSTANTS = []; + + // Compute Constants + (function () { + // Compute rho offset constants + var x = 1, y = 0; + for (var t = 0; t < 24; t++) { + RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64; + + var newX = y % 5; + var newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + // Compute pi index constants + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; + } + } + + // Compute round constants + var LFSR = 0x01; + for (var i = 0; i < 24; i++) { + var roundConstantMsw = 0; + var roundConstantLsw = 0; + + for (var j = 0; j < 7; j++) { + if (LFSR & 0x01) { + var bitPosition = (1 << j) - 1; + if (bitPosition < 32) { + roundConstantLsw ^= 1 << bitPosition; + } else /* if (bitPosition >= 32) */ { + roundConstantMsw ^= 1 << (bitPosition - 32); + } + } + + // Compute next LFSR + if (LFSR & 0x80) { + // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 + LFSR = (LFSR << 1) ^ 0x71; + } else { + LFSR <<= 1; + } + } + + ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw); + } + }()); + + // Reusable objects for temporary values + var T = []; + (function () { + for (var i = 0; i < 25; i++) { + T[i] = X64Word.create(); + } + }()); + + /** + * SHA-3 hash algorithm. + */ + var SHA3 = C_algo.SHA3 = Hasher.extend({ + /** + * Configuration options. + * + * @property {number} outputLength + * The desired number of bits in the output hash. + * Only values permitted are: 224, 256, 384, 512. + * Default: 512 + */ + cfg: Hasher.cfg.extend({ + outputLength: 512 + }), + + _doReset: function () { + var state = this._state = [] + for (var i = 0; i < 25; i++) { + state[i] = new X64Word.init(); + } + + this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var state = this._state; + var nBlockSizeLanes = this.blockSize / 2; + + // Absorb + for (var i = 0; i < nBlockSizeLanes; i++) { + // Shortcuts + var M2i = M[offset + 2 * i]; + var M2i1 = M[offset + 2 * i + 1]; + + // Swap endian + M2i = ( + (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | + (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00) + ); + M2i1 = ( + (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | + (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00) + ); + + // Absorb message into state + var lane = state[i]; + lane.high ^= M2i1; + lane.low ^= M2i; + } + + // Rounds + for (var round = 0; round < 24; round++) { + // Theta + for (var x = 0; x < 5; x++) { + // Mix column lanes + var tMsw = 0, tLsw = 0; + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + tMsw ^= lane.high; + tLsw ^= lane.low; + } + + // Temporary values + var Tx = T[x]; + Tx.high = tMsw; + Tx.low = tLsw; + } + for (var x = 0; x < 5; x++) { + // Shortcuts + var Tx4 = T[(x + 4) % 5]; + var Tx1 = T[(x + 1) % 5]; + var Tx1Msw = Tx1.high; + var Tx1Lsw = Tx1.low; + + // Mix surrounding columns + var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); + var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + lane.high ^= tMsw; + lane.low ^= tLsw; + } + } + + // Rho Pi + for (var laneIndex = 1; laneIndex < 25; laneIndex++) { + // Shortcuts + var lane = state[laneIndex]; + var laneMsw = lane.high; + var laneLsw = lane.low; + var rhoOffset = RHO_OFFSETS[laneIndex]; + + // Rotate lanes + if (rhoOffset < 32) { + var tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); + var tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); + } else /* if (rhoOffset >= 32) */ { + var tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); + var tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); + } + + // Transpose lanes + var TPiLane = T[PI_INDEXES[laneIndex]]; + TPiLane.high = tMsw; + TPiLane.low = tLsw; + } + + // Rho pi at x = y = 0 + var T0 = T[0]; + var state0 = state[0]; + T0.high = state0.high; + T0.low = state0.low; + + // Chi + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + // Shortcuts + var laneIndex = x + 5 * y; + var lane = state[laneIndex]; + var TLane = T[laneIndex]; + var Tx1Lane = T[((x + 1) % 5) + 5 * y]; + var Tx2Lane = T[((x + 2) % 5) + 5 * y]; + + // Mix rows + lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); + lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); + } + } + + // Iota + var lane = state[0]; + var roundConstant = ROUND_CONSTANTS[round]; + lane.high ^= roundConstant.high; + lane.low ^= roundConstant.low;; + } + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + var blockSizeBits = this.blockSize * 32; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); + dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var state = this._state; + var outputLengthBytes = this.cfg.outputLength / 8; + var outputLengthLanes = outputLengthBytes / 8; + + // Squeeze + var hashWords = []; + for (var i = 0; i < outputLengthLanes; i++) { + // Shortcuts + var lane = state[i]; + var laneMsw = lane.high; + var laneLsw = lane.low; + + // Swap endian + laneMsw = ( + (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | + (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00) + ); + laneLsw = ( + (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | + (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00) + ); + + // Squeeze state to retrieve hash + hashWords.push(laneLsw); + hashWords.push(laneMsw); + } + + // Return final computed hash + return new WordArray.init(hashWords, outputLengthBytes); + }, + + clone: function () { + var clone = Hasher.clone.call(this); + + var state = clone._state = this._state.slice(0); + for (var i = 0; i < 25; i++) { + state[i] = state[i].clone(); + } + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ + C.SHA3 = Hasher._createHelper(SHA3); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ + C.HmacSHA3 = Hasher._createHmacHelper(SHA3); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + + function X64Word_create() { + return X64Word.create.apply(X64Word, arguments); + } + + // Constants + var K = [ + X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), + X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), + X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), + X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), + X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), + X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), + X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), + X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), + X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), + X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), + X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), + X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), + X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), + X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), + X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), + X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), + X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), + X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), + X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), + X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), + X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), + X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), + X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), + X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), + X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), + X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), + X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), + X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), + X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), + X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), + X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), + X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), + X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), + X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), + X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), + X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), + X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), + X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), + X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), + X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) + ]; + + // Reusable objects + var W = []; + (function () { + for (var i = 0; i < 80; i++) { + W[i] = X64Word_create(); + } + }()); + + /** + * SHA-512 hash algorithm. + */ + var SHA512 = C_algo.SHA512 = Hasher.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), + new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), + new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), + new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var H = this._hash.words; + + var H0 = H[0]; + var H1 = H[1]; + var H2 = H[2]; + var H3 = H[3]; + var H4 = H[4]; + var H5 = H[5]; + var H6 = H[6]; + var H7 = H[7]; + + var H0h = H0.high; + var H0l = H0.low; + var H1h = H1.high; + var H1l = H1.low; + var H2h = H2.high; + var H2l = H2.low; + var H3h = H3.high; + var H3l = H3.low; + var H4h = H4.high; + var H4l = H4.low; + var H5h = H5.high; + var H5l = H5.low; + var H6h = H6.high; + var H6l = H6.low; + var H7h = H7.high; + var H7l = H7.low; + + // Working variables + var ah = H0h; + var al = H0l; + var bh = H1h; + var bl = H1l; + var ch = H2h; + var cl = H2l; + var dh = H3h; + var dl = H3l; + var eh = H4h; + var el = H4l; + var fh = H5h; + var fl = H5l; + var gh = H6h; + var gl = H6l; + var hh = H7h; + var hl = H7l; + + // Rounds + for (var i = 0; i < 80; i++) { + // Shortcut + var Wi = W[i]; + + // Extend message + if (i < 16) { + var Wih = Wi.high = M[offset + i * 2] | 0; + var Wil = Wi.low = M[offset + i * 2 + 1] | 0; + } else { + // Gamma0 + var gamma0x = W[i - 15]; + var gamma0xh = gamma0x.high; + var gamma0xl = gamma0x.low; + var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); + var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + var gamma1x = W[i - 2]; + var gamma1xh = gamma1x.high; + var gamma1xl = gamma1x.low; + var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); + var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[i - 7]; + var Wi7h = Wi7.high; + var Wi7l = Wi7.low; + + var Wi16 = W[i - 16]; + var Wi16h = Wi16.high; + var Wi16l = Wi16.low; + + var Wil = gamma0l + Wi7l; + var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + var Wil = Wil + gamma1l; + var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + var Wil = Wil + Wi16l; + var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[i]; + var Kih = Ki.high; + var Kil = Ki.low; + + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + var t1l = t1l + chl; + var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + var t1l = t1l + Kil; + var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + var t1l = t1l + Wil; + var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash value + H0l = H0.low = (H0l + al); + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1l = H1.low = (H1l + bl); + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2l = H2.low = (H2l + cl); + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3l = H3.low = (H3l + dl); + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4l = H4.low = (H4l + el); + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5l = H5.low = (H5l + fl); + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6l = H6.low = (H6l + gl); + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7l = H7.low = (H7l + hl); + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + var hash = this._hash.toX32(); + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + }, + + blockSize: 1024/32 + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + C.SHA512 = Hasher._createHelper(SHA512); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + C.HmacSHA512 = Hasher._createHmacHelper(SHA512); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + var SHA512 = C_algo.SHA512; + + /** + * SHA-384 hash algorithm. + */ + var SHA384 = C_algo.SHA384 = SHA512.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), + new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), + new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), + new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) + ]); + }, + + _doFinalize: function () { + var hash = SHA512._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + C.SHA384 = SHA512._createHelper(SHA384); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + C.HmacSHA384 = SHA512._createHmacHelper(SHA384); + }()); + + + /** + * Cipher core components. + */ + CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + var block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + var modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + var modeCreator = mode.createDecryptor; + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + + if (this._mode && this._mode.__creator == modeCreator) { + this._mode.init(this, iv && iv.words); + } else { + this._mode = modeCreator.call(mode, this, iv && iv.words); + this._mode.__creator = modeCreator; + } + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + var wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + var salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); + }()); + + + /** + * Cipher Feedback block mode. + */ + CryptoJS.mode.CFB = (function () { + var CFB = CryptoJS.lib.BlockCipherMode.extend(); + + CFB.Encryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + CFB.Decryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + // Shortcut + var iv = this._iv; + + // Generate keystream + if (iv) { + var keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + + return CFB; + }()); + + + /** + * Electronic Codebook block mode. + */ + CryptoJS.mode.ECB = (function () { + var ECB = CryptoJS.lib.BlockCipherMode.extend(); + + ECB.Encryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.encryptBlock(words, offset); + } + }); + + ECB.Decryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.decryptBlock(words, offset); + } + }); + + return ECB; + }()); + + + /** + * ANSI X.923 padding strategy. + */ + CryptoJS.pad.AnsiX923 = { + pad: function (data, blockSize) { + // Shortcuts + var dataSigBytes = data.sigBytes; + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; + + // Compute last byte position + var lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + data.clamp(); + data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + data.sigBytes += nPaddingBytes; + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO 10126 padding strategy. + */ + CryptoJS.pad.Iso10126 = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Pad + data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). + concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + CryptoJS.pad.Iso97971 = { + pad: function (data, blockSize) { + // Add 0x80 byte + data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); + + // Zero pad the rest + CryptoJS.pad.ZeroPadding.pad(data, blockSize); + }, + + unpad: function (data) { + // Remove zero padding + CryptoJS.pad.ZeroPadding.unpad(data); + + // Remove one more byte -- the 0x80 byte + data.sigBytes--; + } + }; + + + /** + * Output Feedback block mode. + */ + CryptoJS.mode.OFB = (function () { + var OFB = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = OFB.Encryptor = OFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var keystream = this._keystream; + + // Generate keystream + if (iv) { + keystream = this._keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + OFB.Decryptor = Encryptor; + + return OFB; + }()); + + + /** + * A noop padding strategy. + */ + CryptoJS.pad.NoPadding = { + pad: function () { + }, + + unpad: function () { + } + }; + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var CipherParams = C_lib.CipherParams; + var C_enc = C.enc; + var Hex = C_enc.Hex; + var C_format = C.format; + + var HexFormatter = C_format.Hex = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify: function (cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse: function (input) { + var ciphertext = Hex.parse(input); + return CipherParams.create({ ciphertext: ciphertext }); + } + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Lookup tables + var SBOX = []; + var INV_SBOX = []; + var SUB_MIX_0 = []; + var SUB_MIX_1 = []; + var SUB_MIX_2 = []; + var SUB_MIX_3 = []; + var INV_SUB_MIX_0 = []; + var INV_SUB_MIX_1 = []; + var INV_SUB_MIX_2 = []; + var INV_SUB_MIX_3 = []; + + // Compute lookup tables + (function () { + // Compute double table + var d = []; + for (var i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } + } + + // Walk GF(2^8) + var x = 0; + var xi = 0; + for (var i = 0; i < 256; i++) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + var x2 = d[x]; + var x4 = d[x2]; + var x8 = d[x4]; + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100); + SUB_MIX_0[x] = (t << 24) | (t >>> 8); + SUB_MIX_1[x] = (t << 16) | (t >>> 16); + SUB_MIX_2[x] = (t << 8) | (t >>> 24); + SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + x = xi = 1; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } + } + }()); + + // Precomputed Rcon lookup + var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + + /** + * AES block cipher algorithm. + */ + var AES = C_algo.AES = BlockCipher.extend({ + _doReset: function () { + // Skip reset of nRounds has been set before and key did not change + if (this._nRounds && this._keyPriorReset === this._key) { + return; + } + + // Shortcuts + var key = this._keyPriorReset = this._key; + var keyWords = key.words; + var keySize = key.sigBytes / 4; + + // Compute number of rounds + var nRounds = this._nRounds = keySize + 6; + + // Compute number of key schedule rows + var ksRows = (nRounds + 1) * 4; + + // Compute key schedule + var keySchedule = this._keySchedule = []; + for (var ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + var t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize == 4) { + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + var invKeySchedule = this._invKeySchedule = []; + for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { + var ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + var t = keySchedule[ksRow]; + } else { + var t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ + INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; + } + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); + }, + + decryptBlock: function (M, offset) { + // Swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + + this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); + + // Inv swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + }, + + _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + // Shortcut + var nRounds = this._nRounds; + + // Get input, add round key + var s0 = M[offset] ^ keySchedule[0]; + var s1 = M[offset + 1] ^ keySchedule[1]; + var s2 = M[offset + 2] ^ keySchedule[2]; + var s3 = M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + var ksRow = 4; + + // Rounds + for (var round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; + var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; + var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; + var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; + var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; + var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; + var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; + + // Set output + M[offset] = t0; + M[offset + 1] = t1; + M[offset + 2] = t2; + M[offset + 3] = t3; + }, + + keySize: 256/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + C.AES = BlockCipher._createHelper(AES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Permuted Choice 1 constants + var PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4 + ]; + + // Permuted Choice 2 constants + var PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 + ]; + + // Cumulative bit shift constants + var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + + // SBOXes and round permutation constants + var SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } + ]; + + // Masks that select the SBOX input + var SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f + ]; + + /** + * DES block cipher algorithm. + */ + var DES = C_algo.DES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Select 56 bits according to PC1 + var keyBits = []; + for (var i = 0; i < 56; i++) { + var keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; + } + + // Assemble 16 subkeys + var subKeys = this._subKeys = []; + for (var nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + var subKey = subKeys[nSubKey] = []; + + // Shortcut + var bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (var i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (var i = 1; i < 7; i++) { + subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } + + // Compute inverse subkeys + var invSubKeys = this._invSubKeys = []; + for (var i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + }, + + decryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + }, + + _doCryptBlock: function (M, offset, subKeys) { + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (var round = 0; round < 16; round++) { + // Shortcuts + var subKey = subKeys[round]; + var lBlock = this._lBlock; + var rBlock = this._rBlock; + + // Feistel function + var f = 0; + for (var i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } + + // Undo swap from last round + var t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; + + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); + + // Set output + M[offset] = this._lBlock; + M[offset + 1] = this._rBlock; + }, + + keySize: 64/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + // Swap bits across the left and right words + function exchangeLR(offset, mask) { + var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; + } + + function exchangeRL(offset, mask) { + var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + C.DES = BlockCipher._createHelper(DES); + + /** + * Triple-DES block cipher algorithm. + */ + var TripleDES = C_algo.TripleDES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Create DES instances + this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2))); + this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4))); + this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6))); + }, + + encryptBlock: function (M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + }, + + decryptBlock: function (M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + }, + + keySize: 192/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + C.TripleDES = BlockCipher._createHelper(TripleDES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + /** + * RC4 stream cipher algorithm. + */ + var RC4 = C_algo.RC4 = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySigBytes = key.sigBytes; + + // Init sbox + var S = this._S = []; + for (var i = 0; i < 256; i++) { + S[i] = i; + } + + // Key setup + for (var i = 0, j = 0; i < 256; i++) { + var keyByteIndex = i % keySigBytes; + var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; + + j = (j + S[i] + keyByte) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + } + + // Counters + this._i = this._j = 0; + }, + + _doProcessBlock: function (M, offset) { + M[offset] ^= generateKeystreamWord.call(this); + }, + + keySize: 256/32, + + ivSize: 0 + }); + + function generateKeystreamWord() { + // Shortcuts + var S = this._S; + var i = this._i; + var j = this._j; + + // Generate keystream word + var keystreamWord = 0; + for (var n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } + + // Update counters + this._i = i; + this._j = j; + + return keystreamWord; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + C.RC4 = StreamCipher._createHelper(RC4); + + /** + * Modified RC4 stream cipher algorithm. + */ + var RC4Drop = C_algo.RC4Drop = RC4.extend({ + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + cfg: RC4.cfg.extend({ + drop: 192 + }), + + _doReset: function () { + RC4._doReset.call(this); + + // Drop + for (var i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + C.RC4Drop = StreamCipher._createHelper(RC4Drop); + }()); + + + /** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + CryptoJS.mode.CTRGladman = (function () { + var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); + + function incWord(word) + { + if (((word >> 24) & 0xff) === 0xff) { //overflow + var b1 = (word >> 16)&0xff; + var b2 = (word >> 8)&0xff; + var b3 = word & 0xff; + + if (b1 === 0xff) // overflow b1 + { + b1 = 0; + if (b2 === 0xff) + { + b2 = 0; + if (b3 === 0xff) + { + b3 = 0; + } + else + { + ++b3; + } + } + else + { + ++b2; + } + } + else + { + ++b1; + } + + word = 0; + word += (b1 << 16); + word += (b2 << 8); + word += b3; + } + else + { + word += (0x01 << 24); + } + return word; + } + + function incCounter(counter) + { + if ((counter[0] = incWord(counter[0])) === 0) + { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + counter[1] = incWord(counter[1]); + } + return counter; + } + + var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTRGladman.Decryptor = Encryptor; + + return CTRGladman; + }()); + + + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm + */ + var Rabbit = C_algo.Rabbit = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Swap endian + for (var i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | + (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + C.Rabbit = StreamCipher._createHelper(Rabbit); + }()); + + + /** + * Counter block mode. + */ + CryptoJS.mode.CTR = (function () { + var CTR = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = CTR.Encryptor = CTR.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTR.Decryptor = Encryptor; + + return CTR; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); + }()); + + + /** + * Zero padding strategy. + */ + CryptoJS.pad.ZeroPadding = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Pad + data.clamp(); + data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad: function (data) { + // Shortcut + var dataWords = data.words; + + // Unpad + var i = data.sigBytes - 1; + while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + i--; + } + data.sigBytes = i + 1; + } + }; + + + return CryptoJS; + +})); +},{}],125:[function(require,module,exports){ +'use strict'; + +var elliptic = exports; + +// hello ladies ;) +function inherits (ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; +} + +elliptic.inherits = inherits +elliptic.version = '6.5.0'; +elliptic.utils = require('./elliptic/utils'); +elliptic.curve = require('./elliptic/curve'); +elliptic.curves = require('./elliptic/curves'); + +// Protocols +elliptic.ec = require('./elliptic/ec'); +elliptic.eddsa = require('./elliptic/eddsa'); + +},{"./elliptic/curve":128,"./elliptic/curves":131,"./elliptic/ec":132,"./elliptic/eddsa":135,"./elliptic/utils":139}],126:[function(require,module,exports){ +'use strict'; + +var BN = require('../../../../BN/bn'); +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var getNAF = utils.getNAF; +var getJSF = utils.getJSF; +var assert = utils.assert; + +function BaseCurve(type, conf) { + this.type = type; + this.p = new BN(conf.p, 16); + + // Use Montgomery, when there is no fast reduction for the prime + this.red = conf.prime ? BN.red(conf.prime) : BN.mont(this.p); + + // Useful for many curves + this.zero = new BN(0).toRed(this.red); + this.one = new BN(1).toRed(this.red); + this.two = new BN(2).toRed(this.red); + + // Curve configuration, optional + this.n = conf.n && new BN(conf.n, 16); + this.g = conf.g && this.pointFromJSON(conf.g, conf.gRed); + + // Temporary arrays + this._wnafT1 = new Array(4); + this._wnafT2 = new Array(4); + this._wnafT3 = new Array(4); + this._wnafT4 = new Array(4); + + // Generalized Greg Maxwell's trick + var adjustCount = this.n && this.p.div(this.n); + if (!adjustCount || adjustCount.cmpn(100) > 0) { + this.redN = null; + } else { + this._maxwellTrick = true; + this.redN = this.n.toRed(this.red); + } +} +module.exports = BaseCurve; + +BaseCurve.prototype.point = function point() { + throw new Error('Not implemented'); +}; + +BaseCurve.prototype.validate = function validate() { + throw new Error('Not implemented'); +}; + +BaseCurve.prototype._fixedNafMul = function _fixedNafMul(p, k) { + assert(p.precomputed); + var doubles = p._getDoubles(); + + var naf = getNAF(k, 1); + var I = (1 << (doubles.step + 1)) - (doubles.step % 2 === 0 ? 2 : 1); + I /= 3; + + // Translate into more windowed form + var repr = []; + for (var j = 0; j < naf.length; j += doubles.step) { + var nafW = 0; + for (var k = j + doubles.step - 1; k >= j; k--) + nafW = (nafW << 1) + naf[k]; + repr.push(nafW); + } + + var a = this.jpoint(null, null, null); + var b = this.jpoint(null, null, null); + for (var i = I; i > 0; i--) { + for (var j = 0; j < repr.length; j++) { + var nafW = repr[j]; + if (nafW === i) + b = b.mixedAdd(doubles.points[j]); + else if (nafW === -i) + b = b.mixedAdd(doubles.points[j].neg()); + } + a = a.add(b); + } + return a.toP(); +}; + +BaseCurve.prototype._wnafMul = function _wnafMul(p, k) { + var w = 4; + + // Precompute window + var nafPoints = p._getNAFPoints(w); + w = nafPoints.wnd; + var wnd = nafPoints.points; + + // Get NAF form + var naf = getNAF(k, w); + + // Add `this`*(N+1) for every w-NAF index + var acc = this.jpoint(null, null, null); + for (var i = naf.length - 1; i >= 0; i--) { + // Count zeroes + for (var k = 0; i >= 0 && naf[i] === 0; i--) + k++; + if (i >= 0) + k++; + acc = acc.dblp(k); + + if (i < 0) + break; + var z = naf[i]; + assert(z !== 0); + if (p.type === 'affine') { + // J +- P + if (z > 0) + acc = acc.mixedAdd(wnd[(z - 1) >> 1]); + else + acc = acc.mixedAdd(wnd[(-z - 1) >> 1].neg()); + } else { + // J +- J + if (z > 0) + acc = acc.add(wnd[(z - 1) >> 1]); + else + acc = acc.add(wnd[(-z - 1) >> 1].neg()); + } + } + return p.type === 'affine' ? acc.toP() : acc; +}; + +BaseCurve.prototype._wnafMulAdd = function _wnafMulAdd(defW, + points, + coeffs, + len, + jacobianResult) { + var wndWidth = this._wnafT1; + var wnd = this._wnafT2; + var naf = this._wnafT3; + + // Fill all arrays + var max = 0; + for (var i = 0; i < len; i++) { + var p = points[i]; + var nafPoints = p._getNAFPoints(defW); + wndWidth[i] = nafPoints.wnd; + wnd[i] = nafPoints.points; + } + + // Comb small window NAFs + for (var i = len - 1; i >= 1; i -= 2) { + var a = i - 1; + var b = i; + if (wndWidth[a] !== 1 || wndWidth[b] !== 1) { + naf[a] = getNAF(coeffs[a], wndWidth[a]); + naf[b] = getNAF(coeffs[b], wndWidth[b]); + max = Math.max(naf[a].length, max); + max = Math.max(naf[b].length, max); + continue; + } + + var comb = [ + points[a], /* 1 */ + null, /* 3 */ + null, /* 5 */ + points[b] /* 7 */ + ]; + + // Try to avoid Projective points, if possible + if (points[a].y.cmp(points[b].y) === 0) { + comb[1] = points[a].add(points[b]); + comb[2] = points[a].toJ().mixedAdd(points[b].neg()); + } else if (points[a].y.cmp(points[b].y.redNeg()) === 0) { + comb[1] = points[a].toJ().mixedAdd(points[b]); + comb[2] = points[a].add(points[b].neg()); + } else { + comb[1] = points[a].toJ().mixedAdd(points[b]); + comb[2] = points[a].toJ().mixedAdd(points[b].neg()); + } + + var index = [ + -3, /* -1 -1 */ + -1, /* -1 0 */ + -5, /* -1 1 */ + -7, /* 0 -1 */ + 0, /* 0 0 */ + 7, /* 0 1 */ + 5, /* 1 -1 */ + 1, /* 1 0 */ + 3 /* 1 1 */ + ]; + + var jsf = getJSF(coeffs[a], coeffs[b]); + max = Math.max(jsf[0].length, max); + naf[a] = new Array(max); + naf[b] = new Array(max); + for (var j = 0; j < max; j++) { + var ja = jsf[0][j] | 0; + var jb = jsf[1][j] | 0; + + naf[a][j] = index[(ja + 1) * 3 + (jb + 1)]; + naf[b][j] = 0; + wnd[a] = comb; + } + } + + var acc = this.jpoint(null, null, null); + var tmp = this._wnafT4; + for (var i = max; i >= 0; i--) { + var k = 0; + + while (i >= 0) { + var zero = true; + for (var j = 0; j < len; j++) { + tmp[j] = naf[j][i] | 0; + if (tmp[j] !== 0) + zero = false; + } + if (!zero) + break; + k++; + i--; + } + if (i >= 0) + k++; + acc = acc.dblp(k); + if (i < 0) + break; + + for (var j = 0; j < len; j++) { + var z = tmp[j]; + var p; + if (z === 0) + continue; + else if (z > 0) + p = wnd[j][(z - 1) >> 1]; + else if (z < 0) + p = wnd[j][(-z - 1) >> 1].neg(); + + if (p.type === 'affine') + acc = acc.mixedAdd(p); + else + acc = acc.add(p); + } + } + // Zeroify references + for (var i = 0; i < len; i++) + wnd[i] = null; + + if (jacobianResult) + return acc; + else + return acc.toP(); +}; + +function BasePoint(curve, type) { + this.curve = curve; + this.type = type; + this.precomputed = null; +} +BaseCurve.BasePoint = BasePoint; + +BasePoint.prototype.eq = function eq(/*other*/) { + throw new Error('Not implemented'); +}; + +BasePoint.prototype.validate = function validate() { + return this.curve.validate(this); +}; + +BaseCurve.prototype.decodePoint = function decodePoint(bytes, enc) { + bytes = utils.toArray(bytes, enc); + + var len = this.p.byteLength(); + + // uncompressed, hybrid-odd, hybrid-even + if ((bytes[0] === 0x04 || bytes[0] === 0x06 || bytes[0] === 0x07) && + bytes.length - 1 === 2 * len) { + if (bytes[0] === 0x06) + assert(bytes[bytes.length - 1] % 2 === 0); + else if (bytes[0] === 0x07) + assert(bytes[bytes.length - 1] % 2 === 1); + + var res = this.point(bytes.slice(1, 1 + len), + bytes.slice(1 + len, 1 + 2 * len)); + + return res; + } else if ((bytes[0] === 0x02 || bytes[0] === 0x03) && + bytes.length - 1 === len) { + return this.pointFromX(bytes.slice(1, 1 + len), bytes[0] === 0x03); + } + throw new Error('Unknown point format'); +}; + +BasePoint.prototype.encodeCompressed = function encodeCompressed(enc) { + return this.encode(enc, true); +}; + +BasePoint.prototype._encode = function _encode(compact) { + var len = this.curve.p.byteLength(); + var x = this.getX().toArray('be', len); + + if (compact) + return [ this.getY().isEven() ? 0x02 : 0x03 ].concat(x); + + return [ 0x04 ].concat(x, this.getY().toArray('be', len)) ; +}; + +BasePoint.prototype.encode = function encode(enc, compact) { + return utils.encode(this._encode(compact), enc); +}; + +BasePoint.prototype.precompute = function precompute(power) { + if (this.precomputed) + return this; + + var precomputed = { + doubles: null, + naf: null, + beta: null + }; + precomputed.naf = this._getNAFPoints(8); + precomputed.doubles = this._getDoubles(4, power); + precomputed.beta = this._getBeta(); + this.precomputed = precomputed; + + return this; +}; + +BasePoint.prototype._hasDoubles = function _hasDoubles(k) { + if (!this.precomputed) + return false; + + var doubles = this.precomputed.doubles; + if (!doubles) + return false; + + return doubles.points.length >= Math.ceil((k.bitLength() + 1) / doubles.step); +}; + +BasePoint.prototype._getDoubles = function _getDoubles(step, power) { + if (this.precomputed && this.precomputed.doubles) + return this.precomputed.doubles; + + var doubles = [ this ]; + var acc = this; + for (var i = 0; i < power; i += step) { + for (var j = 0; j < step; j++) + acc = acc.dbl(); + doubles.push(acc); + } + return { + step: step, + points: doubles + }; +}; + +BasePoint.prototype._getNAFPoints = function _getNAFPoints(wnd) { + if (this.precomputed && this.precomputed.naf) + return this.precomputed.naf; + + var res = [ this ]; + var max = (1 << wnd) - 1; + var dbl = max === 1 ? null : this.dbl(); + for (var i = 1; i < max; i++) + res[i] = res[i - 1].add(dbl); + return { + wnd: wnd, + points: res + }; +}; + +BasePoint.prototype._getBeta = function _getBeta() { + return null; +}; + +BasePoint.prototype.dblp = function dblp(k) { + var r = this; + for (var i = 0; i < k; i++) + r = r.dbl(); + return r; +}; + +},{"../../../../BN/bn":123,"../../elliptic":125}],127:[function(require,module,exports){ +'use strict'; + +var curve = require('../curve'); +var elliptic = require('../../elliptic'); +var BN = require('../../../../BN/bn'); +var inherits = elliptic.inherits; +var Base = curve.base; + +var assert = elliptic.utils.assert; + +function EdwardsCurve(conf) { + // NOTE: Important as we are creating point in Base.call() + this.twisted = (conf.a | 0) !== 1; + this.mOneA = this.twisted && (conf.a | 0) === -1; + this.extended = this.mOneA; + + Base.call(this, 'edwards', conf); + + this.a = new BN(conf.a, 16).umod(this.red.m); + this.a = this.a.toRed(this.red); + this.c = new BN(conf.c, 16).toRed(this.red); + this.c2 = this.c.redSqr(); + this.d = new BN(conf.d, 16).toRed(this.red); + this.dd = this.d.redAdd(this.d); + + assert(!this.twisted || this.c.fromRed().cmpn(1) === 0); + this.oneC = (conf.c | 0) === 1; +} +inherits(EdwardsCurve, Base); +module.exports = EdwardsCurve; + +EdwardsCurve.prototype._mulA = function _mulA(num) { + if (this.mOneA) + return num.redNeg(); + else + return this.a.redMul(num); +}; + +EdwardsCurve.prototype._mulC = function _mulC(num) { + if (this.oneC) + return num; + else + return this.c.redMul(num); +}; + +// Just for compatibility with Short curve +EdwardsCurve.prototype.jpoint = function jpoint(x, y, z, t) { + return this.point(x, y, z, t); +}; + +EdwardsCurve.prototype.pointFromX = function pointFromX(x, odd) { + x = new BN(x, 16); + if (!x.red) + x = x.toRed(this.red); + + var x2 = x.redSqr(); + var rhs = this.c2.redSub(this.a.redMul(x2)); + var lhs = this.one.redSub(this.c2.redMul(this.d).redMul(x2)); + + var y2 = rhs.redMul(lhs.redInvm()); + var y = y2.redSqrt(); + if (y.redSqr().redSub(y2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + var isOdd = y.fromRed().isOdd(); + if (odd && !isOdd || !odd && isOdd) + y = y.redNeg(); + + return this.point(x, y); +}; + +EdwardsCurve.prototype.pointFromY = function pointFromY(y, odd) { + y = new BN(y, 16); + if (!y.red) + y = y.toRed(this.red); + + // x^2 = (y^2 - 1) / (d y^2 + 1) + var y2 = y.redSqr(); + var lhs = y2.redSub(this.one); + var rhs = y2.redMul(this.d).redAdd(this.one); + var x2 = lhs.redMul(rhs.redInvm()); + + if (x2.cmp(this.zero) === 0) { + if (odd) + throw new Error('invalid point'); + else + return this.point(this.zero, y); + } + + var x = x2.redSqrt(); + if (x.redSqr().redSub(x2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + if (x.isOdd() !== odd) + x = x.redNeg(); + + return this.point(x, y); +}; + +EdwardsCurve.prototype.validate = function validate(point) { + if (point.isInfinity()) + return true; + + // Curve: A * X^2 + Y^2 = C^2 * (1 + D * X^2 * Y^2) + point.normalize(); + + var x2 = point.x.redSqr(); + var y2 = point.y.redSqr(); + var lhs = x2.redMul(this.a).redAdd(y2); + var rhs = this.c2.redMul(this.one.redAdd(this.d.redMul(x2).redMul(y2))); + + return lhs.cmp(rhs) === 0; +}; + +function Point(curve, x, y, z, t) { + Base.BasePoint.call(this, curve, 'projective'); + if (x === null && y === null && z === null) { + this.x = this.curve.zero; + this.y = this.curve.one; + this.z = this.curve.one; + this.t = this.curve.zero; + this.zOne = true; + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + this.z = z ? new BN(z, 16) : this.curve.one; + this.t = t && new BN(t, 16); + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + if (this.t && !this.t.red) + this.t = this.t.toRed(this.curve.red); + this.zOne = this.z === this.curve.one; + + // Use extended coordinates + if (this.curve.extended && !this.t) { + this.t = this.x.redMul(this.y); + if (!this.zOne) + this.t = this.t.redMul(this.z.redInvm()); + } + } +} +inherits(Point, Base.BasePoint); + +EdwardsCurve.prototype.pointFromJSON = function pointFromJSON(obj) { + return Point.fromJSON(this, obj); +}; + +EdwardsCurve.prototype.point = function point(x, y, z, t) { + return new Point(this, x, y, z, t); +}; + +Point.fromJSON = function fromJSON(curve, obj) { + return new Point(curve, obj[0], obj[1], obj[2]); +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.x.cmpn(0) === 0 && + this.y.cmp(this.z) === 0; +}; + +Point.prototype._extDbl = function _extDbl() { + // hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + // #doubling-dbl-2008-hwcd + // 4M + 4S + + // A = X1^2 + var a = this.x.redSqr(); + // B = Y1^2 + var b = this.y.redSqr(); + // C = 2 * Z1^2 + var c = this.z.redSqr(); + c = c.redIAdd(c); + // D = a * A + var d = this.curve._mulA(a); + // E = (X1 + Y1)^2 - A - B + var e = this.x.redAdd(this.y).redSqr().redISub(a).redISub(b); + // G = D + B + var g = d.redAdd(b); + // F = G - C + var f = g.redSub(c); + // H = D - B + var h = d.redSub(b); + // X3 = E * F + var nx = e.redMul(f); + // Y3 = G * H + var ny = g.redMul(h); + // T3 = E * H + var nt = e.redMul(h); + // Z3 = F * G + var nz = f.redMul(g); + return this.curve.point(nx, ny, nz, nt); +}; + +Point.prototype._projDbl = function _projDbl() { + // hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + // #doubling-dbl-2008-bbjlp + // #doubling-dbl-2007-bl + // and others + // Generally 3M + 4S or 2M + 4S + + // B = (X1 + Y1)^2 + var b = this.x.redAdd(this.y).redSqr(); + // C = X1^2 + var c = this.x.redSqr(); + // D = Y1^2 + var d = this.y.redSqr(); + + var nx; + var ny; + var nz; + if (this.curve.twisted) { + // E = a * C + var e = this.curve._mulA(c); + // F = E + D + var f = e.redAdd(d); + if (this.zOne) { + // X3 = (B - C - D) * (F - 2) + nx = b.redSub(c).redSub(d).redMul(f.redSub(this.curve.two)); + // Y3 = F * (E - D) + ny = f.redMul(e.redSub(d)); + // Z3 = F^2 - 2 * F + nz = f.redSqr().redSub(f).redSub(f); + } else { + // H = Z1^2 + var h = this.z.redSqr(); + // J = F - 2 * H + var j = f.redSub(h).redISub(h); + // X3 = (B-C-D)*J + nx = b.redSub(c).redISub(d).redMul(j); + // Y3 = F * (E - D) + ny = f.redMul(e.redSub(d)); + // Z3 = F * J + nz = f.redMul(j); + } + } else { + // E = C + D + var e = c.redAdd(d); + // H = (c * Z1)^2 + var h = this.curve._mulC(this.c.redMul(this.z)).redSqr(); + // J = E - 2 * H + var j = e.redSub(h).redSub(h); + // X3 = c * (B - E) * J + nx = this.curve._mulC(b.redISub(e)).redMul(j); + // Y3 = c * E * (C - D) + ny = this.curve._mulC(e).redMul(c.redISub(d)); + // Z3 = E * J + nz = e.redMul(j); + } + return this.curve.point(nx, ny, nz); +}; + +Point.prototype.dbl = function dbl() { + if (this.isInfinity()) + return this; + + // Double in extended coordinates + if (this.curve.extended) + return this._extDbl(); + else + return this._projDbl(); +}; + +Point.prototype._extAdd = function _extAdd(p) { + // hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + // #addition-add-2008-hwcd-3 + // 8M + + // A = (Y1 - X1) * (Y2 - X2) + var a = this.y.redSub(this.x).redMul(p.y.redSub(p.x)); + // B = (Y1 + X1) * (Y2 + X2) + var b = this.y.redAdd(this.x).redMul(p.y.redAdd(p.x)); + // C = T1 * k * T2 + var c = this.t.redMul(this.curve.dd).redMul(p.t); + // D = Z1 * 2 * Z2 + var d = this.z.redMul(p.z.redAdd(p.z)); + // E = B - A + var e = b.redSub(a); + // F = D - C + var f = d.redSub(c); + // G = D + C + var g = d.redAdd(c); + // H = B + A + var h = b.redAdd(a); + // X3 = E * F + var nx = e.redMul(f); + // Y3 = G * H + var ny = g.redMul(h); + // T3 = E * H + var nt = e.redMul(h); + // Z3 = F * G + var nz = f.redMul(g); + return this.curve.point(nx, ny, nz, nt); +}; + +Point.prototype._projAdd = function _projAdd(p) { + // hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + // #addition-add-2008-bbjlp + // #addition-add-2007-bl + // 10M + 1S + + // A = Z1 * Z2 + var a = this.z.redMul(p.z); + // B = A^2 + var b = a.redSqr(); + // C = X1 * X2 + var c = this.x.redMul(p.x); + // D = Y1 * Y2 + var d = this.y.redMul(p.y); + // E = d * C * D + var e = this.curve.d.redMul(c).redMul(d); + // F = B - E + var f = b.redSub(e); + // G = B + E + var g = b.redAdd(e); + // X3 = A * F * ((X1 + Y1) * (X2 + Y2) - C - D) + var tmp = this.x.redAdd(this.y).redMul(p.x.redAdd(p.y)).redISub(c).redISub(d); + var nx = a.redMul(f).redMul(tmp); + var ny; + var nz; + if (this.curve.twisted) { + // Y3 = A * G * (D - a * C) + ny = a.redMul(g).redMul(d.redSub(this.curve._mulA(c))); + // Z3 = F * G + nz = f.redMul(g); + } else { + // Y3 = A * G * (D - C) + ny = a.redMul(g).redMul(d.redSub(c)); + // Z3 = c * F * G + nz = this.curve._mulC(f).redMul(g); + } + return this.curve.point(nx, ny, nz); +}; + +Point.prototype.add = function add(p) { + if (this.isInfinity()) + return p; + if (p.isInfinity()) + return this; + + if (this.curve.extended) + return this._extAdd(p); + else + return this._projAdd(p); +}; + +Point.prototype.mul = function mul(k) { + if (this._hasDoubles(k)) + return this.curve._fixedNafMul(this, k); + else + return this.curve._wnafMul(this, k); +}; + +Point.prototype.mulAdd = function mulAdd(k1, p, k2) { + return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, false); +}; + +Point.prototype.jmulAdd = function jmulAdd(k1, p, k2) { + return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, true); +}; + +Point.prototype.normalize = function normalize() { + if (this.zOne) + return this; + + // Normalize coordinates + var zi = this.z.redInvm(); + this.x = this.x.redMul(zi); + this.y = this.y.redMul(zi); + if (this.t) + this.t = this.t.redMul(zi); + this.z = this.curve.one; + this.zOne = true; + return this; +}; + +Point.prototype.neg = function neg() { + return this.curve.point(this.x.redNeg(), + this.y, + this.z, + this.t && this.t.redNeg()); +}; + +Point.prototype.getX = function getX() { + this.normalize(); + return this.x.fromRed(); +}; + +Point.prototype.getY = function getY() { + this.normalize(); + return this.y.fromRed(); +}; + +Point.prototype.eq = function eq(other) { + return this === other || + this.getX().cmp(other.getX()) === 0 && + this.getY().cmp(other.getY()) === 0; +}; + +Point.prototype.eqXToP = function eqXToP(x) { + var rx = x.toRed(this.curve.red).redMul(this.z); + if (this.x.cmp(rx) === 0) + return true; + + var xc = x.clone(); + var t = this.curve.redN.redMul(this.z); + for (;;) { + xc.iadd(this.curve.n); + if (xc.cmp(this.curve.p) >= 0) + return false; + + rx.redIAdd(t); + if (this.x.cmp(rx) === 0) + return true; + } + return false; +}; + +// Compatibility with BaseCurve +Point.prototype.toP = Point.prototype.normalize; +Point.prototype.mixedAdd = Point.prototype.add; + +},{"../../../../BN/bn":123,"../../elliptic":125,"../curve":128}],128:[function(require,module,exports){ +'use strict'; + +var curve = exports; + +curve.base = require('./base'); +curve.short = require('./short'); +curve.mont = require('./mont'); +curve.edwards = require('./edwards'); + +},{"./base":126,"./edwards":127,"./mont":129,"./short":130}],129:[function(require,module,exports){ +'use strict'; + +var curve = require('../curve'); +var BN = require('../../../../BN/bn'); +var inherits = require('../../elliptic').inherits; +var Base = curve.base; + +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; + +function MontCurve(conf) { + Base.call(this, 'mont', conf); + + this.a = new BN(conf.a, 16).toRed(this.red); + this.b = new BN(conf.b, 16).toRed(this.red); + this.i4 = new BN(4).toRed(this.red).redInvm(); + this.two = new BN(2).toRed(this.red); + this.a24 = this.i4.redMul(this.a.redAdd(this.two)); +} +inherits(MontCurve, Base); +module.exports = MontCurve; + +MontCurve.prototype.validate = function validate(point) { + var x = point.normalize().x; + var x2 = x.redSqr(); + var rhs = x2.redMul(x).redAdd(x2.redMul(this.a)).redAdd(x); + var y = rhs.redSqrt(); + + return y.redSqr().cmp(rhs) === 0; +}; + +function Point(curve, x, z) { + Base.BasePoint.call(this, curve, 'projective'); + if (x === null && z === null) { + this.x = this.curve.one; + this.z = this.curve.zero; + } else { + this.x = new BN(x, 16); + this.z = new BN(z, 16); + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + } +} +inherits(Point, Base.BasePoint); + +MontCurve.prototype.decodePoint = function decodePoint(bytes, enc) { + return this.point(utils.toArray(bytes, enc), 1); +}; + +MontCurve.prototype.point = function point(x, z) { + return new Point(this, x, z); +}; + +MontCurve.prototype.pointFromJSON = function pointFromJSON(obj) { + return Point.fromJSON(this, obj); +}; + +Point.prototype.precompute = function precompute() { + // No-op +}; + +Point.prototype._encode = function _encode() { + return this.getX().toArray('be', this.curve.p.byteLength()); +}; + +Point.fromJSON = function fromJSON(curve, obj) { + return new Point(curve, obj[0], obj[1] || curve.one); +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.z.cmpn(0) === 0; +}; + +Point.prototype.dbl = function dbl() { + // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#doubling-dbl-1987-m-3 + // 2M + 2S + 4A + + // A = X1 + Z1 + var a = this.x.redAdd(this.z); + // AA = A^2 + var aa = a.redSqr(); + // B = X1 - Z1 + var b = this.x.redSub(this.z); + // BB = B^2 + var bb = b.redSqr(); + // C = AA - BB + var c = aa.redSub(bb); + // X3 = AA * BB + var nx = aa.redMul(bb); + // Z3 = C * (BB + A24 * C) + var nz = c.redMul(bb.redAdd(this.curve.a24.redMul(c))); + return this.curve.point(nx, nz); +}; + +Point.prototype.add = function add() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.diffAdd = function diffAdd(p, diff) { + // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#diffadd-dadd-1987-m-3 + // 4M + 2S + 6A + + // A = X2 + Z2 + var a = this.x.redAdd(this.z); + // B = X2 - Z2 + var b = this.x.redSub(this.z); + // C = X3 + Z3 + var c = p.x.redAdd(p.z); + // D = X3 - Z3 + var d = p.x.redSub(p.z); + // DA = D * A + var da = d.redMul(a); + // CB = C * B + var cb = c.redMul(b); + // X5 = Z1 * (DA + CB)^2 + var nx = diff.z.redMul(da.redAdd(cb).redSqr()); + // Z5 = X1 * (DA - CB)^2 + var nz = diff.x.redMul(da.redISub(cb).redSqr()); + return this.curve.point(nx, nz); +}; + +Point.prototype.mul = function mul(k) { + var t = k.clone(); + var a = this; // (N / 2) * Q + Q + var b = this.curve.point(null, null); // (N / 2) * Q + var c = this; // Q + + for (var bits = []; t.cmpn(0) !== 0; t.iushrn(1)) + bits.push(t.andln(1)); + + for (var i = bits.length - 1; i >= 0; i--) { + if (bits[i] === 0) { + // N * Q + Q = ((N / 2) * Q + Q)) + (N / 2) * Q + a = a.diffAdd(b, c); + // N * Q = 2 * ((N / 2) * Q + Q)) + b = b.dbl(); + } else { + // N * Q = ((N / 2) * Q + Q) + ((N / 2) * Q) + b = a.diffAdd(b, c); + // N * Q + Q = 2 * ((N / 2) * Q + Q) + a = a.dbl(); + } + } + return b; +}; + +Point.prototype.mulAdd = function mulAdd() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.jumlAdd = function jumlAdd() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.eq = function eq(other) { + return this.getX().cmp(other.getX()) === 0; +}; + +Point.prototype.normalize = function normalize() { + this.x = this.x.redMul(this.z.redInvm()); + this.z = this.curve.one; + return this; +}; + +Point.prototype.getX = function getX() { + // Normalize coordinates + this.normalize(); + + return this.x.fromRed(); +}; + +},{"../../../../BN/bn":123,"../../elliptic":125,"../curve":128}],130:[function(require,module,exports){ +'use strict'; + +var curve = require('../curve'); +var elliptic = require('../../elliptic'); +var BN = require('../../../../BN/bn'); +var inherits = elliptic.inherits; +var Base = curve.base; + +var assert = elliptic.utils.assert; + +function ShortCurve(conf) { + Base.call(this, 'short', conf); + + this.a = new BN(conf.a, 16).toRed(this.red); + this.b = new BN(conf.b, 16).toRed(this.red); + this.tinv = this.two.redInvm(); + + this.zeroA = this.a.fromRed().cmpn(0) === 0; + this.threeA = this.a.fromRed().sub(this.p).cmpn(-3) === 0; + + // If the curve is endomorphic, precalculate beta and lambda + this.endo = this._getEndomorphism(conf); + this._endoWnafT1 = new Array(4); + this._endoWnafT2 = new Array(4); +} +inherits(ShortCurve, Base); +module.exports = ShortCurve; + +ShortCurve.prototype._getEndomorphism = function _getEndomorphism(conf) { + // No efficient endomorphism + if (!this.zeroA || !this.g || !this.n || this.p.modn(3) !== 1) + return; + + // Compute beta and lambda, that lambda * P = (beta * Px; Py) + var beta; + var lambda; + if (conf.beta) { + beta = new BN(conf.beta, 16).toRed(this.red); + } else { + var betas = this._getEndoRoots(this.p); + // Choose the smallest beta + beta = betas[0].cmp(betas[1]) < 0 ? betas[0] : betas[1]; + beta = beta.toRed(this.red); + } + if (conf.lambda) { + lambda = new BN(conf.lambda, 16); + } else { + // Choose the lambda that is matching selected beta + var lambdas = this._getEndoRoots(this.n); + if (this.g.mul(lambdas[0]).x.cmp(this.g.x.redMul(beta)) === 0) { + lambda = lambdas[0]; + } else { + lambda = lambdas[1]; + assert(this.g.mul(lambda).x.cmp(this.g.x.redMul(beta)) === 0); + } + } + + // Get basis vectors, used for balanced length-two representation + var basis; + if (conf.basis) { + basis = conf.basis.map(function(vec) { + return { + a: new BN(vec.a, 16), + b: new BN(vec.b, 16) + }; + }); + } else { + basis = this._getEndoBasis(lambda); + } + + return { + beta: beta, + lambda: lambda, + basis: basis + }; +}; + +ShortCurve.prototype._getEndoRoots = function _getEndoRoots(num) { + // Find roots of for x^2 + x + 1 in F + // Root = (-1 +- Sqrt(-3)) / 2 + // + var red = num === this.p ? this.red : BN.mont(num); + var tinv = new BN(2).toRed(red).redInvm(); + var ntinv = tinv.redNeg(); + + var s = new BN(3).toRed(red).redNeg().redSqrt().redMul(tinv); + + var l1 = ntinv.redAdd(s).fromRed(); + var l2 = ntinv.redSub(s).fromRed(); + return [ l1, l2 ]; +}; + +ShortCurve.prototype._getEndoBasis = function _getEndoBasis(lambda) { + // aprxSqrt >= sqrt(this.n) + var aprxSqrt = this.n.ushrn(Math.floor(this.n.bitLength() / 2)); + + // 3.74 + // Run EGCD, until r(L + 1) < aprxSqrt + var u = lambda; + var v = this.n.clone(); + var x1 = new BN(1); + var y1 = new BN(0); + var x2 = new BN(0); + var y2 = new BN(1); + + // NOTE: all vectors are roots of: a + b * lambda = 0 (mod n) + var a0; + var b0; + // First vector + var a1; + var b1; + // Second vector + var a2; + var b2; + + var prevR; + var i = 0; + var r; + var x; + while (u.cmpn(0) !== 0) { + var q = v.div(u); + r = v.sub(q.mul(u)); + x = x2.sub(q.mul(x1)); + var y = y2.sub(q.mul(y1)); + + if (!a1 && r.cmp(aprxSqrt) < 0) { + a0 = prevR.neg(); + b0 = x1; + a1 = r.neg(); + b1 = x; + } else if (a1 && ++i === 2) { + break; + } + prevR = r; + + v = u; + u = r; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + a2 = r.neg(); + b2 = x; + + var len1 = a1.sqr().add(b1.sqr()); + var len2 = a2.sqr().add(b2.sqr()); + if (len2.cmp(len1) >= 0) { + a2 = a0; + b2 = b0; + } + + // Normalize signs + if (a1.negative) { + a1 = a1.neg(); + b1 = b1.neg(); + } + if (a2.negative) { + a2 = a2.neg(); + b2 = b2.neg(); + } + + return [ + { a: a1, b: b1 }, + { a: a2, b: b2 } + ]; +}; + +ShortCurve.prototype._endoSplit = function _endoSplit(k) { + var basis = this.endo.basis; + var v1 = basis[0]; + var v2 = basis[1]; + + var c1 = v2.b.mul(k).divRound(this.n); + var c2 = v1.b.neg().mul(k).divRound(this.n); + + var p1 = c1.mul(v1.a); + var p2 = c2.mul(v2.a); + var q1 = c1.mul(v1.b); + var q2 = c2.mul(v2.b); + + // Calculate answer + var k1 = k.sub(p1).sub(p2); + var k2 = q1.add(q2).neg(); + return { k1: k1, k2: k2 }; +}; + +ShortCurve.prototype.pointFromX = function pointFromX(x, odd) { + x = new BN(x, 16); + if (!x.red) + x = x.toRed(this.red); + + var y2 = x.redSqr().redMul(x).redIAdd(x.redMul(this.a)).redIAdd(this.b); + var y = y2.redSqrt(); + if (y.redSqr().redSub(y2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + // XXX Is there any way to tell if the number is odd without converting it + // to non-red form? + var isOdd = y.fromRed().isOdd(); + if (odd && !isOdd || !odd && isOdd) + y = y.redNeg(); + + return this.point(x, y); +}; + +ShortCurve.prototype.validate = function validate(point) { + if (point.inf) + return true; + + var x = point.x; + var y = point.y; + + var ax = this.a.redMul(x); + var rhs = x.redSqr().redMul(x).redIAdd(ax).redIAdd(this.b); + return y.redSqr().redISub(rhs).cmpn(0) === 0; +}; + +ShortCurve.prototype._endoWnafMulAdd = + function _endoWnafMulAdd(points, coeffs, jacobianResult) { + var npoints = this._endoWnafT1; + var ncoeffs = this._endoWnafT2; + for (var i = 0; i < points.length; i++) { + var split = this._endoSplit(coeffs[i]); + var p = points[i]; + var beta = p._getBeta(); + + if (split.k1.negative) { + split.k1.ineg(); + p = p.neg(true); + } + if (split.k2.negative) { + split.k2.ineg(); + beta = beta.neg(true); + } + + npoints[i * 2] = p; + npoints[i * 2 + 1] = beta; + ncoeffs[i * 2] = split.k1; + ncoeffs[i * 2 + 1] = split.k2; + } + var res = this._wnafMulAdd(1, npoints, ncoeffs, i * 2, jacobianResult); + + // Clean-up references to points and coefficients + for (var j = 0; j < i * 2; j++) { + npoints[j] = null; + ncoeffs[j] = null; + } + return res; +}; + +function Point(curve, x, y, isRed) { + Base.BasePoint.call(this, curve, 'affine'); + if (x === null && y === null) { + this.x = null; + this.y = null; + this.inf = true; + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + // Force redgomery representation when loading from JSON + if (isRed) { + this.x.forceRed(this.curve.red); + this.y.forceRed(this.curve.red); + } + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + this.inf = false; + } +} +inherits(Point, Base.BasePoint); + +ShortCurve.prototype.point = function point(x, y, isRed) { + return new Point(this, x, y, isRed); +}; + +ShortCurve.prototype.pointFromJSON = function pointFromJSON(obj, red) { + return Point.fromJSON(this, obj, red); +}; + +Point.prototype._getBeta = function _getBeta() { + if (!this.curve.endo) + return; + + var pre = this.precomputed; + if (pre && pre.beta) + return pre.beta; + + var beta = this.curve.point(this.x.redMul(this.curve.endo.beta), this.y); + if (pre) { + var curve = this.curve; + var endoMul = function(p) { + return curve.point(p.x.redMul(curve.endo.beta), p.y); + }; + pre.beta = beta; + beta.precomputed = { + beta: null, + naf: pre.naf && { + wnd: pre.naf.wnd, + points: pre.naf.points.map(endoMul) + }, + doubles: pre.doubles && { + step: pre.doubles.step, + points: pre.doubles.points.map(endoMul) + } + }; + } + return beta; +}; + +Point.prototype.toJSON = function toJSON() { + if (!this.precomputed) + return [ this.x, this.y ]; + + return [ this.x, this.y, this.precomputed && { + doubles: this.precomputed.doubles && { + step: this.precomputed.doubles.step, + points: this.precomputed.doubles.points.slice(1) + }, + naf: this.precomputed.naf && { + wnd: this.precomputed.naf.wnd, + points: this.precomputed.naf.points.slice(1) + } + } ]; +}; + +Point.fromJSON = function fromJSON(curve, obj, red) { + if (typeof obj === 'string') + obj = JSON.parse(obj); + var res = curve.point(obj[0], obj[1], red); + if (!obj[2]) + return res; + + function obj2point(obj) { + return curve.point(obj[0], obj[1], red); + } + + var pre = obj[2]; + res.precomputed = { + beta: null, + doubles: pre.doubles && { + step: pre.doubles.step, + points: [ res ].concat(pre.doubles.points.map(obj2point)) + }, + naf: pre.naf && { + wnd: pre.naf.wnd, + points: [ res ].concat(pre.naf.points.map(obj2point)) + } + }; + return res; +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + return this.inf; +}; + +Point.prototype.add = function add(p) { + // O + P = P + if (this.inf) + return p; + + // P + O = P + if (p.inf) + return this; + + // P + P = 2P + if (this.eq(p)) + return this.dbl(); + + // P + (-P) = O + if (this.neg().eq(p)) + return this.curve.point(null, null); + + // P + Q = O + if (this.x.cmp(p.x) === 0) + return this.curve.point(null, null); + + var c = this.y.redSub(p.y); + if (c.cmpn(0) !== 0) + c = c.redMul(this.x.redSub(p.x).redInvm()); + var nx = c.redSqr().redISub(this.x).redISub(p.x); + var ny = c.redMul(this.x.redSub(nx)).redISub(this.y); + return this.curve.point(nx, ny); +}; + +Point.prototype.dbl = function dbl() { + if (this.inf) + return this; + + // 2P = O + var ys1 = this.y.redAdd(this.y); + if (ys1.cmpn(0) === 0) + return this.curve.point(null, null); + + var a = this.curve.a; + + var x2 = this.x.redSqr(); + var dyinv = ys1.redInvm(); + var c = x2.redAdd(x2).redIAdd(x2).redIAdd(a).redMul(dyinv); + + var nx = c.redSqr().redISub(this.x.redAdd(this.x)); + var ny = c.redMul(this.x.redSub(nx)).redISub(this.y); + return this.curve.point(nx, ny); +}; + +Point.prototype.getX = function getX() { + return this.x.fromRed(); +}; + +Point.prototype.getY = function getY() { + return this.y.fromRed(); +}; + +Point.prototype.mul = function mul(k) { + k = new BN(k, 16); + + if (this._hasDoubles(k)) + return this.curve._fixedNafMul(this, k); + else if (this.curve.endo) + return this.curve._endoWnafMulAdd([ this ], [ k ]); + else + return this.curve._wnafMul(this, k); +}; + +Point.prototype.mulAdd = function mulAdd(k1, p2, k2) { + var points = [ this, p2 ]; + var coeffs = [ k1, k2 ]; + if (this.curve.endo) + return this.curve._endoWnafMulAdd(points, coeffs); + else + return this.curve._wnafMulAdd(1, points, coeffs, 2); +}; + +Point.prototype.jmulAdd = function jmulAdd(k1, p2, k2) { + var points = [ this, p2 ]; + var coeffs = [ k1, k2 ]; + if (this.curve.endo) + return this.curve._endoWnafMulAdd(points, coeffs, true); + else + return this.curve._wnafMulAdd(1, points, coeffs, 2, true); +}; + +Point.prototype.eq = function eq(p) { + return this === p || + this.inf === p.inf && + (this.inf || this.x.cmp(p.x) === 0 && this.y.cmp(p.y) === 0); +}; + +Point.prototype.neg = function neg(_precompute) { + if (this.inf) + return this; + + var res = this.curve.point(this.x, this.y.redNeg()); + if (_precompute && this.precomputed) { + var pre = this.precomputed; + var negate = function(p) { + return p.neg(); + }; + res.precomputed = { + naf: pre.naf && { + wnd: pre.naf.wnd, + points: pre.naf.points.map(negate) + }, + doubles: pre.doubles && { + step: pre.doubles.step, + points: pre.doubles.points.map(negate) + } + }; + } + return res; +}; + +Point.prototype.toJ = function toJ() { + if (this.inf) + return this.curve.jpoint(null, null, null); + + var res = this.curve.jpoint(this.x, this.y, this.curve.one); + return res; +}; + +function JPoint(curve, x, y, z) { + Base.BasePoint.call(this, curve, 'jacobian'); + if (x === null && y === null && z === null) { + this.x = this.curve.one; + this.y = this.curve.one; + this.z = new BN(0); + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + this.z = new BN(z, 16); + } + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + + this.zOne = this.z === this.curve.one; +} +inherits(JPoint, Base.BasePoint); + +ShortCurve.prototype.jpoint = function jpoint(x, y, z) { + return new JPoint(this, x, y, z); +}; + +JPoint.prototype.toP = function toP() { + if (this.isInfinity()) + return this.curve.point(null, null); + + var zinv = this.z.redInvm(); + var zinv2 = zinv.redSqr(); + var ax = this.x.redMul(zinv2); + var ay = this.y.redMul(zinv2).redMul(zinv); + + return this.curve.point(ax, ay); +}; + +JPoint.prototype.neg = function neg() { + return this.curve.jpoint(this.x, this.y.redNeg(), this.z); +}; + +JPoint.prototype.add = function add(p) { + // O + P = P + if (this.isInfinity()) + return p; + + // P + O = P + if (p.isInfinity()) + return this; + + // 12M + 4S + 7A + var pz2 = p.z.redSqr(); + var z2 = this.z.redSqr(); + var u1 = this.x.redMul(pz2); + var u2 = p.x.redMul(z2); + var s1 = this.y.redMul(pz2.redMul(p.z)); + var s2 = p.y.redMul(z2.redMul(this.z)); + + var h = u1.redSub(u2); + var r = s1.redSub(s2); + if (h.cmpn(0) === 0) { + if (r.cmpn(0) !== 0) + return this.curve.jpoint(null, null, null); + else + return this.dbl(); + } + + var h2 = h.redSqr(); + var h3 = h2.redMul(h); + var v = u1.redMul(h2); + + var nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v); + var ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3)); + var nz = this.z.redMul(p.z).redMul(h); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.mixedAdd = function mixedAdd(p) { + // O + P = P + if (this.isInfinity()) + return p.toJ(); + + // P + O = P + if (p.isInfinity()) + return this; + + // 8M + 3S + 7A + var z2 = this.z.redSqr(); + var u1 = this.x; + var u2 = p.x.redMul(z2); + var s1 = this.y; + var s2 = p.y.redMul(z2).redMul(this.z); + + var h = u1.redSub(u2); + var r = s1.redSub(s2); + if (h.cmpn(0) === 0) { + if (r.cmpn(0) !== 0) + return this.curve.jpoint(null, null, null); + else + return this.dbl(); + } + + var h2 = h.redSqr(); + var h3 = h2.redMul(h); + var v = u1.redMul(h2); + + var nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v); + var ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3)); + var nz = this.z.redMul(h); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.dblp = function dblp(pow) { + if (pow === 0) + return this; + if (this.isInfinity()) + return this; + if (!pow) + return this.dbl(); + + if (this.curve.zeroA || this.curve.threeA) { + var r = this; + for (var i = 0; i < pow; i++) + r = r.dbl(); + return r; + } + + // 1M + 2S + 1A + N * (4S + 5M + 8A) + // N = 1 => 6M + 6S + 9A + var a = this.curve.a; + var tinv = this.curve.tinv; + + var jx = this.x; + var jy = this.y; + var jz = this.z; + var jz4 = jz.redSqr().redSqr(); + + // Reuse results + var jyd = jy.redAdd(jy); + for (var i = 0; i < pow; i++) { + var jx2 = jx.redSqr(); + var jyd2 = jyd.redSqr(); + var jyd4 = jyd2.redSqr(); + var c = jx2.redAdd(jx2).redIAdd(jx2).redIAdd(a.redMul(jz4)); + + var t1 = jx.redMul(jyd2); + var nx = c.redSqr().redISub(t1.redAdd(t1)); + var t2 = t1.redISub(nx); + var dny = c.redMul(t2); + dny = dny.redIAdd(dny).redISub(jyd4); + var nz = jyd.redMul(jz); + if (i + 1 < pow) + jz4 = jz4.redMul(jyd4); + + jx = nx; + jz = nz; + jyd = dny; + } + + return this.curve.jpoint(jx, jyd.redMul(tinv), jz); +}; + +JPoint.prototype.dbl = function dbl() { + if (this.isInfinity()) + return this; + + if (this.curve.zeroA) + return this._zeroDbl(); + else if (this.curve.threeA) + return this._threeDbl(); + else + return this._dbl(); +}; + +JPoint.prototype._zeroDbl = function _zeroDbl() { + var nx; + var ny; + var nz; + // Z = 1 + if (this.zOne) { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html + // #doubling-mdbl-2007-bl + // 1M + 5S + 14A + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // S = 2 * ((X1 + YY)^2 - XX - YYYY) + var s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + s = s.redIAdd(s); + // M = 3 * XX + a; a = 0 + var m = xx.redAdd(xx).redIAdd(xx); + // T = M ^ 2 - 2*S + var t = m.redSqr().redISub(s).redISub(s); + + // 8 * YYYY + var yyyy8 = yyyy.redIAdd(yyyy); + yyyy8 = yyyy8.redIAdd(yyyy8); + yyyy8 = yyyy8.redIAdd(yyyy8); + + // X3 = T + nx = t; + // Y3 = M * (S - T) - 8 * YYYY + ny = m.redMul(s.redISub(t)).redISub(yyyy8); + // Z3 = 2*Y1 + nz = this.y.redAdd(this.y); + } else { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html + // #doubling-dbl-2009-l + // 2M + 5S + 13A + + // A = X1^2 + var a = this.x.redSqr(); + // B = Y1^2 + var b = this.y.redSqr(); + // C = B^2 + var c = b.redSqr(); + // D = 2 * ((X1 + B)^2 - A - C) + var d = this.x.redAdd(b).redSqr().redISub(a).redISub(c); + d = d.redIAdd(d); + // E = 3 * A + var e = a.redAdd(a).redIAdd(a); + // F = E^2 + var f = e.redSqr(); + + // 8 * C + var c8 = c.redIAdd(c); + c8 = c8.redIAdd(c8); + c8 = c8.redIAdd(c8); + + // X3 = F - 2 * D + nx = f.redISub(d).redISub(d); + // Y3 = E * (D - X3) - 8 * C + ny = e.redMul(d.redISub(nx)).redISub(c8); + // Z3 = 2 * Y1 * Z1 + nz = this.y.redMul(this.z); + nz = nz.redIAdd(nz); + } + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype._threeDbl = function _threeDbl() { + var nx; + var ny; + var nz; + // Z = 1 + if (this.zOne) { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html + // #doubling-mdbl-2007-bl + // 1M + 5S + 15A + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // S = 2 * ((X1 + YY)^2 - XX - YYYY) + var s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + s = s.redIAdd(s); + // M = 3 * XX + a + var m = xx.redAdd(xx).redIAdd(xx).redIAdd(this.curve.a); + // T = M^2 - 2 * S + var t = m.redSqr().redISub(s).redISub(s); + // X3 = T + nx = t; + // Y3 = M * (S - T) - 8 * YYYY + var yyyy8 = yyyy.redIAdd(yyyy); + yyyy8 = yyyy8.redIAdd(yyyy8); + yyyy8 = yyyy8.redIAdd(yyyy8); + ny = m.redMul(s.redISub(t)).redISub(yyyy8); + // Z3 = 2 * Y1 + nz = this.y.redAdd(this.y); + } else { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b + // 3M + 5S + + // delta = Z1^2 + var delta = this.z.redSqr(); + // gamma = Y1^2 + var gamma = this.y.redSqr(); + // beta = X1 * gamma + var beta = this.x.redMul(gamma); + // alpha = 3 * (X1 - delta) * (X1 + delta) + var alpha = this.x.redSub(delta).redMul(this.x.redAdd(delta)); + alpha = alpha.redAdd(alpha).redIAdd(alpha); + // X3 = alpha^2 - 8 * beta + var beta4 = beta.redIAdd(beta); + beta4 = beta4.redIAdd(beta4); + var beta8 = beta4.redAdd(beta4); + nx = alpha.redSqr().redISub(beta8); + // Z3 = (Y1 + Z1)^2 - gamma - delta + nz = this.y.redAdd(this.z).redSqr().redISub(gamma).redISub(delta); + // Y3 = alpha * (4 * beta - X3) - 8 * gamma^2 + var ggamma8 = gamma.redSqr(); + ggamma8 = ggamma8.redIAdd(ggamma8); + ggamma8 = ggamma8.redIAdd(ggamma8); + ggamma8 = ggamma8.redIAdd(ggamma8); + ny = alpha.redMul(beta4.redISub(nx)).redISub(ggamma8); + } + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype._dbl = function _dbl() { + var a = this.curve.a; + + // 4M + 6S + 10A + var jx = this.x; + var jy = this.y; + var jz = this.z; + var jz4 = jz.redSqr().redSqr(); + + var jx2 = jx.redSqr(); + var jy2 = jy.redSqr(); + + var c = jx2.redAdd(jx2).redIAdd(jx2).redIAdd(a.redMul(jz4)); + + var jxd4 = jx.redAdd(jx); + jxd4 = jxd4.redIAdd(jxd4); + var t1 = jxd4.redMul(jy2); + var nx = c.redSqr().redISub(t1.redAdd(t1)); + var t2 = t1.redISub(nx); + + var jyd8 = jy2.redSqr(); + jyd8 = jyd8.redIAdd(jyd8); + jyd8 = jyd8.redIAdd(jyd8); + jyd8 = jyd8.redIAdd(jyd8); + var ny = c.redMul(t2).redISub(jyd8); + var nz = jy.redAdd(jy).redMul(jz); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.trpl = function trpl() { + if (!this.curve.zeroA) + return this.dbl().add(this); + + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#tripling-tpl-2007-bl + // 5M + 10S + ... + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // ZZ = Z1^2 + var zz = this.z.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // M = 3 * XX + a * ZZ2; a = 0 + var m = xx.redAdd(xx).redIAdd(xx); + // MM = M^2 + var mm = m.redSqr(); + // E = 6 * ((X1 + YY)^2 - XX - YYYY) - MM + var e = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + e = e.redIAdd(e); + e = e.redAdd(e).redIAdd(e); + e = e.redISub(mm); + // EE = E^2 + var ee = e.redSqr(); + // T = 16*YYYY + var t = yyyy.redIAdd(yyyy); + t = t.redIAdd(t); + t = t.redIAdd(t); + t = t.redIAdd(t); + // U = (M + E)^2 - MM - EE - T + var u = m.redIAdd(e).redSqr().redISub(mm).redISub(ee).redISub(t); + // X3 = 4 * (X1 * EE - 4 * YY * U) + var yyu4 = yy.redMul(u); + yyu4 = yyu4.redIAdd(yyu4); + yyu4 = yyu4.redIAdd(yyu4); + var nx = this.x.redMul(ee).redISub(yyu4); + nx = nx.redIAdd(nx); + nx = nx.redIAdd(nx); + // Y3 = 8 * Y1 * (U * (T - U) - E * EE) + var ny = this.y.redMul(u.redMul(t.redISub(u)).redISub(e.redMul(ee))); + ny = ny.redIAdd(ny); + ny = ny.redIAdd(ny); + ny = ny.redIAdd(ny); + // Z3 = (Z1 + E)^2 - ZZ - EE + var nz = this.z.redAdd(e).redSqr().redISub(zz).redISub(ee); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.mul = function mul(k, kbase) { + k = new BN(k, kbase); + + return this.curve._wnafMul(this, k); +}; + +JPoint.prototype.eq = function eq(p) { + if (p.type === 'affine') + return this.eq(p.toJ()); + + if (this === p) + return true; + + // x1 * z2^2 == x2 * z1^2 + var z2 = this.z.redSqr(); + var pz2 = p.z.redSqr(); + if (this.x.redMul(pz2).redISub(p.x.redMul(z2)).cmpn(0) !== 0) + return false; + + // y1 * z2^3 == y2 * z1^3 + var z3 = z2.redMul(this.z); + var pz3 = pz2.redMul(p.z); + return this.y.redMul(pz3).redISub(p.y.redMul(z3)).cmpn(0) === 0; +}; + +JPoint.prototype.eqXToP = function eqXToP(x) { + var zs = this.z.redSqr(); + var rx = x.toRed(this.curve.red).redMul(zs); + if (this.x.cmp(rx) === 0) + return true; + + var xc = x.clone(); + var t = this.curve.redN.redMul(zs); + for (;;) { + xc.iadd(this.curve.n); + if (xc.cmp(this.curve.p) >= 0) + return false; + + rx.redIAdd(t); + if (this.x.cmp(rx) === 0) + return true; + } + return false; +}; + +JPoint.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +JPoint.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.z.cmpn(0) === 0; +}; + +},{"../../../../BN/bn":123,"../../elliptic":125,"../curve":128}],131:[function(require,module,exports){ +'use strict'; + +var curves = exports; + +var elliptic = require('../elliptic'); + +var assert = elliptic.utils.assert; + +function PresetCurve(options) { + if (options.type === 'short') + this.curve = new elliptic.curve.short(options); + else if (options.type === 'edwards') + this.curve = new elliptic.curve.edwards(options); + else + this.curve = new elliptic.curve.mont(options); + this.g = this.curve.g; + this.n = this.curve.n; + this.hash = options.hash; + + assert(this.g.validate(), 'Invalid curve'); + assert(this.g.mul(this.n).isInfinity(), 'Invalid curve, G*N != O'); +} +curves.PresetCurve = PresetCurve; + +function defineCurve(name, options) { + Object.defineProperty(curves, name, { + configurable: true, + enumerable: true, + get: function() { + var curve = new PresetCurve(options); + Object.defineProperty(curves, name, { + configurable: true, + enumerable: true, + value: curve + }); + return curve; + } + }); +} + +defineCurve('p192', { + type: 'short', + prime: 'p192', + p: 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff', + a: 'ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc', + b: '64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1', + n: 'ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831', + hash: 'SHA256', + gRed: false, + g: [ + '188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012', + '07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811' + ] +}); + +defineCurve('p224', { + type: 'short', + prime: 'p224', + p: 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001', + a: 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe', + b: 'b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4', + n: 'ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d', + hash: 'SHA256', + gRed: false, + g: [ + 'b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21', + 'bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34' + ] +}); + +defineCurve('p256', { + type: 'short', + prime: null, + p: 'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff', + a: 'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc', + b: '5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b', + n: 'ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551', + hash: 'SHA256', + gRed: false, + g: [ + '6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296', + '4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5' + ] +}); + +defineCurve('p384', { + type: 'short', + prime: null, + p: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'fffffffe ffffffff 00000000 00000000 ffffffff', + a: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'fffffffe ffffffff 00000000 00000000 fffffffc', + b: 'b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f ' + + '5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef', + n: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 ' + + 'f4372ddf 581a0db2 48b0a77a ecec196a ccc52973', + hash: 'SHA384', + gRed: false, + g: [ + 'aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 ' + + '5502f25d bf55296c 3a545e38 72760ab7', + '3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 ' + + '0a60b1ce 1d7e819d 7a431d7c 90ea0e5f' + ] +}); + +defineCurve('p521', { + type: 'short', + prime: null, + p: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff', + a: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff fffffffc', + b: '00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b ' + + '99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd ' + + '3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00', + n: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 ' + + 'f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409', + hash: 'SHA512', + gRed: false, + g: [ + '000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 ' + + '053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 ' + + 'a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66', + '00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 ' + + '579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 ' + + '3fad0761 353c7086 a272c240 88be9476 9fd16650' + ] +}); + +defineCurve('curve25519', { + type: 'mont', + prime: 'p25519', + p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed', + a: '76d06', + b: '1', + n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed', + hash: 'SHA256', + gRed: false, + g: [ + '9' + ] +}); + +defineCurve('ed25519', { + type: 'edwards', + prime: 'p25519', + p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed', + a: '-1', + c: '1', + // -121665 * (121666^(-1)) (mod P) + d: '52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3', + n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed', + hash: 'SHA256', + gRed: false, + g: [ + '216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a', + + // 4/5 + '6666666666666666666666666666666666666666666666666666666666666658' + ] +}); + +var pre; +try { + pre = require('./precomputed/secp256k1'); +} catch (e) { + pre = undefined; +} + +defineCurve('secp256k1', { + type: 'short', + prime: 'k256', + p: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f', + a: '0', + b: '7', + n: 'ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141', + h: '1', + hash: 'SHA256', + + // Precomputed endomorphism + beta: '7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee', + lambda: '5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72', + basis: [ + { + a: '3086d221a7d46bcde86c90e49284eb15', + b: '-e4437ed6010e88286f547fa90abfe4c3' + }, + { + a: '114ca50f7a8e2f3f657c1108d9d44cfd8', + b: '3086d221a7d46bcde86c90e49284eb15' + } + ], + + gRed: false, + g: [ + '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', + '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', + pre + ] +}); + +},{"../elliptic":125,"./precomputed/secp256k1":138}],132:[function(require,module,exports){ +'use strict'; + +var BN = require('../../../../BN/bn'); +var HmacDRBG = require('../../hmac-drbg/hmac-drbg'); +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var assert = utils.assert; + +var KeyPair = require('./key'); +var Signature = require('./signature'); + +function EC(options) { + if (!(this instanceof EC)) + return new EC(options); + + // Shortcut `elliptic.ec(curve-name)` + if (typeof options === 'string') { + assert(elliptic.curves.hasOwnProperty(options), 'Unknown curve ' + options); + + options = elliptic.curves[options]; + } + + // Shortcut for `elliptic.ec(elliptic.curves.curveName)` + if (options instanceof elliptic.curves.PresetCurve) + options = { curve: options }; + + this.curve = options.curve.curve; + this.n = this.curve.n; + this.nh = this.n.ushrn(1); + this.g = this.curve.g; + + // Point on curve + this.g = options.curve.g; + this.g.precompute(options.curve.n.bitLength() + 1); + + // Hash for function for DRBG + this.hash = options.hash || options.curve.hash; +} +module.exports = EC; + +EC.prototype.keyPair = function keyPair(options) { + return new KeyPair(this, options); +}; + +EC.prototype.keyFromPrivate = function keyFromPrivate(priv, enc) { + return KeyPair.fromPrivate(this, priv, enc); +}; + +EC.prototype.keyFromPublic = function keyFromPublic(pub, enc) { + return KeyPair.fromPublic(this, pub, enc); +}; + +EC.prototype.genKeyPair = function genKeyPair(options) { + if (!options) + options = {}; + + // Instantiate Hmac_DRBG + var drbg = new HmacDRBG({ + hash: this.hash, + pers: options.pers, + persEnc: options.persEnc || 'utf8', + entropy: options.entropy || null, //elliptic.rand(this.hash.hmacStrength), + entropyEnc: options.entropy && options.entropyEnc || 'utf8', + nonce: this.n.toArray() + }); + + var bytes = this.n.byteLength(); + var ns2 = this.n.sub(new BN(2)); + do { + var priv = new BN(drbg.generate(bytes)); + if (priv.cmp(ns2) > 0) + continue; + + priv.iaddn(1); + return this.keyFromPrivate(priv); + } while (true); +}; + +EC.prototype._truncateToN = function truncateToN(msg, truncOnly) { + var delta = msg.byteLength() * 8 - this.n.bitLength(); + if (delta > 0) + msg = msg.ushrn(delta); + if (!truncOnly && msg.cmp(this.n) >= 0) + return msg.sub(this.n); + else + return msg; +}; + +EC.prototype.sign = function sign(msg, key, enc, options) { + if (typeof enc === 'object') { + options = enc; + enc = null; + } + if (!options) + options = {}; + + key = this.keyFromPrivate(key, enc); + msg = this._truncateToN(new BN(msg, 16)); + + // Zero-extend key to provide enough entropy + var bytes = this.n.byteLength(); + var bkey = key.getPrivate().toArray('be', bytes); + + // Zero-extend nonce to have the same byte size as N + var nonce = msg.toArray('be', bytes); + + // Instantiate Hmac_DRBG + var drbg = new HmacDRBG({ + hash: this.hash, + entropy: options['extraEntropy'] ? bkey.concat (options['extraEntropy']) : bkey, + nonce: nonce, + pers: options.pers, + persEnc: options.persEnc || 'utf8' + }); + + // Number of bytes to generate + var ns1 = this.n.sub(new BN(1)); + + for (var iter = 0; true; iter++) { + var k = options.k ? + options.k(iter) : + new BN(drbg.generate(this.n.byteLength())); + k = this._truncateToN(k, true); + if (k.cmpn(1) <= 0 || k.cmp(ns1) >= 0) + continue; + + var kp = this.g.mul(k); + if (kp.isInfinity()) + continue; + + var kpX = kp.getX(); + var r = kpX.umod(this.n); + if (r.cmpn(0) === 0) + continue; + + var s = k.invm(this.n).mul(r.mul(key.getPrivate()).iadd(msg)); + s = s.umod(this.n); + if (s.cmpn(0) === 0) + continue; + + var recoveryParam = (kp.getY().isOdd() ? 1 : 0) | + (kpX.cmp(r) !== 0 ? 2 : 0); + + // Use complement of `s`, if it is > `n / 2` + if (options.canonical && s.cmp(this.nh) > 0) { + s = this.n.sub(s); + recoveryParam ^= 1; + } + + return new Signature({ r: r, s: s, recoveryParam: recoveryParam }); + } +}; + +EC.prototype.verify = function verify(msg, signature, key, enc) { + msg = this._truncateToN(new BN(msg, 16)); + key = this.keyFromPublic(key, enc); + signature = new Signature(signature, 'hex'); + + // Perform primitive values validation + var r = signature.r; + var s = signature.s; + if (r.cmpn(1) < 0 || r.cmp(this.n) >= 0) + return false; + if (s.cmpn(1) < 0 || s.cmp(this.n) >= 0) + return false; + + // Validate signature + var sinv = s.invm(this.n); + var u1 = sinv.mul(msg).umod(this.n); + var u2 = sinv.mul(r).umod(this.n); + + if (!this.curve._maxwellTrick) { + var p = this.g.mulAdd(u1, key.getPublic(), u2); + if (p.isInfinity()) + return false; + + return p.getX().umod(this.n).cmp(r) === 0; + } + + // NOTE: Greg Maxwell's trick, inspired by: + // https://git.io/vad3K + + var p = this.g.jmulAdd(u1, key.getPublic(), u2); + if (p.isInfinity()) + return false; + + // Compare `p.x` of Jacobian point with `r`, + // this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the + // inverse of `p.z^2` + return p.eqXToP(r); +}; + +EC.prototype.recoverPubKey = function(msg, signature, j, enc) { + assert((3 & j) === j, 'The recovery param is more than two bits'); + signature = new Signature(signature, enc); + + var n = this.n; + var e = new BN(msg); + var r = signature.r; + var s = signature.s; + + // A set LSB signifies that the y-coordinate is odd + var isYOdd = j & 1; + var isSecondKey = j >> 1; + if (r.cmp(this.curve.p.umod(this.curve.n)) >= 0 && isSecondKey) + throw new Error('Unable to find sencond key candinate'); + + // 1.1. Let x = r + jn. + if (isSecondKey) + r = this.curve.pointFromX(r.add(this.curve.n), isYOdd); + else + r = this.curve.pointFromX(r, isYOdd); + + var rInv = signature.r.invm(n); + var s1 = n.sub(e).mul(rInv).umod(n); + var s2 = s.mul(rInv).umod(n); + + // 1.6.1 Compute Q = r^-1 (sR - eG) + // Q = r^-1 (sR + -eG) + return this.g.mulAdd(s1, r, s2); +}; + +EC.prototype.getKeyRecoveryParam = function(e, signature, Q, enc) { + signature = new Signature(signature, enc); + if (signature.recoveryParam !== null) + return signature.recoveryParam; + + for (var i = 0; i < 4; i++) { + var Qprime; + try { + Qprime = this.recoverPubKey(e, signature, i); + } catch (e) { + continue; + } + + if (Qprime.eq(Q)) + return i; + } + throw new Error('Unable to find valid recovery factor'); +}; + +},{"../../../../BN/bn":123,"../../elliptic":125,"../../hmac-drbg/hmac-drbg":140,"./key":133,"./signature":134}],133:[function(require,module,exports){ +'use strict'; + +var BN = require('../../../../BN/bn'); +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var assert = utils.assert; + +function KeyPair(ec, options) { + this.ec = ec; + this.priv = null; + this.pub = null; + + // KeyPair(ec, { priv: ..., pub: ... }) + if (options.priv) + this._importPrivate(options.priv, options.privEnc); + if (options.pub) + this._importPublic(options.pub, options.pubEnc); +} +module.exports = KeyPair; + +KeyPair.fromPublic = function fromPublic(ec, pub, enc) { + if (pub instanceof KeyPair) + return pub; + + return new KeyPair(ec, { + pub: pub, + pubEnc: enc + }); +}; + +KeyPair.fromPrivate = function fromPrivate(ec, priv, enc) { + if (priv instanceof KeyPair) + return priv; + + return new KeyPair(ec, { + priv: priv, + privEnc: enc + }); +}; + +KeyPair.prototype.validate = function validate() { + var pub = this.getPublic(); + + if (pub.isInfinity()) + return { result: false, reason: 'Invalid public key' }; + if (!pub.validate()) + return { result: false, reason: 'Public key is not a point' }; + if (!pub.mul(this.ec.curve.n).isInfinity()) + return { result: false, reason: 'Public key * N != O' }; + + return { result: true, reason: null }; +}; + +KeyPair.prototype.getPublic = function getPublic(compact, enc) { + // compact is optional argument + if (typeof compact === 'string') { + enc = compact; + compact = null; + } + + if (!this.pub) + this.pub = this.ec.g.mul(this.priv); + + if (!enc) + return this.pub; + + return this.pub.encode(enc, compact); +}; + +KeyPair.prototype.getPrivate = function getPrivate(enc) { + if (enc === 'hex') + return this.priv.toString(16, 2); + else + return this.priv; +}; + +KeyPair.prototype._importPrivate = function _importPrivate(key, enc) { + this.priv = new BN(key, enc || 16); + + // Ensure that the priv won't be bigger than n, otherwise we may fail + // in fixed multiplication method + this.priv = this.priv.umod(this.ec.curve.n); +}; + +KeyPair.prototype._importPublic = function _importPublic(key, enc) { + if (key.x || key.y) { + // Montgomery points only have an `x` coordinate. + // Weierstrass/Edwards points on the other hand have both `x` and + // `y` coordinates. + if (this.ec.curve.type === 'mont') { + assert(key.x, 'Need x coordinate'); + } else if (this.ec.curve.type === 'short' || + this.ec.curve.type === 'edwards') { + assert(key.x && key.y, 'Need both x and y coordinate'); + } + this.pub = this.ec.curve.point(key.x, key.y); + return; + } + this.pub = this.ec.curve.decodePoint(key, enc); +}; + +// ECDH +KeyPair.prototype.derive = function derive(pub) { + return pub.mul(this.priv).getX(); +}; + +// ECDSA +KeyPair.prototype.sign = function sign(msg, enc, options) { + return this.ec.sign(msg, this, enc, options); +}; + +KeyPair.prototype.verify = function verify(msg, signature) { + return this.ec.verify(msg, signature, this); +}; + +KeyPair.prototype.inspect = function inspect() { + return ''; +}; + +},{"../../../../BN/bn":123,"../../elliptic":125}],134:[function(require,module,exports){ +'use strict'; + +var BN = require('../../../../BN/bn'); + +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var assert = utils.assert; + +function Signature(options, enc) { + if (options instanceof Signature) + return options; + + //if (this._importDER(options, enc)) + // return; + + assert(options.r && options.s, 'Signature without r or s'); + this.r = new BN(options.r, 16); + this.s = new BN(options.s, 16); + if (options.recoveryParam === undefined) + this.recoveryParam = null; + else + this.recoveryParam = options.recoveryParam; +} +module.exports = Signature; + +},{"../../../../BN/bn":123,"../../elliptic":125}],135:[function(require,module,exports){ +'use strict'; + +var curves = require('../curves'); +var utils = require('../utils'); +var assert = utils.assert; +var parseBytes = utils.parseBytes; +var KeyPair = require('./key'); +var Signature = require('./signature'); +const BN = require ('../../../../BN/bn.js') +const { byteArrayToWordArray } = require ('../../../../../base/functions/encode') +const CryptoJS = require('../../../../crypto-js/crypto-js'); + + +function EDDSA(curveName) { + assert(curveName === 'ed25519', 'only tested with ed25519 so far'); + + if (!(this instanceof EDDSA)) + return new EDDSA(curveName); + + var curve = curves[curveName].curve; + this.curve = curve; + this.g = curve.g; + this.g.precompute(curve.n.bitLength() + 1); + + this.pointClass = curve.point().constructor; + this.encodingLength = Math.ceil(curve.n.bitLength() / 8); + //this.hash = hash.sha512; +} + +module.exports = EDDSA; + +/** +* @param {Array|String} message - message bytes +* @param {Array|String|KeyPair} secret - secret bytes or a keypair +* @returns {Signature} - signature +*/ +EDDSA.prototype.sign = function sign(message, secret) { + message = parseBytes(message); + var key = this.keyFromSecret(secret); + var r = this.hashInt(key.secret (), message); + var R = this.g.mul(r); + var Rencoded = this.encodePoint(R); + var s_ = this.hashInt(Rencoded, key.pubBytes(), message) + s_ = s_.mul (key.priv ()) + var S = r.add(s_).umod(this.curve.n); + return this.makeSignature({ R: R, S: S, Rencoded: Rencoded }); +}; + +/** + * @param {Array|String} message - message bytes + * @param {Array|String|KeyPair} secret - secret bytes or a keypair + * @returns {Signature} - signature + */ +EDDSA.prototype.signModified = function sign(message, secret) { + message = parseBytes(message); + var key = this.keyFromSecret(secret); + // convert between curve25519 and ed25519 keys + const secretLE = new BN (key.secret (), 16, 'le') + const pubKey = this.encodePoint (this.g.mul (secretLE)) + const signBit = pubKey[31] & 0x80 + var r = this.hashInt (key.secret (), message) + var R = this.g.mul(r); + var Rencoded = this.encodePoint (R); + let s_ = this.hashInt (Rencoded, pubKey, message) + s_ = s_.mul (secretLE) + var S = r.add(s_).umod(this.curve.n); + var Sencoded = S.toArray ('le') + Sencoded[31] |= signBit + return this.makeSignature({ R: R, S: S, Rencoded: Rencoded, Sencoded: Sencoded }); +}; + + +/** +* @param {Array} message - message bytes +* @param {Array|String|Signature} sig - sig bytes +* @param {Array|String|Point|KeyPair} pub - public key +* @returns {Boolean} - true if public key matches sig of message +*/ +EDDSA.prototype.verify = function verify(message, sig, pub) { + message = parseBytes(message); + sig = this.makeSignature(sig); + var key = this.keyFromPublic(pub); + var h = this.hashInt(sig.Rencoded(), key.pubBytes(), message); + var SG = this.g.mul(sig.S()); + var RplusAh = sig.R().add(key.pub().mul(h)); + return RplusAh.eq(SG); +}; + +EDDSA.prototype.hashInt = function hashInt() { + let toHash = Array.from (arguments).reduce ((a, b) => a.concat (b)) + toHash = byteArrayToWordArray (toHash) + const digest = CryptoJS['SHA512'] (toHash).toString (CryptoJS.enc.Hex) + return utils.intFromLE(digest).umod(this.curve.n); +}; + +EDDSA.prototype.keyFromPublic = function keyFromPublic(pub) { + return KeyPair.fromPublic(this, pub); +}; + +EDDSA.prototype.keyFromSecret = function keyFromSecret(secret) { + return KeyPair.fromSecret(this, secret); +}; + +EDDSA.prototype.makeSignature = function makeSignature(sig) { + if (sig instanceof Signature) + return sig; + return new Signature(this, sig); +}; + +/** +* * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03#section-5.2 +* +* EDDSA defines methods for encoding and decoding points and integers. These are +* helper convenience methods, that pass along to utility functions implied +* parameters. +* +*/ +EDDSA.prototype.encodePoint = function encodePoint(point) { + var enc = point.getY().toArray('le', this.encodingLength); + enc[this.encodingLength - 1] |= point.getX().isOdd() ? 0x80 : 0; + return enc; +}; + +EDDSA.prototype.decodePoint = function decodePoint(bytes) { + bytes = utils.parseBytes(bytes); + + var lastIx = bytes.length - 1; + var normed = bytes.slice(0, lastIx).concat(bytes[lastIx] & ~0x80); + var xIsOdd = (bytes[lastIx] & 0x80) !== 0; + + var y = utils.intFromLE(normed); + return this.curve.pointFromY(y, xIsOdd); +}; + +EDDSA.prototype.encodeInt = function encodeInt(num) { + return num.toArray('le', this.encodingLength); +}; + +EDDSA.prototype.decodeInt = function decodeInt(bytes) { + return utils.intFromLE(bytes); +}; + +EDDSA.prototype.isPoint = function isPoint(val) { + return val instanceof this.pointClass; +}; + +},{"../../../../../base/functions/encode":10,"../../../../BN/bn.js":123,"../../../../crypto-js/crypto-js":124,"../curves":131,"../utils":139,"./key":136,"./signature":137}],136:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var assert = utils.assert; +var parseBytes = utils.parseBytes; +var cachedProperty = utils.cachedProperty; + +/** +* @param {EDDSA} eddsa - instance +* @param {Object} params - public/private key parameters +* +* @param {Array} [params.secret] - secret seed bytes +* @param {Point} [params.pub] - public key point (aka `A` in eddsa terms) +* @param {Array} [params.pub] - public key point encoded as bytes +* +*/ +function KeyPair(eddsa, params) { + this.eddsa = eddsa; + this._secret = parseBytes(params.secret); + if (eddsa.isPoint(params.pub)) + this._pub = params.pub; + else + this._pubBytes = parseBytes(params.pub); +} + +KeyPair.fromPublic = function fromPublic(eddsa, pub) { + if (pub instanceof KeyPair) + return pub; + return new KeyPair(eddsa, { pub: pub }); +}; + +KeyPair.fromSecret = function fromSecret(eddsa, secret) { + if (secret instanceof KeyPair) + return secret; + return new KeyPair(eddsa, { secret: secret }); +}; + +KeyPair.prototype.secret = function secret() { + return this._secret; +}; + +cachedProperty(KeyPair, 'pubBytes', function pubBytes() { + return this.eddsa.encodePoint(this.pub()); +}); + +cachedProperty(KeyPair, 'pub', function pub() { + if (this._pubBytes) + return this.eddsa.decodePoint(this._pubBytes); + return this.eddsa.g.mul(this.priv()); +}); + +cachedProperty(KeyPair, 'privBytes', function privBytes() { + var eddsa = this.eddsa; + var hash = this.hash(); + var lastIx = eddsa.encodingLength - 1; + + var a = hash.slice(0, eddsa.encodingLength); + a[0] &= 248; + a[lastIx] &= 127; + a[lastIx] |= 64; + + return a; +}); + +cachedProperty(KeyPair, 'priv', function priv() { + return this.eddsa.decodeInt(this.privBytes()); +}); + +cachedProperty(KeyPair, 'hash', function hash() { + return this.eddsa.hash().update(this.secret()).digest(); +}); + +cachedProperty(KeyPair, 'messagePrefix', function messagePrefix() { + return this.hash().slice(this.eddsa.encodingLength); +}); + +KeyPair.prototype.sign = function sign(message) { + assert(this._secret, 'KeyPair can only verify'); + return this.eddsa.sign(message, this); +}; + +KeyPair.prototype.signModified = function sign(message) { + assert(this._secret, 'KeyPair can only verify'); + return this.eddsa.signModified(message, this); +}; + +KeyPair.prototype.verify = function verify(message, sig) { + return this.eddsa.verify(message, sig, this); +}; + +KeyPair.prototype.getSecret = function getSecret(enc) { + assert(this._secret, 'KeyPair is public only'); + return utils.encode(this.secret(), enc); +}; + +KeyPair.prototype.getPublic = function getPublic(enc) { + return utils.encode(this.pubBytes(), enc); +}; + +module.exports = KeyPair; + +},{"../utils":139}],137:[function(require,module,exports){ +'use strict'; + +var BN = require('../../../../BN/bn'); +var utils = require('../utils'); +var assert = utils.assert; +var cachedProperty = utils.cachedProperty; +var parseBytes = utils.parseBytes; + +/** +* @param {EDDSA} eddsa - eddsa instance +* @param {Array|Object} sig - +* @param {Array|Point} [sig.R] - R point as Point or bytes +* @param {Array|bn} [sig.S] - S scalar as bn or bytes +* @param {Array} [sig.Rencoded] - R point encoded +* @param {Array} [sig.Sencoded] - S scalar encoded +*/ +function Signature(eddsa, sig) { + this.eddsa = eddsa; + + if (typeof sig !== 'object') + sig = parseBytes(sig); + + if (Array.isArray(sig)) { + sig = { + R: sig.slice(0, eddsa.encodingLength), + S: sig.slice(eddsa.encodingLength) + }; + } + + assert(sig.R && sig.S, 'Signature without R or S'); + + if (eddsa.isPoint(sig.R)) + this._R = sig.R; + if (sig.S instanceof BN) + this._S = sig.S; + + this._Rencoded = Array.isArray(sig.R) ? sig.R : sig.Rencoded; + this._Sencoded = Array.isArray(sig.S) ? sig.S : sig.Sencoded; +} + +cachedProperty(Signature, 'S', function S() { + return this.eddsa.decodeInt(this.Sencoded()); +}); + +cachedProperty(Signature, 'R', function R() { + return this.eddsa.decodePoint(this.Rencoded()); +}); + +cachedProperty(Signature, 'Rencoded', function Rencoded() { + return this.eddsa.encodePoint(this.R()); +}); + +cachedProperty(Signature, 'Sencoded', function Sencoded() { + return this.eddsa.encodeInt(this.S()); +}); + +Signature.prototype.toBytes = function toBytes() { + return this.Rencoded().concat(this.Sencoded()); +}; + +Signature.prototype.toHex = function toHex() { + return utils.encode(this.toBytes(), 'hex').toUpperCase(); +}; + +module.exports = Signature; + +},{"../../../../BN/bn":123,"../utils":139}],138:[function(require,module,exports){ +module.exports = { + doubles: { + step: 4, + points: [ + [ + 'e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a', + 'f7e3507399e595929db99f34f57937101296891e44d23f0be1f32cce69616821' + ], + [ + '8282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508', + '11f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf' + ], + [ + '175e159f728b865a72f99cc6c6fc846de0b93833fd2222ed73fce5b551e5b739', + 'd3506e0d9e3c79eba4ef97a51ff71f5eacb5955add24345c6efa6ffee9fed695' + ], + [ + '363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640', + '4e273adfc732221953b445397f3363145b9a89008199ecb62003c7f3bee9de9' + ], + [ + '8b4b5f165df3c2be8c6244b5b745638843e4a781a15bcd1b69f79a55dffdf80c', + '4aad0a6f68d308b4b3fbd7813ab0da04f9e336546162ee56b3eff0c65fd4fd36' + ], + [ + '723cbaa6e5db996d6bf771c00bd548c7b700dbffa6c0e77bcb6115925232fcda', + '96e867b5595cc498a921137488824d6e2660a0653779494801dc069d9eb39f5f' + ], + [ + 'eebfa4d493bebf98ba5feec812c2d3b50947961237a919839a533eca0e7dd7fa', + '5d9a8ca3970ef0f269ee7edaf178089d9ae4cdc3a711f712ddfd4fdae1de8999' + ], + [ + '100f44da696e71672791d0a09b7bde459f1215a29b3c03bfefd7835b39a48db0', + 'cdd9e13192a00b772ec8f3300c090666b7ff4a18ff5195ac0fbd5cd62bc65a09' + ], + [ + 'e1031be262c7ed1b1dc9227a4a04c017a77f8d4464f3b3852c8acde6e534fd2d', + '9d7061928940405e6bb6a4176597535af292dd419e1ced79a44f18f29456a00d' + ], + [ + 'feea6cae46d55b530ac2839f143bd7ec5cf8b266a41d6af52d5e688d9094696d', + 'e57c6b6c97dce1bab06e4e12bf3ecd5c981c8957cc41442d3155debf18090088' + ], + [ + 'da67a91d91049cdcb367be4be6ffca3cfeed657d808583de33fa978bc1ec6cb1', + '9bacaa35481642bc41f463f7ec9780e5dec7adc508f740a17e9ea8e27a68be1d' + ], + [ + '53904faa0b334cdda6e000935ef22151ec08d0f7bb11069f57545ccc1a37b7c0', + '5bc087d0bc80106d88c9eccac20d3c1c13999981e14434699dcb096b022771c8' + ], + [ + '8e7bcd0bd35983a7719cca7764ca906779b53a043a9b8bcaeff959f43ad86047', + '10b7770b2a3da4b3940310420ca9514579e88e2e47fd68b3ea10047e8460372a' + ], + [ + '385eed34c1cdff21e6d0818689b81bde71a7f4f18397e6690a841e1599c43862', + '283bebc3e8ea23f56701de19e9ebf4576b304eec2086dc8cc0458fe5542e5453' + ], + [ + '6f9d9b803ecf191637c73a4413dfa180fddf84a5947fbc9c606ed86c3fac3a7', + '7c80c68e603059ba69b8e2a30e45c4d47ea4dd2f5c281002d86890603a842160' + ], + [ + '3322d401243c4e2582a2147c104d6ecbf774d163db0f5e5313b7e0e742d0e6bd', + '56e70797e9664ef5bfb019bc4ddaf9b72805f63ea2873af624f3a2e96c28b2a0' + ], + [ + '85672c7d2de0b7da2bd1770d89665868741b3f9af7643397721d74d28134ab83', + '7c481b9b5b43b2eb6374049bfa62c2e5e77f17fcc5298f44c8e3094f790313a6' + ], + [ + '948bf809b1988a46b06c9f1919413b10f9226c60f668832ffd959af60c82a0a', + '53a562856dcb6646dc6b74c5d1c3418c6d4dff08c97cd2bed4cb7f88d8c8e589' + ], + [ + '6260ce7f461801c34f067ce0f02873a8f1b0e44dfc69752accecd819f38fd8e8', + 'bc2da82b6fa5b571a7f09049776a1ef7ecd292238051c198c1a84e95b2b4ae17' + ], + [ + 'e5037de0afc1d8d43d8348414bbf4103043ec8f575bfdc432953cc8d2037fa2d', + '4571534baa94d3b5f9f98d09fb990bddbd5f5b03ec481f10e0e5dc841d755bda' + ], + [ + 'e06372b0f4a207adf5ea905e8f1771b4e7e8dbd1c6a6c5b725866a0ae4fce725', + '7a908974bce18cfe12a27bb2ad5a488cd7484a7787104870b27034f94eee31dd' + ], + [ + '213c7a715cd5d45358d0bbf9dc0ce02204b10bdde2a3f58540ad6908d0559754', + '4b6dad0b5ae462507013ad06245ba190bb4850f5f36a7eeddff2c27534b458f2' + ], + [ + '4e7c272a7af4b34e8dbb9352a5419a87e2838c70adc62cddf0cc3a3b08fbd53c', + '17749c766c9d0b18e16fd09f6def681b530b9614bff7dd33e0b3941817dcaae6' + ], + [ + 'fea74e3dbe778b1b10f238ad61686aa5c76e3db2be43057632427e2840fb27b6', + '6e0568db9b0b13297cf674deccb6af93126b596b973f7b77701d3db7f23cb96f' + ], + [ + '76e64113f677cf0e10a2570d599968d31544e179b760432952c02a4417bdde39', + 'c90ddf8dee4e95cf577066d70681f0d35e2a33d2b56d2032b4b1752d1901ac01' + ], + [ + 'c738c56b03b2abe1e8281baa743f8f9a8f7cc643df26cbee3ab150242bcbb891', + '893fb578951ad2537f718f2eacbfbbbb82314eef7880cfe917e735d9699a84c3' + ], + [ + 'd895626548b65b81e264c7637c972877d1d72e5f3a925014372e9f6588f6c14b', + 'febfaa38f2bc7eae728ec60818c340eb03428d632bb067e179363ed75d7d991f' + ], + [ + 'b8da94032a957518eb0f6433571e8761ceffc73693e84edd49150a564f676e03', + '2804dfa44805a1e4d7c99cc9762808b092cc584d95ff3b511488e4e74efdf6e7' + ], + [ + 'e80fea14441fb33a7d8adab9475d7fab2019effb5156a792f1a11778e3c0df5d', + 'eed1de7f638e00771e89768ca3ca94472d155e80af322ea9fcb4291b6ac9ec78' + ], + [ + 'a301697bdfcd704313ba48e51d567543f2a182031efd6915ddc07bbcc4e16070', + '7370f91cfb67e4f5081809fa25d40f9b1735dbf7c0a11a130c0d1a041e177ea1' + ], + [ + '90ad85b389d6b936463f9d0512678de208cc330b11307fffab7ac63e3fb04ed4', + 'e507a3620a38261affdcbd9427222b839aefabe1582894d991d4d48cb6ef150' + ], + [ + '8f68b9d2f63b5f339239c1ad981f162ee88c5678723ea3351b7b444c9ec4c0da', + '662a9f2dba063986de1d90c2b6be215dbbea2cfe95510bfdf23cbf79501fff82' + ], + [ + 'e4f3fb0176af85d65ff99ff9198c36091f48e86503681e3e6686fd5053231e11', + '1e63633ad0ef4f1c1661a6d0ea02b7286cc7e74ec951d1c9822c38576feb73bc' + ], + [ + '8c00fa9b18ebf331eb961537a45a4266c7034f2f0d4e1d0716fb6eae20eae29e', + 'efa47267fea521a1a9dc343a3736c974c2fadafa81e36c54e7d2a4c66702414b' + ], + [ + 'e7a26ce69dd4829f3e10cec0a9e98ed3143d084f308b92c0997fddfc60cb3e41', + '2a758e300fa7984b471b006a1aafbb18d0a6b2c0420e83e20e8a9421cf2cfd51' + ], + [ + 'b6459e0ee3662ec8d23540c223bcbdc571cbcb967d79424f3cf29eb3de6b80ef', + '67c876d06f3e06de1dadf16e5661db3c4b3ae6d48e35b2ff30bf0b61a71ba45' + ], + [ + 'd68a80c8280bb840793234aa118f06231d6f1fc67e73c5a5deda0f5b496943e8', + 'db8ba9fff4b586d00c4b1f9177b0e28b5b0e7b8f7845295a294c84266b133120' + ], + [ + '324aed7df65c804252dc0270907a30b09612aeb973449cea4095980fc28d3d5d', + '648a365774b61f2ff130c0c35aec1f4f19213b0c7e332843967224af96ab7c84' + ], + [ + '4df9c14919cde61f6d51dfdbe5fee5dceec4143ba8d1ca888e8bd373fd054c96', + '35ec51092d8728050974c23a1d85d4b5d506cdc288490192ebac06cad10d5d' + ], + [ + '9c3919a84a474870faed8a9c1cc66021523489054d7f0308cbfc99c8ac1f98cd', + 'ddb84f0f4a4ddd57584f044bf260e641905326f76c64c8e6be7e5e03d4fc599d' + ], + [ + '6057170b1dd12fdf8de05f281d8e06bb91e1493a8b91d4cc5a21382120a959e5', + '9a1af0b26a6a4807add9a2daf71df262465152bc3ee24c65e899be932385a2a8' + ], + [ + 'a576df8e23a08411421439a4518da31880cef0fba7d4df12b1a6973eecb94266', + '40a6bf20e76640b2c92b97afe58cd82c432e10a7f514d9f3ee8be11ae1b28ec8' + ], + [ + '7778a78c28dec3e30a05fe9629de8c38bb30d1f5cf9a3a208f763889be58ad71', + '34626d9ab5a5b22ff7098e12f2ff580087b38411ff24ac563b513fc1fd9f43ac' + ], + [ + '928955ee637a84463729fd30e7afd2ed5f96274e5ad7e5cb09eda9c06d903ac', + 'c25621003d3f42a827b78a13093a95eeac3d26efa8a8d83fc5180e935bcd091f' + ], + [ + '85d0fef3ec6db109399064f3a0e3b2855645b4a907ad354527aae75163d82751', + '1f03648413a38c0be29d496e582cf5663e8751e96877331582c237a24eb1f962' + ], + [ + 'ff2b0dce97eece97c1c9b6041798b85dfdfb6d8882da20308f5404824526087e', + '493d13fef524ba188af4c4dc54d07936c7b7ed6fb90e2ceb2c951e01f0c29907' + ], + [ + '827fbbe4b1e880ea9ed2b2e6301b212b57f1ee148cd6dd28780e5e2cf856e241', + 'c60f9c923c727b0b71bef2c67d1d12687ff7a63186903166d605b68baec293ec' + ], + [ + 'eaa649f21f51bdbae7be4ae34ce6e5217a58fdce7f47f9aa7f3b58fa2120e2b3', + 'be3279ed5bbbb03ac69a80f89879aa5a01a6b965f13f7e59d47a5305ba5ad93d' + ], + [ + 'e4a42d43c5cf169d9391df6decf42ee541b6d8f0c9a137401e23632dda34d24f', + '4d9f92e716d1c73526fc99ccfb8ad34ce886eedfa8d8e4f13a7f7131deba9414' + ], + [ + '1ec80fef360cbdd954160fadab352b6b92b53576a88fea4947173b9d4300bf19', + 'aeefe93756b5340d2f3a4958a7abbf5e0146e77f6295a07b671cdc1cc107cefd' + ], + [ + '146a778c04670c2f91b00af4680dfa8bce3490717d58ba889ddb5928366642be', + 'b318e0ec3354028add669827f9d4b2870aaa971d2f7e5ed1d0b297483d83efd0' + ], + [ + 'fa50c0f61d22e5f07e3acebb1aa07b128d0012209a28b9776d76a8793180eef9', + '6b84c6922397eba9b72cd2872281a68a5e683293a57a213b38cd8d7d3f4f2811' + ], + [ + 'da1d61d0ca721a11b1a5bf6b7d88e8421a288ab5d5bba5220e53d32b5f067ec2', + '8157f55a7c99306c79c0766161c91e2966a73899d279b48a655fba0f1ad836f1' + ], + [ + 'a8e282ff0c9706907215ff98e8fd416615311de0446f1e062a73b0610d064e13', + '7f97355b8db81c09abfb7f3c5b2515888b679a3e50dd6bd6cef7c73111f4cc0c' + ], + [ + '174a53b9c9a285872d39e56e6913cab15d59b1fa512508c022f382de8319497c', + 'ccc9dc37abfc9c1657b4155f2c47f9e6646b3a1d8cb9854383da13ac079afa73' + ], + [ + '959396981943785c3d3e57edf5018cdbe039e730e4918b3d884fdff09475b7ba', + '2e7e552888c331dd8ba0386a4b9cd6849c653f64c8709385e9b8abf87524f2fd' + ], + [ + 'd2a63a50ae401e56d645a1153b109a8fcca0a43d561fba2dbb51340c9d82b151', + 'e82d86fb6443fcb7565aee58b2948220a70f750af484ca52d4142174dcf89405' + ], + [ + '64587e2335471eb890ee7896d7cfdc866bacbdbd3839317b3436f9b45617e073', + 'd99fcdd5bf6902e2ae96dd6447c299a185b90a39133aeab358299e5e9faf6589' + ], + [ + '8481bde0e4e4d885b3a546d3e549de042f0aa6cea250e7fd358d6c86dd45e458', + '38ee7b8cba5404dd84a25bf39cecb2ca900a79c42b262e556d64b1b59779057e' + ], + [ + '13464a57a78102aa62b6979ae817f4637ffcfed3c4b1ce30bcd6303f6caf666b', + '69be159004614580ef7e433453ccb0ca48f300a81d0942e13f495a907f6ecc27' + ], + [ + 'bc4a9df5b713fe2e9aef430bcc1dc97a0cd9ccede2f28588cada3a0d2d83f366', + 'd3a81ca6e785c06383937adf4b798caa6e8a9fbfa547b16d758d666581f33c1' + ], + [ + '8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa', + '40a30463a3305193378fedf31f7cc0eb7ae784f0451cb9459e71dc73cbef9482' + ], + [ + '8ea9666139527a8c1dd94ce4f071fd23c8b350c5a4bb33748c4ba111faccae0', + '620efabbc8ee2782e24e7c0cfb95c5d735b783be9cf0f8e955af34a30e62b945' + ], + [ + 'dd3625faef5ba06074669716bbd3788d89bdde815959968092f76cc4eb9a9787', + '7a188fa3520e30d461da2501045731ca941461982883395937f68d00c644a573' + ], + [ + 'f710d79d9eb962297e4f6232b40e8f7feb2bc63814614d692c12de752408221e', + 'ea98e67232d3b3295d3b535532115ccac8612c721851617526ae47a9c77bfc82' + ] + ] + }, + naf: { + wnd: 7, + points: [ + [ + 'f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9', + '388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672' + ], + [ + '2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4', + 'd8ac222636e5e3d6d4dba9dda6c9c426f788271bab0d6840dca87d3aa6ac62d6' + ], + [ + '5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc', + '6aebca40ba255960a3178d6d861a54dba813d0b813fde7b5a5082628087264da' + ], + [ + 'acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe', + 'cc338921b0a7d9fd64380971763b61e9add888a4375f8e0f05cc262ac64f9c37' + ], + [ + '774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb', + 'd984a032eb6b5e190243dd56d7b7b365372db1e2dff9d6a8301d74c9c953c61b' + ], + [ + 'f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8', + 'ab0902e8d880a89758212eb65cdaf473a1a06da521fa91f29b5cb52db03ed81' + ], + [ + 'd7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e', + '581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58' + ], + [ + 'defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34', + '4211ab0694635168e997b0ead2a93daeced1f4a04a95c0f6cfb199f69e56eb77' + ], + [ + '2b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c', + '85e89bc037945d93b343083b5a1c86131a01f60c50269763b570c854e5c09b7a' + ], + [ + '352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5', + '321eb4075348f534d59c18259dda3e1f4a1b3b2e71b1039c67bd3d8bcf81998c' + ], + [ + '2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f', + '2de1068295dd865b64569335bd5dd80181d70ecfc882648423ba76b532b7d67' + ], + [ + '9248279b09b4d68dab21a9b066edda83263c3d84e09572e269ca0cd7f5453714', + '73016f7bf234aade5d1aa71bdea2b1ff3fc0de2a887912ffe54a32ce97cb3402' + ], + [ + 'daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729', + 'a69dce4a7d6c98e8d4a1aca87ef8d7003f83c230f3afa726ab40e52290be1c55' + ], + [ + 'c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db', + '2119a460ce326cdc76c45926c982fdac0e106e861edf61c5a039063f0e0e6482' + ], + [ + '6a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4', + 'e022cf42c2bd4a708b3f5126f16a24ad8b33ba48d0423b6efd5e6348100d8a82' + ], + [ + '1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5', + 'b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396' + ], + [ + '605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479', + '2972d2de4f8d20681a78d93ec96fe23c26bfae84fb14db43b01e1e9056b8c49' + ], + [ + '62d14dab4150bf497402fdc45a215e10dcb01c354959b10cfe31c7e9d87ff33d', + '80fc06bd8cc5b01098088a1950eed0db01aa132967ab472235f5642483b25eaf' + ], + [ + '80c60ad0040f27dade5b4b06c408e56b2c50e9f56b9b8b425e555c2f86308b6f', + '1c38303f1cc5c30f26e66bad7fe72f70a65eed4cbe7024eb1aa01f56430bd57a' + ], + [ + '7a9375ad6167ad54aa74c6348cc54d344cc5dc9487d847049d5eabb0fa03c8fb', + 'd0e3fa9eca8726909559e0d79269046bdc59ea10c70ce2b02d499ec224dc7f7' + ], + [ + 'd528ecd9b696b54c907a9ed045447a79bb408ec39b68df504bb51f459bc3ffc9', + 'eecf41253136e5f99966f21881fd656ebc4345405c520dbc063465b521409933' + ], + [ + '49370a4b5f43412ea25f514e8ecdad05266115e4a7ecb1387231808f8b45963', + '758f3f41afd6ed428b3081b0512fd62a54c3f3afbb5b6764b653052a12949c9a' + ], + [ + '77f230936ee88cbbd73df930d64702ef881d811e0e1498e2f1c13eb1fc345d74', + '958ef42a7886b6400a08266e9ba1b37896c95330d97077cbbe8eb3c7671c60d6' + ], + [ + 'f2dac991cc4ce4b9ea44887e5c7c0bce58c80074ab9d4dbaeb28531b7739f530', + 'e0dedc9b3b2f8dad4da1f32dec2531df9eb5fbeb0598e4fd1a117dba703a3c37' + ], + [ + '463b3d9f662621fb1b4be8fbbe2520125a216cdfc9dae3debcba4850c690d45b', + '5ed430d78c296c3543114306dd8622d7c622e27c970a1de31cb377b01af7307e' + ], + [ + 'f16f804244e46e2a09232d4aff3b59976b98fac14328a2d1a32496b49998f247', + 'cedabd9b82203f7e13d206fcdf4e33d92a6c53c26e5cce26d6579962c4e31df6' + ], + [ + 'caf754272dc84563b0352b7a14311af55d245315ace27c65369e15f7151d41d1', + 'cb474660ef35f5f2a41b643fa5e460575f4fa9b7962232a5c32f908318a04476' + ], + [ + '2600ca4b282cb986f85d0f1709979d8b44a09c07cb86d7c124497bc86f082120', + '4119b88753c15bd6a693b03fcddbb45d5ac6be74ab5f0ef44b0be9475a7e4b40' + ], + [ + '7635ca72d7e8432c338ec53cd12220bc01c48685e24f7dc8c602a7746998e435', + '91b649609489d613d1d5e590f78e6d74ecfc061d57048bad9e76f302c5b9c61' + ], + [ + '754e3239f325570cdbbf4a87deee8a66b7f2b33479d468fbc1a50743bf56cc18', + '673fb86e5bda30fb3cd0ed304ea49a023ee33d0197a695d0c5d98093c536683' + ], + [ + 'e3e6bd1071a1e96aff57859c82d570f0330800661d1c952f9fe2694691d9b9e8', + '59c9e0bba394e76f40c0aa58379a3cb6a5a2283993e90c4167002af4920e37f5' + ], + [ + '186b483d056a033826ae73d88f732985c4ccb1f32ba35f4b4cc47fdcf04aa6eb', + '3b952d32c67cf77e2e17446e204180ab21fb8090895138b4a4a797f86e80888b' + ], + [ + 'df9d70a6b9876ce544c98561f4be4f725442e6d2b737d9c91a8321724ce0963f', + '55eb2dafd84d6ccd5f862b785dc39d4ab157222720ef9da217b8c45cf2ba2417' + ], + [ + '5edd5cc23c51e87a497ca815d5dce0f8ab52554f849ed8995de64c5f34ce7143', + 'efae9c8dbc14130661e8cec030c89ad0c13c66c0d17a2905cdc706ab7399a868' + ], + [ + '290798c2b6476830da12fe02287e9e777aa3fba1c355b17a722d362f84614fba', + 'e38da76dcd440621988d00bcf79af25d5b29c094db2a23146d003afd41943e7a' + ], + [ + 'af3c423a95d9f5b3054754efa150ac39cd29552fe360257362dfdecef4053b45', + 'f98a3fd831eb2b749a93b0e6f35cfb40c8cd5aa667a15581bc2feded498fd9c6' + ], + [ + '766dbb24d134e745cccaa28c99bf274906bb66b26dcf98df8d2fed50d884249a', + '744b1152eacbe5e38dcc887980da38b897584a65fa06cedd2c924f97cbac5996' + ], + [ + '59dbf46f8c94759ba21277c33784f41645f7b44f6c596a58ce92e666191abe3e', + 'c534ad44175fbc300f4ea6ce648309a042ce739a7919798cd85e216c4a307f6e' + ], + [ + 'f13ada95103c4537305e691e74e9a4a8dd647e711a95e73cb62dc6018cfd87b8', + 'e13817b44ee14de663bf4bc808341f326949e21a6a75c2570778419bdaf5733d' + ], + [ + '7754b4fa0e8aced06d4167a2c59cca4cda1869c06ebadfb6488550015a88522c', + '30e93e864e669d82224b967c3020b8fa8d1e4e350b6cbcc537a48b57841163a2' + ], + [ + '948dcadf5990e048aa3874d46abef9d701858f95de8041d2a6828c99e2262519', + 'e491a42537f6e597d5d28a3224b1bc25df9154efbd2ef1d2cbba2cae5347d57e' + ], + [ + '7962414450c76c1689c7b48f8202ec37fb224cf5ac0bfa1570328a8a3d7c77ab', + '100b610ec4ffb4760d5c1fc133ef6f6b12507a051f04ac5760afa5b29db83437' + ], + [ + '3514087834964b54b15b160644d915485a16977225b8847bb0dd085137ec47ca', + 'ef0afbb2056205448e1652c48e8127fc6039e77c15c2378b7e7d15a0de293311' + ], + [ + 'd3cc30ad6b483e4bc79ce2c9dd8bc54993e947eb8df787b442943d3f7b527eaf', + '8b378a22d827278d89c5e9be8f9508ae3c2ad46290358630afb34db04eede0a4' + ], + [ + '1624d84780732860ce1c78fcbfefe08b2b29823db913f6493975ba0ff4847610', + '68651cf9b6da903e0914448c6cd9d4ca896878f5282be4c8cc06e2a404078575' + ], + [ + '733ce80da955a8a26902c95633e62a985192474b5af207da6df7b4fd5fc61cd4', + 'f5435a2bd2badf7d485a4d8b8db9fcce3e1ef8e0201e4578c54673bc1dc5ea1d' + ], + [ + '15d9441254945064cf1a1c33bbd3b49f8966c5092171e699ef258dfab81c045c', + 'd56eb30b69463e7234f5137b73b84177434800bacebfc685fc37bbe9efe4070d' + ], + [ + 'a1d0fcf2ec9de675b612136e5ce70d271c21417c9d2b8aaaac138599d0717940', + 'edd77f50bcb5a3cab2e90737309667f2641462a54070f3d519212d39c197a629' + ], + [ + 'e22fbe15c0af8ccc5780c0735f84dbe9a790badee8245c06c7ca37331cb36980', + 'a855babad5cd60c88b430a69f53a1a7a38289154964799be43d06d77d31da06' + ], + [ + '311091dd9860e8e20ee13473c1155f5f69635e394704eaa74009452246cfa9b3', + '66db656f87d1f04fffd1f04788c06830871ec5a64feee685bd80f0b1286d8374' + ], + [ + '34c1fd04d301be89b31c0442d3e6ac24883928b45a9340781867d4232ec2dbdf', + '9414685e97b1b5954bd46f730174136d57f1ceeb487443dc5321857ba73abee' + ], + [ + 'f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d63', + '4cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1' + ], + [ + 'd7b8740f74a8fbaab1f683db8f45de26543a5490bca627087236912469a0b448', + 'fa77968128d9c92ee1010f337ad4717eff15db5ed3c049b3411e0315eaa4593b' + ], + [ + '32d31c222f8f6f0ef86f7c98d3a3335ead5bcd32abdd94289fe4d3091aa824bf', + '5f3032f5892156e39ccd3d7915b9e1da2e6dac9e6f26e961118d14b8462e1661' + ], + [ + '7461f371914ab32671045a155d9831ea8793d77cd59592c4340f86cbc18347b5', + '8ec0ba238b96bec0cbdddcae0aa442542eee1ff50c986ea6b39847b3cc092ff6' + ], + [ + 'ee079adb1df1860074356a25aa38206a6d716b2c3e67453d287698bad7b2b2d6', + '8dc2412aafe3be5c4c5f37e0ecc5f9f6a446989af04c4e25ebaac479ec1c8c1e' + ], + [ + '16ec93e447ec83f0467b18302ee620f7e65de331874c9dc72bfd8616ba9da6b5', + '5e4631150e62fb40d0e8c2a7ca5804a39d58186a50e497139626778e25b0674d' + ], + [ + 'eaa5f980c245f6f038978290afa70b6bd8855897f98b6aa485b96065d537bd99', + 'f65f5d3e292c2e0819a528391c994624d784869d7e6ea67fb18041024edc07dc' + ], + [ + '78c9407544ac132692ee1910a02439958ae04877151342ea96c4b6b35a49f51', + 'f3e0319169eb9b85d5404795539a5e68fa1fbd583c064d2462b675f194a3ddb4' + ], + [ + '494f4be219a1a77016dcd838431aea0001cdc8ae7a6fc688726578d9702857a5', + '42242a969283a5f339ba7f075e36ba2af925ce30d767ed6e55f4b031880d562c' + ], + [ + 'a598a8030da6d86c6bc7f2f5144ea549d28211ea58faa70ebf4c1e665c1fe9b5', + '204b5d6f84822c307e4b4a7140737aec23fc63b65b35f86a10026dbd2d864e6b' + ], + [ + 'c41916365abb2b5d09192f5f2dbeafec208f020f12570a184dbadc3e58595997', + '4f14351d0087efa49d245b328984989d5caf9450f34bfc0ed16e96b58fa9913' + ], + [ + '841d6063a586fa475a724604da03bc5b92a2e0d2e0a36acfe4c73a5514742881', + '73867f59c0659e81904f9a1c7543698e62562d6744c169ce7a36de01a8d6154' + ], + [ + '5e95bb399a6971d376026947f89bde2f282b33810928be4ded112ac4d70e20d5', + '39f23f366809085beebfc71181313775a99c9aed7d8ba38b161384c746012865' + ], + [ + '36e4641a53948fd476c39f8a99fd974e5ec07564b5315d8bf99471bca0ef2f66', + 'd2424b1b1abe4eb8164227b085c9aa9456ea13493fd563e06fd51cf5694c78fc' + ], + [ + '336581ea7bfbbb290c191a2f507a41cf5643842170e914faeab27c2c579f726', + 'ead12168595fe1be99252129b6e56b3391f7ab1410cd1e0ef3dcdcabd2fda224' + ], + [ + '8ab89816dadfd6b6a1f2634fcf00ec8403781025ed6890c4849742706bd43ede', + '6fdcef09f2f6d0a044e654aef624136f503d459c3e89845858a47a9129cdd24e' + ], + [ + '1e33f1a746c9c5778133344d9299fcaa20b0938e8acff2544bb40284b8c5fb94', + '60660257dd11b3aa9c8ed618d24edff2306d320f1d03010e33a7d2057f3b3b6' + ], + [ + '85b7c1dcb3cec1b7ee7f30ded79dd20a0ed1f4cc18cbcfcfa410361fd8f08f31', + '3d98a9cdd026dd43f39048f25a8847f4fcafad1895d7a633c6fed3c35e999511' + ], + [ + '29df9fbd8d9e46509275f4b125d6d45d7fbe9a3b878a7af872a2800661ac5f51', + 'b4c4fe99c775a606e2d8862179139ffda61dc861c019e55cd2876eb2a27d84b' + ], + [ + 'a0b1cae06b0a847a3fea6e671aaf8adfdfe58ca2f768105c8082b2e449fce252', + 'ae434102edde0958ec4b19d917a6a28e6b72da1834aff0e650f049503a296cf2' + ], + [ + '4e8ceafb9b3e9a136dc7ff67e840295b499dfb3b2133e4ba113f2e4c0e121e5', + 'cf2174118c8b6d7a4b48f6d534ce5c79422c086a63460502b827ce62a326683c' + ], + [ + 'd24a44e047e19b6f5afb81c7ca2f69080a5076689a010919f42725c2b789a33b', + '6fb8d5591b466f8fc63db50f1c0f1c69013f996887b8244d2cdec417afea8fa3' + ], + [ + 'ea01606a7a6c9cdd249fdfcfacb99584001edd28abbab77b5104e98e8e3b35d4', + '322af4908c7312b0cfbfe369f7a7b3cdb7d4494bc2823700cfd652188a3ea98d' + ], + [ + 'af8addbf2b661c8a6c6328655eb96651252007d8c5ea31be4ad196de8ce2131f', + '6749e67c029b85f52a034eafd096836b2520818680e26ac8f3dfbcdb71749700' + ], + [ + 'e3ae1974566ca06cc516d47e0fb165a674a3dabcfca15e722f0e3450f45889', + '2aeabe7e4531510116217f07bf4d07300de97e4874f81f533420a72eeb0bd6a4' + ], + [ + '591ee355313d99721cf6993ffed1e3e301993ff3ed258802075ea8ced397e246', + 'b0ea558a113c30bea60fc4775460c7901ff0b053d25ca2bdeee98f1a4be5d196' + ], + [ + '11396d55fda54c49f19aa97318d8da61fa8584e47b084945077cf03255b52984', + '998c74a8cd45ac01289d5833a7beb4744ff536b01b257be4c5767bea93ea57a4' + ], + [ + '3c5d2a1ba39c5a1790000738c9e0c40b8dcdfd5468754b6405540157e017aa7a', + 'b2284279995a34e2f9d4de7396fc18b80f9b8b9fdd270f6661f79ca4c81bd257' + ], + [ + 'cc8704b8a60a0defa3a99a7299f2e9c3fbc395afb04ac078425ef8a1793cc030', + 'bdd46039feed17881d1e0862db347f8cf395b74fc4bcdc4e940b74e3ac1f1b13' + ], + [ + 'c533e4f7ea8555aacd9777ac5cad29b97dd4defccc53ee7ea204119b2889b197', + '6f0a256bc5efdf429a2fb6242f1a43a2d9b925bb4a4b3a26bb8e0f45eb596096' + ], + [ + 'c14f8f2ccb27d6f109f6d08d03cc96a69ba8c34eec07bbcf566d48e33da6593', + 'c359d6923bb398f7fd4473e16fe1c28475b740dd098075e6c0e8649113dc3a38' + ], + [ + 'a6cbc3046bc6a450bac24789fa17115a4c9739ed75f8f21ce441f72e0b90e6ef', + '21ae7f4680e889bb130619e2c0f95a360ceb573c70603139862afd617fa9b9f' + ], + [ + '347d6d9a02c48927ebfb86c1359b1caf130a3c0267d11ce6344b39f99d43cc38', + '60ea7f61a353524d1c987f6ecec92f086d565ab687870cb12689ff1e31c74448' + ], + [ + 'da6545d2181db8d983f7dcb375ef5866d47c67b1bf31c8cf855ef7437b72656a', + '49b96715ab6878a79e78f07ce5680c5d6673051b4935bd897fea824b77dc208a' + ], + [ + 'c40747cc9d012cb1a13b8148309c6de7ec25d6945d657146b9d5994b8feb1111', + '5ca560753be2a12fc6de6caf2cb489565db936156b9514e1bb5e83037e0fa2d4' + ], + [ + '4e42c8ec82c99798ccf3a610be870e78338c7f713348bd34c8203ef4037f3502', + '7571d74ee5e0fb92a7a8b33a07783341a5492144cc54bcc40a94473693606437' + ], + [ + '3775ab7089bc6af823aba2e1af70b236d251cadb0c86743287522a1b3b0dedea', + 'be52d107bcfa09d8bcb9736a828cfa7fac8db17bf7a76a2c42ad961409018cf7' + ], + [ + 'cee31cbf7e34ec379d94fb814d3d775ad954595d1314ba8846959e3e82f74e26', + '8fd64a14c06b589c26b947ae2bcf6bfa0149ef0be14ed4d80f448a01c43b1c6d' + ], + [ + 'b4f9eaea09b6917619f6ea6a4eb5464efddb58fd45b1ebefcdc1a01d08b47986', + '39e5c9925b5a54b07433a4f18c61726f8bb131c012ca542eb24a8ac07200682a' + ], + [ + 'd4263dfc3d2df923a0179a48966d30ce84e2515afc3dccc1b77907792ebcc60e', + '62dfaf07a0f78feb30e30d6295853ce189e127760ad6cf7fae164e122a208d54' + ], + [ + '48457524820fa65a4f8d35eb6930857c0032acc0a4a2de422233eeda897612c4', + '25a748ab367979d98733c38a1fa1c2e7dc6cc07db2d60a9ae7a76aaa49bd0f77' + ], + [ + 'dfeeef1881101f2cb11644f3a2afdfc2045e19919152923f367a1767c11cceda', + 'ecfb7056cf1de042f9420bab396793c0c390bde74b4bbdff16a83ae09a9a7517' + ], + [ + '6d7ef6b17543f8373c573f44e1f389835d89bcbc6062ced36c82df83b8fae859', + 'cd450ec335438986dfefa10c57fea9bcc521a0959b2d80bbf74b190dca712d10' + ], + [ + 'e75605d59102a5a2684500d3b991f2e3f3c88b93225547035af25af66e04541f', + 'f5c54754a8f71ee540b9b48728473e314f729ac5308b06938360990e2bfad125' + ], + [ + 'eb98660f4c4dfaa06a2be453d5020bc99a0c2e60abe388457dd43fefb1ed620c', + '6cb9a8876d9cb8520609af3add26cd20a0a7cd8a9411131ce85f44100099223e' + ], + [ + '13e87b027d8514d35939f2e6892b19922154596941888336dc3563e3b8dba942', + 'fef5a3c68059a6dec5d624114bf1e91aac2b9da568d6abeb2570d55646b8adf1' + ], + [ + 'ee163026e9fd6fe017c38f06a5be6fc125424b371ce2708e7bf4491691e5764a', + '1acb250f255dd61c43d94ccc670d0f58f49ae3fa15b96623e5430da0ad6c62b2' + ], + [ + 'b268f5ef9ad51e4d78de3a750c2dc89b1e626d43505867999932e5db33af3d80', + '5f310d4b3c99b9ebb19f77d41c1dee018cf0d34fd4191614003e945a1216e423' + ], + [ + 'ff07f3118a9df035e9fad85eb6c7bfe42b02f01ca99ceea3bf7ffdba93c4750d', + '438136d603e858a3a5c440c38eccbaddc1d2942114e2eddd4740d098ced1f0d8' + ], + [ + '8d8b9855c7c052a34146fd20ffb658bea4b9f69e0d825ebec16e8c3ce2b526a1', + 'cdb559eedc2d79f926baf44fb84ea4d44bcf50fee51d7ceb30e2e7f463036758' + ], + [ + '52db0b5384dfbf05bfa9d472d7ae26dfe4b851ceca91b1eba54263180da32b63', + 'c3b997d050ee5d423ebaf66a6db9f57b3180c902875679de924b69d84a7b375' + ], + [ + 'e62f9490d3d51da6395efd24e80919cc7d0f29c3f3fa48c6fff543becbd43352', + '6d89ad7ba4876b0b22c2ca280c682862f342c8591f1daf5170e07bfd9ccafa7d' + ], + [ + '7f30ea2476b399b4957509c88f77d0191afa2ff5cb7b14fd6d8e7d65aaab1193', + 'ca5ef7d4b231c94c3b15389a5f6311e9daff7bb67b103e9880ef4bff637acaec' + ], + [ + '5098ff1e1d9f14fb46a210fada6c903fef0fb7b4a1dd1d9ac60a0361800b7a00', + '9731141d81fc8f8084d37c6e7542006b3ee1b40d60dfe5362a5b132fd17ddc0' + ], + [ + '32b78c7de9ee512a72895be6b9cbefa6e2f3c4ccce445c96b9f2c81e2778ad58', + 'ee1849f513df71e32efc3896ee28260c73bb80547ae2275ba497237794c8753c' + ], + [ + 'e2cb74fddc8e9fbcd076eef2a7c72b0ce37d50f08269dfc074b581550547a4f7', + 'd3aa2ed71c9dd2247a62df062736eb0baddea9e36122d2be8641abcb005cc4a4' + ], + [ + '8438447566d4d7bedadc299496ab357426009a35f235cb141be0d99cd10ae3a8', + 'c4e1020916980a4da5d01ac5e6ad330734ef0d7906631c4f2390426b2edd791f' + ], + [ + '4162d488b89402039b584c6fc6c308870587d9c46f660b878ab65c82c711d67e', + '67163e903236289f776f22c25fb8a3afc1732f2b84b4e95dbda47ae5a0852649' + ], + [ + '3fad3fa84caf0f34f0f89bfd2dcf54fc175d767aec3e50684f3ba4a4bf5f683d', + 'cd1bc7cb6cc407bb2f0ca647c718a730cf71872e7d0d2a53fa20efcdfe61826' + ], + [ + '674f2600a3007a00568c1a7ce05d0816c1fb84bf1370798f1c69532faeb1a86b', + '299d21f9413f33b3edf43b257004580b70db57da0b182259e09eecc69e0d38a5' + ], + [ + 'd32f4da54ade74abb81b815ad1fb3b263d82d6c692714bcff87d29bd5ee9f08f', + 'f9429e738b8e53b968e99016c059707782e14f4535359d582fc416910b3eea87' + ], + [ + '30e4e670435385556e593657135845d36fbb6931f72b08cb1ed954f1e3ce3ff6', + '462f9bce619898638499350113bbc9b10a878d35da70740dc695a559eb88db7b' + ], + [ + 'be2062003c51cc3004682904330e4dee7f3dcd10b01e580bf1971b04d4cad297', + '62188bc49d61e5428573d48a74e1c655b1c61090905682a0d5558ed72dccb9bc' + ], + [ + '93144423ace3451ed29e0fb9ac2af211cb6e84a601df5993c419859fff5df04a', + '7c10dfb164c3425f5c71a3f9d7992038f1065224f72bb9d1d902a6d13037b47c' + ], + [ + 'b015f8044f5fcbdcf21ca26d6c34fb8197829205c7b7d2a7cb66418c157b112c', + 'ab8c1e086d04e813744a655b2df8d5f83b3cdc6faa3088c1d3aea1454e3a1d5f' + ], + [ + 'd5e9e1da649d97d89e4868117a465a3a4f8a18de57a140d36b3f2af341a21b52', + '4cb04437f391ed73111a13cc1d4dd0db1693465c2240480d8955e8592f27447a' + ], + [ + 'd3ae41047dd7ca065dbf8ed77b992439983005cd72e16d6f996a5316d36966bb', + 'bd1aeb21ad22ebb22a10f0303417c6d964f8cdd7df0aca614b10dc14d125ac46' + ], + [ + '463e2763d885f958fc66cdd22800f0a487197d0a82e377b49f80af87c897b065', + 'bfefacdb0e5d0fd7df3a311a94de062b26b80c61fbc97508b79992671ef7ca7f' + ], + [ + '7985fdfd127c0567c6f53ec1bb63ec3158e597c40bfe747c83cddfc910641917', + '603c12daf3d9862ef2b25fe1de289aed24ed291e0ec6708703a5bd567f32ed03' + ], + [ + '74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9', + 'cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08' + ], + [ + '30682a50703375f602d416664ba19b7fc9bab42c72747463a71d0896b22f6da3', + '553e04f6b018b4fa6c8f39e7f311d3176290d0e0f19ca73f17714d9977a22ff8' + ], + [ + '9e2158f0d7c0d5f26c3791efefa79597654e7a2b2464f52b1ee6c1347769ef57', + '712fcdd1b9053f09003a3481fa7762e9ffd7c8ef35a38509e2fbf2629008373' + ], + [ + '176e26989a43c9cfeba4029c202538c28172e566e3c4fce7322857f3be327d66', + 'ed8cc9d04b29eb877d270b4878dc43c19aefd31f4eee09ee7b47834c1fa4b1c3' + ], + [ + '75d46efea3771e6e68abb89a13ad747ecf1892393dfc4f1b7004788c50374da8', + '9852390a99507679fd0b86fd2b39a868d7efc22151346e1a3ca4726586a6bed8' + ], + [ + '809a20c67d64900ffb698c4c825f6d5f2310fb0451c869345b7319f645605721', + '9e994980d9917e22b76b061927fa04143d096ccc54963e6a5ebfa5f3f8e286c1' + ], + [ + '1b38903a43f7f114ed4500b4eac7083fdefece1cf29c63528d563446f972c180', + '4036edc931a60ae889353f77fd53de4a2708b26b6f5da72ad3394119daf408f9' + ] + ] + } +}; + +},{}],139:[function(require,module,exports){ +'use strict'; + +var utils = exports; +var BN = require('../../../BN/bn'); + +utils.assert = function (condition, errorMessage) { + if (!condition) { + throw new Error(errorMessage) + } +}; + +// Represent num in a w-NAF form +function getNAF(num, w) { + var naf = []; + var ws = 1 << (w + 1); + var k = num.clone(); + while (k.cmpn(1) >= 0) { + var z; + if (k.isOdd()) { + var mod = k.andln(ws - 1); + if (mod > (ws >> 1) - 1) + z = (ws >> 1) - mod; + else + z = mod; + k.isubn(z); + } else { + z = 0; + } + naf.push(z); + + // Optimization, shift by word if possible + var shift = (k.cmpn(0) !== 0 && k.andln(ws - 1) === 0) ? (w + 1) : 1; + for (var i = 1; i < shift; i++) + naf.push(0); + k.iushrn(shift); + } + + return naf; +} +utils.getNAF = getNAF; + +// Represent k1, k2 in a Joint Sparse Form +function getJSF(k1, k2) { + var jsf = [ + [], + [] + ]; + + k1 = k1.clone(); + k2 = k2.clone(); + var d1 = 0; + var d2 = 0; + while (k1.cmpn(-d1) > 0 || k2.cmpn(-d2) > 0) { + + // First phase + var m14 = (k1.andln(3) + d1) & 3; + var m24 = (k2.andln(3) + d2) & 3; + if (m14 === 3) + m14 = -1; + if (m24 === 3) + m24 = -1; + var u1; + if ((m14 & 1) === 0) { + u1 = 0; + } else { + var m8 = (k1.andln(7) + d1) & 7; + if ((m8 === 3 || m8 === 5) && m24 === 2) + u1 = -m14; + else + u1 = m14; + } + jsf[0].push(u1); + + var u2; + if ((m24 & 1) === 0) { + u2 = 0; + } else { + var m8 = (k2.andln(7) + d2) & 7; + if ((m8 === 3 || m8 === 5) && m14 === 2) + u2 = -m24; + else + u2 = m24; + } + jsf[1].push(u2); + + // Second phase + if (2 * d1 === u1 + 1) + d1 = 1 - d1; + if (2 * d2 === u2 + 1) + d2 = 1 - d2; + k1.iushrn(1); + k2.iushrn(1); + } + + return jsf; +} +utils.getJSF = getJSF; + +function cachedProperty(obj, name, computer) { + var key = '_' + name; + obj.prototype[name] = function cachedProperty() { + return this[key] !== undefined ? this[key] : + this[key] = computer.call(this); + }; +} +utils.cachedProperty = cachedProperty; + +function parseBytes(bytes) { + return typeof bytes === 'string' ? utils.toArray(bytes, 'hex') : + bytes; +} +utils.parseBytes = parseBytes; + +function intFromLE(bytes) { + return new BN(bytes, 'hex', 'le'); +} +utils.intFromLE = intFromLE; + +// used to convert `CryptoJS` wordArrays into `crypto` hex buffers +function wordToByteArray(word, length) { + var ba = [], + xFF = 0xFF; + if (length > 0) + ba.push(word >>> 24); + if (length > 1) + ba.push((word >>> 16) & xFF); + if (length > 2) + ba.push((word >>> 8) & xFF); + if (length > 3) + ba.push(word & xFF); + + return ba; +} + +function wordArrayToBuffer(wordArray) { + let length = undefined; + if (wordArray.hasOwnProperty("sigBytes") && wordArray.hasOwnProperty("words")) { + length = wordArray.sigBytes; + wordArray = wordArray.words; + } else { + throw Error('Argument not a wordArray') + } + + const result = [] + let bytes = [] + let i = 0; + while (length > 0) { + bytes = wordToByteArray(wordArray[i], Math.min(4, length)); + length -= bytes.length; + result.push(bytes); + i++; + } + return [].concat.apply([], result) +} + +utils.wordArrayToBuffer = wordArrayToBuffer; + +// https://github.com/indutny/minimalistic-crypto-utils/blob/master/lib/utils.js +// moved here to remove the dep + +function toArray(msg, enc) { + if (Array.isArray(msg)) + return msg.slice(); + if (!msg) + return []; + var res = []; + if (typeof msg !== 'string') { + for (var i = 0; i < msg.length; i++) + res[i] = msg[i] | 0; + return res; + } + if (enc === 'hex') { + msg = msg.replace(/[^a-z0-9]+/ig, ''); + if (msg.length % 2 !== 0) + msg = '0' + msg; + for (var i = 0; i < msg.length; i += 2) + res.push(parseInt(msg[i] + msg[i + 1], 16)); + } else { + for (var i = 0; i < msg.length; i++) { + var c = msg.charCodeAt(i); + var hi = c >> 8; + var lo = c & 0xff; + if (hi) + res.push(hi, lo); + else + res.push(lo); + } + } + return res; +} +utils.toArray = toArray; + +function zero2(word) { + if (word.length === 1) + return '0' + word; + else + return word; +} +utils.zero2 = zero2; + +function toHex(msg) { + var res = ''; + for (var i = 0; i < msg.length; i++) + res += zero2(msg[i].toString(16)); + return res; +} +utils.toHex = toHex; + +utils.encode = function encode(arr, enc) { + if (enc === 'hex') + return toHex(arr); + else + return arr; +}; + +},{"../../../BN/bn":123}],140:[function(require,module,exports){ +'use strict'; + +var CryptoJS = require('../../../crypto-js/crypto-js'); +var assert = require('../elliptic/utils').assert; +var utils = require ('../elliptic/utils') +var { byteArrayToWordArray } = require('../../../../base/functions/encode'); + +// some static stuff +const ONE = CryptoJS.enc.Utf8.parse ('\x01') +const ZERO = CryptoJS.enc.Utf8.parse ('\x00') + +function HmacDRBG(options) { + if (!(this instanceof HmacDRBG)) + return new HmacDRBG(options); + this.hash = options.hash + this.predResist = !!options.predResist; + + this.outLen = this.hash.slice (3, 6); // only support SHAXXX hashes + this.minEntropy = options.minEntropy || 192; + + this._reseed = null; + this.reseedInterval = null; + this.K = null; + this.V = null; + + var entropy = options.entropy + var nonce = options.nonce + var pers = [] + assert(entropy.length >= (this.minEntropy / 8), + 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); + this._init(entropy, nonce, pers); +} +module.exports = HmacDRBG; + +HmacDRBG.prototype._init = function init(entropy, nonce, pers) { + var seed = entropy.concat(nonce).concat(pers); + + this.K = new CryptoJS.lib.WordArray.init () + this.V = new CryptoJS.lib.WordArray.init () + const magicNumber = (1 << 24) + (1 << 16) + (1 << 8) + 1 + this.V.words = Array (this.outLen / 32).fill (magicNumber) + this.V.sigBytes = 32 + this._update(seed); + this._reseed = 1; + this.reseedInterval = 0x1000000000000; // 2^48 +}; + +HmacDRBG.prototype._hmac = function hmac() { + return new CryptoJS.lib.WordArray.init (); +}; + +HmacDRBG.prototype._update = function update(seed) { + var kmac = this._hmac () + kmac.concat (this.V) + kmac.concat (ZERO) + if (seed) + kmac.concat (byteArrayToWordArray (seed)); + this.K = CryptoJS['Hmac' + this.hash] (kmac, this.K) + this.V = CryptoJS['Hmac' + this.hash] (this.V, this.K) + if (!seed) + return; + + kmac = this._hmac () + kmac.concat (this.V) + kmac.concat (ONE) + kmac.concat (byteArrayToWordArray (seed)) + this.K = CryptoJS['Hmac' + this.hash] (kmac, this.K) + this.V = CryptoJS['Hmac' + this.hash] (this.V, this.K) +}; + +HmacDRBG.prototype.reseed = function reseed(entropy, entropyEnc, add, addEnc) { + // Optional entropy enc + if (typeof entropyEnc !== 'string') { + addEnc = add; + add = entropyEnc; + entropyEnc = null; + } + + //entropy = utils.toArray(entropy, entropyEnc); + //add = utils.toArray(add, addEnc); + + assert(entropy.length >= (this.minEntropy / 8), + 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); + + this._update(entropy.concat(add || [])); + this._reseed = 1; +}; + +HmacDRBG.prototype.generate = function generate(len, enc, add, addEnc) { + if (this._reseed > this.reseedInterval) + throw new Error ('Reseed is required'); + + // Optional encoding + if (typeof enc !== 'string') { + addEnc = add; + add = enc; + enc = null; + } + + // Optional additional data + if (add) { + add = CryptoJS.enc.Utf8.parse (add); + this._update (add); + } + + var res = new CryptoJS.lib.WordArray.init (); + while (res.sigBytes < len) { + this.V = CryptoJS['Hmac' + this.hash] (this.V, this.K) + res.concat (this.V); + } + this._update (add); + this._reseed++; + return utils.wordArrayToBuffer(res); +}; + +},{"../../../../base/functions/encode":10,"../../../crypto-js/crypto-js":124,"../elliptic/utils":139}],141:[function(require,module,exports){ +'use strict'; + +var fetch = require('../node-fetch/index'); + +function wrapFetchForNode(fetch) { + // Support schemaless URIs on the server for parity with the browser. + // https://github.com/matthew-andrews/isomorphic-fetch/pull/10 + const f = function (u, options) { + if (typeof u === 'string' && u.slice(0, 2) === '//') { + return fetch('https:' + u, options); + } + + return fetch(u, options); + }; + // Igor Kroitor 2019 Dec 30 + // expose http and https + // https://github.com/ccxt/ccxt/issues/6327 + f.http = fetch.http; + f.https = fetch.https; + return f; +} + +module.exports = function (context) { + // This modifies the global `node-fetch` object, which isn't great, since + // different callers to `fetch-ponyfill` which pass a different Promise + // implementation would each expect to have their implementation used. But, + // given the way `node-fetch` is implemented, this is the only way to make + // it work at all. + if (context && context.Promise) { + fetch.Promise = context.Promise; + } + + return { + fetch: wrapFetchForNode(fetch), + Headers: fetch.Headers, + Request: fetch.Request, + Response: fetch.Response + }; +}; + +},{"../node-fetch/index":142}],142:[function(require,module,exports){ +(function (global,Buffer){ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js +// (MIT licensed) + +const BUFFER = Symbol('buffer'); +const TYPE = Symbol('type'); + +class Blob { + constructor() { + this[TYPE] = ''; + + const blobParts = arguments[0]; + const options = arguments[1]; + + const buffers = []; + + if (blobParts) { + const a = blobParts; + const length = Number(a.length); + for (let i = 0; i < length; i++) { + const element = a[i]; + let buffer; + if (element instanceof Buffer) { + buffer = element; + } else if (ArrayBuffer.isView(element)) { + buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); + } else if (element instanceof ArrayBuffer) { + buffer = Buffer.from(element); + } else if (element instanceof Blob) { + buffer = element[BUFFER]; + } else { + buffer = Buffer.from(typeof element === 'string' ? element : String(element)); + } + buffers.push(buffer); + } + } + + this[BUFFER] = Buffer.concat(buffers); + + let type = options && options.type !== undefined && String(options.type).toLowerCase(); + if (type && !/[^\u0020-\u007E]/.test(type)) { + this[TYPE] = type; + } + } + get size() { + return this[BUFFER].length; + } + get type() { + return this[TYPE]; + } + slice() { + const size = this.size; + + const start = arguments[0]; + const end = arguments[1]; + let relativeStart, relativeEnd; + if (start === undefined) { + relativeStart = 0; + } else if (start < 0) { + relativeStart = Math.max(size + start, 0); + } else { + relativeStart = Math.min(start, size); + } + if (end === undefined) { + relativeEnd = size; + } else if (end < 0) { + relativeEnd = Math.max(size + end, 0); + } else { + relativeEnd = Math.min(end, size); + } + const span = Math.max(relativeEnd - relativeStart, 0); + + const buffer = this[BUFFER]; + const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); + const blob = new Blob([], { type: arguments[2] }); + blob[BUFFER] = slicedBuffer; + return blob; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(Blob.prototype, Symbol.toStringTag, { + value: 'Blob', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * fetch-error.js + * + * FetchError interface for operational errors + */ + +/** + * Create FetchError instance + * + * @param String message Error message for human + * @param String type Error type for machine + * @param String systemError For Node.js system error + * @return FetchError + */ +function FetchError(message, type, systemError) { + Error.call(this, message); + + this.message = message; + this.type = type; + + // when err.type is `system`, err.code contains system error code + if (systemError) { + this.code = this.errno = systemError.code; + } + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +FetchError.prototype = Object.create(Error.prototype); +FetchError.prototype.constructor = FetchError; +FetchError.prototype.name = 'FetchError'; + +/** + * body.js + * + * Body interface provides common methods for Request and Response + */ + +const Stream = require('stream'); + +var _require = require('stream'); + +const PassThrough = _require.PassThrough; + +/* +let convert; +try { + convert = require('encoding').convert; +} catch (e) {} +*/ + +const INTERNALS = Symbol('Body internals'); + +/** + * Body mixin + * + * Ref: https://fetch.spec.whatwg.org/#body + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$size = _ref.size; + + let size = _ref$size === undefined ? 0 : _ref$size; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; + + if (body == null) { + // body is undefined or null + body = null; + } else if (typeof body === 'string') { + // body is string + } else if (isURLSearchParams(body)) { + // body is a URLSearchParams + } else if (body instanceof Blob) { + // body is blob + } else if (Buffer.isBuffer(body)) { + // body is buffer + } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { + // body is array buffer + } else if (body instanceof Stream) { + // body is stream + } else { + // none of the above + // coerce to string + body = String(body); + } + this[INTERNALS] = { + body, + disturbed: false, + error: null + }; + this.size = size; + this.timeout = timeout; + + if (body instanceof Stream) { + body.on('error', function (err) { + _this[INTERNALS].error = new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); + }); + } +} + +Body.prototype = { + get body() { + return this[INTERNALS].body; + }, + + get bodyUsed() { + return this[INTERNALS].disturbed; + }, + + /** + * Decode response as ArrayBuffer + * + * @return Promise + */ + arrayBuffer() { + return consumeBody.call(this).then(function (buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + }); + }, + + /** + * Return raw response as Blob + * + * @return Promise + */ + blob() { + let ct = this.headers && this.headers.get('content-type') || ''; + return consumeBody.call(this).then(function (buf) { + return Object.assign( + // Prevent copying + new Blob([], { + type: ct.toLowerCase() + }), { + [BUFFER]: buf + }); + }); + }, + + /** + * Decode response as json + * + * @return Promise + */ + json() { + var _this2 = this; + + return consumeBody.call(this).then(function (buffer) { + try { + return JSON.parse(buffer.toString()); + } catch (err) { + return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); + } + }); + }, + + /** + * Decode response as text + * + * @return Promise + */ + text() { + return consumeBody.call(this).then(function (buffer) { + return buffer.toString(); + }); + }, + + /** + * Decode response as buffer (non-spec api) + * + * @return Promise + */ + buffer() { + return consumeBody.call(this); + }, + + /** + * Decode response as text, while automatically detecting the encoding and + * trying to decode to UTF-8 (non-spec api) + * + * @return Promise + */ + textConverted() { + var _this3 = this; + + return consumeBody.call(this).then(function (buffer) { + return convertBody(buffer, _this3.headers); + }); + } + +}; + +// In browsers, all properties are enumerable. +Object.defineProperties(Body.prototype, { + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true } +}); + +Body.mixIn = function (proto) { + for (const name of Object.getOwnPropertyNames(Body.prototype)) { + // istanbul ignore else: future proof + if (!(name in proto)) { + const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); + Object.defineProperty(proto, name, desc); + } + } +}; + +/** + * Consume and convert an entire Body to a Buffer. + * + * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body + * + * @return Promise + */ +function consumeBody() { + var _this4 = this; + + if (this[INTERNALS].disturbed) { + return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); + } + + this[INTERNALS].disturbed = true; + + if (this[INTERNALS].error) { + return Body.Promise.reject(this[INTERNALS].error); + } + + // body is null + if (this.body === null) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is string + if (typeof this.body === 'string') { + return Body.Promise.resolve(Buffer.from(this.body)); + } + + // body is blob + if (this.body instanceof Blob) { + return Body.Promise.resolve(this.body[BUFFER]); + } + + // body is buffer + if (Buffer.isBuffer(this.body)) { + return Body.Promise.resolve(this.body); + } + + // body is buffer + if (Object.prototype.toString.call(this.body) === '[object ArrayBuffer]') { + return Body.Promise.resolve(Buffer.from(this.body)); + } + + // istanbul ignore if: should never happen + if (!(this.body instanceof Stream)) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is stream + // get ready to actually consume the body + let accum = []; + let accumBytes = 0; + let abort = false; + + return new Body.Promise(function (resolve, reject) { + let resTimeout; + + // allow timeout on slow response body + if (_this4.timeout) { + resTimeout = setTimeout(function () { + abort = true; + reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); + }, _this4.timeout); + } + + // handle stream error, such as incorrect content-encoding + _this4.body.on('error', function (err) { + reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); + }); + + _this4.body.on('data', function (chunk) { + if (abort || chunk === null) { + return; + } + + if (_this4.size && accumBytes + chunk.length > _this4.size) { + abort = true; + reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); + return; + } + + accumBytes += chunk.length; + accum.push(chunk); + }); + + _this4.body.on('end', function () { + if (abort) { + return; + } + + clearTimeout(resTimeout); + + try { + resolve(Buffer.concat(accum)); + } catch (err) { + // handle streams that have accumulated too much data (issue #414) + reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + }); +} + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param Buffer buffer Incoming buffer + * @param String encoding Target encoding + * @return String + */ +function convertBody(buffer, headers) { + if (typeof convert !== 'function') { + throw new Error('The package `encoding` must be installed to use the textConverted() function'); + } + + const ct = headers.get('content-type'); + let charset = 'utf-8'; + let res, str; + + // header + if (ct) { + res = /charset=([^;]*)/i.exec(ct); + } + + // no charset in content type, peek at response body for at most 1024 bytes + str = buffer.slice(0, 1024).toString(); + + // html5 + if (!res && str) { + res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined; + + this[MAP] = Object.create(null); + + if (init instanceof Headers) { + const rawHeaders = init.raw(); + const headerNames = Object.keys(rawHeaders); + + for (const headerName of headerNames) { + for (const value of rawHeaders[headerName]) { + this.append(headerName, value); + } + } + + return; + } + + // We don't worry about converting prop to ByteString here as append() + // will handle it. + if (init == null) { + // no op + } else if (typeof init === 'object') { + const method = init[Symbol.iterator]; + if (method != null) { + if (typeof method !== 'function') { + throw new TypeError('Header pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError('Each header pair must be iterable'); + } + pairs.push(Array.from(pair)); + } + + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError('Each header pair must be a name/value tuple'); + } + this.append(pair[0], pair[1]); + } + } else { + // record + for (const key of Object.keys(init)) { + const value = init[key]; + this.append(key, value); + } + } + } else { + throw new TypeError('Provided initializer must be an object'); + } + } + + /** + * Return combined header value given name + * + * @param String name Header name + * @return Mixed + */ + get(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key === undefined) { + return null; + } + + return this[MAP][key].join(', '); + } + + /** + * Iterate over all headers + * + * @param Function callback Executed for each item with parameters (value, name, thisArg) + * @param Boolean thisArg `this` context for callback function + * @return Void + */ + forEach(callback) { + let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + + let pairs = getHeaders(this); + let i = 0; + while (i < pairs.length) { + var _pairs$i = pairs[i]; + const name = _pairs$i[0], + value = _pairs$i[1]; + + callback.call(thisArg, value, name, this); + pairs = getHeaders(this); + i++; + } + } + + /** + * Overwrite header values given name + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + set(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + this[MAP][key !== undefined ? key : name] = [value]; + } + + /** + * Append a value onto existing header + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + append(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + if (key !== undefined) { + this[MAP][key].push(value); + } else { + this[MAP][name] = [value]; + } + } + + /** + * Check for header name existence + * + * @param String name Header name + * @return Boolean + */ + has(name) { + name = `${name}`; + validateName(name); + return find(this[MAP], name) !== undefined; + } + + /** + * Delete all header values given name + * + * @param String name Header name + * @return Void + */ + delete(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key !== undefined) { + delete this[MAP][key]; + } + } + + /** + * Return raw headers (non-spec api) + * + * @return Object + */ + raw() { + return this[MAP]; + } + + /** + * Get an iterator on keys. + * + * @return Iterator + */ + keys() { + return createHeadersIterator(this, 'key'); + } + + /** + * Get an iterator on values. + * + * @return Iterator + */ + values() { + return createHeadersIterator(this, 'value'); + } + + /** + * Get an iterator on entries. + * + * This is the default iterator of the Headers object. + * + * @return Iterator + */ + [Symbol.iterator]() { + return createHeadersIterator(this, 'key+value'); + } +} +Headers.prototype.entries = Headers.prototype[Symbol.iterator]; + +Object.defineProperty(Headers.prototype, Symbol.toStringTag, { + value: 'Headers', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Headers.prototype, { + get: { enumerable: true }, + forEach: { enumerable: true }, + set: { enumerable: true }, + append: { enumerable: true }, + has: { enumerable: true }, + delete: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true } +}); + +function getHeaders(headers) { + let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; + + const keys = Object.keys(headers[MAP]).sort(); + return keys.map(kind === 'key' ? function (k) { + return k.toLowerCase(); + } : kind === 'value' ? function (k) { + return headers[MAP][k].join(', '); + } : function (k) { + return [k.toLowerCase(), headers[MAP][k].join(', ')]; + }); +} + +const INTERNAL = Symbol('internal'); + +function createHeadersIterator(target, kind) { + const iterator = Object.create(HeadersIteratorPrototype); + iterator[INTERNAL] = { + target, + kind, + index: 0 + }; + return iterator; +} + +const HeadersIteratorPrototype = Object.setPrototypeOf({ + next() { + // istanbul ignore if + if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { + throw new TypeError('Value of `this` is not a HeadersIterator'); + } + + var _INTERNAL = this[INTERNAL]; + const target = _INTERNAL.target, + kind = _INTERNAL.kind, + index = _INTERNAL.index; + + const values = getHeaders(target, kind); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + this[INTERNAL].index = index + 1; + + return { + value: values[index], + done: false + }; + } +}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); + +Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { + value: 'HeadersIterator', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * Export the Headers object in a form that Node.js can consume. + * + * @param Headers headers + * @return Object + */ +function exportNodeCompatibleHeaders(headers) { + const obj = Object.assign({ __proto__: null }, headers[MAP]); + + // http.request() only supports string as Host header. This hack makes + // specifying custom Host header possible. + const hostHeaderKey = find(headers[MAP], 'Host'); + if (hostHeaderKey !== undefined) { + obj[hostHeaderKey] = obj[hostHeaderKey][0]; + } + + return obj; +} + +/** + * Create a Headers object from an object of headers, ignoring those that do + * not conform to HTTP grammar productions. + * + * @param Object obj Object of headers + * @return Headers + */ +function createHeadersLenient(obj) { + const headers = new Headers(); + for (const name of Object.keys(obj)) { + if (invalidTokenRegex.test(name)) { + continue; + } + if (Array.isArray(obj[name])) { + for (const val of obj[name]) { + if (invalidHeaderCharRegex.test(val)) { + continue; + } + if (headers[MAP][name] === undefined) { + headers[MAP][name] = [val]; + } else { + headers[MAP][name].push(val); + } + } + } else if (!invalidHeaderCharRegex.test(obj[name])) { + headers[MAP][name] = [obj[name]]; + } + } + return headers; +} + +/** + * response.js + * + * Response class provides content decoding + */ + +var _require$1 = require('http'); + +const STATUS_CODES = _require$1.STATUS_CODES; + + +const INTERNALS$1 = Symbol('Response internals'); + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +class Response { + constructor() { + let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + Body.call(this, body, opts); + + const status = opts.status || 200; + + this[INTERNALS$1] = { + url: opts.url, + status, + statusText: opts.statusText || STATUS_CODES[status], + headers: new Headers(opts.headers) + }; + } + + get url() { + return this[INTERNALS$1].url; + } + + get status() { + return this[INTERNALS$1].status; + } + + /** + * Convenience property representing if the request ended normally + */ + get ok() { + return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; + } + + get statusText() { + return this[INTERNALS$1].statusText; + } + + get headers() { + return this[INTERNALS$1].headers; + } + + /** + * Clone this response + * + * @return Response + */ + clone() { + return new Response(clone(this), { + url: this.url, + status: this.status, + statusText: this.statusText, + headers: this.headers, + ok: this.ok + }); + } +} + +Body.mixIn(Response.prototype); + +Object.defineProperties(Response.prototype, { + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true } +}); + +Object.defineProperty(Response.prototype, Symbol.toStringTag, { + value: 'Response', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * request.js + * + * Request class contains server only options + * + * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/. + */ + +var _require$2 = require('url'); + +const format_url = _require$2.format; +const parse_url = _require$2.parse; + + +const INTERNALS$2 = Symbol('Request internals'); + +/** + * Check if a value is an instance of Request. + * + * @param Mixed input + * @return Boolean + */ +function isRequest(input) { + return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; +} + +/** + * Request class + * + * @param Mixed input Url or Request instance + * @param Object init Custom options + * @return Void + */ +class Request { + constructor(input) { + let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + let parsedURL; + + // normalize input + if (!isRequest(input)) { + if (input && input.href) { + // in order to support Node.js' Url objects; though WHATWG's URL objects + // will fall into this branch also (since their `toString()` will return + // `href` property anyway) + parsedURL = parse_url(input.href); + } else { + // coerce input to a string before attempting to parse + parsedURL = parse_url(`${input}`); + } + input = {}; + } else { + parsedURL = parse_url(input.url); + } + + let method = init.method || input.method || 'GET'; + method = method.toUpperCase(); + + if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { + throw new TypeError('Request with GET/HEAD method cannot have body'); + } + + let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; + + Body.call(this, inputBody, { + timeout: init.timeout || input.timeout || 0, + size: init.size || input.size || 0 + }); + + const headers = new Headers(init.headers || input.headers || {}); + + if (init.body != null) { + const contentType = extractContentType(this); + if (contentType !== null && !headers.has('Content-Type')) { + headers.append('Content-Type', contentType); + } + } + + this[INTERNALS$2] = { + method, + redirect: init.redirect || input.redirect || 'follow', + headers, + parsedURL + }; + + // node-fetch-only options + this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; + this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; + this.counter = init.counter || input.counter || 0; + this.agent = init.agent || input.agent; + } + + get method() { + return this[INTERNALS$2].method; + } + + get url() { + return format_url(this[INTERNALS$2].parsedURL); + } + + get headers() { + return this[INTERNALS$2].headers; + } + + get redirect() { + return this[INTERNALS$2].redirect; + } + + /** + * Clone this request + * + * @return Request + */ + clone() { + return new Request(this); + } +} + +Body.mixIn(Request.prototype); + +Object.defineProperty(Request.prototype, Symbol.toStringTag, { + value: 'Request', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Request.prototype, { + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true } +}); + +/** + * Convert a Request to Node.js http request options. + * + * @param Request A Request instance + * @return Object The options object to be passed to http.request + */ +function getNodeRequestOptions(request) { + const parsedURL = request[INTERNALS$2].parsedURL; + const headers = new Headers(request[INTERNALS$2].headers); + + // fetch step 1.3 + if (!headers.has('Accept')) { + headers.set('Accept', '*/*'); + } + + // Basic fetch + if (!parsedURL.protocol || !parsedURL.hostname) { + throw new TypeError('Only absolute URLs are supported'); + } + + if (!/^https?:$/.test(parsedURL.protocol)) { + throw new TypeError('Only HTTP(S) protocols are supported'); + } + + // HTTP-network-or-cache fetch steps 2.4-2.7 + let contentLengthValue = null; + if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { + contentLengthValue = '0'; + } + if (request.body != null) { + const totalBytes = getTotalBytes(request); + if (typeof totalBytes === 'number') { + contentLengthValue = String(totalBytes); + } + } + if (contentLengthValue) { + headers.set('Content-Length', contentLengthValue); + } + + // HTTP-network-or-cache fetch step 2.11 + if (!headers.has('User-Agent')) { + headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); + } + + // HTTP-network-or-cache fetch step 2.15 + if (request.compress) { + headers.set('Accept-Encoding', 'gzip,deflate'); + } + if (!headers.has('Connection') && !request.agent) { + headers.set('Connection', 'close'); + } + + // HTTP-network fetch step 4.2 + // chunked encoding is handled by Node.js + + return Object.assign({}, parsedURL, { + method: request.method, + headers: exportNodeCompatibleHeaders(headers), + agent: request.agent + }); +} + +/** + * index.js + * + * a request API compatible with window.fetch + * + * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/. + */ + +const http = require('http'); +const https = require('https'); + +var _require$3 = require('stream'); + +const PassThrough$1 = _require$3.PassThrough; + +var _require2 = require('url'); + +const resolve_url = _require2.resolve; + +const zlib = require('zlib'); + +/** + * Fetch function + * + * @param Mixed url Absolute url or Request instance + * @param Object opts Fetch options + * @return Promise + */ +function fetch(url, opts) { + + // allow custom promise + if (!fetch.Promise) { + throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); + } + + Body.Promise = fetch.Promise; + + // wrap http.request into fetch + return new fetch.Promise(function (resolve, reject) { + // build request object + const request = new Request(url, opts); + const options = getNodeRequestOptions(request); + + const send = (options.protocol === 'https:' ? https : http).request; + + // send request + const req = send(options); + let reqTimeout; + + function finalize() { + req.abort(); + clearTimeout(reqTimeout); + } + + if (request.timeout) { + req.once('socket', function (socket) { + reqTimeout = setTimeout(function () { + reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); + finalize(); + }, request.timeout); + }); + } + + req.on('error', function (err) { + reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + finalize(); + }); + + req.on('response', function (res) { + clearTimeout(reqTimeout); + + const headers = createHeadersLenient(res.headers); + + // HTTP fetch step 5 + if (fetch.isRedirect(res.statusCode)) { + // HTTP fetch step 5.2 + const location = headers.get('Location'); + + // HTTP fetch step 5.3 + const locationURL = location === null ? null : resolve_url(request.url, location); + + // HTTP fetch step 5.5 + switch (request.redirect) { + case 'error': + reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); + finalize(); + return; + case 'manual': + // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + headers.set('Location', locationURL); + } + break; + case 'follow': + // HTTP-redirect fetch step 2 + if (locationURL === null) { + break; + } + + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body + }; + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { + reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { + requestOpts.method = 'GET'; + requestOpts.body = undefined; + requestOpts.headers.delete('content-length'); + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))); + finalize(); + return; + } + } + + // prepare response + let body = res.pipe(new PassThrough$1()); + const response_options = { + url: request.url, + status: res.statusCode, + statusText: res.statusMessage, + headers: headers, + size: request.size, + timeout: request.timeout + }; + + // HTTP-network fetch step 12.1.1.3 + const codings = headers.get('Content-Encoding'); + + // HTTP-network fetch step 12.1.1.4: handle content codings + + // in following scenarios we ignore compression support + // 1. compression support is disabled + // 2. HEAD request + // 3. no Content-Encoding header + // 4. no content response (204) + // 5. content not modified response (304) + if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { + resolve(new Response(body, response_options)); + return; + } + + // For Node v6+ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + const zlibOptions = { + flush: zlib.Z_SYNC_FLUSH, + finishFlush: zlib.Z_SYNC_FLUSH + }; + + // for gzip + if (codings == 'gzip' || codings == 'x-gzip') { + body = body.pipe(zlib.createGunzip(zlibOptions)); + resolve(new Response(body, response_options)); + return; + } + + // for deflate + if (codings == 'deflate' || codings == 'x-deflate') { + // handle the infamous raw deflate response from old servers + // a hack for old IIS and Apache servers + const raw = res.pipe(new PassThrough$1()); + raw.once('data', function (chunk) { + // see http://stackoverflow.com/questions/37519828 + if ((chunk[0] & 0x0F) === 0x08) { + body = body.pipe(zlib.createInflate()); + } else { + body = body.pipe(zlib.createInflateRaw()); + } + resolve(new Response(body, response_options)); + }); + return; + } + + // otherwise, use response as-is + resolve(new Response(body, response_options)); + }); + + writeToStream(req, request); + }); +} + +/** + * Redirect code matching + * + * @param Number code Status code + * @return Boolean + */ +fetch.isRedirect = function (code) { + return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; +}; + +// Needed for TypeScript. +fetch.default = fetch; + +// expose Promise +fetch.Promise = global.Promise; + +// Igor Kroitor 2019 Dec 30 +// expose http and https +// https://github.com/ccxt/ccxt/issues/6327 +fetch.http = http +fetch.https = https + +module.exports = exports = fetch; +exports.Headers = Headers; +exports.Request = Request; +exports.Response = Response; +exports.FetchError = FetchError; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) +},{"buffer":185,"http":227,"https":190,"stream":226,"url":233,"zlib":184}],143:[function(require,module,exports){ +(function (Buffer){ +/*! + * RSA library for Node.js + * + * Author: rzcoder + * License MIT + */ + +var rsa = require('./libs/rsa.js'); +var _ = require('./utils')._; +var schemes = require('./schemes/schemes.js'); +var formats = require('./formats/formats.js'); + +module.exports = (function () { + var SUPPORTED_HASH_ALGORITHMS = { + browser: ['md5', 'ripemd160', 'sha1', 'sha256', 'sha512'] + }; + + var DEFAULT_ENCRYPTION_SCHEME = 'pkcs1'; + var DEFAULT_SIGNING_SCHEME = 'pkcs1'; + + var EXPORT_FORMAT_ALIASES = { + 'private': 'pkcs1-private-pem', + 'private-der': 'pkcs1-private-der', + 'public': 'pkcs8-public-pem', + 'public-der': 'pkcs8-public-der', + }; + + /** + * @param key {string|buffer|object} Key in PEM format, or data for generate key {b: bits, e: exponent} + * @constructor + */ + function NodeRSA(key, format, options) { + if (!(this instanceof NodeRSA)) { + return new NodeRSA(key, format, options); + } + + if (_.isObject(format)) { + options = format; + format = undefined; + } + + this.$options = { + signingScheme: DEFAULT_SIGNING_SCHEME, + signingSchemeOptions: { + hash: 'sha256', + saltLength: null + }, + encryptionScheme: DEFAULT_ENCRYPTION_SCHEME, + encryptionSchemeOptions: { + hash: 'sha1', + label: null + }, + environment: 'browser', + rsaUtils: this + }; + this.keyPair = new rsa.Key(); + this.$cache = {}; + + if (Buffer.isBuffer(key) || _.isString(key)) { + this.importKey(key, format); + } else if (_.isObject(key)) { + this.generateKeyPair(key.b, key.e); + } + + this.setOptions(options); + } + + /** + * Set and validate options for key instance + * @param options + */ + NodeRSA.prototype.setOptions = function (options) { + options = options || {}; + if (options.environment) { + this.$options.environment = options.environment; + } + + if (options.signingScheme) { + if (_.isString(options.signingScheme)) { + var signingScheme = options.signingScheme.toLowerCase().split('-'); + if (signingScheme.length == 1) { + if (SUPPORTED_HASH_ALGORITHMS.node.indexOf(signingScheme[0]) > -1) { + this.$options.signingSchemeOptions = { + hash: signingScheme[0] + }; + this.$options.signingScheme = DEFAULT_SIGNING_SCHEME; + } else { + this.$options.signingScheme = signingScheme[0]; + this.$options.signingSchemeOptions = { + hash: null + }; + } + } else { + this.$options.signingSchemeOptions = { + hash: signingScheme[1] + }; + this.$options.signingScheme = signingScheme[0]; + } + } else if (_.isObject(options.signingScheme)) { + this.$options.signingScheme = options.signingScheme.scheme || DEFAULT_SIGNING_SCHEME; + this.$options.signingSchemeOptions = _.omit(options.signingScheme, 'scheme'); + } + + if (!schemes.isSignature(this.$options.signingScheme)) { + throw Error('Unsupported signing scheme'); + } + + if (this.$options.signingSchemeOptions.hash && + SUPPORTED_HASH_ALGORITHMS[this.$options.environment].indexOf(this.$options.signingSchemeOptions.hash) === -1) { + throw Error('Unsupported hashing algorithm for ' + this.$options.environment + ' environment'); + } + } + + if (options.encryptionScheme) { + if (_.isString(options.encryptionScheme)) { + this.$options.encryptionScheme = options.encryptionScheme.toLowerCase(); + this.$options.encryptionSchemeOptions = {}; + } else if (_.isObject(options.encryptionScheme)) { + this.$options.encryptionScheme = options.encryptionScheme.scheme || DEFAULT_ENCRYPTION_SCHEME; + this.$options.encryptionSchemeOptions = _.omit(options.encryptionScheme, 'scheme'); + } + + if (!schemes.isEncryption(this.$options.encryptionScheme)) { + throw Error('Unsupported encryption scheme'); + } + + if (this.$options.encryptionSchemeOptions.hash && + SUPPORTED_HASH_ALGORITHMS[this.$options.environment].indexOf(this.$options.encryptionSchemeOptions.hash) === -1) { + throw Error('Unsupported hashing algorithm for ' + this.$options.environment + ' environment'); + } + } + + this.keyPair.setOptions(this.$options); + }; + + /** + * Importing key + * @param keyData {string|buffer|Object} + * @param format {string} + */ + NodeRSA.prototype.importKey = function (keyData, format) { + if (!keyData) { + throw Error("Empty key given"); + } + + if (format) { + format = EXPORT_FORMAT_ALIASES[format] || format; + } + + if (!formats.detectAndImport(this.keyPair, keyData, format) && format === undefined) { + throw Error("Key format must be specified"); + } + + this.$cache = {}; + + return this; + }; + + /** + * Check if key pair contains private key + */ + NodeRSA.prototype.isPrivate = function () { + return this.keyPair.isPrivate(); + }; + + /** + * Check if key pair contains public key + * @param [strict] {boolean} - public key only, return false if have private exponent + */ + NodeRSA.prototype.isPublic = function (strict) { + return this.keyPair.isPublic(strict); + }; + + /** + * Check if key pair doesn't contains any data + */ + NodeRSA.prototype.isEmpty = function (strict) { + return !(this.keyPair.n || this.keyPair.e || this.keyPair.d); + }; + + /** + * Signing data + * + * @param buffer {string|number|object|array|Buffer} - data for signing. Object and array will convert to JSON string. + * @param encoding {string} - optional. Encoding for output result, may be 'buffer', 'binary', 'hex' or 'base64'. Default 'buffer'. + * @param source_encoding {string} - optional. Encoding for given string. Default utf8. + * @returns {string|Buffer} + */ + NodeRSA.prototype.sign = function (buffer, encoding, source_encoding) { + if (!this.isPrivate()) { + throw Error("This is not private key"); + } + + var res = this.keyPair.sign(this.$getDataForEncrypt(buffer, source_encoding)); + + if (encoding && encoding != 'buffer') { + res = res.toString(encoding); + } + + return res; + }; + + /** + * Preparing given data for encrypting/signing. Just make new/return Buffer object. + * + * @param buffer {string|number|object|array|Buffer} - data for encrypting. Object and array will convert to JSON string. + * @param encoding {string} - optional. Encoding for given string. Default utf8. + * @returns {Buffer} + */ + NodeRSA.prototype.$getDataForEncrypt = function (buffer, encoding) { + if (_.isString(buffer) || _.isNumber(buffer)) { + return Buffer.from('' + buffer, encoding || 'utf8'); + } else if (Buffer.isBuffer(buffer)) { + return buffer; + } else if (_.isObject(buffer)) { + return Buffer.from(JSON.stringify(buffer)); + } else { + throw Error("Unexpected data type"); + } + }; + + return NodeRSA; +})(); + +}).call(this,require("buffer").Buffer) +},{"./formats/formats.js":150,"./libs/rsa.js":154,"./schemes/schemes.js":156,"./utils":157,"buffer":185}],144:[function(require,module,exports){ +// Copyright 2011 Mark Cavage All rights reserved. + + +module.exports = { + + newInvalidAsn1Error: function (msg) { + var e = new Error(); + e.name = 'InvalidAsn1Error'; + e.message = msg || ''; + return e; + } + +}; + +},{}],145:[function(require,module,exports){ +// Copyright 2011 Mark Cavage All rights reserved. + +var errors = require('./errors'); +var types = require('./types'); + +var Reader = require('./reader'); + +// --- Exports + +module.exports = { + Reader: Reader, +}; + +for (var t in types) { + if (types.hasOwnProperty(t)) + module.exports[t] = types[t]; +} +for (var e in errors) { + if (errors.hasOwnProperty(e)) + module.exports[e] = errors[e]; +} + +},{"./errors":144,"./reader":146,"./types":147}],146:[function(require,module,exports){ +(function (Buffer){ +// Copyright 2011 Mark Cavage All rights reserved. + +var assert = require('assert'); +// var Buffer = require('safer-buffer').Buffer; + +var ASN1 = require('./types'); +var errors = require('./errors'); + + +// --- Globals + +var newInvalidAsn1Error = errors.newInvalidAsn1Error; + + + +// --- API + +function Reader(data) { + if (!data || !Buffer.isBuffer(data)) + throw new TypeError('data must be a node Buffer'); + + this._buf = data; + this._size = data.length; + + // These hold the "current" state + this._len = 0; + this._offset = 0; +} + +Object.defineProperty(Reader.prototype, 'length', { + enumerable: true, + get: function () { return (this._len); } +}); + +Object.defineProperty(Reader.prototype, 'offset', { + enumerable: true, + get: function () { return (this._offset); } +}); + +Object.defineProperty(Reader.prototype, 'remain', { + get: function () { return (this._size - this._offset); } +}); + +Object.defineProperty(Reader.prototype, 'buffer', { + get: function () { return (this._buf.slice(this._offset)); } +}); + + +/** + * Reads a single byte and advances offset; you can pass in `true` to make this + * a "peek" operation (i.e., get the byte, but don't advance the offset). + * + * @param {Boolean} peek true means don't move offset. + * @return {Number} the next byte, null if not enough data. + */ +Reader.prototype.readByte = function (peek) { + if (this._size - this._offset < 1) + return null; + + var b = this._buf[this._offset] & 0xff; + + if (!peek) + this._offset += 1; + + return b; +}; + + +Reader.prototype.peek = function () { + return this.readByte(true); +}; + + +/** + * Reads a (potentially) variable length off the BER buffer. This call is + * not really meant to be called directly, as callers have to manipulate + * the internal buffer afterwards. + * + * As a result of this call, you can call `Reader.length`, until the + * next thing called that does a readLength. + * + * @return {Number} the amount of offset to advance the buffer. + * @throws {InvalidAsn1Error} on bad ASN.1 + */ +Reader.prototype.readLength = function (offset) { + if (offset === undefined) + offset = this._offset; + + if (offset >= this._size) + return null; + + var lenB = this._buf[offset++] & 0xff; + if (lenB === null) + return null; + + if ((lenB & 0x80) === 0x80) { + lenB &= 0x7f; + + if (lenB === 0) + throw newInvalidAsn1Error('Indefinite length not supported'); + + if (lenB > 4) + throw newInvalidAsn1Error('encoding too long'); + + if (this._size - offset < lenB) + return null; + + this._len = 0; + for (var i = 0; i < lenB; i++) + this._len = (this._len << 8) + (this._buf[offset++] & 0xff); + + } else { + // Wasn't a variable length + this._len = lenB; + } + + return offset; +}; + + +/** + * Parses the next sequence in this BER buffer. + * + * To get the length of the sequence, call `Reader.length`. + * + * @return {Number} the sequence's tag. + */ +Reader.prototype.readSequence = function (tag) { + var seq = this.peek(); + if (seq === null) + return null; + if (tag !== undefined && tag !== seq) + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + seq.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + if (o === null) + return null; + + this._offset = o; + return seq; +}; + + +Reader.prototype.readInt = function () { + return this._readTag(ASN1.Integer); +}; + + +Reader.prototype.readBoolean = function () { + return (this._readTag(ASN1.Boolean) === 0 ? false : true); +}; + + +Reader.prototype.readEnumeration = function () { + return this._readTag(ASN1.Enumeration); +}; + + +Reader.prototype.readString = function (tag, retbuf) { + if (!tag) + tag = ASN1.OctetString; + + var b = this.peek(); + if (b === null) + return null; + + if (b !== tag) + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + + if (o === null) + return null; + + if (this.length > this._size - o) + return null; + + this._offset = o; + + if (this.length === 0) + return retbuf ? Buffer.alloc(0) : ''; + + var str = this._buf.slice(this._offset, this._offset + this.length); + this._offset += this.length; + + return retbuf ? str : str.toString('utf8'); +}; + +Reader.prototype.readOID = function (tag) { + if (!tag) + tag = ASN1.OID; + + var b = this.readString(tag, true); + if (b === null) + return null; + + var values = []; + var value = 0; + + for (var i = 0; i < b.length; i++) { + var byte = b[i] & 0xff; + + value <<= 7; + value += byte & 0x7f; + if ((byte & 0x80) === 0) { + values.push(value); + value = 0; + } + } + + value = values.shift(); + values.unshift(value % 40); + values.unshift((value / 40) >> 0); + + return values.join('.'); +}; + + +Reader.prototype._readTag = function (tag) { + assert.ok(tag !== undefined); + + var b = this.peek(); + + if (b === null) + return null; + + if (b !== tag) + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + if (o === null) + return null; + + if (this.length > 4) + throw newInvalidAsn1Error('Integer too long: ' + this.length); + + if (this.length > this._size - o) + return null; + this._offset = o; + + var fb = this._buf[this._offset]; + var value = 0; + + for (var i = 0; i < this.length; i++) { + value <<= 8; + value |= (this._buf[this._offset++] & 0xff); + } + + if ((fb & 0x80) === 0x80 && i !== 4) + value -= (1 << (i * 8)); + + return value >> 0; +}; + + + +// --- Exported API + +module.exports = Reader; + +}).call(this,require("buffer").Buffer) +},{"./errors":144,"./types":147,"assert":180,"buffer":185}],147:[function(require,module,exports){ +// Copyright 2011 Mark Cavage All rights reserved. + + +module.exports = { + EOC: 0, + Boolean: 1, + Integer: 2, + BitString: 3, + OctetString: 4, + Null: 5, + OID: 6, + ObjectDescriptor: 7, + External: 8, + Real: 9, // float + Enumeration: 10, + PDV: 11, + Utf8String: 12, + RelativeOID: 13, + Sequence: 16, + Set: 17, + NumericString: 18, + PrintableString: 19, + T61String: 20, + VideotexString: 21, + IA5String: 22, + UTCTime: 23, + GeneralizedTime: 24, + GraphicString: 25, + VisibleString: 26, + GeneralString: 28, + UniversalString: 29, + CharacterString: 30, + BMPString: 31, + Constructor: 32, + Context: 128 +}; + +},{}],148:[function(require,module,exports){ +// Copyright 2011 Mark Cavage All rights reserved. + +// If you have no idea what ASN.1 or BER is, see this: +// ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc + +var Ber = require('./ber/index'); + + + +// --- Exported API + +module.exports = { + + Ber: Ber, + + BerReader: Ber.Reader, +}; + +},{"./ber/index":145}],149:[function(require,module,exports){ +var _ = require('../utils')._; +var utils = require('../utils'); + +module.exports = { + privateExport: function (key, options) { + return { + n: key.n.toBuffer(), + e: key.e, + d: key.d.toBuffer(), + p: key.p.toBuffer(), + q: key.q.toBuffer(), + dmp1: key.dmp1.toBuffer(), + dmq1: key.dmq1.toBuffer(), + coeff: key.coeff.toBuffer() + }; + }, + + privateImport: function (key, data, options) { + if (data.n && data.e && data.d && data.p && data.q && data.dmp1 && data.dmq1 && data.coeff) { + key.setPrivate( + data.n, + data.e, + data.d, + data.p, + data.q, + data.dmp1, + data.dmq1, + data.coeff + ); + } else { + throw Error("Invalid key data"); + } + }, + + publicExport: function (key, options) { + return { + n: key.n.toBuffer(), + e: key.e + }; + }, + + publicImport: function (key, data, options) { + if (data.n && data.e) { + key.setPublic( + data.n, + data.e + ); + } else { + throw Error("Invalid key data"); + } + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + if (data.n && data.e) { + if (data.d && data.p && data.q && data.dmp1 && data.dmq1 && data.coeff) { + module.exports.privateImport(key, data); + return true; + } else { + module.exports.publicImport(key, data); + return true; + } + } + + return false; + } +}; + +},{"../utils":157}],150:[function(require,module,exports){ +var _ = require('../utils')._; + +module.exports = { + pkcs1: require('./pkcs1'), + pkcs8: require('./pkcs8'), + components: require('./components'), + + detectAndImport: function (key, data, format) { + if (format === undefined) { + for (var scheme in module.exports) { + if (typeof module.exports[scheme].autoImport === 'function' && module.exports[scheme].autoImport(key, data)) { + return true; + } + } + } else if (format) { + var fmt = formatParse(format); + + if (module.exports[fmt.scheme]) { + if (fmt.keyType === 'private') { + module.exports[fmt.scheme].privateImport(key, data, fmt.keyOpt); + } else { + module.exports[fmt.scheme].publicImport(key, data, fmt.keyOpt); + } + } else { + throw Error('Unsupported key format'); + } + } + + return false; + }, +}; + +},{"../utils":157,"./components":149,"./pkcs1":151,"./pkcs8":152}],151:[function(require,module,exports){ +(function (Buffer){ +var ber = require('../asn1/index').Ber; +var _ = require('../utils')._; +var utils = require('../utils'); + +const PRIVATE_OPENING_BOUNDARY = '-----BEGIN RSA PRIVATE KEY-----'; +const PRIVATE_CLOSING_BOUNDARY = '-----END RSA PRIVATE KEY-----'; + +const PUBLIC_OPENING_BOUNDARY = '-----BEGIN RSA PUBLIC KEY-----'; +const PUBLIC_CLOSING_BOUNDARY = '-----END RSA PUBLIC KEY-----'; + +module.exports = { + privateExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var d = key.d.toBuffer(); + var p = key.p.toBuffer(); + var q = key.q.toBuffer(); + var dmp1 = key.dmp1.toBuffer(); + var dmq1 = key.dmq1.toBuffer(); + var coeff = key.coeff.toBuffer(); + + var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic + var writer = new ber.Writer({size: length}); + + writer.startSequence(); + writer.writeInt(0); + writer.writeBuffer(n, 2); + writer.writeInt(key.e); + writer.writeBuffer(d, 2); + writer.writeBuffer(p, 2); + writer.writeBuffer(q, 2); + writer.writeBuffer(dmp1, 2); + writer.writeBuffer(dmq1, 2); + writer.writeBuffer(coeff, 2); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return PRIVATE_OPENING_BOUNDARY + '\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n' + PRIVATE_CLOSING_BOUNDARY; + } + }, + + privateImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PRIVATE_OPENING_BOUNDARY, PRIVATE_CLOSING_BOUNDARY) + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } else { + throw Error('Unsupported key format'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + reader.readString(2, true); // just zero + key.setPrivate( + reader.readString(2, true), // modulus + reader.readString(2, true), // publicExponent + reader.readString(2, true), // privateExponent + reader.readString(2, true), // prime1 + reader.readString(2, true), // prime2 + reader.readString(2, true), // exponent1 -- d mod (p1) + reader.readString(2, true), // exponent2 -- d mod (q-1) + reader.readString(2, true) // coefficient -- (inverse of q) mod p + ); + }, + + publicExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var length = n.length + 512; // magic + + var bodyWriter = new ber.Writer({size: length}); + bodyWriter.startSequence(); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.endSequence(); + + if (options.type === 'der') { + return bodyWriter.buffer; + } else { + return PUBLIC_OPENING_BOUNDARY + '\n' + utils.linebrk(bodyWriter.buffer.toString('base64'), 64) + '\n' + PUBLIC_CLOSING_BOUNDARY; + } + }, + + publicImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PUBLIC_OPENING_BOUNDARY, PUBLIC_CLOSING_BOUNDARY) + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var body = new ber.Reader(buffer); + body.readSequence(); + key.setPublic( + body.readString(0x02, true), // modulus + body.readString(0x02, true) // publicExponent + ); + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + // [\S\s]* matches zero or more of any character + if (/^[\S\s]*-----BEGIN RSA PRIVATE KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END RSA PRIVATE KEY-----[\S\s]*$/g.test(data)) { + module.exports.privateImport(key, data); + return true; + } + + if (/^[\S\s]*-----BEGIN RSA PUBLIC KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END RSA PUBLIC KEY-----[\S\s]*$/g.test(data)) { + module.exports.publicImport(key, data); + return true; + } + + return false; + } +}; + +}).call(this,require("buffer").Buffer) +},{"../asn1/index":148,"../utils":157,"buffer":185}],152:[function(require,module,exports){ +(function (Buffer){ +var ber = require('../asn1/index').Ber; +var _ = require('../utils')._; +var PUBLIC_RSA_OID = '1.2.840.113549.1.1.1'; +var utils = require('../utils'); + +const PRIVATE_OPENING_BOUNDARY = '-----BEGIN PRIVATE KEY-----'; +const PRIVATE_CLOSING_BOUNDARY = '-----END PRIVATE KEY-----'; + +const PUBLIC_OPENING_BOUNDARY = '-----BEGIN PUBLIC KEY-----'; +const PUBLIC_CLOSING_BOUNDARY = '-----END PUBLIC KEY-----'; + +module.exports = { + privateExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var d = key.d.toBuffer(); + var p = key.p.toBuffer(); + var q = key.q.toBuffer(); + var dmp1 = key.dmp1.toBuffer(); + var dmq1 = key.dmq1.toBuffer(); + var coeff = key.coeff.toBuffer(); + + var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic + var bodyWriter = new ber.Writer({size: length}); + + bodyWriter.startSequence(); + bodyWriter.writeInt(0); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.writeBuffer(d, 2); + bodyWriter.writeBuffer(p, 2); + bodyWriter.writeBuffer(q, 2); + bodyWriter.writeBuffer(dmp1, 2); + bodyWriter.writeBuffer(dmq1, 2); + bodyWriter.writeBuffer(coeff, 2); + bodyWriter.endSequence(); + + var writer = new ber.Writer({size: length}); + writer.startSequence(); + writer.writeInt(0); + writer.startSequence(); + writer.writeOID(PUBLIC_RSA_OID); + writer.writeNull(); + writer.endSequence(); + writer.writeBuffer(bodyWriter.buffer, 4); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return PRIVATE_OPENING_BOUNDARY + '\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n' + PRIVATE_CLOSING_BOUNDARY; + } + }, + + privateImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PRIVATE_OPENING_BOUNDARY, PRIVATE_CLOSING_BOUNDARY) + .replace('-----END PRIVATE KEY-----', '') + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } else { + throw Error('Unsupported key format'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + reader.readInt(0); + var header = new ber.Reader(reader.readString(0x30, true)); + + if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { + throw Error('Invalid Public key format'); + } + + var body = new ber.Reader(reader.readString(0x04, true)); + body.readSequence(); + body.readString(2, true); // just zero + key.setPrivate( + body.readString(2, true), // modulus + body.readString(2, true), // publicExponent + body.readString(2, true), // privateExponent + body.readString(2, true), // prime1 + body.readString(2, true), // prime2 + body.readString(2, true), // exponent1 -- d mod (p1) + body.readString(2, true), // exponent2 -- d mod (q-1) + body.readString(2, true) // coefficient -- (inverse of q) mod p + ); + }, + + publicExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var length = n.length + 512; // magic + + var bodyWriter = new ber.Writer({size: length}); + bodyWriter.writeByte(0); + bodyWriter.startSequence(); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.endSequence(); + + var writer = new ber.Writer({size: length}); + writer.startSequence(); + writer.startSequence(); + writer.writeOID(PUBLIC_RSA_OID); + writer.writeNull(); + writer.endSequence(); + writer.writeBuffer(bodyWriter.buffer, 3); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return PUBLIC_OPENING_BOUNDARY + '\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n' + PUBLIC_CLOSING_BOUNDARY; + } + }, + + publicImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PUBLIC_OPENING_BOUNDARY, PUBLIC_CLOSING_BOUNDARY) + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + var header = new ber.Reader(reader.readString(0x30, true)); + + if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { + throw Error('Invalid Public key format'); + } + + var body = new ber.Reader(reader.readString(0x03, true)); + body.readByte(); + body.readSequence(); + key.setPublic( + body.readString(0x02, true), // modulus + body.readString(0x02, true) // publicExponent + ); + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + if (/^[\S\s]*-----BEGIN PRIVATE KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END PRIVATE KEY-----[\S\s]*$/g.test(data)) { + module.exports.privateImport(key, data); + return true; + } + + if (/^[\S\s]*-----BEGIN PUBLIC KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END PUBLIC KEY-----[\S\s]*$/g.test(data)) { + module.exports.publicImport(key, data); + return true; + } + + return false; + } +}; + +}).call(this,require("buffer").Buffer) +},{"../asn1/index":148,"../utils":157,"buffer":185}],153:[function(require,module,exports){ +(function (Buffer){ +/* + * Basic JavaScript BN library - subset useful for RSA encryption. + * + * Copyright (c) 2003-2005 Tom Wu + * ~BSD license~ + * + * Added Node.js Buffers support + * 2014 rzcoder + * + * Deleted some stuff to save bytes + * frosty00 + */ + +var _ = require('../utils')._; + +// Bits per digit +var dbits; + +// (public) Constructor +function BigInteger(a, b) { + if (a != null) { + if ("number" == typeof a) { + this.fromNumber(a, b); + } else if (Buffer.isBuffer(a)) { + this.fromBuffer(a); + } else if (b == null && "string" != typeof a) { + this.fromByteArray(a); + } else { + this.fromString(a, b); + } + } +} + +// return new, unset BigInteger +function nbi() { + return new BigInteger(null); +} + +// am: Compute w_j += (x*this_i), propagate carries, +// c is initial carry, returns final carry. +// c < 3*dvalue, x < 2*dvalue, this_i < dvalue +// We need to select the fastest one that works in this environment. + +// Alternately, set max digit bits to 28 since some +// browsers slow down when dealing with 32-bit numbers. +function am3(i, x, w, j, c, n) { + var xl = x & 0x3fff, xh = x >> 14; + while (--n >= 0) { + var l = this[i] & 0x3fff; + var h = this[i++] >> 14; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; + c = (l >> 28) + (m >> 14) + xh * h; + w[j++] = l & 0xfffffff; + } + return c; +} + +// We need to select the fastest one that works in this environment. +//if (j_lm && (navigator.appName == "Microsoft Internet Explorer")) { +// BigInteger.prototype.am = am2; +// dbits = 30; +//} else if (j_lm && (navigator.appName != "Netscape")) { +// BigInteger.prototype.am = am1; +// dbits = 26; +//} else { // Mozilla/Netscape seems to prefer am3 +// BigInteger.prototype.am = am3; +// dbits = 28; +//} + +// For node.js, we pick am3 with max dbits to 28. +BigInteger.prototype.am = am3; +dbits = 28; + +BigInteger.prototype.DB = dbits; +BigInteger.prototype.DM = ((1 << dbits) - 1); +BigInteger.prototype.DV = (1 << dbits); + +var BI_FP = 52; +BigInteger.prototype.FV = Math.pow(2, BI_FP); +BigInteger.prototype.F1 = BI_FP - dbits; +BigInteger.prototype.F2 = 2 * dbits - BI_FP; + +// Digit conversions +var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; +var BI_RC = new Array(); +var rr, vv; +rr = "0".charCodeAt(0); +for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; +rr = "a".charCodeAt(0); +for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; +rr = "A".charCodeAt(0); +for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; + +function int2char(n) { + return BI_RM.charAt(n); +} +function intAt(s, i) { + var c = BI_RC[s.charCodeAt(i)]; + return (c == null) ? -1 : c; +} + +// (protected) copy this to r +function bnpCopyTo(r) { + for (var i = this.t - 1; i >= 0; --i) r[i] = this[i]; + r.t = this.t; + r.s = this.s; +} + +// (protected) set from integer value x, -DV <= x < DV +function bnpFromInt(x) { + this.t = 1; + this.s = (x < 0) ? -1 : 0; + if (x > 0) this[0] = x; + else if (x < -1) this[0] = x + DV; + else this.t = 0; +} + +// return bigint initialized to value +function nbv(i) { + var r = nbi(); + r.fromInt(i); + return r; +} + +// (protected) set from string and radix +function bnpFromString(data, radix, unsigned) { + var k; + switch (radix) { + case 2: + k = 1; + break; + case 4: + k = 2; + break; + case 8: + k = 3; + break; + case 16: + k = 4; + break; + case 32: + k = 5; + break; + case 256: + k = 8; + break; + default: + this.fromRadix(data, radix); + return; + } + + this.t = 0; + this.s = 0; + + var i = data.length; + var mi = false; + var sh = 0; + + while (--i >= 0) { + var x = (k == 8) ? data[i] & 0xff : intAt(data, i); + if (x < 0) { + if (data.charAt(i) == "-") mi = true; + continue; + } + mi = false; + if (sh === 0) + this[this.t++] = x; + else if (sh + k > this.DB) { + this[this.t - 1] |= (x & ((1 << (this.DB - sh)) - 1)) << sh; + this[this.t++] = (x >> (this.DB - sh)); + } + else + this[this.t - 1] |= x << sh; + sh += k; + if (sh >= this.DB) sh -= this.DB; + } + if ((!unsigned) && k == 8 && (data[0] & 0x80) != 0) { + this.s = -1; + if (sh > 0) this[this.t - 1] |= ((1 << (this.DB - sh)) - 1) << sh; + } + this.clamp(); + if (mi) BigInteger.ZERO.subTo(this, this); +} + +function bnpFromByteArray(a, unsigned) { + this.fromString(a, 256, unsigned) +} + +function bnpFromBuffer(a) { + this.fromString(a, 256, true) +} + +// (protected) clamp off excess high words +function bnpClamp() { + var c = this.s & this.DM; + while (this.t > 0 && this[this.t - 1] == c) --this.t; +} + +// (public) return string representation in given radix +function bnToString(b) { + if (this.s < 0) return "-" + this.negate().toString(b); + var k; + if (b == 16) k = 4; + else if (b == 8) k = 3; + else if (b == 2) k = 1; + else if (b == 32) k = 5; + else if (b == 4) k = 2; + else return this.toRadix(b); + var km = (1 << k) - 1, d, m = false, r = "", i = this.t; + var p = this.DB - (i * this.DB) % k; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) > 0) { + m = true; + r = int2char(d); + } + while (i >= 0) { + if (p < k) { + d = (this[i] & ((1 << p) - 1)) << (k - p); + d |= this[--i] >> (p += this.DB - k); + } + else { + d = (this[i] >> (p -= k)) & km; + if (p <= 0) { + p += this.DB; + --i; + } + } + if (d > 0) m = true; + if (m) r += int2char(d); + } + } + return m ? r : "0"; +} + +// (public) -this +function bnNegate() { + var r = nbi(); + BigInteger.ZERO.subTo(this, r); + return r; +} + +// (public) |this| +function bnAbs() { + return (this.s < 0) ? this.negate() : this; +} + +// (public) return + if this > a, - if this < a, 0 if equal +function bnCompareTo(a) { + var r = this.s - a.s; + if (r != 0) return r; + var i = this.t; + r = i - a.t; + if (r != 0) return (this.s < 0) ? -r : r; + while (--i >= 0) if ((r = this[i] - a[i]) != 0) return r; + return 0; +} + +// returns bit length of the integer x +function nbits(x) { + var r = 1, t; + if ((t = x >>> 16) != 0) { + x = t; + r += 16; + } + if ((t = x >> 8) != 0) { + x = t; + r += 8; + } + if ((t = x >> 4) != 0) { + x = t; + r += 4; + } + if ((t = x >> 2) != 0) { + x = t; + r += 2; + } + if ((t = x >> 1) != 0) { + x = t; + r += 1; + } + return r; +} + +// (public) return the number of bits in "this" +function bnBitLength() { + if (this.t <= 0) return 0; + return this.DB * (this.t - 1) + nbits(this[this.t - 1] ^ (this.s & this.DM)); +} + +// (protected) r = this << n*DB +function bnpDLShiftTo(n, r) { + var i; + for (i = this.t - 1; i >= 0; --i) r[i + n] = this[i]; + for (i = n - 1; i >= 0; --i) r[i] = 0; + r.t = this.t + n; + r.s = this.s; +} + +// (protected) r = this >> n*DB +function bnpDRShiftTo(n, r) { + for (var i = n; i < this.t; ++i) r[i - n] = this[i]; + r.t = Math.max(this.t - n, 0); + r.s = this.s; +} + +// (protected) r = this << n +function bnpLShiftTo(n, r) { + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << cbs) - 1; + var ds = Math.floor(n / this.DB), c = (this.s << bs) & this.DM, i; + for (i = this.t - 1; i >= 0; --i) { + r[i + ds + 1] = (this[i] >> cbs) | c; + c = (this[i] & bm) << bs; + } + for (i = ds - 1; i >= 0; --i) r[i] = 0; + r[ds] = c; + r.t = this.t + ds + 1; + r.s = this.s; + r.clamp(); +} + +// (protected) r = this >> n +function bnpRShiftTo(n, r) { + r.s = this.s; + var ds = Math.floor(n / this.DB); + if (ds >= this.t) { + r.t = 0; + return; + } + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << bs) - 1; + r[0] = this[ds] >> bs; + for (var i = ds + 1; i < this.t; ++i) { + r[i - ds - 1] |= (this[i] & bm) << cbs; + r[i - ds] = this[i] >> bs; + } + if (bs > 0) r[this.t - ds - 1] |= (this.s & bm) << cbs; + r.t = this.t - ds; + r.clamp(); +} + +// (protected) r = this - a +function bnpSubTo(a, r) { + var i = 0, c = 0, m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] - a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c -= a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while (i < a.t) { + c -= a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c < -1) r[i++] = this.DV + c; + else if (c > 0) r[i++] = c; + r.t = i; + r.clamp(); +} + +// (protected) r = this * a, r != this,a (HAC 14.12) +// "this" should be the larger one if appropriate. +function bnpMultiplyTo(a, r) { + var x = this.abs(), y = a.abs(); + var i = x.t; + r.t = i + y.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < y.t; ++i) r[i + x.t] = x.am(0, y[i], r, i, 0, x.t); + r.s = 0; + r.clamp(); + if (this.s != a.s) BigInteger.ZERO.subTo(r, r); +} + +// (protected) r = this^2, r != this (HAC 14.16) +function bnpSquareTo(r) { + var x = this.abs(); + var i = r.t = 2 * x.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < x.t - 1; ++i) { + var c = x.am(i, x[i], r, 2 * i, 0, 1); + if ((r[i + x.t] += x.am(i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { + r[i + x.t] -= x.DV; + r[i + x.t + 1] = 1; + } + } + if (r.t > 0) r[r.t - 1] += x.am(i, x[i], r, 2 * i, 0, 1); + r.s = 0; + r.clamp(); +} + +// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) +// r != q, this != m. q or r may be null. +function bnpDivRemTo(m, q, r) { + var pm = m.abs(); + if (pm.t <= 0) return; + var pt = this.abs(); + if (pt.t < pm.t) { + if (q != null) q.fromInt(0); + if (r != null) this.copyTo(r); + return; + } + if (r == null) r = nbi(); + var y = nbi(), ts = this.s, ms = m.s; + var nsh = this.DB - nbits(pm[pm.t - 1]); // normalize modulus + if (nsh > 0) { + pm.lShiftTo(nsh, y); + pt.lShiftTo(nsh, r); + } + else { + pm.copyTo(y); + pt.copyTo(r); + } + var ys = y.t; + var y0 = y[ys - 1]; + if (y0 === 0) return; + var yt = y0 * (1 << this.F1) + ((ys > 1) ? y[ys - 2] >> this.F2 : 0); + var d1 = this.FV / yt, d2 = (1 << this.F1) / yt, e = 1 << this.F2; + var i = r.t, j = i - ys, t = (q == null) ? nbi() : q; + y.dlShiftTo(j, t); + if (r.compareTo(t) >= 0) { + r[r.t++] = 1; + r.subTo(t, r); + } + BigInteger.ONE.dlShiftTo(ys, t); + t.subTo(y, y); // "negative" y so we can replace sub with am later + while (y.t < ys) y[y.t++] = 0; + while (--j >= 0) { + // Estimate quotient digit + var qd = (r[--i] == y0) ? this.DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); + if ((r[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out + y.dlShiftTo(j, t); + r.subTo(t, r); + while (r[i] < --qd) r.subTo(t, r); + } + } + if (q != null) { + r.drShiftTo(ys, q); + if (ts != ms) BigInteger.ZERO.subTo(q, q); + } + r.t = ys; + r.clamp(); + if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder + if (ts < 0) BigInteger.ZERO.subTo(r, r); +} + +// (public) this mod a +function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a, null, r); + if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); + return r; +} + +// Modular reduction using "classic" algorithm +function Classic(m) { + this.m = m; +} +function cConvert(x) { + if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; +} +function cRevert(x) { + return x; +} +function cReduce(x) { + x.divRemTo(this.m, null, x); +} +function cMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} +function cSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +Classic.prototype.convert = cConvert; +Classic.prototype.revert = cRevert; +Classic.prototype.reduce = cReduce; +Classic.prototype.mulTo = cMulTo; +Classic.prototype.sqrTo = cSqrTo; + +// (protected) return "-1/this % 2^DB"; useful for Mont. reduction +// justification: +// xy == 1 (mod m) +// xy = 1+km +// xy(2-xy) = (1+km)(1-km) +// x[y(2-xy)] = 1-k^2m^2 +// x[y(2-xy)] == 1 (mod m^2) +// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 +// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. +// JS multiply "overflows" differently from C/C++, so care is needed here. +function bnpInvDigit() { + if (this.t < 1) return 0; + var x = this[0]; + if ((x & 1) === 0) return 0; + var y = x & 3; // y == 1/x mod 2^2 + y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 + y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 + y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y * (2 - x * y % this.DV)) % this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return (y > 0) ? this.DV - y : -y; +} + +// Montgomery reduction +function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp & 0x7fff; + this.mph = this.mp >> 15; + this.um = (1 << (m.DB - 15)) - 1; + this.mt2 = 2 * m.t; +} + +// xR mod m +function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t, r); + r.divRemTo(this.m, null, r); + if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); + return r; +} + +// x/R mod m +function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; +} + +// x = x/R mod m (HAC 14.32) +function montReduce(x) { + while (x.t <= this.mt2) // pad x so am has enough room later + x[x.t++] = 0; + for (var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x[i]*mp mod DV + var j = x[i] & 0x7fff; + var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & x.DM; + // use am to combine the multiply-shift-add into one call + j = i + this.m.t; + x[j] += this.m.am(0, u0, x, i, 0, this.m.t); + // propagate carry + while (x[j] >= x.DV) { + x[j] -= x.DV; + x[++j]++; + } + } + x.clamp(); + x.drShiftTo(this.m.t, x); + if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); +} + +// r = "x^2/R mod m"; x != r +function montSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +// r = "xy/R mod m"; x,y != r +function montMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} + +Montgomery.prototype.convert = montConvert; +Montgomery.prototype.revert = montRevert; +Montgomery.prototype.reduce = montReduce; +Montgomery.prototype.mulTo = montMulTo; +Montgomery.prototype.sqrTo = montSqrTo; + +// (protected) true iff this is even +function bnpIsEven() { + return ((this.t > 0) ? (this[0] & 1) : this.s) === 0; +} + +// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) +function bnpExp(e, z) { + if (e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e) - 1; + g.copyTo(r); + while (--i >= 0) { + z.sqrTo(r, r2); + if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); + else { + var t = r; + r = r2; + r2 = t; + } + } + return z.revert(r); +} + +// Copyright (c) 2005-2009 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Extended JavaScript BN functions, required for RSA private ops. + +// Version 1.1: new BigInteger("0", 10) returns "proper" zero +// Version 1.2: square() API, isProbablePrime fix + +//(public) +function bnClone() { + var r = nbi(); + this.copyTo(r); + return r; +} + +//(public) return value as integer +function bnIntValue() { + if (this.s < 0) { + if (this.t == 1) return this[0] - this.DV; + else if (this.t === 0) return -1; + } + else if (this.t == 1) return this[0]; + else if (this.t === 0) return 0; +// assumes 16 < DB < 32 + return ((this[1] & ((1 << (32 - this.DB)) - 1)) << this.DB) | this[0]; +} + +//(protected) return x s.t. r^x < DV +function bnpChunkSize(r) { + return Math.floor(Math.LN2 * this.DB / Math.log(r)); +} + +//(public) 0 if this === 0, 1 if this > 0 +function bnSigNum() { + if (this.s < 0) return -1; + else if (this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; + else return 1; +} + +//(protected) convert to radix string +function bnpToRadix(b) { + if (b == null) b = 10; + if (this.signum() === 0 || b < 2 || b > 36) return "0"; + var cs = this.chunkSize(b); + var a = Math.pow(b, cs); + var d = nbv(a), y = nbi(), z = nbi(), r = ""; + this.divRemTo(d, y, z); + while (y.signum() > 0) { + r = (a + z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d, y, z); + } + return z.intValue().toString(b) + r; +} + +//(protected) convert from radix string +function bnpFromRadix(s, b) { + this.fromInt(0); + if (b == null) b = 10; + var cs = this.chunkSize(b); + var d = Math.pow(b, cs), mi = false, j = 0, w = 0; + for (var i = 0; i < s.length; ++i) { + var x = intAt(s, i); + if (x < 0) { + if (s.charAt(i) == "-" && this.signum() === 0) mi = true; + continue; + } + w = b * w + x; + if (++j >= cs) { + this.dMultiply(d); + this.dAddOffset(w, 0); + j = 0; + w = 0; + } + } + if (j > 0) { + this.dMultiply(Math.pow(b, j)); + this.dAddOffset(w, 0); + } + if (mi) BigInteger.ZERO.subTo(this, this); +} + +//(public) convert to bigendian byte array +function bnToByteArray() { + var i = this.t, r = new Array(); + r[0] = this.s; + var p = this.DB - (i * this.DB) % 8, d, k = 0; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) != (this.s & this.DM) >> p) + r[k++] = d | (this.s << (this.DB - p)); + while (i >= 0) { + if (p < 8) { + d = (this[i] & ((1 << p) - 1)) << (8 - p); + d |= this[--i] >> (p += this.DB - 8); + } + else { + d = (this[i] >> (p -= 8)) & 0xff; + if (p <= 0) { + p += this.DB; + --i; + } + } + if ((d & 0x80) != 0) d |= -256; + if (k === 0 && (this.s & 0x80) != (d & 0x80)) ++k; + if (k > 0 || d != this.s) r[k++] = d; + } + } + return r; +} + +/** + * return Buffer object + * @param trim {boolean} slice buffer if first element == 0 + * @returns {Buffer} + */ +function bnToBuffer(trimOrSize) { + var res = Buffer.from(this.toByteArray()); + if (trimOrSize === true && res[0] === 0) { + res = res.slice(1); + } else if (_.isNumber(trimOrSize)) { + if (res.length > trimOrSize) { + for (var i = 0; i < res.length - trimOrSize; i++) { + if (res[i] !== 0) { + return null; + } + } + return res.slice(res.length - trimOrSize); + } else if (res.length < trimOrSize) { + var padded = Buffer.alloc(trimOrSize); + padded.fill(0, 0, trimOrSize - res.length); + res.copy(padded, trimOrSize - res.length); + return padded; + } + } + return res; +} + +function bnEquals(a) { + return (this.compareTo(a) == 0); +} +function bnMin(a) { + return (this.compareTo(a) < 0) ? this : a; +} +function bnMax(a) { + return (this.compareTo(a) > 0) ? this : a; +} + +//(protected) r = this op a (bitwise) +function bnpBitwiseTo(a, op, r) { + var i, f, m = Math.min(a.t, this.t); + for (i = 0; i < m; ++i) r[i] = op(this[i], a[i]); + if (a.t < this.t) { + f = a.s & this.DM; + for (i = m; i < this.t; ++i) r[i] = op(this[i], f); + r.t = this.t; + } + else { + f = this.s & this.DM; + for (i = m; i < a.t; ++i) r[i] = op(f, a[i]); + r.t = a.t; + } + r.s = op(this.s, a.s); + r.clamp(); +} + +//(public) this & a +function op_and(x, y) { + return x & y; +} +function bnAnd(a) { + var r = nbi(); + this.bitwiseTo(a, op_and, r); + return r; +} + +//(public) this | a +function op_or(x, y) { + return x | y; +} +function bnOr(a) { + var r = nbi(); + this.bitwiseTo(a, op_or, r); + return r; +} + +//(public) this ^ a +function op_xor(x, y) { + return x ^ y; +} +function bnXor(a) { + var r = nbi(); + this.bitwiseTo(a, op_xor, r); + return r; +} + +//(public) this & ~a +function op_andnot(x, y) { + return x & ~y; +} + +//(public) ~this +function bnNot() { + var r = nbi(); + for (var i = 0; i < this.t; ++i) r[i] = this.DM & ~this[i]; + r.t = this.t; + r.s = ~this.s; + return r; +} + +//return index of lowest 1-bit in x, x < 2^31 +function lbit(x) { + if (x === 0) return -1; + var r = 0; + if ((x & 0xffff) === 0) { + x >>= 16; + r += 16; + } + if ((x & 0xff) === 0) { + x >>= 8; + r += 8; + } + if ((x & 0xf) === 0) { + x >>= 4; + r += 4; + } + if ((x & 3) === 0) { + x >>= 2; + r += 2; + } + if ((x & 1) === 0) ++r; + return r; +} + +//(public) returns index of lowest 1-bit (or -1 if none) +function bnGetLowestSetBit() { + for (var i = 0; i < this.t; ++i) + if (this[i] != 0) return i * this.DB + lbit(this[i]); + if (this.s < 0) return this.t * this.DB; + return -1; +} + +//return number of 1 bits in x +function cbit(x) { + var r = 0; + while (x != 0) { + x &= x - 1; + ++r; + } + return r; +} + +//(protected) r = this + a +function bnpAddTo(a, r) { + var i = 0, c = 0, m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] + a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c += a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while (i < a.t) { + c += a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c > 0) r[i++] = c; + else if (c < -1) r[i++] = this.DV + c; + r.t = i; + r.clamp(); +} + +//(public) this + a +function bnAdd(a) { + var r = nbi(); + this.addTo(a, r); + return r; +} + +//(public) this - a +function bnSubtract(a) { + var r = nbi(); + this.subTo(a, r); + return r; +} + +//(public) this * a +function bnMultiply(a) { + var r = nbi(); + this.multiplyTo(a, r); + return r; +} + +// (public) this^2 +function bnSquare() { + var r = nbi(); + this.squareTo(r); + return r; +} + +//(public) this / a +function bnDivide(a) { + var r = nbi(); + this.divRemTo(a, r, null); + return r; +} + +//(public) this % a +function bnRemainder(a) { + var r = nbi(); + this.divRemTo(a, null, r); + return r; +} + +//(protected) this *= n, this >= 0, 1 < n < DV +function bnpDMultiply(n) { + this[this.t] = this.am(0, n - 1, this, 0, 0, this.t); + ++this.t; + this.clamp(); +} + +//(protected) this += n << w words, this >= 0 +function bnpDAddOffset(n, w) { + if (n === 0) return; + while (this.t <= w) this[this.t++] = 0; + this[w] += n; + while (this[w] >= this.DV) { + this[w] -= this.DV; + if (++w >= this.t) this[this.t++] = 0; + ++this[w]; + } +} + +//A "null" reducer +function NullExp() { +} +function nNop(x) { + return x; +} +function nMulTo(x, y, r) { + x.multiplyTo(y, r); +} +function nSqrTo(x, r) { + x.squareTo(r); +} + +NullExp.prototype.convert = nNop; +NullExp.prototype.revert = nNop; +NullExp.prototype.mulTo = nMulTo; +NullExp.prototype.sqrTo = nSqrTo; + +//(public) this^e +function bnPow(e) { + return this.exp(e, new NullExp()); +} + +//(protected) r = lower n words of "this * a", a.t <= n +//"this" should be the larger one if appropriate. +function bnpMultiplyLowerTo(a, n, r) { + var i = Math.min(this.t + a.t, n); + r.s = 0; // assumes a,this >= 0 + r.t = i; + while (i > 0) r[--i] = 0; + var j; + for (j = r.t - this.t; i < j; ++i) r[i + this.t] = this.am(0, a[i], r, i, 0, this.t); + for (j = Math.min(a.t, n); i < j; ++i) this.am(0, a[i], r, i, 0, n - i); + r.clamp(); +} + +//(protected) r = "this * a" without lower n words, n > 0 +//"this" should be the larger one if appropriate. +function bnpMultiplyUpperTo(a, n, r) { + --n; + var i = r.t = this.t + a.t - n; + r.s = 0; // assumes a,this >= 0 + while (--i >= 0) r[i] = 0; + for (i = Math.max(n - this.t, 0); i < a.t; ++i) + r[this.t + i - n] = this.am(n - i, a[i], r, 0, 0, this.t + i - n); + r.clamp(); + r.drShiftTo(1, r); +} + +//Barrett modular reduction +function Barrett(m) { +// setup Barrett + this.r2 = nbi(); + this.q3 = nbi(); + BigInteger.ONE.dlShiftTo(2 * m.t, this.r2); + this.mu = this.r2.divide(m); + this.m = m; +} + +function barrettConvert(x) { + if (x.s < 0 || x.t > 2 * this.m.t) return x.mod(this.m); + else if (x.compareTo(this.m) < 0) return x; + else { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; + } +} + +function barrettRevert(x) { + return x; +} + +//x = x mod m (HAC 14.42) +function barrettReduce(x) { + x.drShiftTo(this.m.t - 1, this.r2); + if (x.t > this.m.t + 1) { + x.t = this.m.t + 1; + x.clamp(); + } + this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3); + this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); + while (x.compareTo(this.r2) < 0) x.dAddOffset(1, this.m.t + 1); + x.subTo(this.r2, x); + while (x.compareTo(this.m) >= 0) x.subTo(this.m, x); +} + +//r = x^2 mod m; x != r +function barrettSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +//r = x*y mod m; x,y != r +function barrettMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} + +Barrett.prototype.convert = barrettConvert; +Barrett.prototype.revert = barrettRevert; +Barrett.prototype.reduce = barrettReduce; +Barrett.prototype.mulTo = barrettMulTo; +Barrett.prototype.sqrTo = barrettSqrTo; + +//(public) this^e % m (HAC 14.85) +function bnModPow(e, m) { + var i = e.bitLength(), k, r = nbv(1), z; + if (i <= 0) return r; + else if (i < 18) k = 1; + else if (i < 48) k = 3; + else if (i < 144) k = 4; + else if (i < 768) k = 5; + else k = 6; + if (i < 8) + z = new Classic(m); + else if (m.isEven()) + z = new Barrett(m); + else + z = new Montgomery(m); + +// precomputation + var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; + g[1] = z.convert(this); + if (k > 1) { + var g2 = nbi(); + z.sqrTo(g[1], g2); + while (n <= km) { + g[n] = nbi(); + z.mulTo(g2, g[n - 2], g[n]); + n += 2; + } + } + + var j = e.t - 1, w, is1 = true, r2 = nbi(), t; + i = nbits(e[j]) - 1; + while (j >= 0) { + if (i >= k1) w = (e[j] >> (i - k1)) & km; + else { + w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); + if (j > 0) w |= e[j - 1] >> (this.DB + i - k1); + } + + n = k; + while ((w & 1) === 0) { + w >>= 1; + --n; + } + if ((i -= n) < 0) { + i += this.DB; + --j; + } + if (is1) { // ret == 1, don't bother squaring or multiplying it + g[w].copyTo(r); + is1 = false; + } + else { + while (n > 1) { + z.sqrTo(r, r2); + z.sqrTo(r2, r); + n -= 2; + } + if (n > 0) z.sqrTo(r, r2); else { + t = r; + r = r2; + r2 = t; + } + z.mulTo(r2, g[w], r); + } + + while (j >= 0 && (e[j] & (1 << i)) === 0) { + z.sqrTo(r, r2); + t = r; + r = r2; + r2 = t; + if (--i < 0) { + i = this.DB - 1; + --j; + } + } + } + return z.revert(r); +} + +//(public) gcd(this,a) (HAC 14.54) +function bnGCD(a) { + var x = (this.s < 0) ? this.negate() : this.clone(); + var y = (a.s < 0) ? a.negate() : a.clone(); + if (x.compareTo(y) < 0) { + var t = x; + x = y; + y = t; + } + var i = x.getLowestSetBit(), g = y.getLowestSetBit(); + if (g < 0) return x; + if (i < g) g = i; + if (g > 0) { + x.rShiftTo(g, x); + y.rShiftTo(g, y); + } + while (x.signum() > 0) { + if ((i = x.getLowestSetBit()) > 0) x.rShiftTo(i, x); + if ((i = y.getLowestSetBit()) > 0) y.rShiftTo(i, y); + if (x.compareTo(y) >= 0) { + x.subTo(y, x); + x.rShiftTo(1, x); + } + else { + y.subTo(x, y); + y.rShiftTo(1, y); + } + } + if (g > 0) y.lShiftTo(g, y); + return y; +} + +// protected +BigInteger.prototype.copyTo = bnpCopyTo; +BigInteger.prototype.fromInt = bnpFromInt; +BigInteger.prototype.fromString = bnpFromString; +BigInteger.prototype.fromByteArray = bnpFromByteArray; +BigInteger.prototype.fromBuffer = bnpFromBuffer; +BigInteger.prototype.clamp = bnpClamp; +BigInteger.prototype.dlShiftTo = bnpDLShiftTo; +BigInteger.prototype.drShiftTo = bnpDRShiftTo; +BigInteger.prototype.lShiftTo = bnpLShiftTo; +BigInteger.prototype.rShiftTo = bnpRShiftTo; +BigInteger.prototype.subTo = bnpSubTo; +BigInteger.prototype.multiplyTo = bnpMultiplyTo; +BigInteger.prototype.squareTo = bnpSquareTo; +BigInteger.prototype.divRemTo = bnpDivRemTo; +BigInteger.prototype.invDigit = bnpInvDigit; +BigInteger.prototype.isEven = bnpIsEven; +BigInteger.prototype.exp = bnpExp; + +BigInteger.prototype.chunkSize = bnpChunkSize; +BigInteger.prototype.toRadix = bnpToRadix; +BigInteger.prototype.fromRadix = bnpFromRadix; +BigInteger.prototype.bitwiseTo = bnpBitwiseTo; +BigInteger.prototype.addTo = bnpAddTo; +BigInteger.prototype.dMultiply = bnpDMultiply; +BigInteger.prototype.dAddOffset = bnpDAddOffset; +BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; +BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; + +// public +BigInteger.prototype.toString = bnToString; +BigInteger.prototype.negate = bnNegate; +BigInteger.prototype.abs = bnAbs; +BigInteger.prototype.compareTo = bnCompareTo; +BigInteger.prototype.bitLength = bnBitLength; +BigInteger.prototype.mod = bnMod; + +BigInteger.prototype.clone = bnClone; +BigInteger.prototype.intValue = bnIntValue; +BigInteger.prototype.signum = bnSigNum; +BigInteger.prototype.toByteArray = bnToByteArray; +BigInteger.prototype.toBuffer = bnToBuffer; +BigInteger.prototype.equals = bnEquals; +BigInteger.prototype.min = bnMin; +BigInteger.prototype.max = bnMax; +BigInteger.prototype.and = bnAnd; +BigInteger.prototype.or = bnOr; +BigInteger.prototype.xor = bnXor; +BigInteger.prototype.not = bnNot; +BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; +BigInteger.prototype.add = bnAdd; +BigInteger.prototype.subtract = bnSubtract; +BigInteger.prototype.multiply = bnMultiply; +BigInteger.prototype.divide = bnDivide; +BigInteger.prototype.remainder = bnRemainder; +BigInteger.prototype.modPow = bnModPow; +BigInteger.prototype.pow = bnPow; +BigInteger.prototype.gcd = bnGCD; +BigInteger.int2char = int2char; + +// "constants" +BigInteger.ZERO = nbv(0); +BigInteger.ONE = nbv(1); + +// JSBN-specific extension +BigInteger.prototype.square = bnSquare; + +//BigInteger interfaces not implemented in jsbn: + +//BigInteger(int signum, byte[] magnitude) +//double doubleValue() +//float floatValue() +//int hashCode() +//long longValue() +//static BigInteger valueOf(long val) + +module.exports = BigInteger; + +}).call(this,require("buffer").Buffer) +},{"../utils":157,"buffer":185}],154:[function(require,module,exports){ +/* + * RSA Encryption / Decryption with PKCS1 v2 Padding. + * + * ~MIT License~ + * Contributors: + * Tom Wu, rzcoder, frosty00 +*/ + +var _ = require('../utils')._; +var BigInteger = require('./jsbn.js'); +var utils = require('../utils.js'); +var schemes = require('../schemes/schemes.js'); + +//exports.BigInteger = BigInteger; +module.exports.Key = (function () { + /** + * RSA key constructor + * + * n - modulus + * e - publicExponent + * d - privateExponent + * p - prime1 + * q - prime2 + * dmp1 - exponent1 -- d mod (p1) + * dmq1 - exponent2 -- d mod (q-1) + * coeff - coefficient -- (inverse of q) mod p + */ + function RSAKey() { + this.n = null; + this.e = 0; + this.d = null; + this.p = null; + this.q = null; + this.dmp1 = null; + this.dmq1 = null; + this.coeff = null; + } + + RSAKey.prototype.setOptions = function (options) { + var signingSchemeProvider = schemes[options.signingScheme]; + var encryptionSchemeProvider = schemes[options.encryptionScheme]; + + if (signingSchemeProvider === encryptionSchemeProvider) { + this.signingScheme = this.encryptionScheme = encryptionSchemeProvider.makeScheme(this, options); + } else { + this.encryptionScheme = encryptionSchemeProvider.makeScheme(this, options); + this.signingScheme = signingSchemeProvider.makeScheme(this, options); + } + + }; + /** + * Set the private key fields N, e, d and CRT params from buffers + * + * @param N + * @param E + * @param D + * @param P + * @param Q + * @param DP + * @param DQ + * @param C + */ + RSAKey.prototype.setPrivate = function (N, E, D, P, Q, DP, DQ, C) { + if (N && E && D && N.length > 0 && (_.isNumber(E) || E.length > 0) && D.length > 0) { + this.n = new BigInteger(N); + this.e = _.isNumber(E) ? E : utils.get32IntFromBuffer(E, 0); + this.d = new BigInteger(D); + + if (P && Q && DP && DQ && C) { + this.p = new BigInteger(P); + this.q = new BigInteger(Q); + this.dmp1 = new BigInteger(DP); + this.dmq1 = new BigInteger(DQ); + this.coeff = new BigInteger(C); + } else { + // TODO: re-calculate any missing CRT params + } + this.$$recalculateCache(); + } else { + throw Error("Invalid RSA private key"); + } + }; + + /** + * private + * Perform raw private operation on "x": return x^d (mod n) + * + * @param x + * @returns {*} + */ + RSAKey.prototype.$doPrivate = function (x) { + if (this.p || this.q) { + return x.modPow(this.d, this.n); + } + + // TODO: re-calculate any missing CRT params + var xp = x.mod(this.p).modPow(this.dmp1, this.p); + var xq = x.mod(this.q).modPow(this.dmq1, this.q); + + while (xp.compareTo(xq) < 0) { + xp = xp.add(this.p); + } + return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); + }; + + RSAKey.prototype.sign = function (buffer) { + return this.signingScheme.sign.apply(this.signingScheme, arguments); + }; + + /** + * Check if key pair contains private key + */ + RSAKey.prototype.isPrivate = function () { + return this.n && this.e && this.d || false; + }; + + Object.defineProperty(RSAKey.prototype, 'keySize', { + get: function () { + return this.cache.keyBitLength; + } + }); + + Object.defineProperty(RSAKey.prototype, 'encryptedDataLength', { + get: function () { + return this.cache.keyByteLength; + } + }); + + Object.defineProperty(RSAKey.prototype, 'maxMessageLength', { + get: function () { + return this.encryptionScheme.maxMessageLength(); + } + }); + + /** + * Caching key data + */ + RSAKey.prototype.$$recalculateCache = function () { + this.cache = this.cache || {}; + // Bit & byte length + this.cache.keyBitLength = this.n.bitLength(); + this.cache.keyByteLength = (this.cache.keyBitLength + 6) >> 3; + }; + + return RSAKey; +})(); + + +},{"../schemes/schemes.js":156,"../utils":157,"../utils.js":157,"./jsbn.js":153}],155:[function(require,module,exports){ +(function (Buffer){ +/** + * PKCS1 padding and signature scheme + */ + +var BigInteger = require('../libs/jsbn'); +var CryptoJS = require('../../crypto-js/crypto-js'); +var constants = { RSA_NO_PADDING: 3 } +require('constants'); +var SIGN_INFO_HEAD = { + md2: Buffer.from('3020300c06082a864886f70d020205000410', 'hex'), + md5: Buffer.from('3020300c06082a864886f70d020505000410', 'hex'), + sha1: Buffer.from('3021300906052b0e03021a05000414', 'hex'), + sha224: Buffer.from('302d300d06096086480165030402040500041c', 'hex'), + sha256: Buffer.from('3031300d060960864801650304020105000420', 'hex'), + sha384: Buffer.from('3041300d060960864801650304020205000430', 'hex'), + sha512: Buffer.from('3051300d060960864801650304020305000440', 'hex'), + ripemd160: Buffer.from('3021300906052b2403020105000414', 'hex'), + rmd160: Buffer.from('3021300906052b2403020105000414', 'hex') +}; + +var SIGN_ALG_TO_HASH_ALIASES = { + 'ripemd160': 'rmd160' +}; + +var DEFAULT_HASH_FUNCTION = 'sha256'; + +module.exports = { + isEncryption: true, + isSignature: true +}; + +module.exports.makeScheme = function (key, options) { + function Scheme(key, options) { + this.key = key; + this.options = options; + } + + Scheme.prototype.maxMessageLength = function () { + if (this.options.encryptionSchemeOptions && this.options.encryptionSchemeOptions.padding == constants.RSA_NO_PADDING) { + return this.key.encryptedDataLength; + } + return this.key.encryptedDataLength - 11; + }; + + /** + * Unpad input Buffer and, if valid, return the Buffer object + * alg: PKCS#1 (type 2, random) + * @param buffer + * @returns {Buffer} + */ + Scheme.prototype.encUnPad = function (buffer, options) { + options = options || {}; + var i = 0; + + if (this.options.encryptionSchemeOptions && this.options.encryptionSchemeOptions.padding == constants.RSA_NO_PADDING) { + //RSA_NO_PADDING treated like JAVA left pad with zero character + var unPad; + if (typeof buffer.lastIndexOf == "function") { //patch for old node version + unPad = buffer.slice(buffer.lastIndexOf('\0') + 1, buffer.length); + } else { + unPad = buffer.slice(String.prototype.lastIndexOf.call(buffer, '\0') + 1, buffer.length); + } + return unPad; + } + + if (buffer.length < 4) { + return null; + } + + /* Type 1: zeros padding for private key decrypt */ + if (options.type === 1) { + if (buffer[0] !== 0 && buffer[1] !== 1) { + return null; + } + i = 3; + while (buffer[i] !== 0) { + if (buffer[i] != 0xFF || ++i >= buffer.length) { + return null; + } + } + } else { + /* random padding for public key decrypt */ + if (buffer[0] !== 0 && buffer[1] !== 2) { + return null; + } + i = 3; + while (buffer[i] !== 0) { + if (++i >= buffer.length) { + return null; + } + } + } + return buffer.slice(i + 1, buffer.length); + }; + + Scheme.prototype.sign = function (buffer) { + var hashAlgorithm = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION; + if (this.options.environment === 'browser') { + hashAlgorithm = SIGN_ALG_TO_HASH_ALIASES[hashAlgorithm] || hashAlgorithm; + var hasher = CryptoJS[hashAlgorithm.toUpperCase()] (buffer.toString()); + var asBuffer = wordArrayToBuffer(hasher) + var paddedHash = this.pkcs1pad(asBuffer, hashAlgorithm); + var res = this.key.$doPrivate(new BigInteger(paddedHash)).toBuffer(this.key.encryptedDataLength); + return res; + } else { + throw new Error ('CCXT only supports browser mode :P'); + } + }; + + /** + * PKCS#1 pad input buffer to max data length + * @param hashBuf + * @param hashAlgorithm + * @returns {*} + */ + Scheme.prototype.pkcs1pad = function (hashBuf, hashAlgorithm) { + var digest = SIGN_INFO_HEAD[hashAlgorithm]; + if (!digest) { + throw Error('Unsupported hash algorithm'); + } + + var data = Buffer.concat([digest, hashBuf]); + + if (data.length + 10 > this.key.encryptedDataLength) { + throw Error('Key is too short for signing algorithm (' + hashAlgorithm + ')'); + } + + var filled = Buffer.alloc(this.key.encryptedDataLength - data.length - 1); + filled.fill(0xff, 0, filled.length - 1); + filled[0] = 1; + filled[filled.length - 1] = 0; + + var res = Buffer.concat([filled, data]); + + return res; + }; + + return new Scheme(key, options); +}; + +// used to convert `CryptoJS` wordArrays into `crypto` hex buffers +function wordToByteArray(word, length) { + var ba = [], + xFF = 0xFF; + if (length > 0) + ba.push(word >>> 24); + if (length > 1) + ba.push((word >>> 16) & xFF); + if (length > 2) + ba.push((word >>> 8) & xFF); + if (length > 3) + ba.push(word & xFF); + + return ba; +} + +function wordArrayToBuffer(wordArray) { + let length = undefined; + if (wordArray.hasOwnProperty("sigBytes") && wordArray.hasOwnProperty("words")) { + length = wordArray.sigBytes; + wordArray = wordArray.words; + } else { + throw Error('Argument not a wordArray') + } + + const result = [] + let bytes = [] + let i = 0; + while (length > 0) { + bytes = wordToByteArray(wordArray[i], Math.min(4, length)); + length -= bytes.length; + result.push(bytes); + i++; + } + return new Buffer.from([].concat.apply([], result), 'hex'); +} + +}).call(this,require("buffer").Buffer) +},{"../../crypto-js/crypto-js":124,"../libs/jsbn":153,"buffer":185,"constants":187}],156:[function(require,module,exports){ +module.exports = { + pkcs1: require('./pkcs1'), + + /** + * Check if scheme has padding methods + * @param scheme {string} + * @returns {Boolean} + */ + isEncryption: function (scheme) { + return module.exports[scheme] && module.exports[scheme].isEncryption; + }, + + /** + * Check if scheme has sign/verify methods + * @param scheme {string} + * @returns {Boolean} + */ + isSignature: function (scheme) { + return module.exports[scheme] && module.exports[scheme].isSignature; + } +}; + +},{"./pkcs1":155}],157:[function(require,module,exports){ +/* + * Utils functions + * + */ + +/** + * Break string str each maxLen symbols + * @param str + * @param maxLen + * @returns {string} + */ +module.exports.linebrk = function (str, maxLen) { + var res = ''; + var i = 0; + while (i + maxLen < str.length) { + res += str.substring(i, i + maxLen) + "\n"; + i += maxLen; + } + return res + str.substring(i, str.length); +}; + +/** + * Trying get a 32-bit unsigned integer from the partial buffer + * @param buffer + * @param offset + * @returns {Number} + */ +module.exports.get32IntFromBuffer = function (buffer, offset) { + offset = offset || 0; + var size = 0; + if ((size = buffer.length - offset) > 0) { + if (size >= 4) { + return buffer.readUInt32BE(offset); + } else { + var res = 0; + for (var i = offset + size, d = 0; i > offset; i--, d += 2) { + res += buffer[i - 1] * Math.pow(16, d); + } + return res; + } + } else { + return NaN; + } +}; + +module.exports._ = { + isObject: function (value) { + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); + }, + + isString: function (value) { + return typeof value == 'string' || value instanceof String; + }, + + isNumber: function (value) { + return typeof value == 'number' || !isNaN(parseFloat(value)) && isFinite(value); + }, + + /** + * Returns copy of `obj` without `removeProp` field. + * @param obj + * @param removeProp + * @returns Object + */ + omit: function (obj, removeProp) { + var newObj = {}; + for (var prop in obj) { + if (!obj.hasOwnProperty(prop) || prop === removeProp) { + continue; + } + newObj[prop] = obj[prop]; + } + + return newObj; + } +}; + +/** + * Strips everything around the opening and closing lines, including the lines + * themselves. + */ +module.exports.trimSurroundingText = function (data, opening, closing) { + var trimStartIndex = 0; + var trimEndIndex = data.length; + + var openingBoundaryIndex = data.indexOf(opening); + if (openingBoundaryIndex >= 0) { + trimStartIndex = openingBoundaryIndex + opening.length; + } + + var closingBoundaryIndex = data.indexOf(closing, openingBoundaryIndex); + if (closingBoundaryIndex >= 0) { + trimEndIndex = closingBoundaryIndex; + } + + return data.substring(trimStartIndex, trimEndIndex); +} + +},{}],158:[function(require,module,exports){ +'use strict'; + +var replace = String.prototype.replace; +var percentTwenties = /%20/g; + +module.exports = { + 'default': 'RFC3986', + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return value; + } + }, + RFC1738: 'RFC1738', + RFC3986: 'RFC3986' +}; + +},{}],159:[function(require,module,exports){ +'use strict'; + +var stringify = require('./stringify'); +var parse = require('./parse'); +var formats = require('./formats'); + +module.exports = { + formats: formats, + parse: parse, + stringify: stringify +}; + +},{"./formats":158,"./parse":160,"./stringify":161}],160:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); + +var has = Object.prototype.hasOwnProperty; + +var defaults = { + allowDots: false, + allowPrototypes: false, + arrayLimit: 20, + charset: 'utf-8', + charsetSentinel: false, + comma: false, + decoder: utils.decode, + delimiter: '&', + depth: 5, + ignoreQueryPrefix: false, + interpretNumericEntities: false, + parameterLimit: 1000, + parseArrays: true, + plainObjects: false, + strictNullHandling: false +}; + +var interpretNumericEntities = function (str) { + return str.replace(/&#(\d+);/g, function ($0, numberStr) { + return String.fromCharCode(parseInt(numberStr, 10)); + }); +}; + +// This is what browsers will submit when the ✓ character occurs in an +// application/x-www-form-urlencoded body and the encoding of the page containing +// the form is iso-8859-1, or when the submitted form has an accept-charset +// attribute of iso-8859-1. Presumably also with other charsets that do not contain +// the ✓ character, such as us-ascii. +var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓') + +// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded. +var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓') + +var parseValues = function parseQueryStringValues(str, options) { + var obj = {}; + var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str; + var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit; + var parts = cleanStr.split(options.delimiter, limit); + var skipIndex = -1; // Keep track of where the utf8 sentinel was found + var i; + + var charset = options.charset; + if (options.charsetSentinel) { + for (i = 0; i < parts.length; ++i) { + if (parts[i].indexOf('utf8=') === 0) { + if (parts[i] === charsetSentinel) { + charset = 'utf-8'; + } else if (parts[i] === isoSentinel) { + charset = 'iso-8859-1'; + } + skipIndex = i; + i = parts.length; // The eslint settings do not allow break; + } + } + } + + for (i = 0; i < parts.length; ++i) { + if (i === skipIndex) { + continue; + } + var part = parts[i]; + + var bracketEqualsPos = part.indexOf(']='); + var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1; + + var key, val; + if (pos === -1) { + key = options.decoder(part, defaults.decoder, charset); + val = options.strictNullHandling ? null : ''; + } else { + key = options.decoder(part.slice(0, pos), defaults.decoder, charset); + val = options.decoder(part.slice(pos + 1), defaults.decoder, charset); + } + + if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { + val = interpretNumericEntities(val); + } + + if (val && options.comma && val.indexOf(',') > -1) { + val = val.split(','); + } + + if (has.call(obj, key)) { + obj[key] = utils.combine(obj[key], val); + } else { + obj[key] = val; + } + } + + return obj; +}; + +var parseObject = function (chain, val, options) { + var leaf = val; + + for (var i = chain.length - 1; i >= 0; --i) { + var obj; + var root = chain[i]; + + if (root === '[]' && options.parseArrays) { + obj = [].concat(leaf); + } else { + obj = options.plainObjects ? Object.create(null) : {}; + var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; + var index = parseInt(cleanRoot, 10); + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: leaf }; + } else if ( + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) + ) { + obj = []; + obj[index] = leaf; + } else { + obj[cleanRoot] = leaf; + } + } + + leaf = obj; + } + + return leaf; +}; + +var parseKeys = function parseQueryStringKeys(givenKey, val, options) { + if (!givenKey) { + return; + } + + // Transform dot notation to bracket notation + var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey; + + // The regex chunks + + var brackets = /(\[[^[\]]*])/; + var child = /(\[[^[\]]*])/g; + + // Get the parent + + var segment = brackets.exec(key); + var parent = segment ? key.slice(0, segment.index) : key; + + // Stash the parent if it exists + + var keys = []; + if (parent) { + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties + if (!options.plainObjects && has.call(Object.prototype, parent)) { + if (!options.allowPrototypes) { + return; + } + } + + keys.push(parent); + } + + // Loop through children appending to the array until we hit depth + + var i = 0; + while ((segment = child.exec(key)) !== null && i < options.depth) { + i += 1; + if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) { + if (!options.allowPrototypes) { + return; + } + } + keys.push(segment[1]); + } + + // If there's a remainder, just add whatever is left + + if (segment) { + keys.push('[' + key.slice(segment.index) + ']'); + } + + return parseObject(keys, val, options); +}; + +var normalizeParseOptions = function normalizeParseOptions(opts) { + if (!opts) { + return defaults; + } + + if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { + throw new TypeError('Decoder has to be a function.'); + } + + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset; + + return { + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes, + arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma, + decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder, + delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter, + depth: typeof opts.depth === 'number' ? opts.depth : defaults.depth, + ignoreQueryPrefix: opts.ignoreQueryPrefix === true, + interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities, + parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit, + parseArrays: opts.parseArrays !== false, + plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (str, opts) { + var options = normalizeParseOptions(opts); + + if (str === '' || str === null || typeof str === 'undefined') { + return options.plainObjects ? Object.create(null) : {}; + } + + var tempObj = typeof str === 'string' ? parseValues(str, options) : str; + var obj = options.plainObjects ? Object.create(null) : {}; + + // Iterate over the keys and setup the new object + + var keys = Object.keys(tempObj); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var newObj = parseKeys(key, tempObj[key], options); + obj = utils.merge(obj, newObj, options); + } + + return utils.compact(obj); +}; + +},{"./utils":162}],161:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); +var formats = require('./formats'); +var has = Object.prototype.hasOwnProperty; + +var arrayPrefixGenerators = { + brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + return prefix + '[]'; + }, + comma: 'comma', + indices: function indices(prefix, key) { // eslint-disable-line func-name-matching + return prefix + '[' + key + ']'; + }, + repeat: function repeat(prefix) { // eslint-disable-line func-name-matching + return prefix; + } +}; + +var isArray = Array.isArray; +var push = Array.prototype.push; +var pushToArray = function (arr, valueOrArray) { + push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]); +}; + +var toISO = Date.prototype.toISOString; + +var defaults = { + addQueryPrefix: false, + allowDots: false, + charset: 'utf-8', + charsetSentinel: false, + delimiter: '&', + encode: true, + encoder: utils.encode, + encodeValuesOnly: false, + formatter: formats.formatters[formats['default']], + // deprecated + indices: false, + serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching + return toISO.call(date); + }, + skipNulls: false, + strictNullHandling: false +}; + +var stringify = function stringify( // eslint-disable-line func-name-matching + object, + prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset +) { + var obj = object; + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } else if (obj instanceof Date) { + obj = serializeDate(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = obj.join(','); + } + + if (obj === null) { + if (strictNullHandling) { + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix; + } + + obj = ''; + } + + if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { + if (encoder) { + var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); + return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; + } + return [formatter(prefix) + '=' + formatter(String(obj))]; + } + + var values = []; + + if (typeof obj === 'undefined') { + return values; + } + + var objKeys; + if (isArray(filter)) { + objKeys = filter; + } else { + var keys = Object.keys(obj); + objKeys = sort ? keys.sort(sort) : keys; + } + + for (var i = 0; i < objKeys.length; ++i) { + var key = objKeys[i]; + + if (skipNulls && obj[key] === null) { + continue; + } + + if (isArray(obj)) { + pushToArray(values, stringify( + obj[key], + typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); + } else { + pushToArray(values, stringify( + obj[key], + prefix + (allowDots ? '.' + key : '[' + key + ']'), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); + } + } + + return values; +}; + +var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { + if (!opts) { + return defaults; + } + + if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') { + throw new TypeError('Encoder has to be a function.'); + } + + var charset = opts.charset || defaults.charset; + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + + var format = formats['default']; + if (typeof opts.format !== 'undefined') { + if (!has.call(formats.formatters, opts.format)) { + throw new TypeError('Unknown format option provided.'); + } + format = opts.format; + } + var formatter = formats.formatters[format]; + + var filter = defaults.filter; + if (typeof opts.filter === 'function' || isArray(opts.filter)) { + filter = opts.filter; + } + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + formatter: formatter, + serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate, + skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls, + sort: typeof opts.sort === 'function' ? opts.sort : null, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (object, opts) { + var obj = object; + var options = normalizeStringifyOptions(opts); + + var objKeys; + var filter; + + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } else if (isArray(options.filter)) { + filter = options.filter; + objKeys = filter; + } + + var keys = []; + + if (typeof obj !== 'object' || obj === null) { + return ''; + } + + var arrayFormat; + if (opts && opts.arrayFormat in arrayPrefixGenerators) { + arrayFormat = opts.arrayFormat; + } else if (opts && 'indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; + } else { + arrayFormat = 'indices'; + } + + var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; + + if (!objKeys) { + objKeys = Object.keys(obj); + } + + if (options.sort) { + objKeys.sort(options.sort); + } + + for (var i = 0; i < objKeys.length; ++i) { + var key = objKeys[i]; + + if (options.skipNulls && obj[key] === null) { + continue; + } + pushToArray(keys, stringify( + obj[key], + key, + generateArrayPrefix, + options.strictNullHandling, + options.skipNulls, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.formatter, + options.encodeValuesOnly, + options.charset + )); + } + + var joined = keys.join(options.delimiter); + var prefix = options.addQueryPrefix === true ? '?' : ''; + + if (options.charsetSentinel) { + if (options.charset === 'iso-8859-1') { + // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark + prefix += 'utf8=%26%2310003%3B&'; + } else { + // encodeURIComponent('✓') + prefix += 'utf8=%E2%9C%93&'; + } + } + + return joined.length > 0 ? prefix + joined : ''; +}; + +},{"./formats":158,"./utils":162}],162:[function(require,module,exports){ +'use strict'; + +var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; + +var hexTable = (function () { + var array = []; + for (var i = 0; i < 256; ++i) { + array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); + } + + return array; +}()); + +var compactQueue = function compactQueue(queue) { + while (queue.length > 1) { + var item = queue.pop(); + var obj = item.obj[item.prop]; + + if (isArray(obj)) { + var compacted = []; + + for (var j = 0; j < obj.length; ++j) { + if (typeof obj[j] !== 'undefined') { + compacted.push(obj[j]); + } + } + + item.obj[item.prop] = compacted; + } + } +}; + +var arrayToObject = function arrayToObject(source, options) { + var obj = options && options.plainObjects ? Object.create(null) : {}; + for (var i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +}; + +var merge = function merge(target, source, options) { + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (isArray(target)) { + target.push(source); + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { + target[source] = true; + } + } else { + return [target, source]; + } + + return target; + } + + if (!target || typeof target !== 'object') { + return [target].concat(source); + } + + var mergeTarget = target; + if (isArray(target) && !isArray(source)) { + mergeTarget = arrayToObject(target, options); + } + + if (isArray(target) && isArray(source)) { + source.forEach(function (item, i) { + if (has.call(target, i)) { + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); + } else { + target.push(item); + } + } else { + target[i] = item; + } + }); + return target; + } + + return Object.keys(source).reduce(function (acc, key) { + var value = source[key]; + + if (has.call(acc, key)) { + acc[key] = merge(acc[key], value, options); + } else { + acc[key] = value; + } + return acc; + }, mergeTarget); +}; + +var assign = function assignSingleSource(target, source) { + return Object.keys(source).reduce(function (acc, key) { + acc[key] = source[key]; + return acc; + }, target); +}; + +var decode = function (str, decoder, charset) { + var strWithoutPlus = str.replace(/\+/g, ' '); + if (charset === 'iso-8859-1') { + // unescape never throws, no try...catch needed: + return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape); + } + // utf-8 + try { + return decodeURIComponent(strWithoutPlus); + } catch (e) { + return strWithoutPlus; + } +}; + +var encode = function encode(str, defaultEncoder, charset) { + // This code was originally written by Brian White (mscdex) for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + var string = typeof str === 'string' ? str : String(str); + + if (charset === 'iso-8859-1') { + return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) { + return '%26%23' + parseInt($0.slice(2), 16) + '%3B'; + }); + } + + var out = ''; + for (var i = 0; i < string.length; ++i) { + var c = string.charCodeAt(i); + + if ( + c === 0x2D // - + || c === 0x2E // . + || c === 0x5F // _ + || c === 0x7E // ~ + || (c >= 0x30 && c <= 0x39) // 0-9 + || (c >= 0x41 && c <= 0x5A) // a-z + || (c >= 0x61 && c <= 0x7A) // A-Z + ) { + out += string.charAt(i); + continue; + } + + if (c < 0x80) { + out = out + hexTable[c]; + continue; + } + + if (c < 0x800) { + out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]); + continue; + } + + if (c < 0xD800 || c >= 0xE000) { + out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]); + continue; + } + + i += 1; + c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; + } + + return out; +}; + +var compact = function compact(value) { + var queue = [{ obj: { o: value }, prop: 'o' }]; + var refs = []; + + for (var i = 0; i < queue.length; ++i) { + var item = queue[i]; + var obj = item.obj[item.prop]; + + var keys = Object.keys(obj); + for (var j = 0; j < keys.length; ++j) { + var key = keys[j]; + var val = obj[key]; + if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { + queue.push({ obj: obj, prop: key }); + refs.push(val); + } + } + } + + compactQueue(queue); + + return value; +}; + +var isRegExp = function isRegExp(obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +}; + +var isBuffer = function isBuffer(obj) { + if (!obj || typeof obj !== 'object') { + return false; + } + + return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); +}; + +var combine = function combine(a, b) { + return [].concat(a, b); +}; + +module.exports = { + arrayToObject: arrayToObject, + assign: assign, + combine: combine, + compact: compact, + decode: decode, + encode: encode, + isBuffer: isBuffer, + isRegExp: isRegExp, + merge: merge +}; + +},{}],163:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, AuthenticationError, ExchangeError, InsufficientFunds, OrderNotFound, PermissionDenied, BadRequest, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class stex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'stex', + 'name': 'STEX', // formerly known as stocks.exchange + 'countries': [ 'EE' ], // Estonia + 'rateLimit': 500, // https://help.stex.com/en/articles/2815043-api-3-rate-limits + 'certified': false, + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, // limit orders only + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'version': 'v3', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/69680782-03fd0b80-10bd-11ea-909e-7f603500e9cc.jpg', + 'api': 'https://api3.stex.com', + 'www': 'https://www.stex.com', + 'doc': [ + 'https://help.stex.com/en/collections/1593608-api-v3-documentation', + ], + 'fees': 'https://app.stex.com/en/pairs-specification', + 'referral': 'https://app.stex.com?ref=36416021', + }, + 'requiredCredentials': { + 'apiKey': false, + 'secret': false, + 'token': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '30m': '30', + '1h': '60', + '4h': '240', + '12h': '720', + '1d': '1D', // default + }, + 'api': { + 'public': { + 'get': [ + 'currencies', // Available Currencies + 'currencies/{currencyId}', // Get currency info + 'markets', // Available markets + 'pairs-groups', // Available currency pairs groups (as displayed at stex trading page) + 'currency_pairs/list/{code}', // Available currency pairs + 'currency_pairs/group/{currencyPairGroupId}', // Available currency pairs for a given group + 'currency_pairs/{currencyPairId}', // Get currency pair information + 'ticker', // Tickers list for all currency pairs + 'ticker/{currencyPairId}', // Ticker for currency pair + 'trades/{currencyPairId}', // Trades for given currency pair + 'orderbook/{currencyPairId}', // Orderbook for given currency pair + 'chart/{currencyPairId}/{candlesType}', // A list of candles for given currency pair + 'deposit-statuses', // Available Deposit Statuses + 'deposit-statuses/{statusId}', // Get deposit status info + 'withdrawal-statuses', // Available Withdrawal Statuses + 'withdrawal-statuses/{statusId}', // Get status info + 'ping', // Test API is working and get server time + 'mobile-versions', // Shows the official mobile applications data + ], + }, + 'trading': { + 'get': [ + 'fees/{currencyPairId}', // Returns the user's fees for a given currency pair + 'orders', // List your currently open orders + 'orders/{currencyPairId}', // List your currently open orders for given currency pair + 'order/{orderId}', // Get a single order + ], + 'post': [ + 'orders/{currencyPairId}', // Create new order and put it to the orders processing queue + ], + 'delete': [ + 'orders', // Delete all active orders + 'orders/{currencyPairId}', // Delete active orders for given currency pair + 'order/{orderId}', // Cancel order + ], + }, + 'reports': { + 'get': [ + 'orders', // Get past orders + 'orders/{orderId}', // Get specified order details + 'trades/{currencyPairId}', // Get a list of user trades according to request parameters + 'background/{listMode}', // Get reports list for category + 'background/{id}', // Get some report info + 'background/download/{id}', // Get file by id + ], + 'post': [ + 'background/create', // Create new report + ], + 'delete': [ + 'background/{id}', // Remove report by id + ], + }, + 'profile': { + 'get': [ + 'info', // Account information + 'wallets', // Get a list of user wallets + 'wallets/{walletId}', // Single wallet information + 'wallets/address/{walletId}', // Get deposit address for given wallet + 'deposits', // Get a list of deposits made by user + 'deposits/{id}', // Get deposit by id + 'withdrawals', // Get a list of withdrawals made by user + 'withdrawals/{id}', // Get withdrawal by id + 'notifications', // Get notifications + 'favorite/currency_pairs', // Get favorite currency pairs + 'token-scopes', // Get current token scopes + ], + 'post': [ + 'wallets/burn/{walletId}', // Burns the given wallet + 'wallets/{currencyId}', // Create a wallet for given currency + 'wallets/address/{walletId}', // Create new deposit address + 'withdraw', // Create withdrawal request + 'referral/program', // Create referral program + 'referral/insert/{code}', // Insert referral code + 'referral/bonus_transfer/{currencyId}', // Transfer referral bonuses balance to main balance for given currency + ], + 'put': [ + 'profile/favorite/currency_pairs/set', // Set favorite currency pairs + ], + 'delete': [ + 'profile/withdraw/{withdrawalId}', // Cancel unconfirmed withdrawal + ], + }, + 'verification': { + 'get': [ + 'verification/countries', // Countries list, beta + 'verification/stex', // Get information about your KYC, beta + ], + 'post': [ + 'verification/stex', // Update information regarding of your KYC verification, beta + ], + }, + 'settings': { + 'get': [ + 'notifications/{event}', // User event notification settings + 'notifications', // User events notification settings + ], + 'put': [ + 'notifications', // Set notification settings + 'notifications/set', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.002, + }, + }, + 'commonCurrencies': { + 'BC': 'Bitcoin Confidential', + 'BITS': 'Bitcoinus', + 'BITSW': 'BITS', + 'BHD': 'Bithold', + }, + 'options': { + 'parseOrderToPrecision': false, + }, + 'exceptions': { + 'exact': { + // {"success":false,"message":"Wrong parameters","errors":{"candleType":["Invalid Candle Type!"]}} + // {"success":false,"message":"Wrong parameters","errors":{"time":["timeStart or timeEnd is less then 1"]}} + 'Wrong parameters': BadRequest, + 'Unauthenticated.': AuthenticationError, // {"message":"Unauthenticated."} + 'Server Error': ExchangeError, // { "message": "Server Error" } + 'This feature is only enabled for users verifies by Cryptonomica': PermissionDenied, // {"success":false,"message":"This feature is only enabled for users verifies by Cryptonomica"} + 'Too Many Attempts.': DDoSProtection, // { "message": "Too Many Attempts." } + }, + 'broad': { + 'Not enough': InsufficientFunds, // {"success":false,"message":"Not enough ETH"} + }, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "success":true, + // "data":[ + // { + // "id":1, + // "code":"BTC", + // "name":"Bitcoin", + // "active":true, + // "delisted":false, + // "precision":8, + // "minimum_tx_confirmations":1, + // "minimum_withdrawal_amount":"0.00200000", + // "minimum_deposit_amount":"0.00000000", + // "deposit_fee_currency_id":1, + // "deposit_fee_currency_code":"BTC", + // "deposit_fee_const":"0.00000000", + // "deposit_fee_percent":"0.00000000", + // "withdrawal_fee_currency_id":1, + // "withdrawal_fee_currency_code":"BTC", + // "withdrawal_fee_const":"0.00100000", + // "withdrawal_fee_percent":"0.00000000", + // "block_explorer_url":"https:\/\/blockchain.info\/tx\/", + // "protocol_specific_settings":null + // }, + // ] + // } + // + const result = {}; + const currencies = this.safeValue (response, 'data', []); + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'id'); + const numericId = this.safeInteger (currency, 'id'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const code = this.safeCurrencyCode (this.safeString (currency, 'code')); + const precision = this.safeInteger (currency, 'precision'); + const fee = this.safeFloat (currency, 'withdrawal_fee_const'); // todo: redesign + const active = this.safeValue (currency, 'active', true); + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': this.safeString (currency, 'name'), + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { 'min': Math.pow (10, -precision), 'max': undefined }, + 'price': { 'min': Math.pow (10, -precision), 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'deposit': { + 'min': this.safeFloat (currency, 'minimum_deposit_amount'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minimum_withdrawal_amount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const request = { + 'code': 'ALL', + }; + const response = await this.publicGetCurrencyPairsListCode (this.extend (request, params)); + // + // { + // "success":true, + // "data":[ + // { + // "id":935, + // "currency_id":662, + // "currency_code":"ABET", + // "currency_name":"Altbet", + // "market_currency_id":1, + // "market_code":"BTC", + // "market_name":"Bitcoin", + // "min_order_amount":"0.00000010", + // "min_buy_price":"0.00000001", + // "min_sell_price":"0.00000001", + // "buy_fee_percent":"0.20000000", + // "sell_fee_percent":"0.20000000", + // "active":true, + // "delisted":false, + // "pair_message":"", + // "currency_precision":8, + // "market_precision":8, + // "symbol":"ABET_BTC", + // "group_name":"BTC", + // "group_id":1 + // } + // ] + // } + // + const result = []; + const markets = this.safeValue (response, 'data', []); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const numericId = this.safeInteger (market, 'id'); + const baseId = this.safeString (market, 'currency_id'); + const quoteId = this.safeString (market, 'market_currency_id'); + const baseNumericId = this.safeInteger (market, 'currency_id'); + const quoteNumericId = this.safeInteger (market, 'market_currency_id'); + const base = this.safeCurrencyCode (this.safeString (market, 'currency_code')); + const quote = this.safeCurrencyCode (this.safeString (market, 'market_code')); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'currency_precision'), + 'price': this.safeInteger (market, 'market_precision'), + }; + const active = this.safeValue (market, 'active'); + const minBuyPrice = this.safeFloat (market, 'min_buy_price'); + const minSellPrice = this.safeFloat (market, 'min_sell_price'); + const minPrice = Math.max (minBuyPrice, minSellPrice); + const buyFee = this.safeFloat (market, 'buy_fee_percent') / 100; + const sellFee = this.safeFloat (market, 'sell_fee_percent') / 100; + const fee = Math.max (buyFee, sellFee); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'baseNumericId': baseNumericId, + 'quoteNumericId': quoteNumericId, + 'info': market, + 'active': active, + 'maker': fee, + 'taker': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_order_amount'), + 'max': undefined, + }, + 'price': { 'min': minPrice, 'max': undefined }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + }; + const response = await this.publicGetTickerCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 2, + // "amount_multiplier": 1, + // "currency_code": "ETH", + // "market_code": "BTC", + // "currency_name": "Ethereum", + // "market_name": "Bitcoin", + // "symbol": "ETH_BTC", + // "group_name": "BTC", + // "group_id": 1, + // "ask": "0.02069998", + // "bid": "0.02028622", + // "last": "0.02049224", + // "open": "0.02059605", + // "low": "0.01977744", + // "high": "0.02097005", + // "volume": "480.43248971", + // "volumeQuote": "23491.29826130", + // "count": "7384", + // "fiatsRate": { + // "USD": 7230.86, + // "EUR": 6590.79, + // "UAH": 173402, + // "AUD": 10595.51, + // "IDR": 101568085, + // "CNY": 50752, + // "KRW": 8452295, + // "JPY": 784607, + // "VND": 167315119, + // "INR": 517596, + // "GBP": 5607.25, + // "CAD": 9602.63, + // "BRL": 30472, + // "RUB": 460718 + // }, + // "timestamp": 1574698235601 + // } + // } + // + const ticker = this.safeValue (response, 'data', {}); + return this.parseTicker (ticker, market); + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { + // "success": true, + // "data": { + // "server_datetime": { + // "date": "2019-01-22 15:13:34.233796", + // "timezone_type": 3, + // "timezone": "UTC" + // }, + // "server_timestamp": 1548170014 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const serverDatetime = this.safeValue (data, 'server_datetime', {}); + return this.parse8601 (this.safeString (serverDatetime, 'date')); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + }; + if (limit !== undefined) { + request['limit_bids'] = limit; // returns all if set to 0, default 100 + request['limit_asks'] = limit; // returns all if set to 0, default 100 + } + const response = await this.publicGetOrderbookCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "ask": [ + // { "currency_pair_id": 2, "amount": "2.17865373", "price": "0.02062917", "amount2": "0.04494382", "count": 1, "cumulative_amount": 2.17865373 }, + // { "currency_pair_id": 2, "amount": "2.27521743", "price": "0.02062918", "amount2": "0.04693587", "count": 1, "cumulative_amount": 4.45387116 }, + // { "currency_pair_id": 2, "amount": "1.26980049", "price": "0.02063170", "amount2": "0.02619814", "count": 1, "cumulative_amount": 5.72367165 }, + // ], + // "bid": [ + // { "currency_pair_id": 2, "amount": "0.00978005", "price": "0.02057000", "amount2": "0.00020118", "count": 1, "cumulative_amount": 0.00978005 }, + // { "currency_pair_id": 2, "amount": "0.00500000", "price": "0.02056000", "amount2": "0.00010280", "count": 1, "cumulative_amount": 0.01478005 }, + // { "currency_pair_id": 2, "amount": "0.77679882", "price": "0.02054001", "amount2": "0.01595546", "count": 1, "cumulative_amount": 0.79157887 }, + // ], + // "ask_total_amount": 2555.749174609999, + // "bid_total_amount": 29.180037330000005 + // } + // } + // + const orderbook = this.safeValue (response, 'data', {}); + return this.parseOrderBook (orderbook, undefined, 'bid', 'ask', 'price', 'amount'); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "id": 2, + // "amount_multiplier": 1, + // "currency_code": "ETH", + // "market_code": "BTC", + // "currency_name": "Ethereum", + // "market_name": "Bitcoin", + // "symbol": "ETH_BTC", + // "group_name": "BTC", + // "group_id": 1, + // "ask": "0.02069998", + // "bid": "0.02028622", + // "last": "0.02049224", + // "open": "0.02059605", + // "low": "0.01977744", + // "high": "0.02097005", + // "volume": "480.43248971", + // "volumeQuote": "23491.29826130", + // "count": "7384", + // "fiatsRate": { + // "USD": 7230.86, + // "EUR": 6590.79, + // "UAH": 173402, + // "AUD": 10595.51, + // "IDR": 101568085, + // "CNY": 50752, + // "KRW": 8452295, + // "JPY": 784607, + // "VND": 167315119, + // "INR": 517596, + // "GBP": 5607.25, + // "CAD": 9602.63, + // "BRL": 30472, + // "RUB": 460718 + // }, + // "timestamp": 1574698235601 + // } + // + const timestamp = this.safeInteger (ticker, 'timestamp'); + const marketId = this.safeString2 (ticker, 'id', 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat (ticker, 'last'); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let percentage = undefined; + if (last !== undefined) { + if ((open !== undefined) && (open > 0)) { + change = last - open; + percentage = ((100 / open) * last) - 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volumeQuote'), + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + // + // { + // "success":true, + // "data":[ + // { + // "id":262, + // "amount_multiplier":1, + // "currency_code":"ARDR", + // "market_code":"BTC", + // "currency_name":"ARDOR", + // "market_name":"Bitcoin", + // "symbol":"ARDR_BTC", + // "group_name":"BTC", + // "group_id":1, + // "ask":"0.00000630", + // "bid":"0.00000613", + // "last":"0.00000617", + // "open":"0.00000620", + // "low":"0.00000614", + // "high":"0.00000630", + // "volume":"30.37795305", + // "volumeQuote":"4911487.01996544", + // "count":"710", + // "fiatsRate":{ + // "USD":7230.86, + // "EUR":6590.79, + // "UAH":173402, + // "AUD":10744.52, + // "IDR":101568085, + // "CNY":50752, + // "KRW":8452295, + // "JPY":784607, + // "VND":167315119, + // "INR":517596, + // "GBP":5607.25, + // "CAD":9602.63, + // "BRL":30472, + // "RUB":467358 + // }, + // "timestamp":1574698617304, + // "group_position":1 + // }, + // ] + // } + // + const tickers = this.safeValue (response, 'data', []); + return this.parseTickers (tickers, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time": 1566086400000, + // "close": 0.01895, + // "open": 0.01812427, + // "high": 0.0191588, + // "low": 0.01807001, + // "volume": 2588.597813750006 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1d', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + 'candlesType': this.timeframes[timeframe], // default 1d + // 'timeStart': 1574709092, // unix timestamp in seconds, required + // 'timeEnd': 1574709092, // unix timestamp in seconds, required + // 'limit': 100, // default 100, optional + // 'offset' 100, // optional, pagination within timerange + }; + if (limit === undefined) { + limit = 100; + } else { + request['limit'] = limit; + } + const duration = this.parseTimeframe (timeframe); + const timerange = limit * duration; + if (since === undefined) { + request['timeEnd'] = this.seconds (); + request['timeStart'] = request['timeEnd'] - timerange; + } else { + request['timeStart'] = parseInt (since / 1000); + request['timeEnd'] = this.sum (request['timeStart'], timerange); + } + const response = await this.publicGetChartCurrencyPairIdCandlesType (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "time": 1566086400000, + // "close": 0.01895, + // "open": 0.01812427, + // "high": 0.0191588, + // "low": 0.01807001, + // "volume": 2588.597813750006 + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "id": 35989317, + // "price": "0.02033813", + // "amount": "3.60000000", + // "type": "BUY", + // "timestamp": "1574713503" + // } + // + // private fetchMyTrades, fetchClosedOrder, fetchOrderTrades + // + // { + // "id": 658745, + // "buy_order_id": 6587453, + // "sell_order_id": 6587459, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // + const id = this.safeString (trade, 'id'); + const timestamp = this.safeTimestamp (trade, 'timestamp'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeStringLower2 (trade, 'type', 'trade_type'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': undefined, + 'type': undefined, + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + // 'sort': 'ASC', // ASC or DESC, default DESC + // 'from': 1574709092, // unix timestamp, optional + // 'till': 1574709092, // unix timestamp, optional + // 'limit': 100, // default 100, optional + // 'offset': 100, // optional + }; + if (limit !== undefined) { + request['limit'] = limit; // currently limited to 100 or fewer + } + if (since !== undefined) { + request['sort'] = 'ASC'; // needed to make the from param work + request['from'] = parseInt (since / 1000); + } + const response = await this.publicGetTradesCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 35989317, + // "price": "0.02033813", + // "amount": "3.60000000", + // "type": "BUY", + // "timestamp": "1574713503" + // }, + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + // await this.loadAccounts (); + const response = await this.profileGetWallets (params); + // + // { + // "success": true, + // "data": [ + // { + // "id": null, + // "currency_id": 665, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "currency_code": "ORM", + // "currency_name": "Orium", + // "currency_type_id": 5, + // "balance": "0", + // "frozen_balance": "0", + // "bonus_balance": "0", + // "total_balance": "0", + // "protocol_specific_settings": null, + // "rates": { "BTC": "0.00000000020", "USD": "0.00000147" }, + // }, + // { + // "id": null, + // "currency_id": 272, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "currency_code": "USDT", + // "currency_name": "TetherUSD", + // "currency_type_id": 23, + // "balance": "0", + // "frozen_balance": "0", + // "bonus_balance": "0", + // "total_balance": "0", + // "protocol_specific_settings": [ + // { "protocol_name": "OMNI", "protocol_id": 10, "active": true, "withdrawal_fee_currency_id": 272, "withdrawal_fee_const": 10, "withdrawal_fee_percent": 0, "block_explorer_url": "https://omniexplorer.info/search/" }, + // { "protocol_name": "ERC20", "protocol_id": 5, "active": true, "withdrawal_fee_const": 1.2, "withdrawal_fee_percent": 0, "block_explorer_url": "https://etherscan.io/tx/" }, + // { "protocol_name": "TRON", "protocol_id": 24, "active": true, "withdrawal_fee_currency_id": 272, "withdrawal_fee_const": 0.2, "withdrawal_fee_percent": 0, "block_explorer_url": "https://tronscan.org/#/transaction/" } + // ], + // "rates": { "BTC": "0.00013893", "USD": "1" }, + // }, + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const code = this.safeCurrencyCode (this.safeString (balance, 'currency_id')); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'frozen_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'PROCESSING': 'open', + 'PENDING': 'open', + 'PARTIAL': 'open', + 'FINISHED': 'closed', + 'CANCELLED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder, fetchOpenOrders, fetchClosedOrders, cancelOrder, fetchOrder, fetchClosedOrder + // + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", // missing in fetchClosedOrder + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // // fetchClosedOrder only + // "trades": [ + // { + // "id": 658745, + // "buy_order_id": 658745, + // "sell_order_id": 828680665, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // ], + // // fetchClosedOrder only + // "fees": [ + // { + // "id": 1234567, + // "currency_id": 1, + // "amount": 0.00025, + // "timestamp": "1548149238" + // } + // ] + // } + // + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString2 (order, 'currency_pair_id', 'currency_pair_name'); + const symbol = this.safeSymbol (marketId, market, '_'); + const timestamp = this.safeTimestamp (order, 'timestamp'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'initial_amount'); + const filled = this.safeFloat (order, 'processed_amount'); + let remaining = undefined; + let cost = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + if (this.options['parseOrderToPrecision']) { + remaining = parseFloat (this.amountToPrecision (symbol, remaining)); + } + remaining = Math.max (remaining, 0.0); + } + if (price !== undefined) { + if (cost === undefined) { + cost = price * filled; + } + } + } + let type = this.safeString (order, 'original_type'); + if ((type === 'BUY') || (type === 'SELL')) { + type = undefined; + } + const side = this.safeStringLower (order, 'type'); + const rawTrades = this.safeValue (order, 'trades'); + let trades = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'symbol': symbol, + 'order': id, + }); + } + const stopPrice = this.safeFloat (order, 'trigger_price'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'trades': trades, + }; + const fees = this.safeValue (order, 'fees'); + if (fees === undefined) { + result['fee'] = undefined; + } else { + const numFees = fees.length; + if (numFees > 0) { + result['fees'] = []; + for (let i = 0; i < fees.length; i++) { + const feeCost = this.safeFloat (fees[i], 'amount'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (fees[i], 'currency_id'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + result['fees'].push ({ + 'cost': feeCost, + 'currency': feeCurrencyCode, + }); + } + } + } else { + result['fee'] = undefined; + } + } + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' createOrder allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (type === 'limit') { + type = side; + } + const request = { + 'currencyPairId': market['id'], + 'type': type.toUpperCase (), // 'BUY', 'SELL', 'STOP_LIMIT_BUY', 'STOP_LIMIT_SELL' + 'amount': parseFloat (this.amountToPrecision (symbol, amount)), // required + 'price': parseFloat (this.priceToPrecision (symbol, price)), // required + // 'trigger_price': 123.45 // required for STOP_LIMIT_BUY or STOP_LIMIT_SELL + }; + const response = await this.tradingPostOrdersCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.tradingGetOrderOrderId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrder (data, market); + } + + async fetchClosedOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.reportsGetOrdersOrderId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 5478965, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.00013800", + // "initial_amount": "1.00000000", + // "type": "BUY", + // "created": "2019-01-22 09:27:17", + // "timestamp": 1548149237, + // "status": "FINISHED", + // "trades": [ + // { + // "id": 658745, + // "buy_order_id": 6587453, + // "sell_order_id": 6587459, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // ], + // "fees": [ + // { + // "id": 1234567, + // "currency_id": 1, + // "amount": 0.00025, + // "timestamp": "1548149238" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrder (data, market); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const order = await this.fetchClosedOrder (id, symbol, params); + return order['trades']; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let method = 'tradingGetOrders'; + const request = { + // 'limit': 100, // default 100 + // 'offset': 100, + }; + if (symbol !== undefined) { + method = 'tradingGetOrdersCurrencyPairId'; + market = this.market (symbol); + request['currencyPairId'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.tradingDeleteOrderOrderId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "put_into_processing_queue": [ + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // ], + // "not_put_into_processing_queue": [ + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // ], + // "message": "string" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const acceptedOrders = this.safeValue (data, 'put_into_processing_queue', []); + const rejectedOrders = this.safeValue (data, 'not_put_into_processing_queue', []); + const numAcceptedOrders = acceptedOrders.length; + const numRejectedOrders = rejectedOrders.length; + if (numAcceptedOrders < 1) { + if (numRejectedOrders < 1) { + throw new OrderNotFound (this.id + ' cancelOrder received an empty response: ' + this.json (response)); + } else { + return this.parseOrder (rejectedOrders[0]); + } + } else { + if (numRejectedOrders < 1) { + return this.parseOrder (acceptedOrders[0]); + } else { + throw new OrderNotFound (this.id + ' cancelOrder received an empty response: ' + this.json (response)); + } + } + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'tradingDeleteOrders'; + if (symbol !== undefined) { + const market = this.market (symbol); + request['currencyPairId'] = market['id']; + method = 'tradingDeleteOrdersCurrencyPairId'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "success":true, + // "data":{ + // "put_into_processing_queue":[], + // "not_put_into_processing_queue":[], + // "message":"Orders operations are handled in processing queue, therefore cancelling is not immediate." + // } + // } + // + return response; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + // 'timeStart': '2019-11-26T19:54:55.901Z', // datetime in iso format + // 'timeEnd': '2019-11-26T19:54:55.901Z', // datetime in iso format + // 'limit': 100, // default 100 + // 'offset': 100, + }; + if (since !== undefined) { + request['timeStart'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.reportsGetTradesCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 658745, + // "buy_order_id": 6587453, + // "sell_order_id": 6587459, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyId': currency['id'], + // Default value is the value that represents legacy protocol. + // In case of USDT it is 10 as Tether OMNI was the default previously. + // The list of protocols can be obtained from the /public/currencies/{currencyId} + // 'protocol_id': 10, + }; + const response = await this.profilePostWalletsCurrencyId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 45875, + // "currency_id": 1, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "code": "BTC", + // "balance": "0.198752", + // "frozen_balance": "1.5784", + // "bonus_balance": "0.000", + // "deposit_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // }, + // "multi_deposit_addresses": [ + // { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // ], + // "withdrawal_additional_field_name": "Payment ID (optional)", + // "rates": { "BTC": 0.000001 }, + // "protocol_specific_settings": [ + // { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "active": true, + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_const": 0.002, + // "withdrawal_fee_percent": 0, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const depositAddress = this.safeValue (data, 'deposit_address', {}); + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'additional_address_parameter'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const balance = await this.fetchBalance (); + const wallets = this.safeValue (balance['info'], 'data', []); + const walletsByCurrencyId = this.indexBy (wallets, 'currency_id'); + const currency = this.currency (code); + const wallet = this.safeValue (walletsByCurrencyId, currency['id']); + if (wallet === undefined) { + throw new ExchangeError (this.id + ' fetchDepositAddress() could not find the wallet id for currency code ' + code + ', try to call createDepositAddress() first'); + } + const walletId = this.safeInteger (wallet, 'id'); + if (walletId === undefined) { + throw new ExchangeError (this.id + ' fetchDepositAddress() could not find the wallet id for currency code ' + code + ', try to call createDepositAddress() first'); + } + const request = { + 'walletId': walletId, + }; + const response = await this.profileGetWalletsWalletId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 45875, + // "currency_id": 1, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "code": "BTC", + // "balance": "0.198752", + // "frozen_balance": "1.5784", + // "bonus_balance": "0.000", + // "deposit_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // }, + // "multi_deposit_addresses": [ + // { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // ], + // "withdrawal_additional_field_name": "Payment ID (optional)", + // "rates": { "BTC": 0.000001 }, + // "protocol_specific_settings": [ + // { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "active": true, + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_const": 0.002, + // "withdrawal_fee_percent": 0, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', []); + const depositAddress = this.safeValue (data, 'deposit_address', {}); + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'additional_address_parameter'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + api + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + headers = { + 'Authorization': 'Bearer ' + this.token, + }; + if (method === 'GET' || method === 'DELETE') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + if (Object.keys (query).length) { + headers['Content-Type'] = 'application/json'; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + parseTransactionStatus (status) { + const statuses = { + 'processing': 'pending', + 'checking by system': 'pending', + 'hodl': 'pending', + 'amount too low': 'failed', + 'not confirmed': 'pending', + 'cancelled by User': 'canceled', + 'approved': 'pending', + 'finished': 'ok', + 'withdrawal error': 'failed', + 'deposit error': 'failed', + 'cancelled by admin': 'canceled', + 'awaiting': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "id": 123654789, + // "currency_id": 1, + // "currency_code": "BTC", + // "deposit_fee_currency_id": 1, + // "deposit_fee_currency_code": "BTC", + // "amount": 0.25, + // "fee": 0.00025, + // "txid": "qwertyuhgfdsasdfgh", + // "protocol_id": 0, + // "deposit_status_id": 1, + // "status": "PROCESSING", + // "status_color": "#BC3D51", + // "created_at": "2018-11-28 12:32:08", + // "timestamp": "1543409389", + // "confirmations": "1 of 2" + // } + // + // fetchWithdrawals + // + // { + // "id": 65899, + // "amount": "0.00600000", + // "currency_id": 1, + // "currency_code": "BTC", + // "fee": "0.00400000", + // "fee_currency_id": 1, + // "fee_currency_code": "BTC", + // "withdrawal_status_id": 1, + // "status": "Not Confirmed", + // "status_color": "#BC3D51", + // "created_at": "2019-01-21 09:36:05", + // "created_ts": "1548063365", + // "updated_at": "2019-01-21 09:36:05", + // "updated_ts": "1548063365", + // "txid": null, + // "protocol_id": 0, + // "withdrawal_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // } + // + const id = this.safeString (transaction, 'id'); + const withdrawalAddress = this.safeValue (transaction, 'withdrawal_address', {}); + const address = this.safeString (withdrawalAddress, 'address'); + const tag = this.safeString (withdrawalAddress, 'additional_address_parameter'); + const currencyId = this.safeString (transaction, 'currency_id'); + let code = undefined; + if (currencyId in this.currencies_by_id) { + currency = this.currencies_by_id[currencyId]; + } else { + code = this.commonCurrencyCode (this.safeString (transaction, 'currency_code')); + } + if ((code === undefined) && (currency !== undefined)) { + code = currency['code']; + } + const type = ('deposit_status_id' in transaction) ? 'deposit' : 'withdrawal'; + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeStringLower (transaction, 'status')); + const timestamp = this.safeTimestamp2 (transaction, 'timestamp', 'created_ts'); + const updated = this.safeTimestamp (transaction, 'updated_ts'); + const txid = this.safeString (transaction, 'txid'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (transaction, 'fee_currency_id', 'deposit_fee_currency_id'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currencyId'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timeStart'] = since; + } + const response = await this.profileGetDeposits (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 123654789, + // "currency_id": 1, + // "currency_code": "BTC", + // "deposit_fee_currency_id": 1, + // "deposit_fee_currency_code": "BTC", + // "amount": 0.25, + // "fee": 0.00025, + // "txid": "qwertyuhgfdsasdfgh", + // "protocol_id": 0, + // "deposit_status_id": 1, + // "status": "PROCESSING", + // "status_color": "#BC3D51", + // "created_at": "2018-11-28 12:32:08", + // "timestamp": "1543409389", + // "confirmations": "1 of 2", + // "protocol_specific_settings": { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // } + // ] + // } + // + const deposits = this.safeValue (response, 'data', []); + return this.parseTransactions (deposits, code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currencyId'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timeStart'] = since; + } + const response = await this.profileGetWithdrawals (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 65899, + // "amount": "0.00600000", + // "currency_id": 1, + // "currency_code": "BTC", + // "fee": "0.00400000", + // "fee_currency_id": 1, + // "fee_currency_code": "BTC", + // "withdrawal_status_id": 1, + // "status": "Not Confirmed", + // "status_color": "#BC3D51", + // "created_at": "2019-01-21 09:36:05", + // "created_ts": "1548063365", + // "updated_at": "2019-01-21 09:36:05", + // "updated_ts": "1548063365", + // "txid": null, + // "protocol_id": 0, + // "withdrawal_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // }, + // "protocol_specific_settings": { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // } + // ] + // } + // + const withdrawals = this.safeValue (response, 'data', []); + return this.parseTransactions (withdrawals, code, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_id': currency['id'], + 'amount': parseFloat (this.currencyToPrecision (code, amount)), + 'address': address, + // 'protocol_id': 10, // optional, to be used with multicurrency wallets like USDT + // 'additional_address_parameter': tag, // optional + }; + if (tag !== undefined) { + request['additional_address_parameter'] = tag; + } + const response = await this.profilePostWithdraw (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 65899, + // "amount": "0.00600000", + // "currency_id": 1, + // "currency_code": "BTC", + // "fee": "0.00400000", + // "fee_currency_id": 1, + // "fee_currency_code": "BTC", + // "withdrawal_status_id": 1, + // "status": "Not Confirmed", + // "status_color": "#BC3D51", + // "created_at": "2019-01-21 09:36:05", + // "created_ts": "1548063365", + // "updated_at": "2019-01-21 09:36:05", + // "updated_ts": "1548063365", + // "txid": null, + // "protocol_id": 0, + // "withdrawal_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransaction (data, currency); + } + + async fetchFundingFees (codes = undefined, params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "success": true, + // "data": [ + // { + // "id": 1, + // "code": "BTC", + // "name": "Bitcoin", + // "active": true, + // "delisted": false, + // "precision": 8, + // "minimum_tx_confirmations": 24, + // "minimum_withdrawal_amount": "0.009", + // "minimum_deposit_amount": "0.000003", + // "deposit_fee_currency_id": 1, + // "deposit_fee_currency_code": "ETH", + // "deposit_fee_const": "0.00001", + // "deposit_fee_percent": "0", + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_currency_code": "ETH", + // "withdrawal_fee_const": "0.0015", + // "withdrawal_fee_percent": "0", + // "withdrawal_limit": "string", + // "block_explorer_url": "https://blockchain.info/tx/", + // "protocol_specific_settings": [ + // { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "active": true, + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_const": 0.002, + // "withdrawal_fee_percent": 0, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // ] + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const withdrawFees = {}; + const depositFees = {}; + for (let i = 0; i < data.length; i++) { + const id = this.safeString (data[i], 'id'); + const code = this.safeCurrencyCode (id); + withdrawFees[code] = this.safeFloat (data[i], 'withdrawal_fee_const'); + depositFees[code] = this.safeFloat (data[i], 'deposit_fee_const'); + } + return { + 'withdraw': withdrawFees, + 'deposit': depositFees, + 'info': response, + }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"success":false,"message":"Wrong parameters","errors":{"candleType":["Invalid Candle Type!"]}} + // {"success":false,"message":"Wrong parameters","errors":{"time":["timeStart or timeEnd is less then 1"]}} + // {"success":false,"message":"Not enough ETH"} + // + const success = this.safeValue (response, 'success', false); + if (!success) { + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],164:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const foxbit = require ('./foxbit.js'); + +// --------------------------------------------------------------------------- + +module.exports = class surbitcoin extends foxbit { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'surbitcoin', + 'name': 'SurBitcoin', + 'countries': [ 'VE' ], + 'has': { + 'CORS': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27991511-f0a50194-6481-11e7-99b5-8f02932424cc.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://surbitcoin.com', + 'doc': 'https://blinktrade.com/docs', + }, + 'options': { + 'brokerId': '1', // https://blinktrade.com/docs/#brokers + }, + }); + } +}; + +},{"./foxbit.js":84}],165:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, OrderNotFound, InvalidAddress } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class therock extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'therock', + 'name': 'TheRockTrading', + 'countries': [ 'MT' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': 'emulated', + 'fetchWithdrawals': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg', + 'api': 'https://api.therocktrading.com', + 'www': 'https://therocktrading.com', + 'doc': [ + 'https://api.therocktrading.com/doc/v1/index.html', + 'https://api.therocktrading.com/doc/', + ], + }, + 'api': { + 'public': { + 'get': [ + 'funds', + 'funds/{id}/orderbook', + 'funds/{id}/ticker', + 'funds/{id}/trades', + 'funds/tickers', + ], + }, + 'private': { + 'get': [ + 'balances', + 'balances/{id}', + 'discounts', + 'discounts/{id}', + 'funds', + 'funds/{id}', + 'funds/{id}/trades', + 'funds/{fund_id}/orders', + 'funds/{fund_id}/orders/{id}', + 'funds/{fund_id}/position_balances', + 'funds/{fund_id}/positions', + 'funds/{fund_id}/positions/{id}', + 'transactions', + 'transactions/{id}', + 'withdraw_limits/{id}', + 'withdraw_limits', + ], + 'post': [ + 'atms/withdraw', + 'funds/{fund_id}/orders', + ], + 'delete': [ + 'funds/{fund_id}/orders/{id}', + 'funds/{fund_id}/orders/remove_all', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.0005, + 'BCH': 0.0005, + 'PPC': 0.02, + 'ETH': 0.001, + 'ZEC': 0.001, + 'LTC': 0.002, + 'EUR': 2.5, // worst-case scenario: https://therocktrading.com/en/pages/fees + }, + 'deposit': { + 'BTC': 0, + 'BCH': 0, + 'PPC': 0, + 'ETH': 0, + 'ZEC': 0, + 'LTC': 0, + 'EUR': 0, + }, + }, + }, + 'exceptions': { + 'exact': { + 'Request already running': BadRequest, + 'cannot specify multiple address types': BadRequest, + 'Currency is not included in the list': BadRequest, + 'Record not found': OrderNotFound, + }, + 'broad': { + 'before must be greater than after param': BadRequest, + 'must be shorter than 60 days': BadRequest, + 'must be a multiple of (period param) in minutes': BadRequest, + 'Address allocation limit reached for currency': InvalidAddress, + 'is not a valid value for param currency': BadRequest, + ' is invalid': InvalidAddress, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetFunds (params); + // + // { funds: [ { id: "BTCEUR", + // description: "Trade Bitcoin with Euro", + // type: "currency", + // base_currency: "EUR", + // trade_currency: "BTC", + // buy_fee: 0.2, + // sell_fee: 0.2, + // minimum_price_offer: 0.01, + // minimum_quantity_offer: 0.0005, + // base_currency_decimals: 2, + // trade_currency_decimals: 4, + // leverages: [] }, + // { id: "LTCEUR", + // description: "Trade Litecoin with Euro", + // type: "currency", + // base_currency: "EUR", + // trade_currency: "LTC", + // buy_fee: 0.2, + // sell_fee: 0.2, + // minimum_price_offer: 0.01, + // minimum_quantity_offer: 0.01, + // base_currency_decimals: 2, + // trade_currency_decimals: 2, + // leverages: [] } ] } + // + const markets = this.safeValue (response, 'funds'); + const result = []; + if (markets === undefined) { + throw new ExchangeError (this.id + ' fetchMarkets got an unexpected response'); + } else { + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'trade_currency'); + const quoteId = this.safeString (market, 'base_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const buy_fee = this.safeFloat (market, 'buy_fee'); + const sell_fee = this.safeFloat (market, 'sell_fee'); + let taker = Math.max (buy_fee, sell_fee); + taker = taker / 100; + const maker = taker; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': true, + 'maker': maker, + 'taker': taker, + 'precision': { + 'amount': this.safeInteger (market, 'trade_currency_decimals'), + 'price': this.safeInteger (market, 'base_currency_decimals'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minimum_quantity_offer'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (market, 'minimum_price_offer'), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalances (params); + const balances = this.safeValue (response, 'balances', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'trading_balance'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const orderbook = await this.publicGetFundsIdOrderbook (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (orderbook, 'date')); + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.parse8601 (ticker['date']); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': this.safeFloat (ticker, 'close'), // previous day close, if any + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume_traded'), + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetFundsTickers (params); + const tickers = this.indexBy (response['tickers'], 'fund_id'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const ticker = await this.publicGetFundsIdTicker (this.extend ({ + 'id': market['id'], + }, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades, fetchOrder trades + // + // { id: 4493548, + // fund_id: "ETHBTC", + // amount: 0.203, + // price: 0.02783576, + // side: "buy", + // dark: false, + // date: "2018-11-30T08:19:18.236Z" } + // + // fetchMyTrades + // + // { id: 237338, + // fund_id: "BTCEUR", + // amount: 0.348, + // price: 348, + // side: "sell", + // dark: false, + // order_id: 14920648, + // date: "2015-06-03T00:49:49.000Z", + // transactions: [ { id: 2770768, + // date: "2015-06-03T00:49:49.000Z", + // type: "sold_currency_to_fund", + // price: 121.1, + // currency: "EUR" }, + // { id: 2770769, + // date: "2015-06-03T00:49:49.000Z", + // type: "released_currency_to_fund", + // price: 0.348, + // currency: "BTC" }, + // { id: 2770772, + // date: "2015-06-03T00:49:49.000Z", + // type: "paid_commission", + // price: 0.06, + // currency: "EUR", + // trade_id: 440492 } ] } + // + const marketId = this.safeString (trade, 'fund_id'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (trade, 'date')); + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'order_id'); + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + let feeCost = undefined; + const transactions = this.safeValue (trade, 'transactions', []); + const transactionsByType = this.groupBy (transactions, 'type'); + const feeTransactions = this.safeValue (transactionsByType, 'paid_commission', []); + for (let i = 0; i < feeTransactions.length; i++) { + if (feeCost === undefined) { + feeCost = 0; + } + feeCost = this.sum (feeCost, this.safeFloat (feeTransactions[i], 'price')); + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseLedgerEntryDirection (direction) { + const directions = { + 'affiliate_earnings': 'in', + 'atm_payment': 'in', + 'bought_currency_from_fund': 'out', + 'bought_shares': 'out', + 'paid_commission': 'out', + 'paypal_payment': 'in', + 'pos_payment': 'in', + 'released_currency_to_fund': 'out', + 'rollover_commission': 'out', + 'sold_currency_to_fund': 'in', + 'sold_shares': 'in', + 'transfer_received': 'in', + 'transfer_sent': 'out', + 'withdraw': 'out', + // commented types will be shown as-is + // 'acquired_currency_from_fund': '', + // 'acquired_insurance': '', + // 'dividend_distributed_to_holders': '', + // 'dividend_from_shares': '', + // 'exposed_position': '', + // 'insurances_reimbursement': '', + // 'lent_currency': '', + // 'linden_lab_assessment': '', + // 'position_transfer_received': '', + // 'return_lent_currency': '', + // 'returned_lent_currency': '', + // 'the_rock_assessment': '', + }; + return this.safeString (directions, direction, direction); + } + + parseLedgerEntryType (type) { + const types = { + 'affiliate_earnings': 'referral', + 'atm_payment': 'transaction', + 'bought_currency_from_fund': 'trade', + 'bought_shares': 'trade', + 'paid_commission': 'fee', + 'paypal_payment': 'transaction', + 'pos_payment': 'transaction', + 'released_currency_to_fund': 'trade', + 'rollover_commission': 'fee', + 'sold_currency_to_fund': 'trade', + 'sold_shares': 'trade', + 'transfer_received': 'transfer', + 'transfer_sent': 'transfer', + 'withdraw': 'transaction', + // commented types will be shown as-is + // 'acquired_currency_from_fund': '', + // 'acquired_insurance': '', + // 'dividend_distributed_to_holders': '', + // 'dividend_from_shares': '', + // 'exposed_position': '', + // 'insurances_reimbursement': '', + // 'lent_currency': '', + // 'linden_lab_assessment': '', + // 'position_transfer_received': '', + // 'return_lent_currency': '', + // 'returned_lent_currency': '', + // 'the_rock_assessment': '', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // withdrawal + // + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // } + // + // deposit + // + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // } + // + // trade fee + // + // { + // "id": 21311221, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "paid_commission", + // "price": 0.0001, + // "fund_id": "BTCEUR", + // "order_id": 12832371, + // "trade_id": 12923212, + // "currency": "BTC", + // "transfer_detail": null + // } + // + const id = this.safeString (item, 'id'); + let referenceId = undefined; + let type = this.safeString (item, 'type'); + const direction = this.parseLedgerEntryDirection (type); + type = this.parseLedgerEntryType (type); + if (type === 'trade' || type === 'fee') { + referenceId = this.safeString (item, 'trade_id'); + } + const currencyId = this.safeString (item, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (item, 'price'); + const timestamp = this.parse8601 (this.safeString (item, 'date')); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': undefined, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': undefined, + 'after': undefined, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'fund_id': 'ETHBTC', // filter by fund symbol + // 'currency': 'BTC', // filter by currency + // 'after': '2015-02-06T08:47:26Z', // filter after a certain timestamp + // 'before': '2015-02-06T08:47:26Z', + // 'type': 'withdraw', + // 'order_id': '12832371', // filter by a specific order ID + // 'trade_id': '12923212', // filter by a specific trade ID + // 'transfer_method': 'bitcoin', // wire_transfer, ripple, greenaddress, bitcoin, litecoin, namecoin, peercoin, dogecoin + // 'transfer_recipient': '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', // filter by a specific recipient (e.g. Bitcoin address, IBAN) + // 'transfer_id': '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa', // filter by a specific transfer ID (e.g. Bitcoin TX hash) + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.privateGetTransactions (this.extend (request, params)); + // + // { + // "transactions": [ + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // }, + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // }, + // { + // "id": 21311221, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "paid_commission", + // "price": 0.0001, + // "fund_id": "BTCEUR", + // "order_id": 12832371, + // "trade_id": 12923212, + // "currency": "BTC", + // "transfer_detail": null + // } + // ], + // "meta": { + // "total_count": 1221, + // "first": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "previous": null, + // "current": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "next": { "page": 2, "href": "https://api.therocktrading.com/v1/transactions?page=2" }, + // "last": { "page": 1221, "href": "https://api.therocktrading.com/v1/transactions?page=1221" } + // } + // } + // + const transactions = this.safeValue (response, 'transactions', []); + return this.parseLedger (transactions, currency, since, limit); + } + + parseTransactionType (type) { + const types = { + 'withdraw': 'withdrawal', + 'atm_payment': 'deposit', + }; + return this.safeString (types, type, type); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchWithdrawals + // + // // fiat + // + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // } + // + // { + // "id": 12564223, + // "date": "2017-08-07T08:13:50.023Z", + // "note": "GB7IDL401573388", + // "type": "withdraw", + // "price": 4345.93, + // "fund_id": null, + // "currency": "EUR", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "id": "EXECUTEDBUTUNCHECKED", + // "method": "wire_transfer", + // "recipient": "GB7IDL401573388", + // "confirmations": 0 + // } + // } + // + // // crypto + // + // { + // id: 20914695, + // date: '2018-02-24T07:13:23.002Z', + // type: 'withdraw', + // price: 2.70883607, + // currency: 'BCH', + // fund_id: null, + // order_id: null, + // trade_id: null, + // note: '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', + // transfer_detail: { + // method: 'bitcoin_cash', + // id: '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa', + // recipient: '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', + // confirmations: 0 + // } + // } + // + // + // fetchDeposits + // + // // fiat + // + // { + // id: 16176632, + // date: '2017-11-20T21:00:13.355Z', + // type: 'atm_payment', + // price: 5000, + // currency: 'EUR', + // fund_id: null, + // order_id: null, + // trade_id: null, + // note: 'Mistral deposit', + // transfer_detail: { + // method: 'wire_transfer', + // id: '972JQ49337DX769T', + // recipient: null, + // confirmations: 0 + // } + // } + // + // // crypto + // + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // } + // + const id = this.safeString (transaction, 'id'); + const type = this.parseTransactionType (this.safeString (transaction, 'type')); + const detail = this.safeValue (transaction, 'transfer_detail', {}); + const method = this.safeString (detail, 'method'); + let txid = undefined; + let address = undefined; + if (method !== undefined) { + if (method !== 'wire_transfer') { + txid = this.safeString (detail, 'id'); + address = this.safeString (detail, 'recipient'); + } + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (transaction, 'price'); + const timestamp = this.parse8601 (this.safeString (transaction, 'date')); + const status = 'ok'; + // todo parse tags + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': undefined, + 'addressTo': address, + 'address': address, + 'tagFrom': undefined, + 'tagTo': undefined, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'withdraw', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'atm_payment', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'fund_id': 'ETHBTC', // filter by fund symbol + // 'currency': 'BTC', // filter by currency + // 'after': '2015-02-06T08:47:26Z', // filter after a certain timestamp + // 'before': '2015-02-06T08:47:26Z', + // 'type': 'withdraw', + // 'order_id': '12832371', // filter by a specific order ID + // 'trade_id': '12923212', // filter by a specific trade ID + // 'transfer_method': 'bitcoin', // wire_transfer, ripple, greenaddress, bitcoin, litecoin, namecoin, peercoin, dogecoin + // 'transfer_recipient': '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', // filter by a specific recipient (e.g. Bitcoin address, IBAN) + // 'transfer_id': '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa', // filter by a specific transfer ID (e.g. Bitcoin TX hash) + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + params = this.extend (request, params); + const response = await this.privateGetTransactions (params); + // + // { + // "transactions": [ + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // }, + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // }, + // { + // "id": 21311221, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "paid_commission", + // "price": 0.0001, + // "fund_id": "BTCEUR", + // "order_id": 12832371, + // "trade_id": 12923212, + // "currency": "BTC", + // "transfer_detail": null + // } + // ], + // "meta": { + // "total_count": 1221, + // "first": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "previous": null, + // "current": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "next": { "page": 2, "href": "https://api.therocktrading.com/v1/transactions?page=2" }, + // "last": { "page": 1221, "href": "https://api.therocktrading.com/v1/transactions?page=1221" } + // } + // } + // + const transactions = this.safeValue (response, 'transactions', []); + const transactionTypes = [ 'withdraw', 'atm_payment' ]; + const depositsAndWithdrawals = this.filterByArray (transactions, 'type', transactionTypes, false); + return this.parseTransactions (depositsAndWithdrawals, currency, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'active': 'open', + 'executed': 'closed', + 'deleted': 'canceled', + // don't know what this status means + // 'conditional': '?', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": 4325578, + // "fund_id":"BTCEUR", + // "side":"buy", + // "type":"limit", + // "status":"executed", + // "price":0.0102, + // "amount": 50.0, + // "amount_unfilled": 0.0, + // "conditional_type": null, + // "conditional_price": null, + // "date":"2015-06-03T00:49:48.000Z", + // "close_on": nil, + // "leverage": 1.0, + // "position_id": null, + // "trades": [ + // { + // "id":237338, + // "fund_id":"BTCEUR", + // "amount":50, + // "price":0.0102, + // "side":"buy", + // "dark":false, + // "date":"2015-06-03T00:49:49.000Z" + // } + // ] + // } + // + const id = this.safeString (order, 'id'); + const marketId = this.safeString (order, 'fund_id'); + const symbol = this.safeSymbol (marketId, market); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.parse8601 (this.safeString (order, 'date')); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const amount = this.safeFloat (order, 'amount'); + const remaining = this.safeFloat (order, 'amount_unfilled'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const price = this.safeFloat (order, 'price'); + let trades = this.safeValue (order, 'trades'); + let cost = undefined; + let average = undefined; + let lastTradeTimestamp = undefined; + if (trades !== undefined) { + const numTrades = trades.length; + if (numTrades > 0) { + trades = this.parseTrades (trades, market, undefined, undefined, { + 'orderId': id, + }); + // todo: determine the cost and the average price from trades + cost = 0; + filled = 0; + for (let i = 0; i < numTrades; i++) { + const trade = trades[i]; + cost = this.sum (cost, trade['cost']); + filled = this.sum (filled, trade['amount']); + } + if (filled > 0) { + average = cost / filled; + } + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + } else { + cost = 0; + } + } + const stopPrice = this.safeFloat (order, 'conditional_price'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'average': average, + 'remaining': remaining, + 'fee': undefined, + 'trades': trades, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'active', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'executed', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'fund_id': market['id'], + // 'after': '2015-02-06T08:47:26Z', + // 'before': '2015-02-06T08:47:26Z' + // 'status': 'active', // 'executed', 'conditional' + // 'side': 'buy', // 'sell' + // 'position_id': 123, // filter orders by margin position id + }; + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.privateGetFundsFundIdOrders (this.extend (request, params)); + // + // { + // orders: [ + // { + // id: 299333648, + // fund_id: 'BTCEUR', + // side: 'sell', + // type: 'limit', + // status: 'executed', + // price: 5821, + // amount: 0.1, + // amount_unfilled: 0, + // conditional_type: null, + // conditional_price: null, + // date: '2018-06-18T17:38:16.129Z', + // close_on: null, + // dark: false, + // leverage: 1, + // position_id: 0 + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'fund_id': market['id'], + }; + const response = await this.privatePostFundsFundIdOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + price = 0; + } + const request = { + 'fund_id': this.marketId (symbol), + 'side': side, + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostFundsFundIdOrders (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + 'fund_id': this.marketId (symbol), + }; + const response = await this.privateDeleteFundsFundIdOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + if (limit !== undefined) { + request['per_page'] = limit; // default 25 max 200 + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.privateGetFundsIdTrades (this.extend (request, params)); + // + // { trades: [ { id: 237338, + // fund_id: "BTCEUR", + // amount: 0.348, + // price: 348, + // side: "sell", + // dark: false, + // order_id: 14920648, + // date: "2015-06-03T00:49:49.000Z", + // transactions: [ { id: 2770768, + // date: "2015-06-03T00:49:49.000Z", + // type: "sold_currency_to_fund", + // price: 121.1, + // currency: "EUR" }, + // { id: 2770769, + // date: "2015-06-03T00:49:49.000Z", + // type: "released_currency_to_fund", + // price: 0.348, + // currency: "BTC" }, + // { id: 2770772, + // date: "2015-06-03T00:49:49.000Z", + // type: "paid_commission", + // price: 0.06, + // currency: "EUR", + // trade_id: 440492 } ] } ], + // meta: { total_count: 31, + // first: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=1" }, + // previous: null, + // current: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=1" }, + // next: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=2" }, + // last: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=2" } } } + // + return this.parseTrades (response['trades'], market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + if (limit !== undefined) { + request['per_page'] = limit; // default 25 max 200 + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.publicGetFundsIdTrades (this.extend (request, params)); + // + // { trades: [ { id: 4493548, + // fund_id: "ETHBTC", + // amount: 0.203, + // price: 0.02783576, + // side: "buy", + // dark: false, + // date: "2018-11-30T08:19:18.236Z" }, + // { id: 4492926, + // fund_id: "ETHBTC", + // amount: 0.04, + // price: 0.02767034, + // side: "buy", + // dark: false, + // date: "2018-11-30T07:03:03.897Z" } ], + // meta: { total_count: null, + // first: { page: 1, + // href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=1" }, + // previous: null, + // current: { page: 1, + // href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=1" }, + // next: { page: 2, + // href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=2" }, + // last: null } } + // + return this.parseTrades (response['trades'], market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + headers = (headers === undefined) ? {} : headers; + if (api === 'private') { + this.checkRequiredCredentials (); + if (Object.keys (query).length) { + if (method === 'POST') { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } else { + const queryString = this.rawencode (query); + if (queryString.length) { + url += '?' + queryString; + } + } + } + const nonce = this.nonce ().toString (); + const auth = nonce + url; + headers['X-TRT-KEY'] = this.apiKey; + headers['X-TRT-NONCE'] = nonce; + headers['X-TRT-SIGN'] = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'); + } else if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.rawencode (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { + // "errors": + // [ + // { "message": ":currency is not a valid value for param currency","code": "11","meta": { "key":"currency","value":":currency"} }, + // { "message": "Address allocation limit reached for currency :currency.","code": "13" }, + // { "message": "Request already running", "code": "50"}, + // { "message": "cannot specify multiple address types", "code": "12" }, + // { "message": ":address_type is invalid", "code": "12" } + // ] + // } + // + const errors = this.safeValue (response, 'errors', []); + const numErrors = errors.length; + if (numErrors > 0) { + const feedback = this.id + ' ' + body; + // here we throw the first error we can identify + for (let i = 0; i < numErrors; i++) { + const error = errors[i]; + const message = this.safeString (error, 'message'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],166:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InsufficientFunds, OrderNotFound, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class tidebit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'tidebit', + 'name': 'TideBit', + 'countries': [ 'HK' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '12h': '720', + '1d': '1440', + '3d': '4320', + '1w': '10080', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460811-1e690280-c616-11ea-8652-69f187305add.jpg', + 'api': 'https://www.tidebit.com', + 'www': 'https://www.tidebit.com', + 'doc': [ + 'https://www.tidebit.com/documents/api/guide', + 'https://www.tidebit.com/swagger/#/default', + ], + 'referral': 'http://bit.ly/2IX0LrM', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'tickers', + 'tickers/{market}', + 'timestamp', + 'trades', + 'trades/{market}', + 'order_book', + 'order', + 'k_with_pending_trades', + 'k', + 'depth', + ], + 'post': [], + }, + 'private': { + 'get': [ + 'addresses/{address}', + 'deposits/history', + 'deposits/get_deposit', + 'deposits/deposit_address', + 'historys/orders', + 'historys/vouchers', + 'historys/accounts', + 'historys/snapshots', + 'linkage/get_status', + 'members/me', + 'order', + 'orders', + 'partners/orders/{id}/trades', + 'referral_commissions/get_undeposited', + 'referral_commissions/get_graph_data', + 'trades/my', + 'withdraws/bind_account_list', + 'withdraws/get_withdraw_account', + 'withdraws/fetch_bind_info', + ], + 'post': [ + 'deposits/deposit_cash', + 'favorite_markets/update', + 'order/delete', + 'orders', + 'orders/multi', + 'orders/clear', + 'referral_commissions/deposit', + 'withdraws/apply', + 'withdraws/bind_bank', + 'withdraws/bind_address', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.3 / 100, + 'taker': 0.3 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': true, + 'withdraw': {}, // There is only 1% fee on withdrawals to your bank account. + }, + }, + 'exceptions': { + '2002': InsufficientFunds, + '2003': OrderNotFound, + }, + }); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + if ('success' in response) { + if (response['success']) { + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'addressTag'); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + } + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'id'); + const symbol = this.safeString (market, 'name'); + const [ baseId, quoteId ] = symbol.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetMembersMe (params); + const balances = this.safeValue (response, 'accounts'); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 300 + } + request['market'] = market['id']; + const response = await this.publicGetDepth (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'at'); + ticker = this.safeValue (ticker, 'ticker', {}); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'ask': this.safeFloat (ticker, 'sell'), + 'bidVolume': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'change': undefined, + 'percentage': undefined, + 'previousClose': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const tickers = await this.publicGetTickers (params); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTickersMarket (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + const cost = this.safeFloat (trade, 'funds'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1498530360, + // 2700.0, + // 2700.0, + // 2700.0, + // 2700.0, + // 0.01 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 30; // default is 30 + } + const request = { + 'market': market['id'], + 'period': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } else { + request['timestamp'] = 1800000; + } + const response = await this.publicGetK (this.extend (request, params)); + // + // [ + // [1498530360,2700.0,2700.0,2700.0,2700.0,0.01], + // [1498530420,2700.0,2700.0,2700.0,2700.0,0], + // [1498530480,2700.0,2700.0,2700.0,2700.0,0], + // ] + // + if (response === 'null') { + return []; + } + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'done': 'closed', + 'wait': 'open', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": 7, // 唯一的 Order ID + // "side": "sell", // Buy/Sell 代表买单/卖单 + // "price": "3100.0", // 出价 + // "avg_price": "3101.2", // 平均成交价 + // "state": "wait", // 订单的当前状态 [wait,done,cancel] + // // wait 表明订单正在市场上挂单 + // // 是一个active order + // // 此时订单可能部分成交或者尚未成交 + // // done 代表订单已经完全成交 + // // cancel 代表订单已经被撤销 + // "market": "btccny", // 订单参与的交易市场 + // "created_at": "2014-04-18T02:02:33Z", // 下单时间 ISO8601格式 + // "volume": "100.0", // 购买/卖出数量 + // "remaining_volume": "89.8", // 还未成交的数量 remaining_volume 总是小于等于 volume + // // 在订单完全成交时变成 0 + // "executed_volume": "10.2", // 已成交的数量 + // // volume = remaining_volume + executed_volume + // "trades_count": 1, // 订单的成交数 整数值 + // // 未成交的订单为 0 有一笔成交的订单为 1 + // // 通过该字段可以判断订单是否处于部分成交状态 + // "trades": [ // 订单的详细成交记录 参见Trade + // // 注意: 只有某些返回详细订单数据的 API 才会包含 Trade 数据 + // { + // "id": 2, + // "price": "3100.0", + // "volume": "10.2", + // "market": "btccny", + // "created_at": "2014-04-18T02:04:49Z", + // "side": "sell" + // } + // ] + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'ord_type'); + const side = this.safeString (order, 'side'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + const filled = this.safeFloat (order, 'executed_volume'); + const remaining = this.safeFloat (order, 'remaining_volume'); + let cost = undefined; + if (price !== undefined) { + if (filled !== undefined) { + cost = price * filled; + } + } + const average = this.safeFloat (order, 'avg_price'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': average, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'side': side, + 'volume': amount.toString (), + 'ord_type': type, + }; + if (type === 'limit') { + request['price'] = price.toString (); + } + const response = await this.privatePostOrders (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const result = await this.privatePostOrderDelete (this.extend (request, params)); + const order = this.parseOrder (result); + const status = this.safeString (order, 'status'); + if (status === 'closed' || status === 'canceled') { + throw new OrderNotFound (this.id + ' ' + this.json (order)); + } + return order; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const id = this.safeString (params, 'id'); + if (id === undefined) { + throw new ArgumentsRequired (this.id + ' withdraw() requires an extra `id` param (withdraw account id according to withdraws/bind_account_list endpoint'); + } + const request = { + 'id': id, + 'currency_type': 'coin', // or 'cash' + 'currency': currency['id'], + 'body': amount, + // 'address': address, // they don't allow withdrawing to direct addresses? + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const result = await this.privatePostWithdrawsApply (this.extend (request, params)); + return { + 'info': result, + 'id': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + encodeParams (params) { + return this.urlencode (this.keysort (params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = '/' + 'api/' + this.version + '/' + this.implodeParams (path, params) + '.json'; + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + request; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const sortedByKey = this.keysort (this.extend ({ + 'access_key': this.apiKey, + 'tonce': nonce, + }, params)); + const query = this.urlencode (sortedByKey); + const payload = method + '|' + request + '|' + query; + const signature = this.hmac (this.encode (payload), this.encode (this.secret)); + const suffix = query + '&signature=' + signature; + if (method === 'GET') { + url += '?' + suffix; + } else { + body = suffix; + headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 400) || (response === undefined)) { + const feedback = this.id + ' ' + body; + if (response === undefined) { + throw new ExchangeError (feedback); + } + const error = this.safeValue (response, 'error', {}); + const errorCode = this.safeString (error, 'code'); + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + // fallback to default error handler + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],167:[function(require,module,exports){ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, DDoSProtection, InvalidOrder, AuthenticationError } = require ('./base/errors'); + +module.exports = class tidex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'tidex', + 'name': 'Tidex', + 'countries': [ 'UK' ], + 'rateLimit': 2000, + 'version': '3', + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30781780-03149dc4-a12e-11e7-82bb-313b269d24d4.jpg', + 'api': { + 'web': 'https://gate.tidex.com/api', + 'public': 'https://api.tidex.com/api/3', + 'private': 'https://api.tidex.com/tapi', + }, + 'www': 'https://tidex.com', + 'doc': 'https://tidex.com/exchange/public-api', + 'referral': 'https://tidex.com/exchange/?ref=57f5638d9cd7', + 'fees': [ + 'https://tidex.com/exchange/assets-spec', + 'https://tidex.com/exchange/pairs-spec', + ], + }, + 'api': { + 'web': { + 'get': [ + 'currency', + 'pairs', + 'tickers', + 'orders', + 'ordershistory', + 'trade-data', + 'trade-data/{id}', + ], + }, + 'public': { + 'get': [ + 'info', + 'ticker/{pair}', + 'depth/{pair}', + 'trades/{pair}', + ], + }, + 'private': { + 'post': [ + 'getInfoExt', + 'getInfo', + 'Trade', + 'ActiveOrders', + 'OrderInfo', + 'CancelOrder', + 'TradeHistory', + 'CoinDepositAddress', + 'WithdrawCoin', + 'CreateCoupon', + 'RedeemCoupon', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.1 / 100, + 'maker': 0.1 / 100, + }, + }, + 'commonCurrencies': { + 'DSH': 'DASH', + 'EMGO': 'MGO', + 'MGO': 'WMGO', + }, + 'exceptions': { + 'exact': { + '803': InvalidOrder, // "Count could not be less than 0.001." (selling below minAmount) + '804': InvalidOrder, // "Count could not be more than 10000." (buying above maxAmount) + '805': InvalidOrder, // "price could not be less than X." (minPrice violation on buy & sell) + '806': InvalidOrder, // "price could not be more than X." (maxPrice violation on buy & sell) + '807': InvalidOrder, // "cost could not be less than X." (minCost violation on buy & sell) + '831': InsufficientFunds, // "Not enougth X to create buy order." (buying with balance.quote < order.cost) + '832': InsufficientFunds, // "Not enougth X to create sell order." (selling with balance.base < order.amount) + '833': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) + }, + 'broad': { + 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} + 'invalid api key': AuthenticationError, + 'invalid sign': AuthenticationError, + 'api key dont have trade permission': AuthenticationError, + 'invalid parameter': InvalidOrder, + 'invalid order': InvalidOrder, + 'Requests too often': DDoSProtection, + 'not available': ExchangeNotAvailable, + 'data unavailable': ExchangeNotAvailable, + 'external service unavailable': ExchangeNotAvailable, + }, + }, + 'options': { + 'fetchTickersMaxLength': 2048, + }, + 'orders': {}, // orders cache / emulation + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.webGetCurrency (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const precision = currency['amountPoint']; + const code = this.safeCurrencyCode (id); + let active = currency['visible'] === true; + const canWithdraw = currency['withdrawEnable'] === true; + const canDeposit = currency['depositEnable'] === true; + if (!canWithdraw || !canDeposit) { + active = false; + } + const name = this.safeString (currency, 'name'); + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'active': active, + 'precision': precision, + 'funding': { + 'withdraw': { + 'active': canWithdraw, + 'fee': currency['withdrawFee'], + }, + 'deposit': { + 'active': canDeposit, + 'fee': 0.0, + }, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'withdrawMinAmount'), + 'max': undefined, + }, + 'deposit': { + 'min': this.safeFloat (currency, 'depositMinAmount'), + 'max': undefined, + }, + }, + 'info': currency, + }; + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInfo (params); + const markets = response['pairs']; + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'decimal_places'), + 'price': this.safeInteger (market, 'decimal_places'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'min_total'), + }, + }; + const hidden = this.safeInteger (market, 'hidden'); + const active = (hidden === 0); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'taker': market['fee'] / 100, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfoExt (params); + const balances = this.safeValue (response, 'return'); + const result = { 'info': balances }; + const funds = this.safeValue (balances, 'funds', {}); + const currencyIds = Object.keys (funds); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (funds, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'value'); + account['used'] = this.safeFloat (balance, 'inOrders'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const market_id_in_reponse = (market['id'] in response); + if (!market_id_in_reponse) { + throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); + } + const orderbook = response[market['id']]; + return this.parseOrderBook (orderbook); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join ('-'); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const result = {}; + ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const symbol = this.safeSymbol (id); + result[symbol] = this.parseOrderBook (response[id]); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { high: 0.03497582, + // low: 0.03248474, + // avg: 0.03373028, + // vol: 120.11485715062999, + // vol_cur: 3572.24914074, + // last: 0.0337611, + // buy: 0.0337442, + // sell: 0.03377798, + // updated: 1537522009 } + // + let timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + if (!market['active']) { + timestamp = undefined; + } + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': this.safeFloat (ticker, 'vol_cur'), + 'quoteVolume': this.safeFloat (ticker, 'vol'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = this.ids; + if (symbols === undefined) { + const numIds = ids.length; + ids = ids.join ('-'); + // max URL length is 2048 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); + throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + const response = await this.publicGetTickerPair (this.extend (request, params)); + const result = {}; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (response[id], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return tickers[symbol]; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'timestamp'); + let side = this.safeString (trade, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const price = this.safeFloat2 (trade, 'rate', 'price'); + const id = this.safeString2 (trade, 'trade_id', 'tid'); + const orderId = this.safeString (trade, 'order_id'); + const marketId = this.safeString (trade, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const amount = this.safeFloat (trade, 'amount'); + const type = 'limit'; // all trades are still limit trades + let takerOrMaker = undefined; + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const isYourOrder = this.safeValue (trade, 'is_your_order'); + if (isYourOrder !== undefined) { + takerOrMaker = 'taker'; + if (isYourOrder) { + takerOrMaker = 'maker'; + } + if (fee === undefined) { + fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker); + } + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTradesPair (this.extend (request, params)); + if (Array.isArray (response)) { + const numElements = response.length; + if (numElements === 0) { + return []; + } + } + return this.parseTrades (response[market['id']], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'amount': this.amountToPrecision (symbol, amount), + 'rate': this.priceToPrecision (symbol, price), + }; + price = parseFloat (price); + amount = parseFloat (amount); + const response = await this.privatePostTrade (this.extend (request, params)); + let id = undefined; + let status = 'open'; + let filled = 0.0; + let remaining = amount; + if ('return' in response) { + id = this.safeString (response['return'], 'order_id'); + if (id === '0') { + id = this.safeString (response['return'], 'init_order_id'); + status = 'closed'; + } + filled = this.safeFloat (response['return'], 'received', 0.0); + remaining = this.safeFloat (response['return'], 'remains', amount); + } + const timestamp = this.milliseconds (); + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * filled, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + // 'trades': this.parseTrades (order['trades'], market), + 'info': response, + 'clientOrderId': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'closed', + '2': 'canceled', + '3': 'canceled', // or partially-filled and still open? https://github.com/ccxt/ccxt/issues/1594 + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'timestamp_created'); + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market); + let remaining = undefined; + let amount = undefined; + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if ('start_amount' in order) { + amount = this.safeFloat (order, 'start_amount'); + remaining = this.safeFloat (order, 'amount'); + } else { + remaining = this.safeFloat (order, 'amount'); + } + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + cost = price * filled; + } + } + const fee = undefined; + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'type': 'limit', + 'timeInForce': undefined, + 'side': this.safeString (order, 'type'), + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + const response = await this.privatePostOrderInfo (this.extend (request, params)); + id = id.toString (); + const result = this.safeValue (response, 'return', {}); + const order = this.safeValue (result, id); + return this.parseOrder (this.extend ({ 'id': id }, order)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostActiveOrders (this.extend (request, params)); + // + // { + // "success":1, + // "return":{ + // "1255468911":{ + // "status":0, + // "pair":"spike_usdt", + // "type":"sell", + // "amount":35028.44256388, + // "rate":0.00199989, + // "timestamp_created":1602684432 + // } + // }, + // "stat":{ + // "isSuccess":true, + // "serverTime":"00:00:00.0000826", + // "time":"00:00:00.0091423", + // "errors":null + // } + // } + // + // it can only return 'open' orders (i.e. no way to fetch 'closed' orders) + const orders = this.safeValue (response, 'return', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + // some derived classes use camelcase notation for request fields + const request = { + // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) + // 'count': 1000, // the number of trades for display numerical, default = 1000 + // 'from_id': trade ID, from which the display starts numerical 0 + // 'end_id': trade ID on which the display ends numerical ∞ + // 'order': 'ASC', // sorting, default = DESC (test result: liqui ignores this field, most recent trade always goes last) + // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) + // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) + // 'pair': 'eth_btc', // default = all markets + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['count'] = parseInt (limit); + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const trades = this.safeValue (response, 'return', []); + return this.parseTrades (trades, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coinName': currency['id'], + 'amount': parseFloat (amount), + 'address': address, + }; + // no docs on the tag, yet... + if (tag !== undefined) { + throw new ExchangeError (this.id + ' withdraw() does not support the tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.'); + } + const response = await this.privatePostWithdrawCoin (this.extend (request, params)); + return { + 'info': response, + 'id': response['return']['tId'], + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + 'method': path, + }, query)); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } else if (api === 'public') { + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + url += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + if (Object.keys (query).length) { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // 1 - The exchange only returns the integer 'success' key from their private API + // + // { "success": 1, ... } httpCode === 200 + // { "success": 0, ... } httpCode === 200 + // + // 2 - However, derived exchanges can return non-integers + // + // It can be a numeric string + // { "sucesss": "1", ... } + // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) + // + // Or just a string + // { "success": "true", ... } + // { "success": "false", ... }, httpCode >= 200 + // + // Or a boolean + // { "success": true, ... } + // { "success": false, ... }, httpCode >= 200 + // + // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types + // + // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui + // + // To cover points 1, 2, 3 and 4 combined this handler should work like this: + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],168:[function(require,module,exports){ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, PermissionDenied, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, RateLimitExceeded, NotSupported, BadRequest, AuthenticationError } = require ('./base/errors'); + +module.exports = class timex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'timex', + 'name': 'TimeX', + 'countries': [ 'AU' ], + 'version': 'v1', + 'rateLimit': 1500, + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, // maker fee only + }, + 'timeframes': { + '1m': 'I1', + '5m': 'I5', + '15m': 'I15', + '30m': 'I30', + '1h': 'H1', + '2h': 'H2', + '4h': 'H4', + '6h': 'H6', + '12h': 'H12', + '1d': 'D1', + '1w': 'W1', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/70423869-6839ab00-1a7f-11ea-8f94-13ae72c31115.jpg', + 'api': 'https://plasma-relay-backend.timex.io', + 'www': 'https://timex.io', + 'doc': 'https://docs.timex.io', + 'referral': 'https://timex.io/?refcode=1x27vNkTbP1uwkCck', + }, + 'api': { + 'custody': { + 'get': [ + 'credentials', // Get api key for address + 'credentials/h/{hash}', // Get api key by hash + 'credentials/k/{key}', // Get api key by key + 'credentials/me/address', // Get api key by hash + 'deposit-addresses', // Get deposit addresses list + 'deposit-addresses/h/{hash}', // Get deposit address by hash + ], + }, + 'history': { + 'get': [ + 'orders', // Gets historical orders + 'orders/details', // Gets order details + 'orders/export/csv', // Export orders to csv + 'trades', // Gets historical trades + 'trades/export/csv', // Export trades to csv + ], + }, + 'currencies': { + 'get': [ + 'a/{address}', // Gets currency by address + 'i/{id}', // Gets currency by id + 's/{symbol}', // Gets currency by symbol + ], + 'post': [ + 'perform', // Creates new currency + 'prepare', // Prepare creates new currency + 'remove/perform', // Removes currency by symbol + 's/{symbol}/remove/prepare', // Prepare remove currency by symbol + 's/{symbol}/update/perform', // Prepare update currency by symbol + 's/{symbol}/update/prepare', // Prepare update currency by symbol + ], + }, + 'markets': { + 'get': [ + 'i/{id}', // Gets market by id + 's/{symbol}', // Gets market by symbol + ], + 'post': [ + 'perform', // Creates new market + 'prepare', // Prepare creates new market + 'remove/perform', // Removes market by symbol + 's/{symbol}/remove/prepare', // Prepare remove market by symbol + 's/{symbol}/update/perform', // Prepare update market by symbol + 's/{symbol}/update/prepare', // Prepare update market by symbol + ], + }, + 'public': { + 'get': [ + 'candles', // Gets candles + 'currencies', // Gets all the currencies + 'markets', // Gets all the markets + 'orderbook', // Gets orderbook + 'orderbook/raw', // Gets raw orderbook + 'orderbook/v2', // Gets orderbook v2 + 'tickers', // Gets all the tickers + 'trades', // Gets trades + ], + }, + 'statistics': { + 'get': [ + 'address', // calculateAddressStatistics + ], + }, + 'trading': { + 'get': [ + 'balances', // Get trading balances for all (or selected) currencies + 'fees', // Get trading fee rates for all (or selected) markets + 'orders', // Gets open orders + ], + 'post': [ + 'orders', // Create new order + 'orders/json', // Create orders + ], + 'put': [ + 'orders', // Cancel or update orders + 'orders/json', // Update orders + ], + 'delete': [ + 'orders', // Delete orders + 'orders/json', // Delete orders + ], + }, + 'tradingview': { + 'get': [ + 'config', // Gets config + 'history', // Gets history + 'symbol_info', // Gets symbol info + 'time', // Gets time + ], + }, + }, + 'exceptions': { + 'exact': { + '0': ExchangeError, + '1': NotSupported, + '4000': BadRequest, + '4001': BadRequest, + '4002': InsufficientFunds, + '4003': AuthenticationError, + '4004': AuthenticationError, + '4005': BadRequest, + '4006': BadRequest, + '4007': BadRequest, + '4300': PermissionDenied, + '4100': AuthenticationError, + '4400': OrderNotFound, + '5001': InvalidOrder, + '5002': ExchangeError, + '400': BadRequest, + '401': AuthenticationError, + '403': PermissionDenied, + '404': OrderNotFound, + '429': RateLimitExceeded, + '500': ExchangeError, + '503': ExchangeNotAvailable, + }, + 'broad': { + 'Insufficient': InsufficientFunds, + }, + }, + 'options': { + 'fetchTickers': { + 'period': '1d', + }, + 'fetchTrades': { + 'sort': 'timestamp,asc', + }, + 'fetchMyTrades': { + 'sort': 'timestamp,asc', + }, + 'fetchOpenOrders': { + 'sort': 'createdAt,asc', + }, + 'fetchClosedOrders': { + 'sort': 'createdAt,asc', + }, + 'defaultSort': 'timestamp,asc', + 'defaultSortOrders': 'createdAt,asc', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "symbol": "ETHBTC", + // "name": "ETH/BTC", + // "baseCurrency": "ETH", + // "baseTokenAddress": "0x45932db54b38af1f5a57136302eeba66a5975c15", + // "quoteCurrency": "BTC", + // "quoteTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "feeCurrency": "BTC", + // "feeTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "quantityIncrement": "0.0000001", + // "takerFee": "0.005", + // "makerFee": "0.0025", + // "tickSize": "0.00000001", + // "baseMinSize": "0.0001", + // "quoteMinSize": "0.00001", + // "locked": false + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + result.push (this.parseMarket (response[i])); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // "symbol": "BTC", + // "name": "Bitcoin", + // "address": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggb3BhY2l0eT0iMC41IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMwIDUzQzQyLjcwMjUgNTMgNTMgNDIuNzAyNSA1MyAzMEM1MyAxNy4yOTc1IDQyLjcwMjUgNyAzMCA3QzE3LjI5NzUgNyA3IDE3LjI5NzUgNyAzMEM3IDQyLjcwMjUgMTcuMjk3NSA1MyAzMCA1M1pNMzAgNTVDNDMuODA3MSA1NSA1NSA0My44MDcxIDU1IDMwQzU1IDE2LjE5MjkgNDMuODA3MSA1IDMwIDVDMTYuMTkyOSA1IDUgMTYuMTkyOSA1IDMwQzUgNDMuODA3MSAxNi4xOTI5IDU1IDMwIDU1WiIvPgo8cGF0aCBkPSJNNDAuOTQyNSAyNi42NTg1QzQxLjQwMDMgMjMuNjExMyAzOS4wNzA1IDIxLjk3MzIgMzUuODg0OCAyMC44ODA0TDM2LjkxODIgMTYuNzUyNkwzNC4zOTUxIDE2LjEyNjRMMzMuMzg5IDIwLjE0NTVDMzIuNzI1OCAxOS45ODA5IDMyLjA0NDUgMTkuODI1NiAzMS4zNjc1IDE5LjY3MTdMMzIuMzgwOCAxNS42MjYyTDI5Ljg1OTEgMTVMMjguODI1IDE5LjEyNjRDMjguMjc2IDE5LjAwMTkgMjcuNzM3IDE4Ljg3ODggMjcuMjEzOSAxOC43NDkzTDI3LjIxNjggMTguNzM2NEwyMy43MzcyIDE3Ljg3MTJMMjMuMDY2IDIwLjU1NDhDMjMuMDY2IDIwLjU1NDggMjQuOTM4IDIwLjk4MjEgMjQuODk4NSAyMS4wMDg1QzI1LjkyMDQgMjEuMjYyNiAyNi4xMDUgMjEuOTM2IDI2LjA3NDEgMjIuNDY5OUwyNC44OTcgMjcuMTcyNEMyNC45Njc1IDI3LjE5MDMgMjUuMDU4NyAyNy4yMTYgMjUuMTU5MyAyNy4yNTYxQzI1LjA3NTMgMjcuMjM1NCAyNC45ODU0IDI3LjIxMjQgMjQuODkyNyAyNy4xOTAzTDIzLjI0MjggMzMuNzc3OEMyMy4xMTc3IDM0LjA4NjkgMjIuODAwOCAzNC41NTA2IDIyLjA4NjUgMzQuMzc0NkMyMi4xMTE3IDM0LjQxMTEgMjAuMjUyNiAzMy45MTg3IDIwLjI1MjYgMzMuOTE4N0wxOSAzNi43OTQ5TDIyLjI4MzQgMzcuNjFDMjIuODk0MiAzNy43NjI0IDIzLjQ5MjggMzcuOTIyIDI0LjA4MjEgMzguMDcyM0wyMy4wMzggNDIuMjQ3NEwyNS41NTgyIDQyLjg3MzZMMjYuNTkyMyAzOC43NDI5QzI3LjI4MDcgMzguOTI5IDI3Ljk0OSAzOS4xMDA3IDI4LjYwMyAzOS4yNjI0TDI3LjU3MjUgNDMuMzczOEwzMC4wOTU2IDQ0TDMxLjEzOTcgMzkuODMyOEMzNS40NDIyIDQwLjY0MzYgMzguNjc3NCA0MC4zMTY2IDQwLjAzOTIgMzYuNDQxNEM0MS4xMzY1IDMzLjMyMTIgMzkuOTg0NiAzMS41MjEzIDM3LjcyMDkgMzAuMzQ3N0MzOS4zNjk0IDI5Ljk2OTEgNDAuNjExMiAyOC44ODkyIDQwLjk0MjUgMjYuNjU4NVYyNi42NTg1Wk0zNS4xNzc3IDM0LjcwODhDMzQuMzk4IDM3LjgyOSAyOS4xMjI2IDM2LjE0MjIgMjcuNDEyMiAzNS43MTkzTDI4Ljc5NzcgMzAuMTg4MUMzMC41MDgxIDMwLjYxMzIgMzUuOTkyNiAzMS40NTQ4IDM1LjE3NzcgMzQuNzA4OFpNMzUuOTU4MSAyNi42MTM0QzM1LjI0NjcgMjkuNDUxNyAzMC44NTU5IDI4LjAwOTcgMjkuNDMxNiAyNy42NTYxTDMwLjY4NzcgMjIuNjM5NUMzMi4xMTIgMjIuOTkzIDM2LjY5OSAyMy42NTI4IDM1Ljk1ODEgMjYuNjEzNFoiLz4KPC9zdmc+Cg==", + // "background": "transparent", + // "fiatSymbol": "BTC", + // "decimals": 8, + // "tradeDecimals": 20, + // "displayDecimals": 4, + // "crypto": true, + // "depositEnabled": true, + // "withdrawalEnabled": true, + // "transferEnabled": true, + // "buyEnabled": false, + // "purchaseEnabled": false, + // "redeemEnabled": false, + // "active": true, + // "withdrawalFee": "50000000000000000", + // "purchaseCommissions": [] + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + result.push (this.parseCurrency (currency)); + } + return this.indexBy (result, 'code'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const period = this.safeString (this.options['fetchTickers'], 'period', '1d'); + const request = { + 'period': this.timeframes[period], // I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1 + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ + // { + // "ask": 0.017, + // "bid": 0.016, + // "high": 0.019, + // "last": 0.017, + // "low": 0.015, + // "market": "TIME/ETH", + // "open": 0.016, + // "period": "H1", + // "timestamp": "2018-12-14T20:50:36.134Z", + // "volume": 4.57, + // "volumeQuote": 0.07312 + // } + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const period = this.safeString (this.options['fetchTickers'], 'period', '1d'); + const request = { + 'market': market['id'], + 'period': this.timeframes[period], // I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1 + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ + // { + // "ask": 0.017, + // "bid": 0.016, + // "high": 0.019, + // "last": 0.017, + // "low": 0.015, + // "market": "TIME/ETH", + // "open": 0.016, + // "period": "H1", + // "timestamp": "2018-12-14T20:50:36.134Z", + // "volume": 4.57, + // "volumeQuote": 0.07312 + // } + // ] + // + const ticker = this.safeValue (response, 0); + return this.parseTicker (ticker, market); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderbookV2 (this.extend (request, params)); + // + // { + // "timestamp":"2019-12-05T00:21:09.538", + // "bid":[ + // { + // "index":"2", + // "price":"0.02024007", + // "baseTokenAmount":"0.0096894", + // "baseTokenCumulativeAmount":"0.0096894", + // "quoteTokenAmount":"0.000196114134258", + // "quoteTokenCumulativeAmount":"0.000196114134258" + // }, + // "ask":[ + // { + // "index":"-3", + // "price":"0.02024012", + // "baseTokenAmount":"0.005", + // "baseTokenCumulativeAmount":"0.005", + // "quoteTokenAmount":"0.0001012006", + // "quoteTokenCumulativeAmount":"0.0001012006" + // }, + // ] + // } + // + const timestamp = this.parse8601 (this.safeString (response, 'timestamp')); + return this.parseOrderBook (response, timestamp, 'bid', 'ask', 'price', 'baseTokenAmount'); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const options = this.safeValue (this.options, 'fetchTrades', {}); + const defaultSort = this.safeValue (options, 'sort', 'timestamp,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'address': 'string', // trade’s member account (?) + // 'cursor': 1234, // int64 (?) + // 'from': this.iso8601 (since), + 'market': market['id'], + // 'page': 0, // results page you want to retrieve 0 .. N + // 'size': limit, // number of records per page, 100 by default + 'sort': sort, // array[string], sorting criteria in the format "property,asc" or "property,desc", default is ascending + // 'till': this.iso8601 (this.milliseconds ()), + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['size'] = limit; // default is 100 + } + const response = await this.publicGetTrades (this.extend (request, query)); + // + // [ + // { + // "id":1, + // "timestamp":"2019-06-25T17:01:50.309", + // "direction":"BUY", + // "price":"0.027", + // "quantity":"0.001" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'period': this.timeframes[timeframe], + }; + // if since and limit are not specified + const duration = this.parseTimeframe (timeframe); + if (since !== undefined) { + request['from'] = this.iso8601 (since); + if (limit !== undefined) { + request['till'] = this.iso8601 (this.sum (since, this.sum (limit, 1) * duration * 1000)); + } + } else if (limit !== undefined) { + const now = this.milliseconds (); + request['till'] = this.iso8601 (now); + request['from'] = this.iso8601 (now - limit * duration * 1000 - 1); + } else { + request['till'] = this.iso8601 (this.milliseconds ()); + } + const response = await this.publicGetCandles (this.extend (request, params)); + // + // [ + // { + // "timestamp":"2019-12-04T23:00:00", + // "open":"0.02024009", + // "high":"0.02024009", + // "low":"0.02024009", + // "close":"0.02024009", + // "volume":"0.00008096036", + // "volumeQuote":"0.004", + // }, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.tradingGetBalances (params); + // + // [ + // {"currency":"BTC","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"AUDT","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"ETH","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"TIME","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"USDT","totalBalance":"0","lockedBalance":"0"} + // ] + // + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'totalBalance'); + account['used'] = this.safeFloat (balance, 'lockedBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'quantity': this.amountToPrecision (symbol, amount), + 'side': side.toUpperCase (), + // 'clientOrderId': '123', + // 'expireIn': 1575523308, // in seconds + // 'expireTime': 1575523308, // unix timestamp + }; + let query = params; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + const defaultExpireIn = this.safeInteger (this.options, 'expireIn'); + const expireTime = this.safeValue (params, 'expireTime'); + const expireIn = this.safeValue (params, 'expireIn', defaultExpireIn); + if (expireTime !== undefined) { + request['expireTime'] = expireTime; + } else if (expireIn !== undefined) { + request['expireIn'] = expireIn; + } else { + throw new InvalidOrder (this.id + ' createOrder method requires a expireTime or expireIn param for a ' + type + ' order, you can also set the expireIn exchange-wide option'); + } + query = this.omit (params, [ 'expireTime', 'expireIn' ]); + } else { + request['price'] = 0; + } + const response = await this.tradingPostOrders (this.extend (request, query)); + // + // { + // "orders": [ + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + const order = this.safeValue (orders, 0, {}); + return this.parseOrder (order, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + }; + if (amount !== undefined) { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.tradingPutOrders (this.extend (request, params)); + // + // { + // "changedOrders": [ + // { + // "newOrder": { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // }, + // "oldId": "string", + // }, + // ], + // "unchangedOrders": [ "string" ], + // } + // + if ('unchangedOrders' in response) { + const orderIds = this.safeValue (response, 'unchangedOrders', []); + const orderId = this.safeString (orderIds, 0); + return { + 'id': orderId, + 'info': response, + }; + } + const orders = this.safeValue (response, 'changedOrders', []); + const firstOrder = this.safeValue (orders, 0, {}); + const order = this.safeValue (firstOrder, 'newOrder', {}); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + return await this.cancelOrders ([ id ], symbol, params); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': ids, + }; + const response = await this.tradingDeleteOrders (this.extend (request, params)); + // + // { + // "changedOrders": [ + // { + // "newOrder": { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // }, + // "oldId": "string", + // }, + // ], + // "unchangedOrders": [ "string" ], + // } + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderHash': id, + }; + const response = await this.historyGetOrdersDetails (request); + // + // { + // "order": { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // }, + // "trades": [ + // { + // "fee": "0.3", + // "id": 100, + // "makerOrTaker": "MAKER", + // "makerOrderId": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "takerOrderId": "string", + // "timestamp": "2019-12-05T07:48:26.310Z" + // } + // ] + // } + // + const order = this.safeValue (response, 'order', {}); + const trades = this.safeValue (response, 'trades', []); + return this.parseOrder (this.extend (order, { 'trades': trades })); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + const defaultSort = this.safeValue (options, 'sort', 'createdAt,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'clientOrderId': '123', // order’s client id list for filter + // page: 0, // results page you want to retrieve (0 .. N) + 'sort': sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.tradingGetOrders (this.extend (request, query)); + // + // { + // "orders": [ + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + const defaultSort = this.safeValue (options, 'sort', 'createdAt,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'clientOrderId': '123', // order’s client id list for filter + // page: 0, // results page you want to retrieve (0 .. N) + 'sort': sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported + 'side': 'BUY', // or 'SELL' + // 'till': this.iso8601 (this.milliseconds ()), + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.historyGetOrders (this.extend (request, query)); + // + // { + // "orders": [ + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchMyTrades', {}); + const defaultSort = this.safeValue (options, 'sort', 'timestamp,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'cursorId': 123, // int64 (?) + // 'from': this.iso8601 (since), + // 'makerOrderId': '1234', // maker order hash + // 'owner': '...', // owner address (?) + // 'page': 0, // results page you want to retrieve (0 .. N) + // 'side': 'BUY', // or 'SELL' + // 'size': limit, + 'sort': sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported + // 'symbol': market['id'], + // 'takerOrderId': '1234', + // 'till': this.iso8601 (this.milliseconds ()), + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.historyGetTrades (this.extend (request, query)); + // + // { + // "trades": [ + // { + // "fee": "0.3", + // "id": 100, + // "makerOrTaker": "MAKER", + // "makerOrderId": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "takerOrderId": "string", + // "timestamp": "2019-12-08T04:54:11.171Z" + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'markets': market['id'], + }; + const response = await this.tradingGetFees (this.extend (request, params)); + // + // [ + // { + // "fee": 0.0075, + // "market": "ETHBTC" + // } + // ] + // + const result = this.safeValue (response, 0, {}); + return { + 'info': response, + 'maker': this.safeFloat (result, 'fee'), + 'taker': undefined, + }; + } + + parseMarket (market) { + // + // { + // "symbol": "ETHBTC", + // "name": "ETH/BTC", + // "baseCurrency": "ETH", + // "baseTokenAddress": "0x45932db54b38af1f5a57136302eeba66a5975c15", + // "quoteCurrency": "BTC", + // "quoteTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "feeCurrency": "BTC", + // "feeTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "quantityIncrement": "0.0000001", + // "takerFee": "0.005", + // "makerFee": "0.0025", + // "tickSize": "0.00000001", + // "baseMinSize": "0.0001", + // "quoteMinSize": "0.00001", + // "locked": false + // } + // + const locked = this.safeValue (market, 'locked'); + const active = !locked; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.precisionFromString (this.safeString (market, 'quantityIncrement')), + 'price': this.precisionFromString (this.safeString (market, 'tickSize')), + }; + const amountIncrement = this.safeFloat (market, 'quantityIncrement'); + const minBase = this.safeFloat (market, 'baseMinSize'); + const minAmount = Math.max (amountIncrement, minBase); + const priceIncrement = this.safeFloat (market, 'tickSize'); + const minCost = this.safeFloat (market, 'quoteMinSize'); + const limits = { + 'amount': { 'min': minAmount, 'max': undefined }, + 'price': { 'min': priceIncrement, 'max': undefined }, + 'cost': { 'min': Math.max (minCost, minAmount * priceIncrement), 'max': undefined }, + }; + const taker = this.safeFloat (market, 'takerFee'); + const maker = this.safeFloat (market, 'makerFee'); + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': 'spot', + 'active': active, + 'precision': precision, + 'limits': limits, + 'taker': taker, + 'maker': maker, + 'info': market, + }; + } + + parseCurrency (currency) { + // + // { + // "symbol": "BTC", + // "name": "Bitcoin", + // "address": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR...mc+Cg==", + // "background": "transparent", + // "fiatSymbol": "BTC", + // "decimals": 8, + // "tradeDecimals": 20, + // "displayDecimals": 4, + // "crypto": true, + // "depositEnabled": true, + // "withdrawalEnabled": true, + // "transferEnabled": true, + // "buyEnabled": false, + // "purchaseEnabled": false, + // "redeemEnabled": false, + // "active": true, + // "withdrawalFee": "50000000000000000", + // "purchaseCommissions": [] + // } + // + // https://github.com/ccxt/ccxt/issues/6878 + // + // { + // "symbol":"XRP", + // "name":"Ripple", + // "address":"0x0dc8882914f3ddeebf4cec6dc20edb99df3def6c", + // "decimals":6, + // "tradeDecimals":16, + // "depositEnabled":true, + // "withdrawalEnabled":true, + // "transferEnabled":true, + // "active":true + // } + // + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + const precision = this.safeInteger (currency, 'decimals'); + const active = this.safeValue (currency, 'active'); + // const fee = this.safeFloat (currency, 'withdrawalFee'); + const feeString = this.safeString (currency, 'withdrawalFee'); + const tradeDecimals = this.safeInteger (currency, 'tradeDecimals'); + let fee = undefined; + if ((feeString !== undefined) && (tradeDecimals !== undefined)) { + const feeStringLen = feeString.length; + const dotIndex = feeStringLen - tradeDecimals; + if (dotIndex > 0) { + const whole = feeString.slice (0, dotIndex); + const fraction = feeString.slice (-dotIndex); + fee = parseFloat (whole + '.' + fraction); + } else { + let fraction = '.'; + for (let i = 0; i < -dotIndex; i++) { + fraction += '0'; + } + fee = parseFloat (fraction + feeString); + } + } + return { + 'id': code, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'withdraw': { 'min': fee, 'max': undefined }, + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + }; + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "ask": 0.017, + // "bid": 0.016, + // "high": 0.019, + // "last": 0.017, + // "low": 0.015, + // "market": "TIME/ETH", + // "open": 0.016, + // "period": "H1", + // "timestamp": "2018-12-14T20:50:36.134Z", + // "volume": 4.57, + // "volumeQuote": 0.07312 + // } + // + const marketId = this.safeString (ticker, 'market'); + const symbol = this.safeSymbol (marketId, market, '/'); + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const last = this.safeFloat (ticker, 'last'); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + } + let percentage = undefined; + if (change !== undefined && open) { + percentage = (change / open) * 100; + } + return { + 'symbol': symbol, + 'info': ticker, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'volumeQuote'), + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":1, + // "timestamp":"2019-06-25T17:01:50.309", + // "direction":"BUY", + // "price":"0.027", + // "quantity":"0.001" + // } + // + // fetchMyTrades, fetchOrder (private) + // + // { + // "fee": "0.3", + // "id": 100, + // "makerOrTaker": "MAKER", + // "makerOrderId": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "takerOrderId": "string", + // "timestamp": "2019-12-08T04:54:11.171Z" + // } + // + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const id = this.safeString (trade, 'id'); + const side = this.safeStringLower2 (trade, 'direction', 'side'); + const takerOrMaker = this.safeStringLower (trade, 'makerOrTaker'); + let orderId = undefined; + if (takerOrMaker !== undefined) { + orderId = this.safeString (trade, takerOrMaker + 'OrderId'); + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrency = (market === undefined) ? undefined : market['quote']; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = this.costToPrecision (symbol, amount * price); + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "timestamp":"2019-12-04T23:00:00", + // "open":"0.02024009", + // "high":"0.02024009", + // "low":"0.02024009", + // "close":"0.02024009", + // "volume":"0.00008096036", + // "volumeQuote":"0.004", + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + parseOrder (order, market = undefined) { + // + // fetchOrder, createOrder, cancelOrder, cancelOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // "trades": [], // injected from the outside + // } + // + const id = this.safeString (order, 'id'); + const type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'createdAt')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'quantity'); + const filled = this.safeFloat (order, 'filledQuantity'); + const canceledQuantity = this.safeFloat (order, 'cancelledQuantity'); + let remaining = undefined; + let status = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = Math.max (amount - filled, 0.0); + if (filled >= amount) { + status = 'closed'; + } else if ((canceledQuantity !== undefined) && (canceledQuantity > 0)) { + status = 'canceled'; + } else { + status = 'open'; + } + } + const cost = parseFloat (this.costToPrecision (symbol, price * filled)); + const fee = undefined; + let lastTradeTimestamp = undefined; + let trades = undefined; + const rawTrades = this.safeValue (order, 'trades'); + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'order': id, + }); + } + if (trades !== undefined) { + const numTrades = trades.length; + if (numTrades > 0) { + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + } + } + const clientOrderId = this.safeString (order, 'clientOrderId'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + api + '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencodeWithArrayRepeat (params); + } + if (api !== 'public') { + this.checkRequiredCredentials (); + const auth = this.stringToBase64 (this.apiKey + ':' + this.secret); + const secret = 'Basic ' + this.decode (auth); + headers = { 'authorization': secret }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (statusCode >= 400) { + // + // {"error":{"timestamp":"05.12.2019T05:25:43.584+0000","status":"BAD_REQUEST","message":"Insufficient ETH balance. Required: 1, actual: 0.","code":4001}} + // {"error":{"timestamp":"05.12.2019T04:03:25.419+0000","status":"FORBIDDEN","message":"Access denied","code":4300}} + // + const feedback = this.id + ' ' + responseBody; + let error = this.safeValue (response, 'error'); + if (error === undefined) { + error = response; + } + const code = this.safeString2 (error, 'code', 'status'); + const message = this.safeString2 (error, 'message', 'debugMessage'); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],169:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadRequest, AuthenticationError, InvalidOrder, InsufficientFunds, OrderNotFound, PermissionDenied, AddressPending } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class upbit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'upbit', + 'name': 'Upbit', + 'countries': [ 'KR' ], + 'version': 'v1', + 'rateLimit': 1000, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'minutes', + '3m': 'minutes', + '5m': 'minutes', + '15m': 'minutes', + '30m': 'minutes', + '1h': 'minutes', + '4h': 'minutes', + '1d': 'days', + '1w': 'weeks', + '1M': 'months', + }, + 'hostname': 'api.upbit.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg', + 'api': { + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + }, + 'www': 'https://upbit.com', + 'doc': 'https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C', + 'fees': 'https://upbit.com/service_center/guide', + }, + 'api': { + 'public': { + 'get': [ + 'market/all', + 'candles/{timeframe}', + 'candles/{timeframe}/{unit}', + 'candles/minutes/{unit}', + 'candles/minutes/1', + 'candles/minutes/3', + 'candles/minutes/5', + 'candles/minutes/15', + 'candles/minutes/30', + 'candles/minutes/60', + 'candles/minutes/240', + 'candles/days', + 'candles/weeks', + 'candles/months', + 'trades/ticks', + 'ticker', + 'orderbook', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'orders/chance', + 'order', + 'orders', + 'withdraws', + 'withdraw', + 'withdraws/chance', + 'deposits', + 'deposit', + 'deposits/coin_addresses', + 'deposits/coin_address', + ], + 'post': [ + 'orders', + 'withdraws/coin', + 'withdraws/krw', + 'deposits/generate_coin_address', + ], + 'delete': [ + 'order', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0025, + 'taker': 0.0025, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'exceptions': { + 'exact': { + 'This key has expired.': AuthenticationError, + 'Missing request parameter error. Check the required parameters!': BadRequest, + 'side is missing, side does not have a valid value': InvalidOrder, + }, + 'broad': { + 'thirdparty_agreement_required': PermissionDenied, + 'out_of_scope': PermissionDenied, + 'order_not_found': OrderNotFound, + 'insufficient_funds': InsufficientFunds, + 'invalid_access_key': AuthenticationError, + 'jwt_verification': AuthenticationError, + 'create_ask_error': ExchangeError, + 'create_bid_error': ExchangeError, + 'volume_too_large': InvalidOrder, + 'invalid_funds': InvalidOrder, + }, + }, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'fetchTickersMaxLength': 4096, // 2048, + 'fetchOrderBooksMaxLength': 4096, // 2048, + 'tradingFeesByQuoteCurrency': { + 'KRW': 0.0005, + }, + }, + 'commonCurrencies': { + 'TON': 'Tokamak Network', + }, + }); + } + + async fetchCurrency (code, params = {}) { + // this method is for retrieving funding fees and limits per currency + // it requires private access and API keys properly set up + await this.loadMarkets (); + const currency = this.currency (code); + return await this.fetchCurrencyById (currency['id'], params); + } + + async fetchCurrencyById (id, params = {}) { + // this method is for retrieving funding fees and limits per currency + // it requires private access and API keys properly set up + const request = { + 'currency': id, + }; + const response = await this.privateGetWithdrawsChance (this.extend (request, params)); + // + // { + // "member_level": { + // "security_level": 3, + // "fee_level": 0, + // "email_verified": true, + // "identity_auth_verified": true, + // "bank_account_verified": true, + // "kakao_pay_auth_verified": false, + // "locked": false, + // "wallet_locked": false + // }, + // "currency": { + // "code": "BTC", + // "withdraw_fee": "0.0005", + // "is_coin": true, + // "wallet_state": "working", + // "wallet_support": [ "deposit", "withdraw" ] + // }, + // "account": { + // "currency": "BTC", + // "balance": "10.0", + // "locked": "0.0", + // "avg_krw_buy_price": "8042000", + // "modified": false + // }, + // "withdraw_limit": { + // "currency": "BTC", + // "minimum": null, + // "onetime": null, + // "daily": "10.0", + // "remaining_daily": "10.0", + // "remaining_daily_krw": "0.0", + // "fixed": null, + // "can_withdraw": true + // } + // } + // + const memberInfo = this.safeValue (response, 'member_level', {}); + const currencyInfo = this.safeValue (response, 'currency', {}); + const withdrawLimits = this.safeValue (response, 'withdraw_limit', {}); + const canWithdraw = this.safeValue (withdrawLimits, 'can_withdraw'); + const walletState = this.safeString (currencyInfo, 'wallet_state'); + const walletLocked = this.safeValue (memberInfo, 'wallet_locked'); + const locked = this.safeValue (memberInfo, 'locked'); + let active = true; + if ((canWithdraw !== undefined) && canWithdraw) { + active = false; + } else if (walletState !== 'working') { + active = false; + } else if ((walletLocked !== undefined) && walletLocked) { + active = false; + } else if ((locked !== undefined) && locked) { + active = false; + } + const maxOnetimeWithdrawal = this.safeFloat (withdrawLimits, 'onetime'); + const maxDailyWithdrawal = this.safeFloat (withdrawLimits, 'daily', maxOnetimeWithdrawal); + const remainingDailyWithdrawal = this.safeFloat (withdrawLimits, 'remaining_daily', maxDailyWithdrawal); + let maxWithdrawLimit = undefined; + if (remainingDailyWithdrawal > 0) { + maxWithdrawLimit = remainingDailyWithdrawal; + } else { + maxWithdrawLimit = maxDailyWithdrawal; + } + const precision = undefined; + const currencyId = this.safeString (currencyInfo, 'code'); + const code = this.safeCurrencyCode (currencyId); + return { + 'info': response, + 'id': currencyId, + 'code': code, + 'name': code, + 'active': active, + 'fee': this.safeFloat (currencyInfo, 'withdraw_fee'), + 'precision': precision, + 'limits': { + 'withdraw': { + 'min': this.safeFloat (withdrawLimits, 'minimum'), + 'max': maxWithdrawLimit, + }, + }, + }; + } + + async fetchMarket (symbol, params = {}) { + // this method is for retrieving trading fees and limits per market + // it requires private access and API keys properly set up + await this.loadMarkets (); + const market = this.market (symbol); + return await this.fetchMarketById (market['id'], params); + } + + async fetchMarketById (id, params = {}) { + // this method is for retrieving trading fees and limits per market + // it requires private access and API keys properly set up + const request = { + 'market': id, + }; + const response = await this.privateGetOrdersChance (this.extend (request, params)); + // + // { bid_fee: "0.0005", + // ask_fee: "0.0005", + // market: { id: "KRW-BTC", + // name: "BTC/KRW", + // order_types: ["limit"], + // order_sides: ["ask", "bid"], + // bid: { currency: "KRW", + // price_unit: null, + // min_total: 1000 }, + // ask: { currency: "BTC", + // price_unit: null, + // min_total: 1000 }, + // max_total: "1000000000.0", + // state: "active" }, + // bid_account: { currency: "KRW", + // balance: "0.0", + // locked: "0.0", + // avg_krw_buy_price: "0", + // modified: false }, + // ask_account: { currency: "BTC", + // balance: "0.00780836", + // locked: "0.0", + // avg_krw_buy_price: "6465564.67", + // modified: false } } + // + const marketInfo = this.safeValue (response, 'market'); + const bid = this.safeValue (marketInfo, 'bid'); + const ask = this.safeValue (marketInfo, 'ask'); + const marketId = this.safeString (marketInfo, 'id'); + const baseId = this.safeString (ask, 'currency'); + const quoteId = this.safeString (bid, 'currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const state = this.safeString (marketInfo, 'state'); + const active = (state === 'active'); + const bidFee = this.safeFloat (response, 'bid_fee'); + const askFee = this.safeFloat (response, 'ask_fee'); + const fee = Math.max (bidFee, askFee); + return { + 'info': response, + 'id': marketId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'maker': fee, + 'taker': fee, + 'limits': { + 'amount': { + 'min': this.safeFloat (ask, 'min_total'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (bid, 'min_total'), + 'max': this.safeFloat (marketInfo, 'max_total'), + }, + }, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarketAll (params); + // + // [ { market: "KRW-BTC", + // korean_name: "비트코인", + // english_name: "Bitcoin" }, + // { market: "KRW-DASH", + // korean_name: "대시", + // english_name: "Dash" }, + // { market: "KRW-ETH", + // korean_name: "이더리움", + // english_name: "Ethereum" }, + // { market: "BTC-ETH", + // korean_name: "이더리움", + // english_name: "Ethereum" }, + // ..., + // { market: "BTC-BSV", + // korean_name: "비트코인에스브이", + // english_name: "Bitcoin SV" } ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'market'); + const [ quoteId, baseId ] = id.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const active = true; + const makerFee = this.safeFloat (this.options['tradingFeesByQuoteCurrency'], quote, this.fees['trading']['maker']); + const takerFee = this.safeFloat (this.options['tradingFeesByQuoteCurrency'], quote, this.fees['trading']['taker']); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'maker': makerFee, + 'taker': takerFee, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + // + // [ { currency: "BTC", + // balance: "0.005", + // locked: "0.0", + // avg_krw_buy_price: "7446000", + // modified: false }, + // { currency: "ETH", + // balance: "0.1", + // locked: "0.0", + // avg_krw_buy_price: "250000", + // modified: false } ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join (','); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchOrderBooksMaxLength']) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols (' + ids.length.toString () + ' characters) exceeding max URL length (' + this.options['fetchOrderBooksMaxLength'].toString () + ' characters), you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join (','); + } + const request = { + 'markets': ids, + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + // + // [ { market: "BTC-ETH", + // timestamp: 1542899030043, + // total_ask_size: 109.57065201, + // total_bid_size: 125.74430631, + // orderbook_units: [ { ask_price: 0.02926679, + // bid_price: 0.02919904, + // ask_size: 4.20293961, + // bid_size: 11.65043576 }, + // ..., + // { ask_price: 0.02938209, + // bid_price: 0.0291231, + // ask_size: 0.05135782, + // bid_size: 13.5595 } ] }, + // { market: "KRW-BTC", + // timestamp: 1542899034662, + // total_ask_size: 12.89790974, + // total_bid_size: 4.88395783, + // orderbook_units: [ { ask_price: 5164000, + // bid_price: 5162000, + // ask_size: 2.57606495, + // bid_size: 0.214 }, + // ..., + // { ask_price: 5176000, + // bid_price: 5152000, + // ask_size: 2.752, + // bid_size: 0.4650305 } ] } ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const orderbook = response[i]; + const marketId = this.safeString (orderbook, 'market'); + const symbol = this.safeSymbol (marketId, undefined, '-'); + const timestamp = this.safeInteger (orderbook, 'timestamp'); + result[symbol] = { + 'bids': this.sortBy (this.parseBidsAsks (orderbook['orderbook_units'], 'bid_price', 'bid_size'), 0, true), + 'asks': this.sortBy (this.parseBidsAsks (orderbook['orderbook_units'], 'ask_price', 'ask_size'), 0), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + const orderbooks = await this.fetchOrderBooks ([ symbol ], limit, params); + return this.safeValue (orderbooks, symbol); + } + + parseTicker (ticker, market = undefined) { + // + // { market: "BTC-ETH", + // trade_date: "20181122", + // trade_time: "104543", + // trade_date_kst: "20181122", + // trade_time_kst: "194543", + // trade_timestamp: 1542883543097, + // opening_price: 0.02976455, + // high_price: 0.02992577, + // low_price: 0.02934283, + // trade_price: 0.02947773, + // prev_closing_price: 0.02966, + // change: "FALL", + // change_price: 0.00018227, + // change_rate: 0.0061453136, + // signed_change_price: -0.00018227, + // signed_change_rate: -0.0061453136, + // trade_volume: 1.00000005, + // acc_trade_price: 100.95825586, + // acc_trade_price_24h: 289.58650166, + // acc_trade_volume: 3409.85311036, + // acc_trade_volume_24h: 9754.40510513, + // highest_52_week_price: 0.12345678, + // highest_52_week_date: "2018-02-01", + // lowest_52_week_price: 0.023936, + // lowest_52_week_date: "2017-12-08", + // timestamp: 1542883543813 } + // + const timestamp = this.safeInteger (ticker, 'trade_timestamp'); + const marketId = this.safeString2 (ticker, 'market', 'code'); + const symbol = this.safeSymbol (marketId, market, '-'); + const previous = this.safeFloat (ticker, 'prev_closing_price'); + const last = this.safeFloat (ticker, 'trade_price'); + const change = this.safeFloat (ticker, 'signed_change_price'); + const percentage = this.safeFloat (ticker, 'signed_change_rate'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_price'), + 'low': this.safeFloat (ticker, 'low_price'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'opening_price'), + 'close': last, + 'last': last, + 'previousClose': previous, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'acc_trade_volume_24h'), + 'quoteVolume': this.safeFloat (ticker, 'acc_trade_price_24h'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join (','); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join (','); + } + const request = { + 'markets': ids, + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // [ { market: "BTC-ETH", + // trade_date: "20181122", + // trade_time: "104543", + // trade_date_kst: "20181122", + // trade_time_kst: "194543", + // trade_timestamp: 1542883543097, + // opening_price: 0.02976455, + // high_price: 0.02992577, + // low_price: 0.02934283, + // trade_price: 0.02947773, + // prev_closing_price: 0.02966, + // change: "FALL", + // change_price: 0.00018227, + // change_rate: 0.0061453136, + // signed_change_price: -0.00018227, + // signed_change_rate: -0.0061453136, + // trade_volume: 1.00000005, + // acc_trade_price: 100.95825586, + // acc_trade_price_24h: 289.58650166, + // acc_trade_volume: 3409.85311036, + // acc_trade_volume_24h: 9754.40510513, + // highest_52_week_price: 0.12345678, + // highest_52_week_date: "2018-02-01", + // lowest_52_week_price: 0.023936, + // lowest_52_week_date: "2017-12-08", + // timestamp: 1542883543813 } ] + // + const result = {}; + for (let t = 0; t < response.length; t++) { + const ticker = this.parseTicker (response[t]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return this.safeValue (tickers, symbol); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades + // + // { market: "BTC-ETH", + // trade_date_utc: "2018-11-22", + // trade_time_utc: "13:55:24", + // timestamp: 1542894924397, + // trade_price: 0.02914289, + // trade_volume: 0.20074397, + // prev_closing_price: 0.02966, + // change_price: -0.00051711, + // ask_bid: "ASK", + // sequential_id: 15428949259430000 } + // + // fetchOrder trades + // + // { + // "market": "KRW-BTC", + // "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3", + // "price": "101000.0", + // "volume": "0.77368323", + // "funds": "78142.00623", + // "ask_fee": "117.213009345", + // "bid_fee": "117.213009345", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid", + // } + // + const id = this.safeString2 (trade, 'sequential_id', 'uuid'); + const orderId = undefined; + let timestamp = this.safeInteger (trade, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + let side = undefined; + const askOrBid = this.safeStringLower2 (trade, 'ask_bid', 'side'); + if (askOrBid === 'ask') { + side = 'sell'; + } else if (askOrBid === 'bid') { + side = 'buy'; + } + let cost = this.safeFloat (trade, 'funds'); + const price = this.safeFloat2 (trade, 'trade_price', 'price'); + const amount = this.safeFloat2 (trade, 'trade_volume', 'volume'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + } + const marketId = this.safeString2 (trade, 'market', 'code'); + market = this.safeMarket (marketId, market); + let fee = undefined; + let feeCurrency = undefined; + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + feeCurrency = quote; + } + const feeCost = this.safeString (trade, askOrBid + '_fee'); + if (feeCost !== undefined) { + fee = { + 'currency': feeCurrency, + 'cost': feeCost, + }; + } + return { + 'id': id, + 'info': trade, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 200; + } + const request = { + 'market': market['id'], + 'count': limit, + }; + const response = await this.publicGetTradesTicks (this.extend (request, params)); + // + // [ { market: "BTC-ETH", + // trade_date_utc: "2018-11-22", + // trade_time_utc: "13:55:24", + // timestamp: 1542894924397, + // trade_price: 0.02914289, + // trade_volume: 0.20074397, + // prev_closing_price: 0.02966, + // change_price: -0.00051711, + // ask_bid: "ASK", + // sequential_id: 15428949259430000 }, + // { market: "BTC-ETH", + // trade_date_utc: "2018-11-22", + // trade_time_utc: "13:03:10", + // timestamp: 1542891790123, + // trade_price: 0.02917, + // trade_volume: 7.392, + // prev_closing_price: 0.02966, + // change_price: -0.00049, + // ask_bid: "ASK", + // sequential_id: 15428917910540000 } ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // market: "BTC-ETH", + // candle_date_time_utc: "2018-11-22T13:47:00", + // candle_date_time_kst: "2018-11-22T22:47:00", + // opening_price: 0.02915963, + // high_price: 0.02915963, + // low_price: 0.02915448, + // trade_price: 0.02915448, + // timestamp: 1542894473674, + // candle_acc_trade_price: 0.0981629437535248, + // candle_acc_trade_volume: 3.36693173, + // unit: 1 + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'candle_date_time_utc')), + this.safeFloat (ohlcv, 'opening_price'), + this.safeFloat (ohlcv, 'high_price'), + this.safeFloat (ohlcv, 'low_price'), + this.safeFloat (ohlcv, 'trade_price'), + this.safeFloat (ohlcv, 'candle_acc_trade_volume'), // base volume + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const timeframePeriod = this.parseTimeframe (timeframe); + const timeframeValue = this.timeframes[timeframe]; + if (limit === undefined) { + limit = 200; + } + const request = { + 'market': market['id'], + 'timeframe': timeframeValue, + 'count': limit, + }; + let method = 'publicGetCandlesTimeframe'; + if (timeframeValue === 'minutes') { + const numMinutes = Math.round (timeframePeriod / 60); + request['unit'] = numMinutes; + method += 'Unit'; + } + if (since !== undefined) { + // convert `since` to `to` value + request['to'] = this.iso8601 (this.sum (since, timeframePeriod * limit * 1000)); + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // { + // market: "BTC-ETH", + // candle_date_time_utc: "2018-11-22T13:47:00", + // candle_date_time_kst: "2018-11-22T22:47:00", + // opening_price: 0.02915963, + // high_price: 0.02915963, + // low_price: 0.02915448, + // trade_price: 0.02915448, + // timestamp: 1542894473674, + // candle_acc_trade_price: 0.0981629437535248, + // candle_acc_trade_volume: 3.36693173, + // unit: 1 + // }, + // { + // market: "BTC-ETH", + // candle_date_time_utc: "2018-11-22T10:06:00", + // candle_date_time_kst: "2018-11-22T19:06:00", + // opening_price: 0.0294, + // high_price: 0.02940882, + // low_price: 0.02934283, + // trade_price: 0.02937354, + // timestamp: 1542881219276, + // candle_acc_trade_price: 0.0762597110943884, + // candle_acc_trade_volume: 2.5949617, + // unit: 1 + // } + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + amount = amount * price; + } + } + } + } + let orderSide = undefined; + if (side === 'buy') { + orderSide = 'bid'; + } else if (side === 'sell') { + orderSide = 'ask'; + } else { + throw new InvalidOrder (this.id + ' createOrder allows buy or sell side only!'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': orderSide, + }; + if (type === 'limit') { + request['volume'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + request['ord_type'] = type; + } else if (type === 'market') { + if (side === 'buy') { + request['ord_type'] = 'price'; + request['price'] = this.priceToPrecision (symbol, amount); + } else if (side === 'sell') { + request['ord_type'] = type; + request['volume'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // 'uuid': 'cdd92199-2897-4e14-9448-f923320408ad', + // 'side': 'bid', + // 'ord_type': 'limit', + // 'price': '100.0', + // 'avg_price': '0.0', + // 'state': 'wait', + // 'market': 'KRW-BTC', + // 'created_at': '2018-04-10T15:42:23+09:00', + // 'volume': '0.01', + // 'remaining_volume': '0.01', + // 'reserved_fee': '0.0015', + // 'remaining_fee': '0.0015', + // 'paid_fee': '0.0', + // 'locked': '1.0015', + // 'executed_volume': '0.0', + // 'trades_count': 0 + // } + // + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'uuid': id, + }; + const response = await this.privateDeleteOrder (this.extend (request, params)); + // + // { + // "uuid": "cdd92199-2897-4e14-9448-f923320408ad", + // "side": "bid", + // "ord_type": "limit", + // "price": "100.0", + // "state": "wait", + // "market": "KRW-BTC", + // "created_at": "2018-04-10T15:42:23+09:00", + // "volume": "0.01", + // "remaining_volume": "0.01", + // "reserved_fee": "0.0015", + // "remaining_fee": "0.0015", + // "paid_fee": "0.0", + // "locked": "1.0015", + // "executed_volume": "0.0", + // "trades_count": 0 + // } + // + return this.parseOrder (response); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'order_by': 'asc', // 'desc' + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + const response = await this.privateGetDeposits (this.extend (request, params)); + // + // [ + // { + // "type": "deposit", + // "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830", + // "currency": "KRW", + // "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a", + // "state": "ACCEPTED", + // "created_at": "2017-12-08T15:38:02+09:00", + // "done_at": "2017-12-08T15:38:02+09:00", + // "amount": "100000.0", + // "fee": "0.0" + // }, + // ..., + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'state': 'submitting', // 'submitted', 'almost_accepted', 'rejected', 'accepted', 'processing', 'done', 'canceled' + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + const response = await this.privateGetWithdraws (this.extend (request, params)); + // + // [ + // { + // "type": "withdraw", + // "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79", + // "currency": "BTC", + // "txid": null, + // "state": "processing", + // "created_at": "2018-04-13T11:24:01+09:00", + // "done_at": null, + // "amount": "0.01", + // "fee": "0.0", + // "krw_amount": "80420.0" + // }, + // ..., + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'ACCEPTED': 'ok', // deposits + // withdrawals: + 'submitting': 'pending', // 처리 중 + 'submitted': 'pending', // 처리 완료 + 'almost_accepted': 'pending', // 출금대기중 + 'rejected': 'failed', // 거부 + 'accepted': 'pending', // 승인됨 + 'processing': 'pending', // 처리 중 + 'done': 'ok', // 완료 + 'canceled': 'canceled', // 취소됨 + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "type": "deposit", + // "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830", + // "currency": "KRW", + // "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a", + // "state": "ACCEPTED", + // "created_at": "2017-12-08T15:38:02+09:00", + // "done_at": "2017-12-08T15:38:02+09:00", + // "amount": "100000.0", + // "fee": "0.0" + // } + // + // fetchWithdrawals + // + // { + // "type": "withdraw", + // "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79", + // "currency": "BTC", + // "txid": "cd81e9b45df8da29f936836e58c907a106057e454a45767a7b06fcb19b966bba", + // "state": "processing", + // "created_at": "2018-04-13T11:24:01+09:00", + // "done_at": null, + // "amount": "0.01", + // "fee": "0.0", + // "krw_amount": "80420.0" + // } + // + const id = this.safeString (transaction, 'uuid'); + const amount = this.safeFloat (transaction, 'amount'); + const address = undefined; // not present in the data structure received from the exchange + const tag = undefined; // not present in the data structure received from the exchange + const txid = this.safeString (transaction, 'txid'); + const updated = this.parse8601 (this.safeString (transaction, 'done_at')); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at', updated)); + let type = this.safeString (transaction, 'type'); + if (type === 'withdraw') { + type = 'withdrawal'; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const feeCost = this.safeFloat (transaction, 'fee'); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': tag, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'wait': 'open', + 'done': 'closed', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee", + // "side": "bid", + // "ord_type": "limit", + // "price": "17417000.0", + // "state": "done", + // "market": "KRW-BTC", + // "created_at": "2018-04-05T14:09:14+09:00", + // "volume": "1.0", + // "remaining_volume": "0.0", + // "reserved_fee": "26125.5", + // "remaining_fee": "25974.0", + // "paid_fee": "151.5", + // "locked": "17341974.0", + // "executed_volume": "1.0", + // "trades_count": 2, + // "trades": [ + // { + // "market": "KRW-BTC", + // "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3", + // "price": "101000.0", + // "volume": "0.77368323", + // "funds": "78142.00623", + // "ask_fee": "117.213009345", + // "bid_fee": "117.213009345", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid", + // }, + // { + // "market": "KRW-BTC", + // "uuid": "f73da467-c42f-407d-92fa-e10d86450a20", + // "price": "101000.0", + // "volume": "0.22631677", + // "funds": "22857.99377", + // "ask_fee": "34.286990655", // missing in market orders + // "bid_fee": "34.286990655", // missing in market orders + // "created_at": "2018-04-05T14:09:15+09:00", // missing in market orders + // "side": "bid", + // }, + // ], + // } + // + const id = this.safeString (order, 'uuid'); + let side = this.safeString (order, 'side'); + if (side === 'bid') { + side = 'buy'; + } else { + side = 'sell'; + } + let type = this.safeString (order, 'ord_type'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + let lastTradeTimestamp = undefined; + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + const remaining = this.safeFloat (order, 'remaining_volume'); + const filled = this.safeFloat (order, 'executed_volume'); + let cost = undefined; + if (type === 'price') { + type = 'market'; + cost = price; + price = undefined; + } + let average = undefined; + let fee = undefined; + let feeCost = this.safeFloat (order, 'paid_fee'); + const marketId = this.safeString (order, 'market'); + market = this.safeMarket (marketId, market); + let trades = this.safeValue (order, 'trades', []); + trades = this.parseTrades (trades, market, undefined, undefined, { + 'order': id, + 'type': type, + }); + const numTrades = trades.length; + if (numTrades > 0) { + // the timestamp in fetchOrder trades is missing + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + let getFeesFromTrades = false; + if (feeCost === undefined) { + getFeesFromTrades = true; + feeCost = 0; + } + cost = 0; + for (let i = 0; i < numTrades; i++) { + const trade = trades[i]; + cost = this.sum (cost, trade['cost']); + if (getFeesFromTrades) { + const tradeFee = this.safeValue (trades[i], 'fee', {}); + const tradeFeeCost = this.safeFloat (tradeFee, 'cost'); + if (tradeFeeCost !== undefined) { + feeCost = this.sum (feeCost, tradeFeeCost); + } + } + } + average = cost / filled; + } + if (feeCost !== undefined) { + fee = { + 'currency': market['quote'], + 'cost': feeCost, + }; + } + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + return result; + } + + async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'market': this.marketId (symbol), + 'state': state, + // 'page': 1, + // 'order_by': 'asc', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // [ + // { + // "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee", + // "side": "bid", + // "ord_type": "limit", + // "price": "17417000.0", + // "state": "done", + // "market": "KRW-BTC", + // "created_at": "2018-04-05T14:09:14+09:00", + // "volume": "1.0", + // "remaining_volume": "0.0", + // "reserved_fee": "26125.5", + // "remaining_fee": "25974.0", + // "paid_fee": "151.5", + // "locked": "17341974.0", + // "executed_volume": "1.0", + // "trades_count":2 + // }, + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('wait', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('done', symbol, since, limit, params); + } + + async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('cancel', symbol, since, limit, params); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'uuid': id, + }; + const response = await this.privateGetOrder (this.extend (request, params)); + // + // { + // "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee", + // "side": "bid", + // "ord_type": "limit", + // "price": "17417000.0", + // "state": "done", + // "market": "KRW-BTC", + // "created_at": "2018-04-05T14:09:14+09:00", + // "volume": "1.0", + // "remaining_volume": "0.0", + // "reserved_fee": "26125.5", + // "remaining_fee": "25974.0", + // "paid_fee": "151.5", + // "locked": "17341974.0", + // "executed_volume": "1.0", + // "trades_count": 2, + // "trades": [ + // { + // "market": "KRW-BTC", + // "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3", + // "price": "101000.0", + // "volume": "0.77368323", + // "funds": "78142.00623", + // "ask_fee": "117.213009345", + // "bid_fee": "117.213009345", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid" + // }, + // { + // "market": "KRW-BTC", + // "uuid": "f73da467-c42f-407d-92fa-e10d86450a20", + // "price": "101000.0", + // "volume": "0.22631677", + // "funds": "22857.99377", + // "ask_fee": "34.286990655", + // "bid_fee": "34.286990655", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid" + // } + // ] + // } + // + return this.parseOrder (response); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + async fetchDepositAddresses (codes = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetDepositsCoinAddresses (params); + // + // [ + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // }, + // { + // "currency": "ETH", + // "deposit_address": "0x0d73e0a482b8cf568976d2e8688f4a899d29301c", + // "secondary_address": null + // }, + // { + // "currency": "XRP", + // "deposit_address": "rN9qNpgnBaZwqCg8CvUZRPqCcPPY7wfWep", + // "secondary_address": "3057887915" + // } + // ] + // + return this.parseDepositAddresses (response); + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // } + // + const address = this.safeString (depositAddress, 'deposit_address'); + const tag = this.safeString (depositAddress, 'secondary_address'); + const currencyId = this.safeString (depositAddress, 'currency'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const response = await this.privateGetDepositsCoinAddress (this.extend ({ + 'currency': currency['id'], + }, params)); + // + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // } + // + return this.parseDepositAddress (response); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + // https://github.com/ccxt/ccxt/issues/6452 + const response = await this.privatePostDepositsGenerateCoinAddress (this.extend (request, params)); + // + // https://docs.upbit.com/v1.0/reference#%EC%9E%85%EA%B8%88-%EC%A3%BC%EC%86%8C-%EC%83%9D%EC%84%B1-%EC%9A%94%EC%B2%AD + // can be any of the two responses: + // + // { + // "success" : true, + // "message" : "Creating BTC deposit address." + // } + // + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // } + // + const message = this.safeString (response, 'message'); + if (message !== undefined) { + throw new AddressPending (this.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress or createDepositAddress one more time later to retrieve the generated address'); + } + return this.parseDepositAddress (response); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'amount': amount, + }; + let method = 'privatePostWithdraws'; + if (code !== 'KRW') { + method += 'Coin'; + request['currency'] = currency['id']; + request['address'] = address; + if (tag !== undefined) { + request['secondary_address'] = tag; + } + } else { + method += 'Krw'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "type": "withdraw", + // "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79", + // "currency": "BTC", + // "txid": "ebe6937b-130e-4066-8ac6-4b0e67f28adc", + // "state": "processing", + // "created_at": "2018-04-13T11:24:01+09:00", + // "done_at": null, + // "amount": "0.01", + // "fee": "0.0", + // "krw_amount": "80420.0" + // } + // + return this.parseTransaction (response); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }); + url += '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method !== 'POST') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = { + 'access_key': this.apiKey, + 'nonce': nonce, + }; + if (Object.keys (query).length) { + const auth = this.urlencode (query); + const hash = this.hash (this.encode (auth), 'sha512'); + request['query_hash'] = hash; + request['query_hash_alg'] = 'SHA512'; + } + const jwt = this.jwt (request, this.encode (this.secret)); + headers = { + 'Authorization': 'Bearer ' + jwt, + }; + if ((method !== 'GET') && (method !== 'DELETE')) { + body = this.json (params); + headers['Content-Type'] = 'application/json'; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { 'error': { 'message': "Missing request parameter error. Check the required parameters!", 'name': 400 } }, + // { 'error': { 'message': "side is missing, side does not have a valid value", 'name': "validation_error" } }, + // { 'error': { 'message': "개인정보 제 3자 제공 동의가 필요합니다.", 'name': "thirdparty_agreement_required" } }, + // { 'error': { 'message': "권한이 부족합니다.", 'name': "out_of_scope" } }, + // { 'error': { 'message': "주문을 찾지 못했습니다.", 'name': "order_not_found" } }, + // { 'error': { 'message': "주문가능한 금액(ETH)이 부족합니다.", 'name': "insufficient_funds_ask" } }, + // { 'error': { 'message': "주문가능한 금액(BTC)이 부족합니다.", 'name': "insufficient_funds_bid" } }, + // { 'error': { 'message': "잘못된 엑세스 키입니다.", 'name': "invalid_access_key" } }, + // { 'error': { 'message': "Jwt 토큰 검증에 실패했습니다.", 'name': "jwt_verification" } } + // + const error = this.safeValue (response, 'error'); + if (error !== undefined) { + const message = this.safeString (error, 'message'); + const name = this.safeString (error, 'name'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], name, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], name, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],170:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class vaultoro extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'vaultoro', + 'name': 'Vaultoro', + 'countries': [ 'CH' ], + 'rateLimit': 1000, + 'version': '1', + 'has': { + 'CORS': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchBalance': true, + 'createOrder': true, + 'cancelOrder': true, + 'fetchTrades': true, + 'fetchTicker': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766880-f205e870-5ee9-11e7-8fe2-0d5b15880752.jpg', + 'api': 'https://api.vaultoro.com', + 'www': 'https://www.vaultoro.com', + 'doc': 'https://api.vaultoro.com', + }, + 'commonCurrencies': { + 'GLD': 'Gold', + }, + 'api': { + 'public': { + 'get': [ + 'bidandask', + 'buyorders', + 'latest', + 'latesttrades', + 'markets', + 'orderbook', + 'sellorders', + 'transactions/day', + 'transactions/hour', + 'transactions/month', + ], + }, + 'private': { + 'get': [ + 'balance', + 'mytrades', + 'orders', + ], + 'post': [ + 'buy/{symbol}/{type}', + 'cancel/{id}', + 'sell/{symbol}/{type}', + 'withdraw', + ], + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const result = []; + const response = await this.publicGetMarkets (params); + const market = this.safeValue (response, 'data'); + const baseId = this.safeString (market, 'MarketCurrency'); + const quoteId = this.safeString (market, 'BaseCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = this.safeString (market, 'MarketName'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const balances = this.safeValue (response, 'data'); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'cash'); + account['used'] = this.safeFloat (balance, 'reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetOrderbook (params); + const orderbook = { + 'bids': response['data'][0]['b'], + 'asks': response['data'][1]['s'], + }; + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'Gold_Price', 'Gold_Amount'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'Time')); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const price = this.safeFloat (trade, 'Gold_Price'); + const amount = this.safeFloat (trade, 'Gold_Amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + return { + 'id': undefined, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.publicGetTransactionsDay (params); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = 'privatePost' + this.capitalize (side) + 'SymbolType'; + const request = { + 'symbol': market['quoteId'].toLowerCase (), + 'type': type, + 'gld': amount, + 'price': price || 1, + }; + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['data']['Order_ID'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancelId (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/'; + if (api === 'public') { + url += path; + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + url += this.version + '/' + this.implodeParams (path, params); + const query = this.extend ({ + 'nonce': nonce, + 'apikey': this.apiKey, + }, this.omit (params, this.extractParams (path))); + url += '?' + this.urlencode (query); + headers = { + 'Content-Type': 'application/json', + 'X-Signature': this.hmac (this.encode (url), this.encode (this.secret)), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5}],171:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const foxbit = require ('./foxbit.js'); + +// --------------------------------------------------------------------------- + +module.exports = class vbtc extends foxbit { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'vbtc', + 'name': 'VBTC', + 'countries': [ 'VN' ], + 'has': { + 'CORS': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27991481-1f53d1d8-6481-11e7-884e-21d17e7939db.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://vbtc.exchange', + 'doc': 'https://blinktrade.com/docs', + }, + 'options': { + 'brokerId': '3', // https://blinktrade.com/docs/#brokers + }, + }); + } +}; + +},{"./foxbit.js":84}],172:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, OrderNotFound, InvalidOrder, BadRequest, AuthenticationError, RateLimitExceeded, RequestTimeout, BadSymbol, AddressPending, PermissionDenied, InsufficientFunds } = require ('./base/errors'); +const { ROUND } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class vcc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'vcc', + 'name': 'VCC Exchange', + 'countries': [ 'VN' ], // Vietnam + 'rateLimit': 1000, + 'version': 'v3', + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'editOrder': false, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': 'emulated', + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFees': false, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '60000', + '5m': '300000', + '15m': '900000', + '30m': '1800000', + '1h': '3600000', + '2h': '7200000', + '4h': '14400000', + '6h': '21600000', + '12h': '43200000', + '1d': '86400000', + '1w': '604800000', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/100545356-8427f500-326c-11eb-9539-7d338242d61b.jpg', + 'api': { + 'public': 'https://api.vcc.exchange', + 'private': 'https://api.vcc.exchange', + }, + 'www': 'https://vcc.exchange', + 'doc': [ + 'https://vcc.exchange/api', + ], + 'fees': 'https://support.vcc.exchange/hc/en-us/articles/360016401754', + 'referral': 'https://vcc.exchange?ref=l4xhrH', + }, + 'api': { + 'public': { + 'get': [ + 'summary', + 'exchange_info', + 'assets', // Available Currencies + 'ticker', // Ticker list for all symbols + 'trades/{market_pair}', // Recent trades + 'orderbook/{market_pair}', // Orderbook + 'chart/bars', // Candles + 'tick_sizes', + ], + }, + 'private': { + 'get': [ + 'user', + 'balance', // Get trading balance + 'orders/{order_id}', // Get a single order by order_id + 'orders/open', // Get open orders + 'orders', // Get closed orders + 'orders/trades', // Get trades history + 'deposit-address', // Generate or get deposit address + 'transactions', // Get deposit/withdrawal history + ], + 'post': [ + 'orders', // Create new order + ], + 'put': [ + 'orders/{order_id}/cancel', // Cancel order + 'orders/cancel-by-type', + 'orders/cancel-all', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + }, + 'exceptions': { + 'exact': {}, + 'broad': { + 'limit may not be greater than': BadRequest, // {"message":"The given data was invalid.","errors":{"limit":["The limit may not be greater than 1000."]}} + 'Insufficient balance': InsufficientFunds, // {"message":"Insufficient balance."} + 'Unauthenticated': AuthenticationError, // {"message":"Unauthenticated."} // wrong api key + 'signature is invalid': AuthenticationError, // {"message":"The given data was invalid.","errors":{"signature":["HMAC signature is invalid"]}} + 'Timeout': RequestTimeout, // {"code":504,"message":"Gateway Timeout","description":""} + 'Too many requests': RateLimitExceeded, // {"code":429,"message":"Too many requests","description":"Too many requests"} + 'quantity field is required': InvalidOrder, // {"message":"The given data was invalid.","errors":{"quantity":["The quantity field is required when type is market."]}} + 'price field is required': InvalidOrder, // {"message":"The given data was invalid.","errors":{"price":["The price field is required when type is limit."]}} + 'error_security_level': PermissionDenied, // {"message":"error_security_level"} + 'pair is invalid': BadSymbol, // {"message":"The given data was invalid.","errors":{"coin":["Trading pair is invalid","Trading pair is offline"]}} + // {"message":"The given data was invalid.","errors":{"type":["The selected type is invalid."]}} + // {"message":"The given data was invalid.","errors":{"trade_type":["The selected trade type is invalid."]}} + 'type is invalid': InvalidOrder, + 'Data not found': OrderNotFound, // {"message":"Data not found"} + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeInfo (params); + // + // { + // "message":null, + // "dataVersion":"4677e56a42f0c29872f3a6e75f5d39d2f07c748c", + // "data":{ + // "timezone":"UTC", + // "serverTime":1605821914333, + // "symbols":[ + // { + // "id":"btcvnd", + // "symbol":"BTC\/VND", + // "coin":"btc", + // "currency":"vnd", + // "baseId":1, + // "quoteId":0, + // "active":true, + // "base_precision":"0.0000010000", + // "quote_precision":"1.0000000000", + // "minimum_quantity":"0.0000010000", + // "minimum_amount":"250000.0000000000", + // "precision":{"price":0,"amount":6,"cost":6}, + // "limits":{ + // "amount":{"min":"0.0000010000"}, + // "price":{"min":"1.0000000000"}, + // "cost":{"min":"250000.0000000000"}, + // }, + // }, + // ], + // }, + // } + // + const data = this.safeValue (response, 'data'); + const markets = this.safeValue (data, 'symbols'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = this.safeValue (markets, i); + const symbol = this.safeString (market, 'symbol'); + const id = symbol.replace ('/', '_'); + const baseId = this.safeString (market, 'coin'); + const quoteId = this.safeString (market, 'currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const active = this.safeValue (market, 'active'); + const precision = this.safeValue (market, 'precision', {}); + const limits = this.safeValue (market, 'limits', {}); + const amountLimits = this.safeValue (limits, 'amount', {}); + const priceLimits = this.safeValue (limits, 'price', {}); + const costLimits = this.safeValue (limits, 'cost', {}); + const entry = { + 'info': market, + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': { + 'price': this.safeInteger (precision, 'price'), + 'amount': this.safeInteger (precision, 'amount'), + 'cost': this.safeInteger (precision, 'cost'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (amountLimits, 'min'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (priceLimits, 'min'), + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (costLimits, 'min'), + 'max': undefined, + }, + }, + }; + result.push (entry); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetAssets (params); + // + // { + // "message":null, + // "dataVersion":"2514c8012d94ea375018fc13e0b5d4d896e435df", + // "data":{ + // "BTC":{ + // "name":"Bitcoin", + // "unified_cryptoasset_id":1, + // "can_withdraw":1, + // "can_deposit":1, + // "min_withdraw":"0.0011250000", + // "max_withdraw":"100.0000000000", + // "maker_fee":"0.002", + // "taker_fee":"0.002", + // "decimal":8, + // "withdrawal_fee":"0.0006250000", + // }, + // }, + // } + // + const result = {}; + const data = this.safeValue (response, 'data'); + const ids = Object.keys (data); + for (let i = 0; i < ids.length; i++) { + const id = this.safeStringLower (ids, i); + const currency = this.safeValue (data, ids[i]); + const code = this.safeCurrencyCode (id); + const canDeposit = this.safeValue (currency, 'can_deposit'); + const canWithdraw = this.safeValue (currency, 'can_withdraw'); + const active = (canDeposit && canWithdraw); + result[code] = { + 'id': id, + 'code': code, + 'name': this.safeString (currency, 'name'), + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawal_fee'), + 'precision': this.safeInteger (currency, 'decimal'), + 'limits': { + 'withdraw': { + 'min': this.safeFloat (currency, 'min_withdraw'), + 'max': this.safeFloat (currency, 'max_withdraw'), + }, + }, + }; + } + return result; + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = this.extend ({ + 'symbol': market['id'], + }, this.omit (params, 'symbol')); + const response = await this.privateGetTradingFeeSymbol (request); + // + // { + // takeLiquidityRate: '0.001', + // provideLiquidityRate: '-0.0001' + // } + // + return { + 'info': response, + 'maker': this.safeFloat (response, 'provideLiquidityRate'), + 'taker': this.safeFloat (response, 'takeLiquidityRate'), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + // + // { + // "message":null, + // "dataVersion":"7168e6c99e90f60673070944d987988eef7d91fa", + // "data":{ + // "vnd":{"balance":0,"available_balance":0}, + // "btc":{"balance":0,"available_balance":0}, + // "eth":{"balance":0,"available_balance":0}, + // }, + // } + // + const data = this.safeValue (response, 'data'); + const result = { 'info': response }; + const currencyIds = Object.keys (data); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (data, currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available_balance'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "low":"415805323.0000000000", + // "high":"415805323.0000000000", + // "open":"415805323.0000000000", + // "close":"415805323.0000000000", + // "time":"1605845940000", + // "volume":"0.0065930000", + // "opening_time":1605845963263, + // "closing_time":1605845963263 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'currency': market['quoteId'], + 'resolution': this.timeframes[timeframe], + }; + limit = (limit === undefined) ? 100 : limit; + limit = Math.min (100, limit); + const duration = this.parseTimeframe (timeframe); + if (since === undefined) { + const end = this.seconds (); + request['to'] = end; + request['from'] = end - limit * duration; + } else { + const start = parseInt (since / 1000); + request['from'] = start; + request['to'] = this.sum (start, limit * duration); + } + const response = await this.publicGetChartBars (this.extend (request, params)); + // + // [ + // {"low":"415805323.0000000000","high":"415805323.0000000000","open":"415805323.0000000000","close":"415805323.0000000000","time":"1605845940000","volume":"0.0065930000","opening_time":1605845963263,"closing_time":1605845963263}, + // {"low":"416344148.0000000000","high":"416344148.0000000000","open":"415805323.0000000000","close":"416344148.0000000000","time":"1605846000000","volume":"0.0052810000","opening_time":1605846011490,"closing_time":1605846011490}, + // {"low":"416299269.0000000000","high":"417278376.0000000000","open":"416344148.0000000000","close":"417278376.0000000000","time":"1605846060000","volume":"0.0136750000","opening_time":1605846070727,"closing_time":1605846102282}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_pair': market['id'], + // 'depth': 0, // 0 = full orderbook, 5, 10, 20, 50, 100, 500 + 'level': 2, // 1 = best bidask, 2 = aggregated by price, 3 = no aggregation + }; + if (limit !== undefined) { + if ((limit !== 0) && (limit !== 5) && (limit !== 10) && (limit !== 20) && (limit !== 50) && (limit !== 100) && (limit !== 500)) { + throw new BadRequest (this.id + ' fetchOrderBook limit must be 0, 5, 10, 20, 50, 100, 500 if specified'); + } + request['depth'] = limit; + } + const response = await this.publicGetOrderbookMarketPair (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"376cee43af26deabcd3762ab11a876b6e7a71e82", + // "data":{ + // "bids":[ + // ["413342637.0000000000","0.165089"], + // ["413274576.0000000000","0.03"], + // ["413274574.0000000000","0.03"], + // ], + // "asks":[ + // ["416979125.0000000000","0.122835"], + // ["417248934.0000000000","0.030006"], + // ["417458879.0000000000","0.1517"], + // ], + // "timestamp":"1605841619147" + // } + // } + // + const data = this.safeValue (response, 'data'); + const timestamp = this.safeValue (data, 'timestamp'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks', 0, 1); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "base_id":1, + // "quote_id":0, + // "last_price":"411119457", + // "max_price":"419893173.0000000000", + // "min_price":"401292577.0000000000", + // "open_price":null, + // "base_volume":"10.5915050000", + // "quote_volume":"4367495977.4484430060", + // "isFrozen":0 + // } + // + const timestamp = this.milliseconds (); + const baseVolume = this.safeFloat (ticker, 'base_volume'); + const quoteVolume = this.safeFloat (ticker, 'quote_volume'); + const open = this.safeFloat (ticker, 'open_price'); + const last = this.safeFloat (ticker, 'last_price'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + const vwap = this.vwap (baseVolume, quoteVolume); + const symbol = (market === undefined) ? undefined : market['symbol']; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max_price'), + 'low': this.safeFloat (ticker, 'min_price'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.publicGetTicker (params); + // + // { + // "message":null, + // "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e", + // "data":{ + // "BTC_VND":{ + // "base_id":1, + // "quote_id":0, + // "last_price":"411119457", + // "max_price":"419893173.0000000000", + // "min_price":"401292577.0000000000", + // "open_price":null, + // "base_volume":"10.5915050000", + // "quote_volume":"4367495977.4484430060", + // "isFrozen":0 + // }, + // } + // } + // + const data = this.safeValue (response, 'data'); + const ticker = this.safeValue (data, market['id']); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + // + // { + // "message":null, + // "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e", + // "data":{ + // "BTC_VND":{ + // "base_id":1, + // "quote_id":0, + // "last_price":"411119457", + // "max_price":"419893173.0000000000", + // "min_price":"401292577.0000000000", + // "open_price":null, + // "base_volume":"10.5915050000", + // "quote_volume":"4367495977.4484430060", + // "isFrozen":0 + // }, + // } + // } + // + const result = {}; + const data = this.safeValue (response, 'data'); + const marketIds = Object.keys (data); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const market = this.safeMarket (marketId, undefined, '_'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (data[marketId], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "trade_id":181509285, + // "price":"415933022.0000000000", + // "base_volume":"0.0022080000", + // "quote_volume":"918380.1125760000", + // "trade_timestamp":1605842150357, + // "type":"buy", + // } + // + // private fetchMyTrades + // + // { + // "trade_type":"sell", + // "fee":"0.0610578086", + // "id":1483372, + // "created_at":1606581578368, + // "currency":"usdt", + // "coin":"btc", + // "price":"17667.1900000000", + // "quantity":"0.0017280000", + // "amount":"30.5289043200", + // } + // + const timestamp = this.safeInteger2 (trade, 'trade_timestamp', 'created_at'); + const baseId = this.safeStringUpper (trade, 'coin'); + const quoteId = this.safeStringUpper (trade, 'currency'); + let marketId = undefined; + if ((baseId !== undefined) && (quoteId !== undefined)) { + marketId = baseId + '_' + quoteId; + } + market = this.safeMarket (marketId, market, '_'); + const symbol = market['symbol']; + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'base_volume', 'quantity'); + let cost = this.safeFloat2 (trade, 'quote_volume', 'amount'); + if (cost === undefined) { + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + } + const side = this.safeString2 (trade, 'type', 'trade_type'); + const id = this.safeString2 (trade, 'trade_id', 'id'); + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_pair': market['id'], + // 'type': 'buy', // 'sell' + // 'count': limit, // default 500, max 1000 + }; + if (limit !== undefined) { + request['count'] = Math.min (1000, limit); + } + const response = await this.publicGetTradesMarketPair (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"1f811b533143f739008a3e4ecaaab2ec82ea50d4", + // "data":[ + // { + // "trade_id":181509285, + // "price":"415933022.0000000000", + // "base_volume":"0.0022080000", + // "quote_volume":"918380.1125760000", + // "trade_timestamp":1605842150357, + // "type":"buy", + // }, + // ], + // } + // + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'type': type, // 'deposit', 'withdraw' + // 'start': parseInt (since / 1000), + // 'end': this.seconds (), + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = Math.min (1000, limit); + } + if (since !== undefined) { + request['start'] = since; + } + const response = await this.privateGetTransactions (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"1fdfb0ec85b666871d62fe59d098d01839b05e97", + // "data":{ + // "current_page":1, + // "data":[ + // { + // "id":85391, + // "user_id":253063, + // "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131", + // "internal_transaction_id":null, + // "temp_transaction_id":"2367", + // "currency":"usdt", + // "amount":"30.0000000000", + // "btc_amount":"0.0000000000", + // "usdt_amount":"0.0000000000", + // "fee":"0.0000000000", + // "tx_cost":"0.0000000000", + // "confirmation":0, + // "deposit_code":null, + // "status":"success", + // "bank_name":null, + // "foreign_bank_account":null, + // "foreign_bank_account_holder":null, + // "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44", + // "destination_tag":null, + // "error_detail":null, + // "refunded":"0.0000000000", + // "transaction_date":"2020-11-28", + // "transaction_timestamp":"1606563143.959", + // "created_at":1606563143959, + // "updated_at":1606563143959, + // "transaction_email_timestamp":0, + // "network":null, + // "collect_tx_id":null, + // "collect_id":null + // } + // ], + // "first_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1", + // "from":1, + // "last_page":1, + // "last_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1", + // "next_page_url":null, + // "path":"http:\/\/api.vcc.exchange\/v3\/transactions", + // "per_page":10, + // "prev_page_url":null, + // "to":1, + // "total":1 + // } + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (data, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'type': 'deposit' }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'type': 'withdraw' }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchTransactions, fetchDeposits, fetchWithdrawals + // + // { + // "id":85391, + // "user_id":253063, + // "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131", + // "internal_transaction_id":null, + // "temp_transaction_id":"2367", + // "currency":"usdt", + // "amount":"30.0000000000", + // "btc_amount":"0.0000000000", + // "usdt_amount":"0.0000000000", + // "fee":"0.0000000000", + // "tx_cost":"0.0000000000", + // "confirmation":0, + // "deposit_code":null, + // "status":"success", + // "bank_name":null, + // "foreign_bank_account":null, + // "foreign_bank_account_holder":null, + // "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44", + // "destination_tag":null, + // "error_detail":null, + // "refunded":"0.0000000000", + // "transaction_date":"2020-11-28", + // "transaction_timestamp":"1606563143.959", + // "created_at":1606563143959, + // "updated_at":1606563143959, + // "transaction_email_timestamp":0, + // "network":null, + // "collect_tx_id":null, + // "collect_id":null + // } + // + const id = this.safeString (transaction, 'id'); + const timestamp = this.safeInteger (transaction, 'created_at'); + const updated = this.safeInteger (transaction, 'updated_at'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + let amount = this.safeFloat (transaction, 'amount'); + if (amount !== undefined) { + amount = Math.abs (amount); + } + const address = this.safeString (transaction, 'blockchain_address'); + const txid = this.safeString (transaction, 'transaction_id'); + const tag = this.safeString (transaction, 'destination_tag'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + const type = amount > 0 ? 'deposit' : 'withdrawal'; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'error': 'failed', + 'success': 'ok', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const types = { + 'deposit': 'deposit', + 'withdraw': 'withdrawal', + }; + return this.safeString (types, type, type); + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, ROUND, this.markets[symbol]['precision']['cost'], this.precisionMode, this.paddingMode); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'currency': market['quoteId'], + 'trade_type': side, + 'type': type, + }; + if (type === 'ceiling_market') { + const ceiling = this.safeValue (params, 'ceiling'); + if (ceiling !== undefined) { + request['ceiling'] = this.costToPrecision (symbol, ceiling); + } else if (price !== undefined) { + request['ceiling'] = this.costToPrecision (symbol, amount * price); + } else { + throw new InvalidOrder (this.id + ' createOrder() requires a price argument or a ceiling parameter for ' + type + ' orders'); + } + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const stopPrice = this.safeValue2 (params, 'stop_price', 'stopPrice'); + if (stopPrice !== undefined) { + request['is_stop'] = 1; + request['stop_condition'] = (side === 'buy') ? 'le' : 'ge'; // ge = greater than or equal, le = less than or equal + request['stop_price'] = this.priceToPrecision (symbol, price); + } + params = this.omit (params, [ 'stop_price', 'stopPrice' ]); + const response = await this.privatePostOrders (this.extend (request, params)); + // + // ceiling_market order + // + // { + // "message":null, + // "dataVersion":"213fc0d433f38307f736cae1cbda4cc310469b7a", + // "data":{ + // "coin":"btc", + // "currency":"usdt", + // "trade_type":"buy", + // "type":"ceiling_market", + // "ceiling":"30", + // "user_id":253063, + // "email":"igor.kroitor@gmail.com", + // "side":"buy", + // "quantity":"0.00172800", + // "status":"pending", + // "fee":0, + // "created_at":1606571333035, + // "updated_at":1606571333035, + // "instrument_symbol":"BTCUSDT", + // "remaining":"0.00172800", + // "fee_rate":"0.002", + // "id":88214435 + // } + // } + // + // limit order + // + // { + // "message":null, + // "dataVersion":"d9b1159d2bcefa2388be156e32ddc7cc324400ee", + // "data":{ + // "id":41230, + // "trade_type":"sell", + // "type":"limit", + // "quantity":"1", + // "price":"14.99", + // "currency":"usdt", + // "coin":"neo", + // "status":"pending", + // "is_stop": "1", + // "stop_price": "13", + // "stop_condition": "ge", + // "fee":0, + // "created_at":1560244052168, + // "updated_at":1560244052168 + // } + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privatePutOrdersOrderIdCancel (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const type = this.safeString (params, 'type'); + const method = (type === undefined) ? 'privatePutOrdersCancelAll' : 'privatePutOrdersCancelByType'; + const request = {}; + if (type !== undefined) { + request['type'] = type; + } + await this.loadMarkets (); + const response = await this[method] (this.extend (request, params)); + // + // { + // "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff", + // "data":{ + // "data":[ + // { + // "id":410, + // "trade_type":"sell", + // "currency":"usdt", + // "coin":"neo", + // "type":"limit", + // "quantity":"1.0000000000", + // "price":"14.9900000000", + // "executed_quantity":"0.0000000000", + // "executed_price":"0.0000000000", + // "fee":"0.0000000000", + // "status":"canceled", + // "created_at":1560244052168, + // "updated_at":1560244052168, + // }, + // ], + // }, + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (response, 'data', []); + return this.parseOrders (data); + } + + parseOrderStatus (status) { + const statuses = { + 'pending': 'open', + 'stopping': 'open', + 'executing': 'open', + 'executed': 'closed', + 'canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // ceiling_market + // + // { + // "coin":"btc", + // "currency":"usdt", + // "trade_type":"buy", + // "type":"ceiling_market", + // "ceiling":"30", + // "user_id":253063, + // "email":"igor.kroitor@gmail.com", + // "side":"buy", + // "quantity":"0.00172800", + // "status":"pending", + // "fee":0, + // "created_at":1606571333035, + // "updated_at":1606571333035, + // "instrument_symbol":"BTCUSDT", + // "remaining":"0.00172800", + // "fee_rate":"0.002", + // "id":88214435 + // } + // + // limit order + // + // { + // "id":41230, + // "trade_type":"sell", + // "type":"limit", + // "quantity":"1", + // "price":"14.99", + // "currency":"usdt", + // "coin":"neo", + // "status":"pending", + // "is_stop": "1", + // "stop_price": "13", + // "stop_condition": "ge", + // "fee":0, + // "created_at":1560244052168, + // "updated_at":1560244052168 + // } + // + const created = this.safeValue (order, 'created_at'); + const updated = this.safeValue (order, 'updated_at'); + const baseId = this.safeStringUpper (order, 'coin'); + const quoteId = this.safeStringUpper (order, 'currency'); + const marketId = baseId + '_' + quoteId; + market = this.safeMarket (marketId, market, '_'); + const symbol = market['symbol']; + const amount = this.safeFloat (order, 'quantity'); + let filled = this.safeFloat (order, 'executed_quantity'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let cost = this.safeFloat (order, 'ceiling'); + const id = this.safeString (order, 'id'); + let average = undefined; + let price = this.safeFloat (order, 'price'); + // in case of market order + if (!price) { + price = this.safeFloat (order, 'executed_price'); + average = price; + } + let remaining = this.safeFloat (order, 'remaining'); + if ((filled === undefined) && (amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + if (filled !== undefined) { + if ((amount !== undefined) && (remaining === undefined)) { + remaining = Math.max (0, amount - filled); + } + if ((price !== undefined) && (cost === undefined)) { + cost = filled * price; + } + if ((average === undefined) && (cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + } + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'trade_type'); + const fee = { + 'currency': market['quote'], + 'cost': this.safeFloat (order, 'fee'), + 'rate': this.safeFloat (order, 'fee_rate'), + }; + let lastTradeTimestamp = undefined; + if (updated !== created) { + lastTradeTimestamp = updated; + } + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'id': id, + 'clientOrderId': id, + 'timestamp': created, + 'datetime': this.iso8601 (created), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'average': average, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'trades': undefined, + 'info': order, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetOrdersOrderId (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"57448aa1fb8f227254e8e2e925b3ade8e1e5bbef", + // "data":{ + // "id":88265741, + // "user_id":253063, + // "email":"igor.kroitor@gmail.com", + // "updated_at":1606581578141, + // "created_at":1606581578141, + // "coin":"btc", + // "currency":"usdt", + // "type":"market", + // "trade_type":"sell", + // "executed_price":"17667.1900000000", + // "price":null, + // "executed_quantity":"0.0017280000", + // "quantity":"0.0017280000", + // "fee":"0.0610578086", + // "status":"executed", + // "is_stop":0, + // "stop_condition":null, + // "stop_price":null, + // "ceiling":null + // } + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (data); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'limit': limit, // max 1000 + // 'start_date': since, + // 'end_date': this.milliseconds (), + // 'currency': market['quoteId'], + // 'coin': market['baseId'], + // 'trade_type': 'buy', // or 'sell' + // 'hide_canceled': 0, // 1 to exclude canceled orders + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['coin'] = market['baseId']; + request['currency'] = market['quoteId']; + } + if (since !== undefined) { + request['start_date'] = since; + } + if (limit !== undefined) { + request['limit'] = Math.min (1000, limit); // max 1000 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"89aa11497f23fdd34cf9de9c55acfad863c78780", + // "data":{ + // "current_page":1, + // "data":[ + // { + // "id":88489678, + // "email":"igor.kroitor@gmail.com", + // "updated_at":1606628593567, + // "created_at":1606628593567, + // "coin":"btc", + // "currency":"usdt", + // "type":"limit", + // "trade_type":"buy", + // "executed_price":"0.0000000000", + // "price":"10000.0000000000", + // "executed_quantity":"0.0000000000", + // "quantity":"0.0010000000", + // "fee":"0.0000000000", + // "status":"pending", + // "is_stop":0, + // "stop_condition":null, + // "stop_price":null, + // "ceiling":null, + // }, + // ], + // "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1", + // "from":1, + // "last_page":1, + // "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1", + // "next_page_url":null, + // "path":"http:\/\/api.vcc.exchange\/v3\/orders\/open", + // "per_page":10, + // "prev_page_url":null, + // "to":1, + // "total":1, + // }, + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (data, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetOrdersOpen', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetOrders', symbol, since, limit, params); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'limit': limit, // max 1000 + // 'start_date': since, + // 'end_date': this.milliseconds (), + // 'currency': market['quoteId'], + // 'coin': market['baseId'], + // 'trade_type': 'buy', // or 'sell' + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['coin'] = market['baseId']; + request['currency'] = market['quoteId']; + } + if (since !== undefined) { + request['start_date'] = since; + } + if (limit !== undefined) { + request['limit'] = Math.min (1000, limit); // max 1000 + } + const response = await this.privateGetOrdersTrades (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"eb890af684cf84e20044e9a9771b96302e7b8dec", + // "data":{ + // "current_page":1, + // "data":[ + // { + // "trade_type":"sell", + // "fee":"0.0610578086", + // "id":1483372, + // "created_at":1606581578368, + // "currency":"usdt", + // "coin":"btc", + // "price":"17667.1900000000", + // "quantity":"0.0017280000", + // "amount":"30.5289043200", + // }, + // ], + // "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1", + // "from":1, + // "last_page":1, + // "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1", + // "next_page_url":null, + // "path":"http:\/\/api.vcc.exchange\/v3\/orders\/trades", + // "per_page":10, + // "prev_page_url":null, + // "to":2, + // "total":2, + // }, + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (data, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + // + // { + // "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff", + // "data":{ + // "status": "REQUESTED", + // "blockchain_address": "", + // "currency": "btc" + // } + // } + // + // { + // "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff", + // "data":{ + // "status": "PROVISIONED", + // "blockchain_address": "rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy", + // "blockchain_tag": "920396135", + // "currency": "xrp" + // } + // } + // + const data = this.safeValue (response, 'data'); + const status = this.safeString (data, 'status'); + if (status === 'REQUESTED') { + throw new AddressPending (this.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress one more time later to retrieve the generated address'); + } + const address = this.safeString (data, 'blockchain_address'); + this.checkAddress (address); + const tag = this.safeString (data, 'blockchain_tag'); + const currencyId = this.safeString (data, 'currency'); + return { + 'currency': this.safeCurrencyCode (currencyId), + 'address': address, + 'tag': tag, + 'info': data, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + if (method !== 'GET') { + body = this.json (query); + } + const auth = method + ' ' + url; + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + headers = { + 'Authorization': 'Bearer ' + this.apiKey, + 'Content-Type': 'application/json', + 'timestamp': timestamp, + 'signature': signature, + }; + } + url = this.urls['api'][api] + '/' + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"message":"Insufficient balance."} + // {"message":"Unauthenticated."} // wrong api key + // {"message":"The given data was invalid.","errors":{"signature":["HMAC signature is invalid"]}} + // {"code":504,"message":"Gateway Timeout","description":""} + // {"code":429,"message":"Too many requests","description":"Too many requests"} + // + const message = this.safeString (response, 'message'); + if (message !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7,"./base/functions/number":13}],173:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, AccountSuspended, ExchangeError, DuplicateOrderId, OrderNotFound, BadSymbol, ExchangeNotAvailable, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class wavesexchange extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'wavesexchange', + 'name': 'Waves.Exchange', + 'countries': ['CH'], // Switzerland + 'rateLimit': 500, + 'certified': true, + 'pro': false, + 'has': { + 'cancelOrder': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '3h': '3h', + '4h': '4h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg', + 'api': { + 'matcher': 'http://matcher.waves.exchange', + 'node': 'https://nodes.waves.exchange', + 'public': 'https://api.wavesplatform.com/v0', + 'private': 'https://api.waves.exchange/v1', + 'forward': 'https://waves.exchange/api/v1/forward/matcher', + 'market': 'https://marketdata.wavesplatform.com/api/v1', + }, + 'doc': 'https://docs.waves.exchange', + 'www': 'https://waves.exchange', + }, + 'api': { + 'matcher': { + 'get': [ + 'matcher', + 'matcher/settings', + 'matcher/settings/rates', + 'matcher/balance/reserved/{publicKey}', + 'matcher/debug/allSnashotOffsets', + 'matcher/debug/currentOffset', + 'matcher/debug/lastOffset', + 'matcher/debug/oldestSnapshotOffset', + 'matcher/orderbook', + 'matcher/orderbook/{amountAsset}/{priceAsset}', + 'matcher/orderbook/{baseId}/{quoteId}/publicKey/{publicKey}', + 'matcher/orderbook/{baseId}/{quoteId}/{orderId}', + 'matcher/orderbook/{baseId}/{quoteId}/info', + 'matcher/orderbook/{baseId}/{quoteId}/status', + 'matcher/orderbook/{baseId}/{quoteId}/tradeableBalance/{address}', + 'matcher/orderbook/{publicKey}', + 'matcher/orderbook/{publicKey}/{orderId}', + 'matcher/orders/{address}', + 'matcher/orders/{address}/{orderId}', + 'matcher/transactions/{orderId}', + ], + 'post': [ + 'matcher/orderbook', + 'matcher/orderbook/market', + 'matcher/orderbook/cancel', + 'matcher/orderbook/{baseId}/{quoteId}/cancel', + 'matcher/debug/saveSnapshots', + 'matcher/orders/{address}/cancel', + 'matcher/orders/cancel/{orderId}', + ], + 'delete': [ + 'matcher/orderbook/{baseId}/{quoteId}', + 'matcher/settings/rates/{assetId}', + ], + 'put': [ + 'matcher/settings/rates/{assetId}', + ], + }, + 'node': { + 'get': [ + 'addresses', + 'addresses/balance/{address}', + 'addresses/balance/{address}/{confirmations}', + 'addresses/balance/details/{address}', + 'addresses/data/{address}', + 'addresses/data/{address}/{key}', + 'addresses/effectiveBalance/{address}', + 'addresses/effectiveBalance/{address}/{confirmations}', + 'addresses/publicKey/{publicKey}', + 'addresses/scriptInfo/{address}', + 'addresses/scriptInfo/{address}/meta', + 'addresses/seed/{address}', + 'addresses/seq/{from}/{to}', + 'addresses/validate/{address}', + 'alias/by-address/{address}', + 'alias/by-alias/{alias}', + 'assets/{assetId}/distribution/{height}/{limit}', + 'assets/balance/{address}', + 'assets/balance/{address}/{assetId}', + 'assets/details/{assetId}', + 'assets/nft/{address}/limit/{limit}', + 'blockchain/rewards', + 'blockchain/rewards/height', + 'blocks/address/{address}/{from}/{to}/', + 'blocks/at/{height}', + 'blocks/delay/{signature}/{blockNum}', + 'blocks/first', + 'blocks/headers/last', + 'blocks/headers/seq/{from}/{to}', + 'blocks/height', + 'blocks/height/{signature}', + 'blocks/last', + 'blocks/seq/{from}/{to}', + 'blocks/signature/{signature}', + 'consensus/algo', + 'consensus/basetarget', + 'consensus/basetarget/{blockId}', + 'consensus/{generatingbalance}/address', + 'consensus/generationsignature', + 'consensus/generationsignature/{blockId}', + 'debug/balances/history/{address}', + 'debug/blocks/{howMany}', + 'debug/configInfo', + 'debug/historyInfo', + 'debug/info', + 'debug/minerInfo', + 'debug/portfolios/{address}', + 'debug/state', + 'debug/stateChanges/address/{address}', + 'debug/stateChanges/info/{id}', + 'debug/stateWaves/{height}', + 'leasing/active/{address}', + 'node/state', + 'node/version', + 'peers/all', + 'peers/blacklisted', + 'peers/connected', + 'peers/suspended', + 'transactions/address/{address}/limit/{limit}', + 'transactions/info/{id}', + 'transactions/status', + 'transactions/unconfirmed', + 'transactions/unconfirmed/info/{id}', + 'transactions/unconfirmed/size', + 'utils/seed', + 'utils/seed/{length}', + 'utils/time', + 'wallet/seed', + ], + 'post': [ + 'addresses', + 'addresses/data/{address}', + 'addresses/sign/{address}', + 'addresses/signText/{address}', + 'addresses/verify/{address}', + 'addresses/verifyText/{address}', + 'debug/blacklist', + 'debug/print', + 'debug/rollback', + 'debug/validate', + 'node/stop', + 'peers/clearblacklist', + 'peers/connect', + 'transactions/broadcast', + 'transactions/calculateFee', + 'tranasctions/sign', + 'transactions/sign/{signerAddress}', + 'tranasctions/status', + 'utils/hash/fast', + 'utils/hash/secure', + 'utils/script/compileCode', + 'utils/script/compileWithImports', + 'utils/script/decompile', + 'utils/script/estimate', + 'utils/sign/{privateKey}', + 'utils/transactionsSerialize', + ], + 'delete': [ + 'addresses/{address}', + 'debug/rollback-to/{signature}', + ], + }, + 'public': { + 'get': [ + 'pairs', + 'candles/{baseId}/{quoteId}', + 'transactions/exchange', + ], + }, + 'private': { + 'get': [ + 'deposit/addresses/{code}', + 'deposit/currencies', + 'withdraw/currencies', + 'withdraw/addresses/{currency}/{address}', + ], + 'post': [ + 'oauth2/token', + ], + }, + 'forward': { + 'get': [ + 'matcher/orders/{address}', // can't get the orders endpoint to work with the matcher api + 'matcher/orders/{address}/{orderId}', + ], + 'post': [ + 'matcher/orders/{wavesAddress}/cancel', + ], + }, + 'market': { + 'get': [ + 'tickers', + ], + }, + }, + 'options': { + 'allowedCandles': 1440, + 'accessToken': undefined, + 'matcherPublicKey': undefined, + 'quotes': undefined, + 'createOrderDefaultExpiry': 2419200000, // 60 * 60 * 24 * 28 * 1000 + 'wavesAddress': undefined, + 'withdrawFeeUSDN': 7420, + 'withdrawFeeWAVES': 100000, + 'wavesPrecision': 8, + }, + 'requiresEddsa': true, + 'exceptions': { + '3147270': InsufficientFunds, // https://github.com/wavesplatform/matcher/wiki/List-of-all-errors + '112': InsufficientFunds, + '4': ExchangeError, + '13': ExchangeNotAvailable, + '14': ExchangeNotAvailable, + '3145733': AccountSuspended, + '3148040': DuplicateOrderId, + '3148801': AuthenticationError, + '9440512': AuthenticationError, + '9440771': BadSymbol, + '9441026': InvalidOrder, + '9441282': InvalidOrder, + '9441286': InvalidOrder, + '9441295': InvalidOrder, + '9441540': InvalidOrder, + '9441542': InvalidOrder, + '106954752': AuthenticationError, + '106954769': AuthenticationError, + '106957828': AuthenticationError, + '106960131': AuthenticationError, + '106981137': AuthenticationError, + '9437193': OrderNotFound, + '1048577': BadRequest, + '1051904': AuthenticationError, + }, + }); + } + + async getQuotes () { + let quotes = this.safeValue (this.options, 'quotes'); + if (quotes) { + return quotes; + } else { + // currencies can have any name because you can create you own token + // as a result someone can create a fake token called BTC + // we use this mapping to determine the real tokens + // https://docs.waves.exchange/en/waves-matcher/matcher-api#asset-pair + const response = await this.matcherGetMatcherSettings (); + // { + // "orderVersions": [ + // 1, + // 2, + // 3 + // ], + // "success": true, + // "matcherPublicKey": "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5", + // "orderFee": { + // "dynamic": { + // "baseFee": 300000, + // "rates": { + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ": 1.22639597, + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH": 0.00989643, + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk": 0.0395674, + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS": 0.00018814, + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8": 26.19721262, + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu": 0.00752978, + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p": 1.84575, + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H": 0.02330273, + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy": 0.00721412, + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3": 0.02659103, + // "WAVES": 1, + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa": 0.03433583 + // } + // } + // }, + // "networkByte": 87, + // "matcherVersion": "2.1.3.5", + // "status": "SimpleResponse", + // "priceAssets": [ + // "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck", + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ", + // "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU", + // "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN", + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", + // "WAVES", + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu", + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy", + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH", + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H", + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3", + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa", + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" + // ] + // } + quotes = {}; + const priceAssets = this.safeValue (response, 'priceAssets'); + for (let i = 0; i < priceAssets.length; i++) { + quotes[priceAssets[i]] = true; + } + this.options['quotes'] = quotes; + return quotes; + } + } + + async fetchMarkets (params = {}) { + const response = await this.marketGetTickers (); + const result = []; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const baseId = this.safeString (entry, 'amountAssetID'); + const quoteId = this.safeString (entry, 'priceAssetID'); + const id = baseId + '/' + quoteId; + const marketId = this.safeString (entry, 'symbol'); + const [ base, quote ] = marketId.split ('/'); + const symbol = this.safeCurrencyCode (base) + '/' + this.safeCurrencyCode (quote); + const precision = { + 'amount': this.safeInteger (entry, 'amountAssetDecimals'), + 'price': this.safeInteger (entry, 'priceAssetDecimals'), + }; + result.push ({ + 'symbol': symbol, + 'id': id, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': entry, + 'precision': precision, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = this.extend ({ + 'amountAsset': market['baseId'], + 'priceAsset': market['quoteId'], + }, params); + const response = await this.matcherGetMatcherOrderbookAmountAssetPriceAsset (request); + const timestamp = this.safeInteger (response, 'timestamp'); + const bids = this.parseOrderBookSide (this.safeValue (response, 'bids'), market, limit); + const asks = this.parseOrderBookSide (this.safeValue (response, 'asks'), market, limit); + return { + 'bids': bids, + 'asks': asks, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + } + + parseOrderBookSide (bookSide, market = undefined, limit = undefined) { + const precision = market['precision']; + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const amountPrecision = Math.pow (10, precision['amount']); + const difference = precision['amount'] - precision['price']; + const pricePrecision = Math.pow (10, wavesPrecision - difference); + const result = []; + for (let i = 0; i < bookSide.length; i++) { + const entry = bookSide[i]; + const price = this.safeInteger (entry, 'price', 0) / pricePrecision; + const amount = this.safeInteger (entry, 'amount', 0) / amountPrecision; + if ((limit !== undefined) && (i > limit)) { + break; + } + result.push ([price, amount]); + } + return result; + } + + checkRequiredKeys () { + if (this.apiKey === undefined) { + throw new AuthenticationError (this.id + ' requires apiKey credential'); + } + if (this.secret === undefined) { + throw new AuthenticationError (this.id + ' requires secret credential'); + } + let apiKeyBytes = undefined; + let secretKeyBytes = undefined; + try { + apiKeyBytes = this.base58ToBinary (this.apiKey); + } catch (e) { + throw new AuthenticationError (this.id + ' apiKey must be a base58 encoded public key'); + } + try { + secretKeyBytes = this.base58ToBinary (this.secret); + } catch (e) { + throw new AuthenticationError (this.id + ' secret must be a base58 encoded private key'); + } + const hexApiKeyBytes = this.binaryToBase16 (apiKeyBytes); + const hexSecretKeyBytes = this.binaryToBase16 (secretKeyBytes); + if (hexApiKeyBytes.length !== 64) { + throw new AuthenticationError (this.id + ' apiKey must be a base58 encoded public key'); + } + if (hexSecretKeyBytes.length !== 64) { + throw new AuthenticationError (this.id + ' secret must be a base58 encoded private key'); + } + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + const isCancelOrder = path === 'matcher/orders/{wavesAddress}/cancel'; + path = this.implodeParams (path, params); + let url = this.urls['api'][api] + '/' + path; + let queryString = this.urlencode (query); + if ((api === 'private') || (api === 'forward')) { + headers = { + 'Accept': 'application/json', + }; + const accessToken = this.safeString (this.options, 'accessToken'); + if (accessToken) { + headers['Authorization'] = 'Bearer ' + accessToken; + } + if (method === 'POST') { + headers['content-type'] = 'application/json'; + } else { + headers['content-type'] = 'application/x-www-form-urlencoded'; + } + if (isCancelOrder) { + body = this.json ([query['orderId']]); + queryString = ''; + } + if (queryString.length > 0) { + url += '?' + queryString; + } + } else if (api === 'matcher') { + if (method === 'POST') { + headers = { + 'content-type': 'application/json', + }; + body = this.json (query); + } else { + headers = query; + } + } else { + if (method === 'POST') { + headers = { + 'content-type': 'application/json', + }; + body = this.json (query); + } else { + headers = { + 'content-type': 'application/x-www-form-urlencoded', + }; + if (queryString.length > 0) { + url += '?' + queryString; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async getAccessToken () { + if (!this.safeString (this.options, 'accessToken')) { + const prefix = 'ffffff01'; + const expiresDelta = 60 * 60 * 24 * 7; + let seconds = this.sum (this.seconds (), expiresDelta); + seconds = seconds.toString (); + const clientId = 'waves.exchange'; + const message = 'W:' + clientId + ':' + seconds; + const messageHex = this.binaryToBase16 (this.stringToBinary (this.encode (message))); + const payload = prefix + messageHex; + const hexKey = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (payload, hexKey, 'ed25519'); + const request = { + 'grant_type': 'password', + 'scope': 'general', + 'username': this.apiKey, + 'password': seconds + ':' + signature, + 'client_id': clientId, + }; + const response = await this.privatePostOauth2Token (request); + // { access_token: 'eyJhbGciOXJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5MTk3NTA1NywiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkOWI5IiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.B-XwexBnUAzbWknVN68RKT0ZP5w6Qk1SKJ8usL3OIwDEzCUUX9PjW-5TQHmiCRcA4oft8lqXEiCwEoNfsblCo_jTpRo518a1vZkIbHQk0-13Dm1K5ewGxfxAwBk0g49odcbKdjl64TN1yM_PO1VtLVuiTeZP-XF-S42Uj-7fcO-r7AulyQLuTE0uo-Qdep8HDCk47rduZwtJOmhFbCCnSgnLYvKWy3CVTeldsR77qxUY-vy8q9McqeP7Id-_MWnsob8vWXpkeJxaEsw1Fke1dxApJaJam09VU8EB3ZJWpkT7V8PdafIrQGeexx3jhKKxo7rRb4hDV8kfpVoCgkvFan', + // token_type: 'bearer', + // refresh_token: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwiYXRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkXWI5IiwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5Mzk2MjI1OCwiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiM2MzZWRlMTktNjI5My00MTNlLWJmMWUtZTRlZDZlYzUzZTgzIiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.gD1Qj0jfqayfZpBvNY0t3ccMyK5hdbT7dY-_5L6LxwV0Knan4ndEtvygxlTOczmJUKtnA4T1r5GBFgNMZTvtViKZIbqZNysEg2OY8UxwDaF4VPeGJLg_QXEnn8wBeBQdyMafh9UQdwD2ci7x-saM4tOAGmncAygfTDxy80201gwDhfAkAGerb9kL00oWzSJScldxu--pNLDBUEHZt52MSEel10HGrzvZkkvvSh67vcQo5TOGb5KG6nh65UdJCwr41AVz4fbQPP-N2Nkxqy0TE_bqVzZxExXgvcS8TS0Z82T3ijJa_ct7B9wblpylBnvmyj3VycUzufD6uy8MUGq32D', + // expires_in: 604798, + // scope: 'general' } + this.options['accessToken'] = this.safeString (response, 'access_token'); + return this.options['accessToken']; + } + } + + parseTicker (ticker, market = undefined) { + // + // { + // "__type":"pair", + // "data":{ + // "firstPrice":0.00012512, + // "lastPrice":0.00012441, + // "low":0.00012167, + // "high":0.00012768, + // "weightedAveragePrice":0.000124710697407246, + // "volume":209554.26356614, + // "quoteVolume":26.1336583539951, + // "volumeWaves":209554.26356614, + // "txsCount":6655 + // }, + // "amountAsset":"WAVES", + // "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" + // } + // + const timestamp = undefined; + const baseId = this.safeString (ticker, 'amountAsset'); + const quoteId = this.safeString (ticker, 'priceAsset'); + let symbol = undefined; + if ((baseId !== undefined) && (quoteId !== undefined)) { + const marketId = baseId + '/' + quoteId; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const data = this.safeValue (ticker, 'data', {}); + const last = this.safeFloat (data, 'lastPrice'); + const low = this.safeFloat (data, 'low'); + const high = this.safeFloat (data, 'high'); + const vwap = this.safeFloat (data, 'weightedAveragePrice'); + const baseVolume = this.safeFloat (data, 'volume'); + const quoteVolume = this.safeFloat (data, 'quoteVolume'); + const open = this.safeValue (data, 'firstPrice'); + let change = undefined; + let average = undefined; + let percentage = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pairs': market['id'], + }; + const response = await this.publicGetPairs (this.extend (request, params)); + // + // { + // "__type":"list", + // "data":[ + // { + // "__type":"pair", + // "data":{ + // "firstPrice":0.00012512, + // "lastPrice":0.00012441, + // "low":0.00012167, + // "high":0.00012768, + // "weightedAveragePrice":0.000124710697407246, + // "volume":209554.26356614, + // "quoteVolume":26.1336583539951, + // "volumeWaves":209554.26356614, + // "txsCount":6655 + // }, + // "amountAsset":"WAVES", + // "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const ticker = this.safeValue (data, 0, {}); + return this.parseTicker (ticker, market); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'baseId': market['baseId'], + 'quoteId': market['quoteId'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['timeStart'] = since.toString (); + } else { + const allowedCandles = this.safeInteger (this.options, 'allowedCandles', 1440); + const timeframeUnix = this.parseTimeframe (timeframe) * 1000; + const currentTime = Math.floor (this.milliseconds () / timeframeUnix) * timeframeUnix; + const delta = (allowedCandles - 1) * timeframeUnix; + const timeStart = currentTime - delta; + request['timeStart'] = timeStart.toString (); + } + const response = await this.publicGetCandlesBaseIdQuoteId (this.extend (request, params)); + // + // { + // "__type": "list", + // "data": [ + // { + // "__type": "candle", + // "data": { + // "time": "2020-06-09T14:47:00.000Z", + // "open": 0.0250385, + // "close": 0.0250385, + // "high": 0.0250385, + // "low": 0.0250385, + // "volume": 0.01033012, + // "quoteVolume": 0.00025865, + // "weightedAveragePrice": 0.0250385, + // "maxHeight": 2099399, + // "txsCount": 5, + // "timeClose": "2020-06-09T14:47:59.999Z" + // } + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const result = this.parseOHLCVs (data, market, timeframe, since, limit); + let lastClose = undefined; + const length = result.length; + for (let i = 0; i < result.length; i++) { + const j = length - i - 1; + const entry = result[j]; + const open = entry[1]; + if (open === undefined) { + entry[1] = lastClose; + entry[2] = lastClose; + entry[3] = lastClose; + entry[4] = lastClose; + result[j] = entry; + } + lastClose = entry[4]; + } + return result; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // __type: 'candle', + // data: { + // time: '2020-06-05T20:46:00.000Z', + // open: 240.573975, + // close: 240.573975, + // high: 240.573975, + // low: 240.573975, + // volume: 0.01278413, + // quoteVolume: 3.075528, + // weightedAveragePrice: 240.573975, + // maxHeight: 2093895, + // txsCount: 5, + // timeClose: '2020-06-05T20:46:59.999Z' + // } + // } + // + const data = this.safeValue (ohlcv, 'data', {}); + return [ + this.parse8601 (this.safeString (data, 'time')), + this.safeFloat (data, 'open'), + this.safeFloat (data, 'high'), + this.safeFloat (data, 'low'), + this.safeFloat (data, 'close'), + this.safeFloat (data, 'volume', 0), + ]; + } + + async fetchDepositAddress (code, params = {}) { + await this.getAccessToken (); + const supportedCurrencies = await this.privateGetDepositCurrencies (); + const currencies = {}; + const items = this.safeValue (supportedCurrencies, 'items', []); + for (let i = 0; i < items.length; i++) { + const entry = items[i]; + const currencyCode = this.safeString (entry, 'id'); + currencies[currencyCode] = true; + } + if (!(code in currencies)) { + const codes = Object.keys (currencies); + throw new ExchangeError (this.id + ' fetch ' + code + ' deposit address not supported. Currency code must be one of ' + codes.toString ()); + } + const request = this.extend ({ + 'code': code, + }, params); + const response = await this.privateGetDepositAddressesCode (request); + // { + // "type": "deposit_addresses", + // "currency": { + // "type": "deposit_currency", + // "id": "ERGO", + // "waves_asset_id": "5dJj4Hn9t2Ve3tRpNGirUHy4yBK6qdJRAJYV21yPPuGz", + // "decimals": 9, + // "status": "active", + // "allowed_amount": { + // "min": 0.001, + // "max": 100000 + // }, + // "fees": { + // "flat": 0, + // "rate": 0 + // } + // }, + // "deposit_addresses": [ + // "9fRAAQjF8Yqg7qicQCL884zjimsRnuwsSavsM1rUdDaoG8mThku" + // ] + // } + const addresses = this.safeValue (response, 'deposit_addresses'); + const address = this.safeString (addresses, 0); + return { + 'address': address, + 'code': code, + 'tag': undefined, + 'info': response, + }; + } + + async getMatcherPublicKey () { + // this method returns a single string + const matcherPublicKey = this.safeString (this.options, 'matcherPublicKey'); + if (matcherPublicKey) { + return matcherPublicKey; + } else { + const response = await this.matcherGetMatcher (); + // remove trailing quotes from string response + this.options['matcherPublicKey'] = response.slice (1, response.length - 1); + return this.options['matcherPublicKey']; + } + } + + getAssetBytes (currencyId) { + if (currencyId === 'WAVES') { + return this.numberToBE (0, 1); + } else { + return this.binaryConcat (this.numberToBE (1, 1), this.base58ToBinary (currencyId)); + } + } + + getAssetId (currencyId) { + if (currencyId === 'WAVES') { + return ''; + } + return currencyId; + } + + priceToPrecision (symbol, price) { + const market = this.markets[symbol]; + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const difference = market['precision']['amount'] - market['precision']['price']; + return parseInt (parseFloat (this.toWei (price, wavesPrecision - difference))); + } + + amountToPrecision (symbol, amount) { + return parseInt (parseFloat (this.toWei (amount, this.markets[symbol]['precision']['amount']))); + } + + currencyToPrecision (currency, amount) { + return parseInt (parseFloat (this.toWei (amount, this.currencies[currency]['precision']))); + } + + currencyFromPrecision (currency, amount) { + return this.fromWei (amount, this.currencies[currency]['precision']); + } + + priceFromPrecision (symbol, price) { + const market = this.markets[symbol]; + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const difference = market['precision']['amount'] - market['precision']['price']; + return this.fromWei (price, wavesPrecision - difference); + } + + getDefaultExpiry () { + const expiry = this.safeInteger (this.options, 'createOrderDefaultExpiry'); + if (expiry) { + return expiry; + } else { + this.options['createOrderDefaultExpiry'] = 60 * 60 * 24 * 28 * 1000; + return this.options['createOrderDefaultExpiry']; + } + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + await this.loadMarkets (); + const market = this.market (symbol); + const matcherPublicKey = await this.getMatcherPublicKey (); + const amountAsset = this.getAssetId (market['baseId']); + const priceAsset = this.getAssetId (market['quoteId']); + amount = this.amountToPrecision (symbol, amount); + price = this.priceToPrecision (symbol, price); + const orderType = (side === 'buy') ? 0 : 1; + const timestamp = this.milliseconds (); + const expiration = this.sum (timestamp, this.getDefaultExpiry ()); + const settings = await this.matcherGetMatcherSettings (); + // { + // "orderVersions": [ + // 1, + // 2, + // 3 + // ], + // "success": true, + // "matcherPublicKey": "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5", + // "orderFee": { + // "dynamic": { + // "baseFee": 300000, + // "rates": { + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ": 1.0257813, + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH": 0.01268146, + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk": 0.05232404, + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS": 0.00023985, + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8": 19.5967716, + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu": 0.00937073, + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p": 2.19825, + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H": 0.03180264, + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy": 0.00996631, + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3": 0.03254476, + // "WAVES": 1, + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa": 0.03703704 + // } + // } + // }, + // "networkByte": 87, + // "matcherVersion": "2.1.4.8", + // "status": "SimpleResponse", + // "priceAssets": [ + // "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck", + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ", + // "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU", + // "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN", + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", + // "WAVES", + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu", + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy", + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH", + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H", + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3", + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa", + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" + // ] + // } + const orderFee = this.safeValue (settings, 'orderFee'); + const dynamic = this.safeValue (orderFee, 'dynamic'); + const baseMatcherFee = this.safeInteger (dynamic, 'baseFee'); + const wavesMatcherFee = this.currencyFromPrecision ('WAVES', baseMatcherFee); + const rates = this.safeValue (dynamic, 'rates'); + // choose sponsored assets from the list of priceAssets above + const priceAssets = Object.keys (rates); + let matcherFeeAssetId = undefined; + let matcherFee = undefined; + if ('feeAssetId' in params) { + matcherFeeAssetId = params['feeAssetId']; + } else if ('feeAssetId' in this.options) { + matcherFeeAssetId = this.options['feeAssetId']; + } else { + const balances = await this.fetchBalance (); + if (balances['WAVES']['free'] > wavesMatcherFee) { + matcherFeeAssetId = 'WAVES'; + matcherFee = baseMatcherFee; + } else { + for (let i = 0; i < priceAssets.length; i++) { + const assetId = priceAssets[i]; + const code = this.safeCurrencyCode (assetId); + const balance = this.safeValue (this.safeValue (balances, code, {}), 'free'); + const assetFee = rates[assetId] * wavesMatcherFee; + if ((balance !== undefined) && (balance > assetFee)) { + matcherFeeAssetId = assetId; + break; + } + } + } + } + if (matcherFeeAssetId === undefined) { + throw InsufficientFunds (this.id + ' not enough funds to cover the fee, specify feeAssetId in params or options, or buy some WAVES'); + } + if (matcherFee === undefined) { + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const rate = this.safeFloat (rates, matcherFeeAssetId); + const code = this.safeCurrencyCode (matcherFeeAssetId); + const currency = this.currency (code); + const newPrecison = Math.pow (10, wavesPrecision - currency['precision']); + matcherFee = Math.ceil (rate * baseMatcherFee / newPrecison); + } + const byteArray = [ + this.numberToBE (3, 1), + this.base58ToBinary (this.apiKey), + this.base58ToBinary (matcherPublicKey), + this.getAssetBytes (market['baseId']), + this.getAssetBytes (market['quoteId']), + this.numberToBE (orderType, 1), + this.numberToBE (price, 8), + this.numberToBE (amount, 8), + this.numberToBE (timestamp, 8), + this.numberToBE (expiration, 8), + this.numberToBE (matcherFee, 8), + this.getAssetBytes (matcherFeeAssetId), + ]; + const binary = this.binaryConcatArray (byteArray); + const signature = this.eddsa (this.binaryToBase16 (binary), this.binaryToBase16 (this.base58ToBinary (this.secret)), 'ed25519'); + const assetPair = { + 'amountAsset': amountAsset, + 'priceAsset': priceAsset, + }; + const body = { + 'senderPublicKey': this.apiKey, + 'matcherPublicKey': matcherPublicKey, + 'assetPair': assetPair, + 'orderType': side, + 'price': price, + 'amount': amount, + 'timestamp': timestamp, + 'expiration': expiration, + 'matcherFee': matcherFee, + 'signature': signature, + 'version': 3, + }; + if (matcherFeeAssetId !== 'WAVES') { + body['matcherFeeAssetId'] = matcherFeeAssetId; + } + const response = await this.matcherPostMatcherOrderbook (body); + // { success: true, + // message: + // { version: 3, + // id: 'Do7cDJMf2MJuFyorvxNNuzS42MXSGGEq1r1hGDn1PHiS', + // sender: '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH', + // senderPublicKey: 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: + // { amountAsset: null, + // priceAsset: '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' }, + // orderType: 'sell', + // amount: 1, + // price: 100000000, + // timestamp: 1591593117995, + // expiration: 1594012317995, + // matcherFee: 300000, + // matcherFeeAssetId: null, + // signature: '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq', + // proofs: + // [ '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq' ] }, + // status: 'OrderAccepted' } + const value = this.safeValue (response, 'message'); + return this.parseOrder (value, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + await this.getAccessToken (); + const wavesAddress = await this.getWavesAddress (); + const response = await this.forwardPostMatcherOrdersWavesAddressCancel ({ + 'wavesAddress': wavesAddress, + 'orderId': id, + }); + // { + // "success":true, + // "message":[[{"orderId":"EBpJeGM36KKFz5gTJAUKDBm89V8wqxKipSFBdU35AN3c","success":true,"status":"OrderCanceled"}]], + // "status":"BatchCancelCompleted" + // } + const message = this.safeValue (response, 'message'); + const firstMessage = this.safeValue (message, 0); + const firstOrder = this.safeValue (firstMessage, 0); + const returnedId = this.safeString (firstOrder, 'orderId'); + return { + 'info': response, + 'id': returnedId, + 'clientOrderId': undefined, + 'timestamp': undefined, + 'datetime': undefined, + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': undefined, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const timestamp = this.milliseconds (); + const byteArray = [ + this.base58ToBinary (this.apiKey), + this.numberToBE (timestamp, 8), + ]; + const binary = this.binaryConcatArray (byteArray); + const hexSecret = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (this.binaryToBase16 (binary), hexSecret, 'ed25519'); + const request = { + 'Accept': 'application/json', + 'Timestamp': timestamp.toString (), + 'Signature': signature, + 'publicKey': this.apiKey, + 'baseId': market['baseId'], + 'quoteId': market['quoteId'], + }; + const response = await this.matcherGetMatcherOrderbookBaseIdQuoteIdPublicKeyPublicKey (this.extend (request, params)); + // [ { id: '3KicDeWayY2mdrRoYdCkP3gUAoUZUNT1AA6GAtWuPLfa', + // type: 'sell', + // orderType: 'limit', + // amount: 1, + // fee: 300000, + // price: 100000000, + // timestamp: 1591651254076, + // filled: 0, + // filledFee: 0, + // feeAsset: 'WAVES', + // status: 'Accepted', + // assetPair: + // { amountAsset: null, + // priceAsset: '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' }, + // avgWeighedPrice: 0 }, ... ] + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.getAccessToken (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const address = await this.getWavesAddress (); + const request = { + 'address': address, + 'activeOnly': true, + }; + const response = await this.forwardGetMatcherOrdersAddress (request); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.getAccessToken (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const address = await this.getWavesAddress (); + const request = { + 'address': address, + 'closedOnly': true, + }; + const response = await this.forwardGetMatcherOrdersAddress (request); + // [ + // { + // "id": "9aXcxvXai73jbAm7tQNnqaQ2PwUjdmWuyjvRTKAHsw4f", + // "type": "buy", + // "orderType": "limit", + // "amount": 23738330, + // "fee": 300000, + // "price": 3828348334, + // "timestamp": 1591926905636, + // "filled": 23738330, + // "filledFee": 300000, + // "feeAsset": "WAVES", + // "status": "Filled", + // "assetPair": { + // "amountAsset": "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", + // "priceAsset": null + // }, + // "avgWeighedPrice": 3828348334 + // }, ... + // ] + return this.parseOrders (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'Cancelled': 'canceled', + 'Accepted': 'open', + 'Filled': 'closed', + 'PartiallyFilled': 'open', + }; + return this.safeString (statuses, status, status); + } + + getSymbolFromAssetPair (assetPair) { + // a blank string or null can indicate WAVES + const baseId = this.safeString (assetPair, 'amountAsset', 'WAVES'); + const quoteId = this.safeString (assetPair, 'priceAsset', 'WAVES'); + return this.safeCurrencyCode (baseId) + '/' + this.safeCurrencyCode (quoteId); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // version: 3, + // id: 'BshyeHXDfJmTnjTdBYt371jD4yWaT3JTP6KpjpsiZepS', + // sender: '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH', + // senderPublicKey: 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: { + // amountAsset: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu', + // priceAsset: 'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p' + // }, + // orderType: 'buy', + // amount: 10000, + // price: 400000000, + // timestamp: 1599848586891, + // expiration: 1602267786891, + // matcherFee: 3008, + // matcherFeeAssetId: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu', + // signature: '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy', + // proofs: [ + // '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy' + // ] + // } + // + // fetchClosedOrders + // + // { + // id: '81D9uKk2NfmZzfG7uaJsDtxqWFbJXZmjYvrL88h15fk8', + // type: 'buy', + // orderType: 'limit', + // amount: 30000000000, + // filled: 0, + // price: 1000000, + // fee: 300000, + // filledFee: 0, + // feeAsset: 'WAVES', + // timestamp: 1594303779322, + // status: 'Cancelled', + // assetPair: { + // amountAsset: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu', + // priceAsset: 'WAVES' + // }, + // avgWeighedPrice: 0, + // version: 3 + // } + // + const timestamp = this.safeInteger (order, 'timestamp'); + const side = this.safeString2 (order, 'type', 'orderType'); + let type = 'limit'; + if ('type' in order) { + // fetchOrders + type = this.safeString (order, 'orderType', type); + } + const id = this.safeString (order, 'id'); + let filled = this.safeString (order, 'filled'); + let price = this.safeString (order, 'price'); + let amount = this.safeString (order, 'amount'); + const assetPair = this.safeValue (order, 'assetPair'); + let symbol = undefined; + if (assetPair !== undefined) { + symbol = this.getSymbolFromAssetPair (assetPair); + } else if (market !== undefined) { + symbol = market['symbol']; + } + const amountCurrency = this.safeCurrencyCode (this.safeString (assetPair, 'amountAsset', 'WAVES')); + price = this.priceFromPrecision (symbol, price); + amount = this.currencyFromPrecision (amountCurrency, amount); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + filled = this.currencyFromPrecision (amountCurrency, filled); + let remaining = undefined; + if ((filled !== undefined) && (amount !== undefined)) { + remaining = amount - filled; + } + const average = this.priceFromPrecision (symbol, this.safeString (order, 'avgWeighedPrice')); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let fee = undefined; + if ('type' in order) { + const currency = this.safeCurrencyCode (this.safeString (order, 'feeAsset')); + fee = { + 'currency': currency, + 'fee': this.currencyFromPrecision (currency, this.safeInteger (order, 'filledFee')), + }; + } else { + const currency = this.safeCurrencyCode (this.safeString (order, 'matcherFeeAssetId', 'WAVES')); + fee = { + 'currency': currency, + 'fee': this.currencyFromPrecision (currency, this.safeInteger (order, 'matcherFee')), + }; + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async getWavesAddress () { + const cachedAddreess = this.safeString (this.options, 'wavesAddress'); + if (cachedAddreess === undefined) { + const request = { + 'publicKey': this.apiKey, + }; + const response = await this.nodeGetAddressesPublicKeyPublicKey (request); + this.options['wavesAddress'] = this.safeString (response, 'address'); + return this.options['wavesAddress']; + } else { + return cachedAddreess; + } + } + + async fetchBalance (params = {}) { + // makes a lot of different requests to get all the data + // in particular: + // fetchMarkets, getWavesAddress, + // getTotalBalance (doesn't include waves), getReservedBalance (doesn't include waves) + // getReservedBalance (includes WAVES) + // I couldn't find another way to get all the data + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + await this.loadMarkets (); + const wavesAddress = await this.getWavesAddress (); + const request = { + 'address': wavesAddress, + }; + const totalBalance = await this.nodeGetAssetsBalanceAddress (request); + // { + // "address": "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH", + // "balances": [ + // { + // "assetId": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "balance": 1177200, + // "reissuable": false, + // "minSponsoredAssetFee": 7420, + // "sponsorBalance": 47492147189709, + // "quantity": 999999999775381400, + // "issueTransaction": { + // "senderPublicKey": "BRnVwSVctnV8pge5vRpsJdWnkjWEJspFb6QvrmZvu3Ht", + // "quantity": 1000000000000000000, + // "fee": 100400000, + // "description": "Neutrino USD", + // "type": 3, + // "version": 2, + // "reissuable": false, + // "script": null, + // "sender": "3PC9BfRwJWWiw9AREE2B3eWzCks3CYtg4yo", + // "feeAssetId": null, + // "chainId": 87, + // "proofs": [ + // "3HNpbVkgP69NWSeb9hGYauiQDaXrRXh3tXFzNsGwsAAXnFrA29SYGbLtziW9JLpXEq7qW1uytv5Fnm5XTUMB2BxU" + // ], + // "assetId": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "decimals": 6, + // "name": "USD-N", + // "id": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "timestamp": 1574429393962 + // } + // } + // ] + // } + const balances = this.safeValue (totalBalance, 'balances'); + const result = {}; + for (let i = 0; i < balances.length; i++) { + const entry = balances[i]; + const issueTransaction = this.safeValue (entry, 'issueTransaction'); + const decimals = this.safeInteger (issueTransaction, 'decimals'); + const currencyId = this.safeString (entry, 'assetId'); + const balance = this.safeFloat (entry, 'balance'); + let code = undefined; + if (currencyId in this.currencies_by_id) { + code = this.safeCurrencyCode (currencyId); + result[code] = this.account (); + result[code]['total'] = this.fromWei (balance, decimals); + } + } + const timestamp = this.milliseconds (); + const byteArray = [ + this.base58ToBinary (this.apiKey), + this.numberToBE (timestamp, 8), + ]; + const binary = this.binaryConcatArray (byteArray); + const hexSecret = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (this.binaryToBase16 (binary), hexSecret, 'ed25519'); + const matcherRequest = { + 'publicKey': this.apiKey, + 'signature': signature, + 'timestamp': timestamp.toString (), + }; + const reservedBalance = await this.matcherGetMatcherBalanceReservedPublicKey (matcherRequest); + // { WAVES: 200300000 } + const reservedKeys = Object.keys (reservedBalance); + for (let i = 0; i < reservedKeys.length; i++) { + const currencyId = reservedKeys[i]; + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + result[code] = this.account (); + } + const amount = this.safeFloat (reservedBalance, currencyId); + result[code]['used'] = this.currencyFromPrecision (code, amount); + } + const wavesRequest = { + 'address': wavesAddress, + }; + const wavesTotal = await this.nodeGetAddressesBalanceAddress (wavesRequest); + // { + // "address": "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH", + // "confirmations": 0, + // "balance": 909085978 + // } + result['WAVES'] = this.safeValue (result, 'WAVES', {}); + result['WAVES']['total'] = this.currencyFromPrecision ('WAVES', this.safeFloat (wavesTotal, 'balance')); + const codes = Object.keys (result); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + if (this.safeValue (result[code], 'used') === undefined) { + result[code]['used'] = 0.0; + } + } + return this.parseBalance (result); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const address = await this.getWavesAddress (); + const request = { + 'sender': address, + 'amountAsset': market['baseId'], + 'priceAsset': market['quoteId'], + }; + const response = await this.publicGetTransactionsExchange (request); + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amountAsset': market['baseId'], + 'priceAsset': market['quoteId'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timeStart'] = since; + } + const response = await this.publicGetTransactionsExchange (request); + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // { __type: 'transaction', + // data: + // { id: 'HSdruioHqvYHeyn9hhyoHdRWPB2bFA8ujeCPZMK6992c', + // timestamp: '2020-06-09T19:34:51.897Z', + // height: 2099684, + // type: 7, + // version: 2, + // proofs: + // [ '26teDHERQgwjjHqEn4REcDotNG8M21xjou3X42XuDuCvrRkQo6aPyrswByH3UrkWG8v27ZAaVNzoxDg4teNcLtde' ], + // fee: 0.003, + // sender: '3PEjHv3JGjcWNpYEEkif2w8NXV4kbhnoGgu', + // senderPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // buyMatcherFee: 0.00299999, + // sellMatcherFee: 0.00299999, + // price: 0.00012003, + // amount: 60.80421562, + // order1: + // { id: 'CBRwP3ar4oMvvpUiGyfxc1syh41488SDi2GkrjuBDegv', + // senderPublicKey: 'DBXSHBz96NFsMu7xh4fi2eT9ZnyxefAHXsMxUayzgC6a', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: [Object], + // orderType: 'buy', + // price: 0.00012003, + // sender: '3PJfFRgVuJ47UY4ckb74EGzEBzkHXtmG1LA', + // amount: 60.80424773, + // timestamp: '2020-06-09T19:34:51.885Z', + // expiration: '2020-06-10T12:31:31.885Z', + // matcherFee: 0.003, + // signature: '4cA3ZAb3XAEEXaFG7caqpto5TRbpR5PkhZpxoNQZ9ZReNvjuJQs5a3THnumv7rcqmVUiVtuHAgk2f67ANcqtKyJ8', + // matcherFeeAssetId: null }, + // order2: + // { id: 'CHJSLQ6dfSPs6gu2mAegrMUcRiDEDqaj2GKfvptMjS3M', + // senderPublicKey: '3RUC4NGFZm9H8VJhSSjJyFLdiE42qNiUagDcZPwjgDf8', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: [Object], + // orderType: 'sell', + // price: 0.00012003, + // sender: '3P9vKoQpMZtaSkHKpNh977YY9ZPzTuntLAq', + // amount: 60.80424773, + // timestamp: '2020-06-09T19:34:51.887Z', + // expiration: '2020-06-10T12:31:31.887Z', + // matcherFee: 0.003, + // signature: '3SFyrcqzou2ddZyNisnLYaGhLt5qRjKxH8Nw3s4T5U7CEKGX9DDo8dS27RgThPVGbYF1rYET1FwrWoQ2UFZ6SMTR', + // matcherFeeAssetId: null } } } + const data = this.safeValue (trade, 'data'); + const datetime = this.safeString (data, 'timestamp'); + const timestamp = this.parse8601 (datetime); + const id = this.safeString (data, 'id'); + const price = this.safeFloat (data, 'price'); + const amount = this.safeFloat (data, 'amount'); + const order1 = this.safeValue (data, 'order1'); + const order2 = this.safeValue (data, 'order2'); + let order = undefined; + // order2 arrived after order1 + if (this.safeString (order1, 'senderPublicKey') === this.apiKey) { + order = order1; + } else { + order = order2; + } + let symbol = undefined; + const assetPair = this.safeValue (order, 'assetPair'); + if (assetPair !== undefined) { + symbol = this.getSymbolFromAssetPair (assetPair); + } else if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (order, 'orderType'); + const orderId = this.safeString (order, 'id'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const fee = { + 'cost': this.safeFloat (data, 'fee'), + 'currency': this.safeCurrencyCode (this.safeString (order, 'matcherFeeAssetId', 'WAVES')), + }; + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': datetime, + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + const errorCode = this.safeString (response, 'error'); + const success = this.safeValue (response, 'success', true); + const Exception = this.safeValue (this.exceptions, errorCode); + if (Exception !== undefined) { + const message = this.safeString (response, 'message'); + throw new Exception (this.id + ' ' + message); + } + const message = this.safeString (response, 'message'); + if (message === 'Validation Error') { + throw new BadRequest (this.id + ' ' + body); + } + if (!success) { + throw new ExchangeError (this.id + ' ' + body); + } + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // currently only works for BTC and WAVES + if (code !== 'WAVES') { + const supportedCurrencies = await this.privateGetWithdrawCurrencies (); + const currencies = {}; + const items = this.safeValue (supportedCurrencies, 'items', []); + for (let i = 0; i < items.length; i++) { + const entry = items[i]; + const currencyCode = this.safeString (entry, 'id'); + currencies[currencyCode] = true; + } + if (!(code in currencies)) { + const codes = Object.keys (currencies); + throw new ExchangeError (this.id + ' fetch ' + code + ' withdrawals are not supported. Currency code must be one of ' + codes.toString ()); + } + } + await this.loadMarkets (); + const hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + const set = {}; + for (let i = 0; i < hexChars.length; i++) { + const key = hexChars[i]; + set[key] = true; + } + let isErc20 = true; + const noPrefix = this.remove0xPrefix (address); + const lower = noPrefix.toLowerCase (); + for (let i = 0; i < lower.length; i++) { + const character = lower[i]; + if (!(character in set)) { + isErc20 = false; + break; + } + } + await this.getAccessToken (); + let proxyAddress = undefined; + if (code === 'WAVES' && !isErc20) { + proxyAddress = address; + } else { + const withdrawAddressRequest = { + 'address': address, + 'currency': code, + }; + const withdrawAddress = await this.privateGetWithdrawAddressesCurrencyAddress (withdrawAddressRequest); + const currency = this.safeValue (withdrawAddress, 'currency'); + const allowedAmount = this.safeValue (currency, 'allowed_amount'); + const minimum = this.safeFloat (allowedAmount, 'min'); + if (amount <= minimum) { + throw new BadRequest (this.id + ' ' + code + ' withdraw failed, amount ' + amount.toString () + ' must be greater than the minimum allowed amount of ' + minimum.toString ()); + } + // { + // "type": "withdrawal_addresses", + // "currency": { + // "type": "withdrawal_currency", + // "id": "BTC", + // "waves_asset_id": "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", + // "decimals": 8, + // "status": "active", + // "allowed_amount": { + // "min": 0.001, + // "max": 20 + // }, + // "fees": { + // "flat": 0.001, + // "rate": 0 + // } + // }, + // "proxy_addresses": [ + // "3P3qqmkiLwNHB7x1FeoE8bvkRtULwGpo9ga" + // ] + // } + const proxyAddresses = this.safeValue (withdrawAddress, 'proxy_addresses', []); + proxyAddress = this.safeString (proxyAddresses, 0); + } + const fee = this.safeInteger (this.options, 'withdrawFeeWAVES', 100000); // 0.001 WAVES + const feeAssetId = 'WAVES'; + const type = 4; // transfer + const version = 2; + const amountInteger = this.currencyToPrecision (code, amount); + const currency = this.currency (code); + const timestamp = this.milliseconds (); + const byteArray = [ + this.numberToBE (4, 1), + this.numberToBE (2, 1), + this.base58ToBinary (this.apiKey), + this.getAssetBytes (currency['id']), + this.getAssetBytes (feeAssetId), + this.numberToBE (timestamp, 8), + this.numberToBE (amountInteger, 8), + this.numberToBE (fee, 8), + this.base58ToBinary (proxyAddress), + this.numberToBE (0, 2), + ]; + const binary = this.binaryConcatArray (byteArray); + const hexSecret = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (this.binaryToBase16 (binary), hexSecret, 'ed25519'); + const request = { + 'senderPublicKey': this.apiKey, + 'amount': amountInteger, + 'fee': fee, + 'type': type, + 'version': version, + 'attachment': '', + 'feeAssetId': this.getAssetId (feeAssetId), + 'proofs': [ + signature, + ], + 'assetId': this.getAssetId (currency['id']), + 'recipient': proxyAddress, + 'timestamp': timestamp, + 'signature': signature, + }; + return await this.nodePostTransactionsBroadcast (request); + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],174:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeNotAvailable, ExchangeError, DDoSProtection, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class whitebit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'whitebit', + 'name': 'WhiteBit', + 'version': 'v2', + 'countries': [ 'EE' ], + 'rateLimit': 500, + 'has': { + 'cancelOrder': false, + 'CORS': false, + 'createDepositAddress': false, + 'createLimitOrder': false, + 'createMarketOrder': false, + 'createOrder': false, + 'deposit': false, + 'editOrder': false, + 'fetchBalance': false, + 'fetchBidsAsks': false, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFees': true, + 'privateAPI': false, + 'publicAPI': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/66732963-8eb7dd00-ee66-11e9-849b-10d9282bb9e0.jpg', + 'api': { + 'web': 'https://whitebit.com/', + 'publicV2': 'https://whitebit.com/api/v2/public', + 'publicV1': 'https://whitebit.com/api/v1/public', + }, + 'www': 'https://www.whitebit.com', + 'doc': 'https://documenter.getpostman.com/view/7473075/Szzj8dgv?version=latest', + 'fees': 'https://whitebit.com/fee-schedule', + 'referral': 'https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963', + }, + 'api': { + 'web': { + 'get': [ + 'v1/healthcheck', + ], + }, + 'publicV1': { + 'get': [ + 'markets', + 'tickers', + 'ticker', + 'symbols', + 'depth/result', + 'history', + 'kline', + ], + }, + 'publicV2': { + 'get': [ + 'markets', + 'ticker', + 'assets', + 'fee', + 'depth/{market}', + 'trades/{market}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + }, + 'options': { + 'fetchTradesMethod': 'fetchTradesV1', + }, + 'exceptions': { + 'exact': { + '503': ExchangeNotAvailable, // {"response":null,"status":503,"errors":{"message":[""]},"notification":null,"warning":null,"_token":null} + }, + 'broad': { + 'Market is not available': BadSymbol, // {"success":false,"message":{"market":["Market is not available"]},"result":[]} + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicV2GetMarkets (params); + // + // { + // "success":true, + // "message":"", + // "result":[ + // { + // "name":"BTC_USD", + // "moneyPrec":"2", + // "stock":"BTC", + // "money":"USD", + // "stockPrec":"6", + // "feePrec":"4", + // "minAmount":"0.001", + // "tradesEnabled":true, + // "minTotal":"0.001" + // } + // ] + // } + // + const markets = this.safeValue (response, 'result'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'stock'); + const quoteId = this.safeString (market, 'money'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = this.safeValue (market, 'tradesEnabled'); + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': { + 'amount': this.safeInteger (market, 'stockPrec'), + 'price': this.safeInteger (market, 'moneyPrec'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minTotal'), + 'max': undefined, + }, + }, + }; + result.push (entry); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicV2GetAssets (params); + // + // { + // "success":true, + // "message":"", + // "result":{ + // "BTC":{ + // "id":"4f37bc79-f612-4a63-9a81-d37f7f9ff622", + // "lastUpdateTimestamp":"2019-10-12T04:40:05.000Z", + // "name":"Bitcoin", + // "canWithdraw":true, + // "canDeposit":true, + // "minWithdrawal":"0.001", + // "maxWithdrawal":"0", + // "makerFee":"0.1", + // "takerFee":"0.1" + // } + // } + // } + // + const currencies = this.safeValue (response, 'result'); + const ids = Object.keys (currencies); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = currencies[id]; + // breaks down in Python due to utf8 encoding issues on the exchange side + // const name = this.safeString (currency, 'name'); + const canDeposit = this.safeValue (currency, 'canDeposit', true); + const canWithdraw = this.safeValue (currency, 'canWithdraw', true); + const active = canDeposit && canWithdraw; + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, // the original payload + 'name': undefined, // see the comment above + 'active': active, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawal'), + 'max': this.safeFloat (currency, 'maxWithdrawal'), + }, + }, + }; + } + return result; + } + + async fetchTradingFees (params = {}) { + const response = await this.publicV2GetFee (params); + const fees = this.safeValue (response, 'result'); + return { + 'maker': this.safeFloat (fees, 'makerFee'), + 'taker': this.safeFloat (fees, 'takerFee'), + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicV1GetTicker (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result": { + // "bid":"0.021979", + // "ask":"0.021996", + // "open":"0.02182", + // "high":"0.022039", + // "low":"0.02161", + // "last":"0.021987", + // "volume":"2810.267", + // "deal":"61.383565474", + // "change":"0.76", + // }, + // } + // + const ticker = this.safeValue (response, 'result', {}); + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "bid":"0.021979", + // "ask":"0.021996", + // "open":"0.02182", + // "high":"0.022039", + // "low":"0.02161", + // "last":"0.021987", + // "volume":"2810.267", + // "deal":"61.383565474", + // "change":"0.76", + // } + // + // fetchTickers v1 + // + // { + // "at":1571022144, + // "ticker": { + // "bid":"0.022024", + // "ask":"0.022042", + // "low":"0.02161", + // "high":"0.022062", + // "last":"0.022036", + // "vol":"2813.503", + // "deal":"61.457279261", + // "change":"0.95" + // } + // } + // + const timestamp = this.safeTimestamp (ticker, 'at', this.milliseconds ()); + ticker = this.safeValue (ticker, 'ticker', ticker); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'change'); + let change = undefined; + if (percentage !== undefined) { + change = this.numberToString (percentage * 0.01); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'deal'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicV1GetTickers (params); + // + // { + // "success":true, + // "message":"", + // "result": { + // "ETH_BTC": { + // "at":1571022144, + // "ticker": { + // "bid":"0.022024", + // "ask":"0.022042", + // "low":"0.02161", + // "high":"0.022062", + // "last":"0.022036", + // "vol":"2813.503", + // "deal":"61.457279261", + // "change":"0.95" + // } + // }, + // }, + // } + // + const data = this.safeValue (response, 'result'); + const marketIds = Object.keys (data); + const result = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const market = this.safeMarket (marketId); + const ticker = this.parseTicker (data[marketId], market); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 50, maximum = 100 + } + const response = await this.publicV2GetDepthMarket (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result":{ + // "lastUpdateTimestamp":"2019-10-14T03:15:47.000Z", + // "asks":[ + // ["0.02204","2.03"], + // ["0.022041","2.492"], + // ["0.022042","2.254"], + // ], + // "bids":[ + // ["0.022018","2.327"], + // ["0.022017","1.336"], + // ["0.022015","2.089"], + // ], + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const timestamp = this.parse8601 (this.safeString (result, 'lastUpdateTimestamp')); + return this.parseOrderBook (result, timestamp); + } + + async fetchTradesV1 (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'lastId': 1, // todo add since + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 50, maximum = 10000 + } + const response = await this.publicV1GetHistory (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result":[ + // { + // "id":11887426, + // "type":"buy", + // "time":1571023057.413769, + // "amount":"0.171", + // "price":"0.022052" + // } + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseTrades (result, market, since, limit); + } + + async fetchTradesV2 (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 50, maximum = 10000 + } + const response = await this.publicV2GetTradesMarket (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result": [ + // { + // "tradeId":11903347, + // "price":"0.022044", + // "volume":"0.029", + // "time":"2019-10-14T06:30:57.000Z", + // "isBuyerMaker":false + // }, + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseTrades (result, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + const method = this.safeString (this.options, 'fetchTradesMethod', 'fetchTradesV2'); + return await this[method] (symbol, since, limit, params); + } + + parseTrade (trade, market = undefined) { + // + // fetchTradesV1 + // + // { + // "id":11887426, + // "type":"buy", + // "time":1571023057.413769, + // "amount":"0.171", + // "price":"0.022052" + // } + // + // fetchTradesV2 + // + // { + // "tradeId":11903347, + // "price":"0.022044", + // "volume":"0.029", + // "time":"2019-10-14T06:30:57.000Z", + // "isBuyerMaker":false + // } + // + let timestamp = this.safeValue (trade, 'time'); + if (typeof timestamp === 'string') { + timestamp = this.parse8601 (timestamp); + } else { + timestamp = parseInt (timestamp * 1000); + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'amount', 'volume'); + const id = this.safeString2 (trade, 'id', 'tradeId'); + let side = this.safeString (trade, 'type'); + if (side === undefined) { + const isBuyerMaker = this.safeValue (trade, 'isBuyerMaker'); + side = isBuyerMaker ? 'buy' : 'sell'; + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let cost = undefined; + if (amount !== undefined && price !== undefined) { + cost = amount * price; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': undefined, + 'type': undefined, + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + const maxLimit = 1440; + if (limit === undefined) { + limit = maxLimit; + } + limit = Math.min (limit, maxLimit); + const start = parseInt (since / 1000); + const duration = this.parseTimeframe (timeframe); + const end = this.sum (start, duration * limit); + request['start'] = start; + request['end'] = end; + } + if (limit !== undefined) { + request['limit'] = limit; // max 1440 + } + const response = await this.publicV1GetKline (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result":[ + // [1591488000,"0.025025","0.025025","0.025029","0.025023","6.181","0.154686629"], + // [1591488060,"0.025028","0.025033","0.025035","0.025026","8.067","0.201921167"], + // [1591488120,"0.025034","0.02505","0.02505","0.025034","20.089","0.503114696"], + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591488000, + // "0.025025", + // "0.025025", + // "0.025029", + // "0.025023", + // "6.181", + // "0.154686629" + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), // timestamp + this.safeFloat (ohlcv, 1), // open + this.safeFloat (ohlcv, 3), // high + this.safeFloat (ohlcv, 4), // low + this.safeFloat (ohlcv, 2), // close + this.safeFloat (ohlcv, 5), // volume + ]; + } + + async fetchStatus (params = {}) { + const response = await this.webGetV1Healthcheck (params); + const status = this.safeInteger (response, 'status'); + let formattedStatus = 'ok'; + if (status === 503) { + formattedStatus = 'maintenance'; + } + this.status = this.extend (this.status, { + 'status': formattedStatus, + 'updated': this.milliseconds (), + }); + return this.status; + } + + sign (path, api = 'publicV1', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 418) || (code === 429)) { + throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body); + } + if (code === 404) { + throw new ExchangeError (this.id + ' ' + code.toString () + ' endpoint not found'); + } + if (response !== undefined) { + const success = this.safeValue (response, 'success'); + if (!success) { + const feedback = this.id + ' ' + body; + const status = this.safeString (response, 'status'); + if (typeof status === 'string') { + this.throwExactlyMatchedException (this.exceptions['exact'], status, feedback); + } + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],175:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, NotSupported, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class xbtce extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'xbtce', + 'name': 'xBTCe', + 'countries': [ 'RU' ], + 'rateLimit': 2000, // responses are cached every 2 seconds + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOHLCV': false, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'referral': 'https://xbtce.com/?agent=XX97BTCXXXG687021000B', + 'logo': 'https://user-images.githubusercontent.com/1294454/28059414-e235970c-662c-11e7-8c3a-08e31f78684b.jpg', + 'api': 'https://cryptottlivewebapi.xbtce.net:8443/api', + 'www': 'https://www.xbtce.com', + 'doc': [ + 'https://www.xbtce.com/tradeapi', + 'https://support.xbtce.info/Knowledgebase/Article/View/52/25/xbtce-exchange-api', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'currency', + 'currency/{filter}', + 'level2', + 'level2/{filter}', + 'quotehistory/{symbol}/{periodicity}/bars/ask', + 'quotehistory/{symbol}/{periodicity}/bars/bid', + 'quotehistory/{symbol}/level2', + 'quotehistory/{symbol}/ticks', + 'symbol', + 'symbol/{filter}', + 'tick', + 'tick/{filter}', + 'ticker', + 'ticker/{filter}', + 'tradesession', + ], + }, + 'private': { + 'get': [ + 'tradeserverinfo', + 'tradesession', + 'currency', + 'currency/{filter}', + 'level2', + 'level2/{filter}', + 'symbol', + 'symbol/{filter}', + 'tick', + 'tick/{filter}', + 'account', + 'asset', + 'asset/{id}', + 'position', + 'position/{id}', + 'trade', + 'trade/{id}', + 'quotehistory/{symbol}/{periodicity}/bars/ask', + 'quotehistory/{symbol}/{periodicity}/bars/ask/info', + 'quotehistory/{symbol}/{periodicity}/bars/bid', + 'quotehistory/{symbol}/{periodicity}/bars/bid/info', + 'quotehistory/{symbol}/level2', + 'quotehistory/{symbol}/level2/info', + 'quotehistory/{symbol}/periodicities', + 'quotehistory/{symbol}/ticks', + 'quotehistory/{symbol}/ticks/info', + 'quotehistory/cache/{symbol}/{periodicity}/bars/ask', + 'quotehistory/cache/{symbol}/{periodicity}/bars/bid', + 'quotehistory/cache/{symbol}/level2', + 'quotehistory/cache/{symbol}/ticks', + 'quotehistory/symbols', + 'quotehistory/version', + ], + 'post': [ + 'trade', + 'tradehistory', + ], + 'put': [ + 'trade', + ], + 'delete': [ + 'trade', + ], + }, + }, + 'commonCurrencies': { + 'DSH': 'DASH', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.privateGetSymbol (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'Symbol'); + const baseId = this.safeString (market, 'MarginCurrency'); + const quoteId = this.safeString (market, 'ProfitCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + symbol = market['IsTradeAllowed'] ? symbol : id; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privateGetAsset (params); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'Currency'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'FreeAmount'), + 'used': this.safeFloat (balance, 'LockedAmount'), + 'total': this.safeFloat (balance, 'Amount'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'filter': market['id'], + }; + const response = await this.privateGetLevel2Filter (this.extend (request, params)); + const orderbook = response[0]; + const timestamp = this.safeInteger (orderbook, 'Timestamp'); + return this.parseOrderBook (orderbook, timestamp, 'Bids', 'Asks', 'Price', 'Volume'); + } + + parseTicker (ticker, market = undefined) { + let timestamp = 0; + let last = undefined; + if ('LastBuyTimestamp' in ticker) { + if (timestamp < ticker['LastBuyTimestamp']) { + timestamp = ticker['LastBuyTimestamp']; + last = ticker['LastBuyPrice']; + } + } + if ('LastSellTimestamp' in ticker) { + if (timestamp < ticker['LastSellTimestamp']) { + timestamp = ticker['LastSellTimestamp']; + last = ticker['LastSellPrice']; + } + } + if (!timestamp) { + timestamp = this.milliseconds (); + } + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': ticker['DailyBestBuyPrice'], + 'low': ticker['DailyBestSellPrice'], + 'bid': ticker['BestBid'], + 'bidVolume': undefined, + 'ask': ticker['BestAsk'], + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': ticker['DailyTradedTotalVolume'], + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const tickers = this.indexBy (response, 'Symbol'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let market = undefined; + let symbol = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } else { + const baseId = id.slice (0, 3); + const quoteId = id.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'filter': market['id'], + }; + const response = await this.publicGetTickerFilter (this.extend (request, params)); + const length = response.length; + if (length < 1) { + throw new ExchangeError (this.id + ' fetchTicker returned empty response, xBTCe public API error'); + } + const tickers = this.indexBy (response, 'Symbol'); + const ticker = tickers[market['id']]; + return this.parseTicker (ticker, market); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // no method for trades? + return await this.privateGetTrade (params); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeInteger (ohlcv, 'Timestamp'), + this.safeFloat (ohlcv, 'Open'), + this.safeFloat (ohlcv, 'High'), + this.safeFloat (ohlcv, 'Low'), + this.safeFloat (ohlcv, 'Close'), + this.safeFloat (ohlcv, 'Volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + // let minutes = parseInt (timeframe / 60); // 1 minute by default + // let periodicity = minutes.toString (); + // await this.loadMarkets (); + // let market = this.market (symbol); + // if (since === undefined) + // since = this.seconds () - 86400 * 7; // last day by defulat + // if (limit === undefined) + // limit = 1000; // default + // let response = await this.privateGetQuotehistorySymbolPeriodicityBarsBid (this.extend ({ + // 'symbol': market['id'], + // 'periodicity': periodicity, + // 'timestamp': since, + // 'count': limit, + // }, params)); + // return this.parseOHLCVs (response['Bars'], market, timeframe, since, limit); + throw new NotSupported (this.id + ' fetchOHLCV is disabled by the exchange'); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'pair': this.marketId (symbol), + 'type': side, + 'amount': amount, + 'rate': price, + }; + const response = await this.privatePostTrade (this.extend (request, params)); + return { + 'info': response, + 'id': response['Id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'Type': 'Cancel', + 'Id': id, + }; + return await this.privateDeleteTrade (this.extend (request, params)); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (!this.apiKey) { + throw new AuthenticationError (this.id + ' requires apiKey for all requests, their public API is always busy'); + } + if (!this.uid) { + throw new AuthenticationError (this.id + ' requires uid property for authentication and trading, their public API is always busy'); + } + let url = this.urls['api'] + '/' + this.version; + if (api === 'public') { + url += '/' + api; + } + url += '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + headers = { 'Accept-Encoding': 'gzip, deflate' }; + const nonce = this.nonce ().toString (); + if (method === 'POST') { + if (Object.keys (query).length) { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + } else { + url += '?' + this.urlencode (query); + } + } + let auth = nonce + this.uid + this.apiKey + method + url; + if (body) { + auth += body; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); + const credentials = this.uid + ':' + this.apiKey + ':' + nonce + ':' + signature; + headers['Authorization'] = 'HMAC ' + credentials; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],176:[function(require,module,exports){ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, InsufficientFunds, InvalidAddress, BadSymbol, InvalidOrder } = require ('./base/errors'); + +module.exports = class xena extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'xena', + 'name': 'Xena Exchange', + 'countries': [ 'VC', 'UK' ], + 'rateLimit': 100, + 'certified': true, + 'has': { + 'CORS': false, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createDepositAddress': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg', + 'api': { + 'public': 'https://trading.xena.exchange/api', + 'private': 'https://api.xena.exchange', + }, + 'www': 'https://xena.exchange', + 'doc': 'https://support.xena.exchange/support/solutions/44000808700', + 'fees': 'https://trading.xena.exchange/en/platform-specification/fee-schedule', + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + }, + 'api': { + 'public': { + 'get': [ + 'common/currencies', + 'common/instruments', + 'common/features', + 'common/commissions', + 'common/news', + 'market-data/candles/{marketId}/{timeframe}', + 'market-data/market-watch', + 'market-data/dom/{symbol}', + 'market-data/candles/{symbol}/{timeframe}', + 'market-data/trades/{symbol}', + 'market-data/server-time', + 'market-data/v2/candles/{symbol}/{timeframe}', + 'market-data/v2/trades/{symbol}', + 'market-data/v2/dom/{symbol}/', + 'market-data/v2/server-time', + ], + }, + 'private': { + 'get': [ + 'trading/accounts/{accountId}/order', + 'trading/accounts/{accountId}/active-orders', + 'trading/accounts/{accountId}/last-order-statuses', + 'trading/accounts/{accountId}/positions', + 'trading/accounts/{accountId}/positions-history', + 'trading/accounts/{accountId}/margin-requirements', + 'trading/accounts', + 'trading/accounts/{accountId}/balance', + 'trading/accounts/{accountId}/trade-history', + // 'trading/accounts/{accountId}/trade-history?symbol=BTC/USDT&client_order_id=EMBB8Veke&trade_id=220143254', + 'transfers/accounts', + 'transfers/accounts/{accountId}', + 'transfers/accounts/{accountId}/deposit-address/{currency}', + 'transfers/accounts/{accountId}/deposits', + 'transfers/accounts/{accountId}/trusted-addresses', + 'transfers/accounts/{accountId}/withdrawals', + 'transfers/accounts/{accountId}/balance-history', + // 'transfers/accounts/{accountId}/balance-history?currency={currency}&from={time}&to={time}&kind={kind}&kind={kind}', + // 'transfers/accounts/{accountId}/balance-history?page={page}&limit={limit}', + // 'transfers/accounts/{accountId}/balance-history?txid=3e1db982c4eed2d6355e276c5bae01a52a27c9cef61574b0e8c67ee05fc26ccf', + ], + 'post': [ + 'trading/order/new', + 'trading/order/heartbeat', + 'trading/order/cancel', + 'trading/order/mass-cancel', + 'trading/order/replace', + 'trading/position/maintenance', + 'transfers/accounts/{accountId}/withdrawals', + 'transfers/accounts/{accountId}/deposit-address/{currency}', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.0005, + 'taker': 0.001, + 'tierBased': true, + 'percentage': true, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'exceptions': { + 'exact': { + 'Validation failed': BadRequest, + 'Unknown derivative symbol': BadSymbol, // {"error":"Unknown derivative symbol"} + 'Unknown account': BadRequest, // {"error":"Unknown account"} + 'Wrong TransactTime': BadRequest, // {"error":"Wrong TransactTime"} + 'ClOrdId is empty': BadRequest, // {"error":"ClOrdId is empty"} + }, + 'broad': { + 'Invalid aggregation ratio or depth': BadRequest, + 'address': InvalidAddress, + 'Money not enough': InsufficientFunds, + 'parse error': BadRequest, + 'Not enough': InsufficientFunds, // {"error":"Not enough free margin"} + }, + }, + 'options': { + 'defaultType': 'margin', // 'margin', + 'accountId': undefined, // '1012838157', + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetMarketDataV2ServerTime (params); + // + // { + // "msgType":"0", + // "transactTime":1594774454112817637 + // } + // + const transactTime = this.safeInteger (response, 'transactTime'); + return parseInt (transactTime / 1000000); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetCommonInstruments (params); + // + // [ + // { + // "id":"ETHUSD_3M_250920", + // "type":"Margin", + // "marginType":"XenaFuture", + // "symbol":"ETHUSD_3M_250920", + // "baseCurrency":"ETH", + // "quoteCurrency":"USD", + // "settlCurrency":"BTC", + // "tickSize":2, + // "minOrderQuantity":"1", + // "orderQtyStep":"1", + // "limitOrderMaxDistance":"10", + // "priceInputMask":"0000.00", + // "enabled":true, + // "liquidationMaxDistance":"0.01", + // "contractValue":"1", + // "contractCurrency":"BTC", + // "lotSize":"1", + // "tickValue":"0.00000001", // linear contracts only + // "maxOrderQty":"175000", + // "maxPosVolume":"1750000", + // "mark":".ETHUSD_3M_250920", + // "underlying":".ETHUSD_TWAP", + // "openInterest":".ETHUSD_3M_250920_OpenInterest", + // "floatingPL":"BidAsk", // perpetual contracts only + // "addUvmToFreeMargin":"ProfitAndLoss", + // "margin":{ + // "netting":"PositionsAndOrders", + // "rates":[ + // {"maxVolume":"175000","initialRate":"0.05","maintenanceRate":"0.0125"}, + // {"maxVolume":"350000","initialRate":"0.1","maintenanceRate":"0.025"}, + // {"maxVolume":"500000","initialRate":"0.2","maintenanceRate":"0.05"}, + // {"maxVolume":"750000","initialRate":"0.3","maintenanceRate":"0.075"}, + // {"maxVolume":"1050000","initialRate":"0.4","maintenanceRate":"0.1"}, + // {"maxVolume":"1400000","initialRate":"0.5","maintenanceRate":"0.125"}, + // {"maxVolume":"1750000","initialRate":"1","maintenanceRate":"0.25"} + // ], + // "rateMultipliers":{ + // "LimitBuy":"1", + // "LimitSell":"1", + // "Long":"1", + // "MarketBuy":"1", + // "MarketSell":"1", + // "Short":"1", + // "StopBuy":"0", + // "StopSell":"0" + // } + // }, + // "clearing":{"enabled":true,"index":".ETHUSD_3M_250920"}, + // "premium":{"enabled":true,"index":".XBTUSD_Premium_IR_Corrected"}, // perpetual contracts only + // "riskAdjustment":{"enabled":true,"index":".RiskAdjustment_IR"}, + // "expiration":{"enabled":true,"index":".ETHUSD_TWAP"}, // futures only + // "pricePrecision":3, + // "priceRange":{ + // "enabled":true, + // "distance":"0.03", + // "movingBoundary":"0", + // "lowIndex":".ETHUSD_3M_250920_LOWRANGE", + // "highIndex":".ETHUSD_3M_250920_HIGHRANGE" + // }, + // "priceLimits":{ + // "enabled":true, + // "distance":"0.5", + // "movingBoundary":"0", + // "lowIndex":".ETHUSD_3M_250920_LOWLIMIT", + // "highIndex":".ETHUSD_3M_250920_HIGHLIMIT" + // }, + // "inverse":true, // inverse contracts only + // "serie":"ETHUSD", // futures only + // "tradingStartDate":"2020-03-27 07:00:00", + // "expiryDate":"2020-09-25 08:00:00" // futures only + // }, + // { + // "type":"Index", + // "symbol":".ETHUSD_Premium_IR_Corrected", + // "tickSize":6, + // "enabled":true, + // "basis":365 + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + let type = this.safeStringLower (market, 'type'); + const id = this.safeString (market, 'symbol'); + const numericId = this.safeString (market, 'id'); + const marginType = this.safeString (market, 'marginType'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = id; + if (type === 'margin') { + if (marginType === 'XenaFuture') { + type = 'future'; + } else if (marginType === 'XenaListedPerpetual') { + type = 'swap'; + symbol = base + '/' + quote; + } + } + const future = (type === 'future'); + const swap = (type === 'swap'); + const pricePrecision = this.safeInteger2 (market, 'tickSize', 'pricePrecision'); + const precision = { + 'price': pricePrecision, + 'amount': 0, + }; + const maxCost = this.safeFloat (market, 'maxOrderQty'); + const minCost = this.safeFloat (market, 'minOrderQuantity'); + const limits = { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': maxCost, + }, + }; + const active = this.safeValue (market, 'enabled', false); + const inverse = this.safeValue (market, 'inverse', false); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'numericId': numericId, + 'active': active, + 'type': type, + 'spot': false, + 'future': future, + 'swap': swap, + 'inverse': inverse, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCommonCurrencies (params); + // + // { + // "BAB": { + // "name":"BAB", + // "title":"Bitcoin ABC", + // "blockchain":{ + // "name":"BAB", + // "title":"Bitcoin ABC", + // "deposit":{"confirmations":6}, + // "withdraw":{"confirmations":1}, + // "addressReuseAllowed":false, + // "view":{ + // "uriTemplate":"bitcoinabc:%s?message=Xena Exchange", + // "recommendedFee":"0.00001", + // "transactionUrl":"https://blockchair.com/bitcoin-cash/transaction/${txId}", + // "walletUrl":"https://blockchair.com/bitcoin-cash/address/${walletId}" + // } + // }, + // "precision":5, + // "withdraw":{"minAmount":"0.01","commission":"0.001"}, + // "view":{ + // "color":"#DC7C08", + // "site":"https://www.bitcoinabc.org" + // }, + // "enabled":true + // }, + // } + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = response[id]; + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'title'); + const precision = this.safeInteger (currency, 'precision'); + const enabled = this.safeValue (currency, 'enabled'); + const active = (enabled === true); + const withdraw = this.safeValue (currency, 'withdraw', {}); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': this.safeFloat (withdraw, 'commission'), + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (withdraw, 'minAmount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "symbol":".XBTUSD_3M_250920_MID", + // "firstPx":"9337.49", + // "lastPx":"9355.81", + // "highPx":"9579.42", + // "lowPx":"9157.63", + // "buyVolume":"0", + // "sellVolume":"0", + // "bid":"0", + // "ask":"0" + // } + // + const timestamp = this.milliseconds (); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'lastPx'); + const open = this.safeFloat (ticker, 'firstPx'); + let percentage = undefined; + let change = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + const buyVolume = this.safeFloat (ticker, 'buyVolume'); + const sellVolume = this.safeFloat (ticker, 'sellVolume'); + const baseVolume = this.sum (buyVolume, sellVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPx'), + 'low': this.safeFloat (ticker, 'lowPx'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const tickers = await this.fetchTickers (undefined, params); + if (symbol in tickers) { + return tickers[symbol]; + } + throw new BadSymbol (this.id + ' fetchTicker could not find a ticker with symbol ' + symbol); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const tickers = await this.publicGetMarketDataMarketWatch (params); + // + // [ + // { + // "symbol":".XBTUSD_3M_250920_MID", + // "firstPx":"9337.49", + // "lastPx":"9355.81", + // "highPx":"9579.42", + // "lowPx":"9157.63", + // "buyVolume":"0", + // "sellVolume":"0", + // "bid":"0", + // "ask":"0" + // } + // ] + // + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetMarketDataV2DomSymbol (this.extend (request, params)); + // + // { + // "msgType":"W", + // "mdStreamId":"DOM:XBTUSD:aggregated", + // "lastUpdateTime":1594772683037691997, + // "mdBookType":"2", + // "symbol":"XBTUSD", + // "lowRangePx":"9132.24", + // "highRangePx":"9410.36", + // "lowLimitPx":"9132.24", + // "highLimitPx":"9410.36", + // "clearingPx":"9253.4", + // "bestBid":"9269.8", + // "bestAsk":"9275.9", + // "mdEntry":[ + // {"mdEntryType":"1","mdEntryPx":"9275.9","mdEntrySize":"3000","numberOfOrders":1}, + // {"mdEntryType":"1","mdEntryPx":"9277.7","mdEntrySize":"50000","numberOfOrders":1}, + // {"mdEntryType":"1","mdEntryPx":"9277.8","mdEntrySize":"2000","numberOfOrders":1}, + // {"mdEntryType":"0","mdEntryPx":"9269.8","mdEntrySize":"2000","numberOfOrders":1}, + // {"mdEntryType":"0","mdEntryPx":"9267.9","mdEntrySize":"3000","numberOfOrders":1}, + // {"mdEntryType":"0","mdEntryPx":"9267.8","mdEntrySize":"50000","numberOfOrders":1}, + // ] + // } + // + const mdEntry = this.safeValue (response, 'mdEntry', []); + const mdEntriesByType = this.groupBy (mdEntry, 'mdEntryType'); + const lastUpdateTime = this.safeInteger (response, 'lastUpdateTime'); + const timestamp = parseInt (lastUpdateTime / 1000000); + return this.parseOrderBook (mdEntriesByType, timestamp, '0', '1', 'mdEntryPx', 'mdEntrySize'); + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetTradingAccounts (params); + // + // { + // "accounts": [ + // { "id":8273231, "kind": "Spot" }, + // { "id":10012833469, "kind": "Margin", "currency": "BTC" } + // ] + // } + // + const accounts = this.safeValue (response, 'accounts'); + const result = []; + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + const accountId = this.safeString (account, 'id'); + const currencyId = this.safeString (account, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const type = this.safeStringLower (account, 'kind'); + result.push ({ + 'id': accountId, + 'type': type, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async findAccountByType (type) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountsByType = this.groupBy (this.accounts, 'type'); + const accounts = this.safeValue (accountsByType, type); + if (accounts === undefined) { + throw new ExchangeError (this.id + " findAccountByType() could not find an accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + const numAccounts = accounts.length; + if (numAccounts > 1) { + throw new ExchangeError (this.id + " findAccountByType() found more than one accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + return accounts[0]; + } + + async getAccountId (params) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountId = this.safeString (this.options, 'accountId'); + const accountId = this.safeString (params, 'accountId', defaultAccountId); + if (accountId !== undefined) { + return accountId; + } + const defaultType = this.safeString (this.options, 'defaultType', 'margin'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " requires an 'accountId' parameter or a 'type' parameter ('spot' or 'margin')"); + } + const account = await this.findAccountByType (type); + return account['id']; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + }; + const response = await this.privateGetTradingAccountsAccountIdBalance (this.extend (request, params)); + // + // { + // "balances": [ + // {"available":"0","onHold":"0","settled":"0","equity":"0","currency":"BAB","lastUpdated":1564811790485125345}, + // {"available":"0","onHold":"0","settled":"0","equity":"0","currency":"BSV","lastUpdated":1564811790485125345}, + // {"available":"0","onHold":"0","settled":"0","equity":"0","currency":"BTC","lastUpdated":1564811790485125345}, + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'balances', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'onHold'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTrade (trade, market = undefined) { + // + // { + // "mdUpdateAction":"0", + // "mdEntryType":"2", + // "mdEntryPx":"9225.16", + // "mdEntrySize":"10000", + // "transactTime":1594728504524977655, + // "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9", + // "aggressorSide":"1" + // } + // + // fetchMyTrades + // + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"xXWKLQVl3", + // "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7", + // "symbol":"ETHUSD", + // "transactTime":1595143349089739000, + // "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4", + // "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1", + // "side":"2", + // "lastQty":"1", + // "lastPx":"234.58", + // "avgPx":"234.58", + // "calculatedCcyLastQty":"0", + // "netMoney":"0", + // "lastLiquidityInd":"2", + // "commission":"0.00000011", + // "commRate":"0.00045", + // "commCurrency":"BTC", + // "positionId":132162662, + // "positionEffect":"C" + // } + // + const id = this.safeString (trade, 'tradeId'); + let timestamp = this.safeInteger (trade, 'transactTime'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp / 1000000); + } + let side = this.safeStringLower2 (trade, 'side', 'aggressorSide'); + if (side === '1') { + side = 'buy'; + } else if (side === '2') { + side = 'sell'; + } + const orderId = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const price = this.safeFloat2 (trade, 'lastPx', 'mdEntryPx'); + const amount = this.safeFloat2 (trade, 'lastQty', 'mdEntrySize'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + const feeRate = this.safeFloat (trade, 'commRate'); + fee = { + 'cost': feeCost, + 'rate': feeRate, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': orderId, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'page': 1, + // 'limit': integer, + // 'from': time, + // 'to': time, + // 'symbol': currency['id'], + // 'trade_id': id, + // 'client_order_id': id, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = since * 1000000; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradingAccountsAccountIdTradeHistory (this.extend (request, params)); + // + // [ + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"xXWKLQVl3", + // "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7", + // "symbol":"ETHUSD", + // "transactTime":1595143349089739000, + // "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4", + // "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1", + // "side":"2", + // "lastQty":"1", + // "lastPx":"234.58", + // "avgPx":"234.58", + // "calculatedCcyLastQty":"0", + // "netMoney":"0", + // "lastLiquidityInd":"2", + // "commission":"0.00000011", + // "commRate":"0.00045", + // "commCurrency":"BTC", + // "positionId":132162662, + // "positionEffect":"C" + // }, + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"3ce8c305-9936-4e97-9206-71ae3ff40305", + // "orderId":"a93c686d-990e-44d9-9cbe-61107744b990", + // "symbol":"ETHUSD", + // "transactTime":1595143315369226000, + // "execId":"1c745881722ad966a4ce71600cd058d59da0d1c3", + // "tradeId":"77f75bd8-27c4-4b1a-a5e8-0d59239ce216", + // "side":"1", + // "lastQty":"1", + // "lastPx":"234.72", + // "avgPx":"234.72", + // "calculatedCcyLastQty":"0", + // "netMoney":"0", + // "lastLiquidityInd":"2", + // "commission":"0.00000011", + // "commRate":"0.00045", + // "commCurrency":"BTC", + // "positionId":132162662, + // "positionEffect":"O" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "transactTime":1594784700000000000, + // "firstPx":"9246.3", + // "lastPx":"9232.8", + // "highPx":"9246.3", + // "lowPx":"9232.8", + // "buyVolume":"0", + // "sellVolume":"0" + // } + // + const transactTime = this.safeInteger (ohlcv, 'transactTime'); + const timestamp = parseInt (transactTime / 1000000); + const buyVolume = this.safeFloat (ohlcv, 'buyVolume'); + const sellVolume = this.safeFloat (ohlcv, 'sellVolume'); + const volume = this.sum (buyVolume, sellVolume); + return [ + timestamp, + this.safeFloat (ohlcv, 'firstPx'), + this.safeFloat (ohlcv, 'highPx'), + this.safeFloat (ohlcv, 'lowPx'), + this.safeFloat (ohlcv, 'lastPx'), + volume, + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + }; + const durationInSeconds = this.parseTimeframe (timeframe); + const duration = durationInSeconds * 1000; + if (since !== undefined) { + request['from'] = since * 1000000; + if (limit !== undefined) { + request['to'] = this.sum (since, limit * duration) * 1000000; + } + } else { + const now = this.milliseconds (); + // max limit is 1000 + if (limit !== undefined) { + request['from'] = (now - limit * duration) * 1000000; + } + } + const response = await this.publicGetMarketDataV2CandlesSymbolTimeframe (this.extend (request, params)); + // + // { + // "mdEntry":[ + // {"transactTime":1594784700000000000,"firstPx":"9246.3","lastPx":"9232.8","highPx":"9246.3","lowPx":"9232.8","buyVolume":"0","sellVolume":"0"}, + // {"transactTime":1594785600000000000,"firstPx":"9231.8","lastPx":"9227.3","highPx":"9232.8","lowPx":"9227.3","buyVolume":"0","sellVolume":"0"}, + // {"transactTime":1594786500000000000,"firstPx":"9226.3","lastPx":"9230.3","highPx":"9230.3","lowPx":"9220.6","buyVolume":"0","sellVolume":"0"} + // ] + // } + // + const mdEntry = this.safeValue (response, 'mdEntry', []); + return this.parseOHLCVs (mdEntry, market, timeframe, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + // 'page': 1, + // 'limit': limit, + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketDataV2TradesSymbol (this.extend (request, params)); + // + // { + // "msgType":"W", + // "lastUpdateTime":1594737830902223803, + // "symbol":"XBTUSD", + // "mdEntry":[ + // { + // "mdUpdateAction":"0", + // "mdEntryType":"2", + // "mdEntryPx":"9225.16", + // "mdEntrySize":"10000", + // "transactTime":1594728504524977655, + // "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9", + // "aggressorSide":"1" + // }, + // ] + // } + // + const mdEntry = this.safeValue (response, 'mdEntry', []); + return this.parseTrades (mdEntry, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'A': 'open', // PendingNew + '0': 'open', // New + '1': 'open', // PartiallyFilled + '2': 'closed', // Filled + '6': 'canceled', // PendingCancel + '4': 'canceled', // Cancelled + 'E': 'open', // PendingReplace + '8': 'rejected', // Rejected + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clOrdId'); + const transactTime = this.safeInteger (order, 'transactTime'); + const timestamp = parseInt (transactTime / 1000000); + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'orderQty'); + const filled = this.safeFloat (order, 'cumQty'); + const remaining = this.safeFloat (order, 'leavesQty'); + let cost = undefined; + let side = this.safeStringLower (order, 'side'); + if (side === '1') { + side = 'buy'; + } else if (side === '1') { + side = 'sell'; + } + let type = this.safeStringLower (order, 'ordType'); + if (type === '1') { + type = 'market'; + } else if (type === '2') { + type = 'limit'; + } else if (type === '3') { + type = 'stop'; + } else if (type === '4') { + type = 'stop-limit'; + } + if (cost === undefined) { + if ((price !== undefined) && (filled !== undefined)) { + cost = price * filled; + } + } + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const orderTypes = { + 'market': '1', + 'limit': '2', + 'stop': '3', + 'stop-limit': '4', + }; + const orderType = this.safeString (orderTypes, type); + if (orderType === undefined) { + throw new InvalidOrder (this.id + ' createOrder does not support order type ' + type + ', supported order types are market, limit, stop, stop-limit'); + } + const orderSides = { + 'buy': '1', + 'sell': '2', + }; + const orderSide = this.safeString (orderSides, side); + if (orderSide === undefined) { + throw new InvalidOrder (this.id + ' createOrder does not support order side ' + side + ', supported order sides are buy, sell'); + } + const market = this.market (symbol); + const request = { + 'account': parseInt (accountId), + 'symbol': market['id'], + 'ordType': orderType, + 'side': orderSide, + 'orderQty': this.amountToPrecision (symbol, amount), + 'transactTime': this.milliseconds () * 1000000, + // 'clOrdId': this.uuid (), // required + // 'price': this.priceToPrecision (symbol, price), // required for limit and stop-limit orders + // 'stopPx': this.priceToPrecision (symbol, stopPx), // required for stop and stop-limit orders + // 'timeInForce': '1', // default '1' = GoodTillCancelled, '3' = ImmediateOrCancel, '4' = FillOrKill + // 'execInst': '0', + // '0' = StayOnOfferSide, maker only, reject instead of aggressive execution + // '9' = PegToOfferSide, maker only, best available level instead of aggressive execution + // 'o' = CancelOnConnectionLoss + // 'positionID': 1013838923, // required when positionEffect == 'C' with hedged accounting + // 'positionEffect': 'O', // 'C' = Close, 'O' = Open, send C along with the positionID if the order must close a position with hedged accounting mode + // 'text': 'comment', // optional + // 'grpID': 'group-identifier', // group identifier for cancel on disconnect orders + }; + if ((type === 'limit') || (type === 'stop-limit')) { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a price argument for order type ' + type); + } + request['price'] = this.priceToPrecision (symbol, price); + } + if ((type === 'stop') || (type === 'stop-limit')) { + const stopPx = this.safeFloat (params, 'stopPx'); + if (stopPx === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a stopPx param for order type ' + type); + } + request['stopPx'] = this.priceToPrecision (symbol, stopPx); + params = this.omit (params, 'stopPx'); + } + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdId', this.uuid ()); + if (clientOrderId !== undefined) { + request['clOrdId'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'clOrdId' ]); + } + const response = await this.privatePostTradingOrderNew (this.extend (request, params)); + // + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // + return this.parseOrder (response, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const market = this.market (symbol); + const request = { + 'account': parseInt (accountId), + 'clOrdId': this.uuid (), + 'symbol': market['id'], + 'transactTime': this.milliseconds () * 1000000, + // 'origClOrdId': this.uuid (), // one of orderId or origClOrdId is required + // 'orderId': id, + // 'side': '1', // 1 = buy, 2 = sell + // 'execInst': '0', + // '0' = StayOnOfferSide, maker only, reject instead of aggressive execution + // '9' = PegToOfferSide, maker only, best available level instead of aggressive execution + // 'o' = CancelOnConnectionLoss + // 'orderQty': 38 M decimal + // 'price': this.priceToPrecision (symbol, price), // required for limit and stop-limit orders + // 'stopPx': this.priceToPrecision (symbol, stopPx), // required for stop and stop-limit orders + // 'capPrice': this.priceToPrecision (symbol, capPrice), // the price beyond which the order will not move for trailing stop and attempt-zero-loss + // 'pegPriceType': '8', // '8' = TrailingStopPeg, identifies a trailing stop or an attempt-zero-loss order + // 'pegOffsetType': '2', // '2' = BasisPoints, the unit of the distance to the stop price for a trailing stop or an attempt-zero-loss order + // 'pegOffsetValue': 123, // distance to the trailing stop or attempt-zero-loss + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'origClOrdId'); + if (clientOrderId !== undefined) { + request['origClOrdId'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'origClOrdId' ]); + } else { + request['orderId'] = id; + } + if (amount !== undefined) { + request['orderQty'] = this.amountToPrecision (symbol, amount); + } + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const stopPx = this.safeFloat (params, 'stopPx'); + if (stopPx !== undefined) { + request['stopPx'] = this.priceToPrecision (symbol, stopPx); + params = this.omit (params, 'stopPx'); + } + const capPrice = this.safeFloat (params, 'capPrice'); + if (capPrice !== undefined) { + request['capPrice'] = this.priceToPrecision (symbol, capPrice); + params = this.omit (params, 'capPrice'); + } + const response = await this.privatePostTradingOrderReplace (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'origClOrdId'); + params = this.omit (params, [ 'clientOrderId', 'origClOrdId' ]); + const market = this.market (symbol); + const request = { + 'account': parseInt (accountId), + 'symbol': market['id'], + 'clOrdId': this.uuid (), + 'transactTime': this.milliseconds () * 1000000, + }; + if (clientOrderId !== undefined) { + request['origClOrdId'] = clientOrderId; + } else { + request['orderId'] = id; + } + const response = await this.privatePostTradingOrderCancel (this.extend (request, params)); + // + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"0fa3fb55-9dc0-4cfc-a1db-6aa8b7dd2d98", + // "origClOrdId":"3b2878bb-24d8-4922-9d2a-5b8009416677", + // "orderId":"665b418e-9d09-4461-b733-d317f6bff43f", + // "symbol":"ETHUSD", + // "ordType":"2", + // "price":"640", + // "transactTime":1595060080941618739, + // "execId":"c541c0ca437c0e6501c3a50a9d4dc8f575f49972", + // "execType":"6", + // "ordStatus":"6", + // "side":"2", + // "orderQty":"1", + // "leavesQty":"0", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.000032", + // "marginAmtType":"11" + // } + // + return this.parseOrder (response, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'account': parseInt (accountId), + 'clOrdId': this.uuid (), + // 'side': '1', // 1 = buy, 2 = sell, optional filter, cancel only orders with the given side + // 'positionEffect': 'C', // C = Close, O = Open, optional filter, cancel only orders with the given positionEffect, applicable only for accounts with hedged accounting + }; + if (symbol !== undefined) { + const market = this.market (symbol); + request['symbol'] = market['id']; + request['massCancelRequestType'] = '1'; // CancelOrdersForASecurity + } else { + request['massCancelRequestType'] = '7'; // CancelAllOrders + } + const response = await this.privatePostTradingOrderMassCancel (this.extend (request, params)); + // + // { + // "msgType":"r", + // "clOrdId":"b3e95759-e43e-4b3a-b664-a4d213e281a7", + // "massActionReportID":"e915b6f4-a7ca-4c5c-b8d6-e39862530248", + // "massCancelResponse":"1", + // "symbol":"ETHUSD", + // "transactTime":1595065630133756426, + // "totalAffectedOrders":2, + // "account":1012838158 + // } + // + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'symbol': market['id'], + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetTradingAccountsAccountIdActiveOrders (this.extend (request, params)); + // + // [ + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'from': this.iso8601 (since) * 1000000, + // 'to': this.iso8601 (this.milliseconds ()) * 1000000, // max range is 7 days + // 'symbol': market['id'], + // 'limit': 100, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since) * 1000000; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradingAccountsAccountIdLastOrderStatuses (this.extend (request, params)); + // + // [ + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + const request = { + 'accountId': accountId, + 'currency': currency['id'], + }; + const response = await this.privatePostTransfersAccountsAccountIdDepositAddressCurrency (this.extend (request, params)); + // + // { + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "uri": "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange", + // "allowsRenewal": true + // } + // + const address = this.safeValue (response, 'address'); + const tag = undefined; + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + const request = { + 'accountId': accountId, + 'currency': currency['id'], + }; + const response = await this.privateGetTransfersAccountsAccountIdDepositAddressCurrency (this.extend (request, params)); + // + // { + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "uri": "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange", + // "allowsRenewal": true + // } + // + const address = this.safeValue (response, 'address'); + const tag = undefined; + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'accountId': accountId, + }; + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const method = 'privateGetTransfersAccountsAccountId' + this.capitalize (type); + const response = await this[method] (this.extend (request, params)); + // + // { + // "withdrawals": [ + // { + // "withdrawalRequestId": 47383243, + // "externalId": "...", // external ID submitted by the client when creating the request + // "status": 1, + // "statusMessage": "Pending confirmation", + // "amount": "10.2", + // "currency": "BTC", + // "lastUpdated": , + // "blockchain": "Bitcoin", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98" + // } + // ] + // } + // + // { + // "deposits": [ + // { + // "currency": "BTC", + // "amount": "1.2", + // "status": 1, + // "statusMessage": "Processing", + // "blockchain": "Bitcoin", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "lastUpdated": + // "confirmations": 2, + // "requiredConfirmations": 6 + // } + // ] + // } + // + // + const transactions = this.safeValue (response, type, []); + return this.parseTransactions (transactions, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdrawals', code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposits', code, since, limit, params); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw() + // + // { + // "withdrawalRequestId": 47383243, + // "status": 1, + // "statusMessage": "Pending confirmation" + // } + // + // fetchWithdrawals + // + // { + // "withdrawalRequestId": 47383243, + // "externalId": "...", // external ID submitted by the client when creating the request + // "status": 1, + // "statusMessage": "Pending confirmation", + // "amount": "10.2", + // "currency": "BTC", + // "lastUpdated": , + // "blockchain": "Bitcoin", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98" + // } + // + // fetchDeposits + // + // { + // "currency": "BTC", + // "amount": "1.2", + // "status": 1, + // "statusMessage": "Processing", + // "blockchain": "Bitcoin", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "lastUpdated": + // "confirmations": 2, + // "requiredConfirmations": 6 + // } + // + const id = this.safeString (transaction, 'withdrawalRequestId'); + const type = (id === undefined) ? 'deposit' : 'withdrawal'; + let updated = this.safeInteger (transaction, 'lastUpdated'); + if (updated !== undefined) { + updated = parseInt (updated / 1000000); + } + const timestamp = undefined; + const txid = this.safeString (transaction, 'txId'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const address = this.safeString (transaction, 'address'); + const addressFrom = undefined; + const addressTo = address; + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const fee = undefined; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': undefined, + 'tagTo': undefined, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + '1': 'pending', // new + '2': 'ok', // completed + '3': 'failed', // duplicate + '4': 'failed', // not enough money + '5': 'pending', // waiting for manual approval from XENA + '100': 'pending', // request is being processed + '101': 'pending', // request is being processed + '102': 'pending', // request is being processed + '103': 'pending', // request is being processed + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + let uuid = this.uuid (); + uuid = uuid.split ('-'); + uuid = uuid.join (''); + const request = { + 'currency': currency['id'], + 'accountId': accountId, + 'amount': this.currencyToPrecision (code, amount), + 'address': address, + 'id': uuid, // mandatory external ID (string), used by the client to identify his request + }; + const response = await this.privatePostTransfersAccountsAccountIdWithdrawals (this.extend (request, params)); + // + // { + // "withdrawalRequestId": 47383243, + // "status": 1, + // "statusMessage": "Pending confirmation" + // } + // + return this.parseTransaction (response, currency); + } + + parseLedgerEntryType (type) { + const types = { + 'deposit': 'transaction', + 'withdrawal': 'transaction', + 'internal deposit': 'transfer', + 'internal withdrawal': 'transfer', + 'rebate': 'rebate', + 'reward': 'reward', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "accountId":8263118, + // "ts":1551974415000000000, + // "amount":"-1", + // "currency":"BTC", + // "kind":"internal withdrawal", + // "commission":"0", + // "id":96 + // } + // + const id = this.safeString (item, 'id'); + let direction = undefined; + const account = this.safeString (item, 'accountId'); + const referenceId = undefined; + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'kind')); + const code = this.safeCurrencyCode (this.safeString (item, 'currency'), currency); + let amount = this.safeFloat (item, 'amount'); + if (amount < 0) { + direction = 'out'; + amount = Math.abs (amount); + } else { + direction = 'in'; + } + let timestamp = this.safeInteger (item, 'ts'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp / 1000000); + } + const fee = { + 'cost': this.safeFloat (item, 'commission'), + 'currency': code, + }; + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'page': 1, + // 'limit': 5000, // max 5000 + // 'from': time, + // 'to': time, + // 'symbol': currency['id'], + // 'trade_id': id, + // 'client_order_id': id, + // 'txid': txid, + // 'kind': 'deposit', // 'withdrawal, 'internal deposit', 'internal withdrawal', 'rebate', 'reward' + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (since !== undefined) { + request['from'] = since * 1000000; + } + if (limit !== undefined) { + request['limit'] = limit; // max 5000 + } + const response = await this.privateGetTransfersAccountsAccountIdBalanceHistory (this.extend (request, params)); + // + // [ + // { + // "accountId":8263118, + // "ts":1551974415000000000, + // "amount":"-1", + // "currency":"BTC", + // "kind":"internal withdrawal", + // "commission":"0", + // "id":96 + // }, + // { + // "accountId":8263118, + // "ts":1551964677000000000, + // "amount":"-1", + // "currency":"BTC", + // "kind":"internal deposit", + // "commission":"0", + // "id":95 + // } + // ] + // + return this.parseLedger (response, currency, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + let nonce = this.nonce (); + // php does not format it properly + // therefore we use string concatenation here + // nonce *= 1000000; + nonce = nonce.toString (); + nonce = nonce + '000000'; // see the comment a few lines above + const payload = 'AUTH' + nonce; + const secret = this.secret.slice (14, 78); + const ecdsa = this.ecdsa (payload, secret, 'p256', 'sha256'); + const signature = ecdsa['r'] + ecdsa['s']; + headers = { + 'X-AUTH-API-KEY': this.apiKey, + 'X-AUTH-API-PAYLOAD': payload, + 'X-AUTH-API-SIGNATURE': signature, + 'X-AUTH-API-NONCE': nonce, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (method === 'POST') { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error":"Validation failed","fields":["address"]} + // {"error":"Money not enough. You have only: 0 ETH","fields":["amount"]} + // + if (code >= 400) { + const feedback = this.id + ' ' + this.json (response); + const message = this.safeString (response, 'error'); + const exact = this.exceptions['exact']; + if (message in exact) { + throw new exact[message] (feedback); + } + const broad = this.exceptions['broad']; + const broadKey = this.findBroadlyMatchedKey (broad, body); + if (broadKey !== undefined) { + throw new broad[broadKey] (feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],177:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InvalidNonce, InsufficientFunds, OrderNotFound, DDoSProtection, InvalidOrder, AuthenticationError, RateLimitExceeded } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class yobit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'yobit', + 'name': 'YoBit', + 'countries': [ 'RU' ], + 'rateLimit': 3000, // responses are cached every 2 seconds + 'version': '3', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchDeposits': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg', + 'api': { + 'public': 'https://yobit.net/api', + 'private': 'https://yobit.net/tapi', + }, + 'www': 'https://www.yobit.net', + 'doc': 'https://www.yobit.net/en/api/', + 'fees': 'https://www.yobit.net/en/fees/', + }, + 'api': { + 'public': { + 'get': [ + 'depth/{pair}', + 'info', + 'ticker/{pair}', + 'trades/{pair}', + ], + }, + 'private': { + 'post': [ + 'ActiveOrders', + 'CancelOrder', + 'GetDepositAddress', + 'getInfo', + 'OrderInfo', + 'Trade', + 'TradeHistory', + 'WithdrawCoinsToAddress', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.002, + 'taker': 0.002, + }, + 'funding': { + 'withdraw': {}, + }, + }, + 'commonCurrencies': { + 'AIR': 'AirCoin', + 'ANI': 'ANICoin', + 'ANT': 'AntsCoin', // what is this, a coin for ants? + 'ATMCHA': 'ATM', + 'ASN': 'Ascension', + 'AST': 'Astral', + 'ATM': 'Autumncoin', + 'BCC': 'BCH', + 'BCS': 'BitcoinStake', + 'BITS': 'Bitstar', + 'BLN': 'Bulleon', + 'BOT': 'BOTcoin', + 'BON': 'BONES', + 'BPC': 'BitcoinPremium', + 'BTS': 'Bitshares2', + 'CAT': 'BitClave', + 'CBC': 'CryptoBossCoin', + 'CMT': 'CometCoin', + 'COV': 'Coven Coin', + 'COVX': 'COV', + 'CPC': 'Capricoin', + 'CREDIT': 'Creditbit', + 'CS': 'CryptoSpots', + 'DCT': 'Discount', + 'DFT': 'DraftCoin', + 'DGD': 'DarkGoldCoin', + 'DIRT': 'DIRTY', + 'DROP': 'FaucetCoin', + 'DSH': 'DASH', + 'EKO': 'EkoCoin', + 'ENTER': 'ENTRC', + 'EPC': 'ExperienceCoin', + 'ESC': 'EdwardSnowden', + 'EUROPE': 'EUROP', + 'EXT': 'LifeExtension', + 'FUNK': 'FUNKCoin', + 'GCC': 'GlobalCryptocurrency', + 'GEN': 'Genstake', + 'GENE': 'Genesiscoin', + 'GOLD': 'GoldMint', + 'GOT': 'Giotto Coin', + 'HTML5': 'HTML', + 'HYPERX': 'HYPER', + 'ICN': 'iCoin', + 'INSANE': 'INSN', + 'JNT': 'JointCoin', + 'JPC': 'JupiterCoin', + 'KNC': 'KingN Coin', + 'LBTCX': 'LiteBitcoin', + 'LIZI': 'LiZi', + 'LOC': 'LocoCoin', + 'LOCX': 'LOC', + 'LUNYR': 'LUN', + 'LUN': 'LunarCoin', // they just change the ticker if it is already taken + 'MDT': 'Midnight', + 'NAV': 'NavajoCoin', + 'NBT': 'NiceBytes', + 'OMG': 'OMGame', + 'PAC': '$PAC', + 'PLAY': 'PlayCoin', + 'PIVX': 'Darknet', + 'PRS': 'PRE', + 'PUTIN': 'PutinCoin', + 'STK': 'StakeCoin', + 'SUB': 'Subscriptio', + 'PAY': 'EPAY', + 'PLC': 'Platin Coin', + 'RCN': 'RCoin', + 'REP': 'Republicoin', + 'RUR': 'RUB', + 'TTC': 'TittieCoin', + 'UNI': 'Universe', + 'UST': 'Uservice', + 'VOL': 'VolumeCoin', + 'XIN': 'XINCoin', + 'XRA': 'Ratecoin', + }, + 'options': { + // 'fetchTickersMaxLength': 2048, + 'fetchOrdersRequiresSymbol': true, + 'fetchTickersMaxLength': 512, + }, + 'exceptions': { + 'exact': { + '803': InvalidOrder, // "Count could not be less than 0.001." (selling below minAmount) + '804': InvalidOrder, // "Count could not be more than 10000." (buying above maxAmount) + '805': InvalidOrder, // "price could not be less than X." (minPrice violation on buy & sell) + '806': InvalidOrder, // "price could not be more than X." (maxPrice violation on buy & sell) + '807': InvalidOrder, // "cost could not be less than X." (minCost violation on buy & sell) + '831': InsufficientFunds, // "Not enougth X to create buy order." (buying with balance.quote < order.cost) + '832': InsufficientFunds, // "Not enougth X to create sell order." (selling with balance.base < order.amount) + '833': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) + }, + 'broad': { + 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} + 'invalid api key': AuthenticationError, + 'invalid sign': AuthenticationError, + 'api key dont have trade permission': AuthenticationError, + 'invalid parameter': InvalidOrder, + 'invalid order': InvalidOrder, + 'The given order has already been cancelled': InvalidOrder, + 'Requests too often': DDoSProtection, + 'not available': ExchangeNotAvailable, + 'data unavailable': ExchangeNotAvailable, + 'external service unavailable': ExchangeNotAvailable, + 'Total transaction amount': InvalidOrder, // { "success": 0, "error": "Total transaction amount is less than minimal total: 0.00010000"} + 'The given order has already been closed and cannot be cancelled': InvalidOrder, + 'Insufficient funds': InsufficientFunds, + 'invalid key': AuthenticationError, + 'invalid nonce': InvalidNonce, // {"success":0,"error":"invalid nonce (has already been used)"}' + 'Total order amount is less than minimal amount': InvalidOrder, + 'Rate Limited': RateLimitExceeded, + }, + }, + 'orders': {}, // orders cache / emulation + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfo (params); + // + // { + // "success":1, + // "return":{ + // "funds":{ + // "ltc":22, + // "nvc":423.998, + // "ppc":10, + // }, + // "funds_incl_orders":{ + // "ltc":32, + // "nvc":523.998, + // "ppc":20, + // }, + // "rights":{ + // "info":1, + // "trade":0, + // "withdraw":0 + // }, + // "transaction_count":0, + // "open_orders":1, + // "server_time":1418654530 + // } + // } + // + const balances = this.safeValue (response, 'return', {}); + const result = { 'info': response }; + const free = this.safeValue (balances, 'funds', {}); + const total = this.safeValue (balances, 'funds_incl_orders', {}); + const currencyIds = Object.keys (this.extend (free, total)); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (free, currencyId); + account['total'] = this.safeFloat (total, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInfo (params); + const markets = this.safeValue (response, 'pairs'); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ baseId, quoteId ] = id.split ('_'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'decimal_places'), + 'price': this.safeInteger (market, 'decimal_places'), + }; + const amountLimits = { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }; + const priceLimits = { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }; + const costLimits = { + 'min': this.safeFloat (market, 'min_total'), + }; + const limits = { + 'amount': amountLimits, + 'price': priceLimits, + 'cost': costLimits, + }; + const hidden = this.safeInteger (market, 'hidden'); + const active = (hidden === 0); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'taker': market['fee'] / 100, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const market_id_in_reponse = (market['id'] in response); + if (!market_id_in_reponse) { + throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); + } + const orderbook = response[market['id']]; + return this.parseOrderBook (orderbook); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join ('-'); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + // 'ignore_invalid': true, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const result = {}; + ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const symbol = this.safeSymbol (id); + result[symbol] = this.parseOrderBook (response[id]); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { high: 0.03497582, + // low: 0.03248474, + // avg: 0.03373028, + // vol: 120.11485715062999, + // vol_cur: 3572.24914074, + // last: 0.0337611, + // buy: 0.0337442, + // sell: 0.03377798, + // updated: 1537522009 } + // + const timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': this.safeFloat (ticker, 'vol_cur'), + 'quoteVolume': this.safeFloat (ticker, 'vol'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = this.ids; + if (symbols === undefined) { + const numIds = ids.length; + ids = ids.join ('-'); + const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); + // max URL length is 2048 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + const tickers = await this.publicGetTickerPair (this.extend (request, params)); + const result = {}; + const keys = Object.keys (tickers); + for (let k = 0; k < keys.length; k++) { + const id = keys[k]; + const ticker = tickers[id]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return tickers[symbol]; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'timestamp'); + let side = this.safeString (trade, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const price = this.safeFloat2 (trade, 'rate', 'price'); + const id = this.safeString2 (trade, 'trade_id', 'tid'); + const order = this.safeString (trade, 'order_id'); + const marketId = this.safeString (trade, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const amount = this.safeFloat (trade, 'amount'); + const type = 'limit'; // all trades are still limit trades + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const isYourOrder = this.safeValue (trade, 'is_your_order'); + if (isYourOrder !== undefined) { + if (fee === undefined) { + fee = this.calculateFee (symbol, type, side, amount, price, 'taker'); + } + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + return { + 'id': id, + 'order': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTradesPair (this.extend (request, params)); + if (Array.isArray (response)) { + const numElements = response.length; + if (numElements === 0) { + return []; + } + } + return this.parseTrades (response[market['id']], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'amount': this.amountToPrecision (symbol, amount), + 'rate': this.priceToPrecision (symbol, price), + }; + price = parseFloat (price); + amount = parseFloat (amount); + const response = await this.privatePostTrade (this.extend (request, params)); + let id = undefined; + let status = 'open'; + let filled = 0.0; + let remaining = amount; + if ('return' in response) { + id = this.safeString (response['return'], 'order_id'); + if (id === '0') { + id = this.safeString (response['return'], 'init_order_id'); + status = 'closed'; + } + filled = this.safeFloat (response['return'], 'received', 0.0); + remaining = this.safeFloat (response['return'], 'remains', amount); + } + const timestamp = this.milliseconds (); + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * filled, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + // 'trades': this.parseTrades (order['trades'], market), + 'info': response, + 'clientOrderId': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'closed', + '2': 'canceled', + '3': 'open', // or partially-filled and canceled? https://github.com/ccxt/ccxt/issues/1594 + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'timestamp_created'); + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const remaining = this.safeFloat (order, 'amount'); + const amount = this.safeFloat (order, 'start_amount'); + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = Math.max (0, amount - remaining); + cost = price * filled; + } + } + const fee = undefined; + const type = 'limit'; + const side = this.safeString (order, 'type'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + const response = await this.privatePostOrderInfo (this.extend (request, params)); + id = id.toString (); + const orders = this.safeValue (response, 'return', {}); + return this.parseOrder (this.extend ({ 'id': id }, orders[id])); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const request = {}; + const market = undefined; + if (symbol !== undefined) { + const market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostActiveOrders (this.extend (request, params)); + const orders = this.safeValue (response, 'return', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + // some derived classes use camelcase notation for request fields + const request = { + // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) + // 'count': 1000, // the number of trades for display numerical, default = 1000 + // 'from_id': trade ID, from which the display starts numerical 0 + // 'end_id': trade ID on which the display ends numerical ∞ + // 'order': 'ASC', // sorting, default = DESC (test result: liqui ignores this field, most recent trade always goes last) + // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) + // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) + 'pair': market['id'], + }; + if (limit !== undefined) { + request['count'] = parseInt (limit); + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const trades = this.safeValue (response, 'return', {}); + const ids = Object.keys (trades); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const trade = this.parseTrade (this.extend (trades[id], { + 'trade_id': id, + }), market); + result.push (trade); + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async createDepositAddress (code, params = {}) { + const request = { + 'need_new': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response['info'], + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coinName': currency['id'], + 'need_new': 0, + }; + const response = await this.privatePostGetDepositAddress (this.extend (request, params)); + const address = this.safeString (response['return'], 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coinName': currency['id'], + 'amount': amount, + 'address': address, + }; + // no docs on the tag, yet... + if (tag !== undefined) { + throw new ExchangeError (this.id + ' withdraw() does not support the tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.'); + } + const response = await this.privatePostWithdrawCoinsToAddress (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + 'method': path, + }, query)); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } else if (api === 'public') { + url += '/' + this.version + '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + url += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + if (Object.keys (query).length) { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // 1 - Liqui only returns the integer 'success' key from their private API + // + // { "success": 1, ... } httpCode === 200 + // { "success": 0, ... } httpCode === 200 + // + // 2 - However, exchanges derived from Liqui, can return non-integers + // + // It can be a numeric string + // { "sucesss": "1", ... } + // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) + // + // Or just a string + // { "success": "true", ... } + // { "success": "false", ... }, httpCode >= 200 + // + // Or a boolean + // { "success": true, ... } + // { "success": false, ... }, httpCode >= 200 + // + // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types + // + // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui + // + // To cover points 1, 2, 3 and 4 combined this handler should work like this: + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],178:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class zaif extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'zaif', + 'name': 'Zaif', + 'countries': [ 'JP' ], + 'rateLimit': 2000, + 'version': '1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766927-39ca2ada-5eeb-11e7-972f-1b4199518ca6.jpg', + 'api': 'https://api.zaif.jp', + 'www': 'https://zaif.jp', + 'doc': [ + 'https://techbureau-api-document.readthedocs.io/ja/latest/index.html', + 'https://corp.zaif.jp/api-docs', + 'https://corp.zaif.jp/api-docs/api_links', + 'https://www.npmjs.com/package/zaif.jp', + 'https://github.com/you21979/node-zaif', + ], + 'fees': 'https://zaif.jp/fee?lang=en', + }, + 'fees': { + 'trading': { + 'percentage': true, + 'taker': 0.1 / 100, + 'maker': 0, + }, + }, + 'api': { + 'public': { + 'get': [ + 'depth/{pair}', + 'currencies/{pair}', + 'currencies/all', + 'currency_pairs/{pair}', + 'currency_pairs/all', + 'last_price/{pair}', + 'ticker/{pair}', + 'trades/{pair}', + ], + }, + 'private': { + 'post': [ + 'active_orders', + 'cancel_order', + 'deposit_history', + 'get_id_info', + 'get_info', + 'get_info2', + 'get_personal_info', + 'trade', + 'trade_history', + 'withdraw', + 'withdraw_history', + ], + }, + 'ecapi': { + 'post': [ + 'createInvoice', + 'getInvoice', + 'getInvoiceIdsByOrderNumber', + 'cancelInvoice', + ], + }, + 'tlapi': { + 'post': [ + 'get_positions', + 'position_history', + 'active_positions', + 'create_position', + 'change_position', + 'cancel_position', + ], + }, + 'fapi': { + 'get': [ + 'groups/{group_id}', + 'last_price/{group_id}/{pair}', + 'ticker/{group_id}/{pair}', + 'trades/{group_id}/{pair}', + 'depth/{group_id}/{pair}', + ], + }, + }, + 'options': { + // zaif schedule defines several market-specific fees + 'fees': { + 'BTC/JPY': { 'maker': 0, 'taker': 0 }, + 'BCH/JPY': { 'maker': 0, 'taker': 0.3 / 100 }, + 'BCH/BTC': { 'maker': 0, 'taker': 0.3 / 100 }, + 'PEPECASH/JPY': { 'maker': 0, 'taker': 0.01 / 100 }, + 'PEPECASH/BT': { 'maker': 0, 'taker': 0.01 / 100 }, + }, + }, + 'exceptions': { + 'exact': { + 'unsupported currency_pair': BadRequest, // {"error": "unsupported currency_pair"} + }, + 'broad': { + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetCurrencyPairsAll (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'currency_pair'); + const name = this.safeString (market, 'name'); + const [ baseId, quoteId ] = name.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': -Math.log10 (market['item_unit_step']), + 'price': market['aux_unit_point'], + }; + const fees = this.safeValue (this.options['fees'], symbol, this.fees['trading']); + const taker = fees['taker']; + const maker = fees['maker']; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, // can trade or not + 'precision': precision, + 'taker': taker, + 'maker': maker, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'item_unit_min'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (market, 'aux_unit_min'), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfo (params); + const balances = this.safeValue (response, 'return', {}); + const result = { 'info': response }; + const funds = this.safeValue (balances, 'funds', {}); + const currencyIds = Object.keys (funds); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (funds, currencyId); + const account = { + 'free': balance, + 'used': 0.0, + 'total': balance, + }; + if ('deposit' in balances) { + if (currencyId in balances['deposit']) { + account['total'] = this.safeFloat (balances['deposit'], currencyId); + account['used'] = account['total'] - account['free']; + } + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetDepthPair (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const ticker = await this.publicGetTickerPair (this.extend (request, params)); + const timestamp = this.milliseconds (); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + let side = this.safeString (trade, 'trade_type'); + side = (side === 'bid') ? 'buy' : 'sell'; + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString2 (trade, 'id', 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const marketId = this.safeString (trade, 'currency_pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + let response = await this.publicGetTradesPair (this.extend (request, params)); + const numTrades = response.length; + if (numTrades === 1) { + const firstTrade = response[0]; + if (!Object.keys (firstTrade).length) { + response = []; + } + } + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'currency_pair': this.marketId (symbol), + 'action': (side === 'buy') ? 'bid' : 'ask', + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostTrade (this.extend (request, params)); + return { + 'info': response, + 'id': response['return']['order_id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrder (order, market = undefined) { + // + // { + // "currency_pair": "btc_jpy", + // "action": "ask", + // "amount": 0.03, + // "price": 56000, + // "timestamp": 1402021125, + // "comment" : "demo" + // } + // + let side = this.safeString (order, 'action'); + side = (side === 'bid') ? 'buy' : 'sell'; + const timestamp = this.safeTimestamp (order, 'timestamp'); + const marketId = this.safeString (order, 'currency_pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': 'open', + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': undefined, + 'remaining': undefined, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // 'is_token': false, + // 'is_token_both': false, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['currency_pair'] = market['id']; + } + const response = await this.privatePostActiveOrders (this.extend (request, params)); + return this.parseOrders (response['return'], market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // 'from': 0, + // 'count': 1000, + // 'from_id': 0, + // 'end_id': 1000, + // 'order': 'DESC', + // 'since': 1503821051, + // 'end': 1503821051, + // 'is_token': false, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['currency_pair'] = market['id']; + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + return this.parseOrders (response['return'], market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (code === 'JPY') { + throw new ExchangeError (this.id + ' withdraw() does not allow ' + code + ' withdrawals'); + } + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + // 'message': 'Hi!', // XEM and others + // 'opt_fee': 0.003, // BTC and MONA only + }; + if (tag !== undefined) { + request['message'] = tag; + } + const result = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': result, + 'id': result['return']['txid'], + 'fee': result['return']['fee'], + }; + } + + nonce () { + const nonce = parseFloat (this.milliseconds () / 1000); + return nonce.toFixed (8); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/'; + if (api === 'public') { + url += 'api/' + this.version + '/' + this.implodeParams (path, params); + } else if (api === 'fapi') { + url += 'fapi/' + this.version + '/' + this.implodeParams (path, params); + } else { + this.checkRequiredCredentials (); + if (api === 'ecapi') { + url += 'ecapi'; + } else if (api === 'tlapi') { + url += 'tlapi'; + } else { + url += 'tapi'; + } + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'method': path, + 'nonce': nonce, + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error": "unsupported currency_pair"} + // + const feedback = this.id + ' ' + body; + const error = this.safeString (response, 'error'); + if (error !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + const success = this.safeValue (response, 'success', true); + if (!success) { + throw new ExchangeError (feedback); + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],179:[function(require,module,exports){ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadRequest, ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, OrderNotFound, ExchangeNotAvailable, DDoSProtection, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class zb extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'zb', + 'name': 'ZB', + 'countries': [ 'CN' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '3m': '3min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '12h': '12hour', + '1d': '1day', + '3d': '3day', + '1w': '1week', + }, + 'exceptions': { + // '1000': 'Successful operation', + '1001': ExchangeError, // 'General error message', + '1002': ExchangeError, // 'Internal error', + '1003': AuthenticationError, // 'Verification does not pass', + '1004': AuthenticationError, // 'Funding security password lock', + '1005': AuthenticationError, // 'Funds security password is incorrect, please confirm and re-enter.', + '1006': AuthenticationError, // 'Real-name certification pending approval or audit does not pass', + '1009': ExchangeNotAvailable, // 'This interface is under maintenance', + '2001': InsufficientFunds, // 'Insufficient CNY Balance', + '2002': InsufficientFunds, // 'Insufficient BTC Balance', + '2003': InsufficientFunds, // 'Insufficient LTC Balance', + '2005': InsufficientFunds, // 'Insufficient ETH Balance', + '2006': InsufficientFunds, // 'Insufficient ETC Balance', + '2007': InsufficientFunds, // 'Insufficient BTS Balance', + '2009': InsufficientFunds, // 'Account balance is not enough', + '3001': OrderNotFound, // 'Pending orders not found', + '3002': InvalidOrder, // 'Invalid price', + '3003': InvalidOrder, // 'Invalid amount', + '3004': AuthenticationError, // 'User does not exist', + '3005': BadRequest, // 'Invalid parameter', + '3006': AuthenticationError, // 'Invalid IP or inconsistent with the bound IP', + '3007': AuthenticationError, // 'The request time has expired', + '3008': OrderNotFound, // 'Transaction records not found', + '3009': InvalidOrder, // 'The price exceeds the limit', + '3011': InvalidOrder, // 'The entrusted price is abnormal, please modify it and place order again', + '4001': ExchangeNotAvailable, // 'API interface is locked or not enabled', + '4002': DDoSProtection, // 'Request too often', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/32859187-cd5214f0-ca5e-11e7-967d-96568e2e2bd1.jpg', + 'api': { + 'public': 'http://api.zb.cn/data', // no https for public API + 'private': 'https://trade.zb.cn/api', + }, + 'www': 'https://www.zb.com', + 'doc': 'https://www.zb.com/i/developer', + 'fees': 'https://www.zb.com/i/rate', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'ticker', + 'allTicker', + 'depth', + 'trades', + 'kline', + ], + }, + 'private': { + 'get': [ + // spot API + 'order', + 'cancelOrder', + 'getOrder', + 'getOrders', + 'getOrdersNew', + 'getOrdersIgnoreTradeType', + 'getUnfinishedOrdersIgnoreTradeType', + 'getAccountInfo', + 'getUserAddress', + 'getWithdrawAddress', + 'getWithdrawRecord', + 'getChargeRecord', + 'getCnyWithdrawRecord', + 'getCnyChargeRecord', + 'withdraw', + // leverage API + 'getLeverAssetsInfo', + 'getLeverBills', + 'transferInLever', + 'transferOutLever', + 'loan', + 'cancelLoan', + 'getLoans', + 'getLoanRecords', + 'borrow', + 'repay', + 'getRepayments', + ], + }, + }, + 'fees': { + 'funding': { + 'withdraw': { + 'BTC': 0.0001, + 'BCH': 0.0006, + 'LTC': 0.005, + 'ETH': 0.01, + 'ETC': 0.01, + 'BTS': 3, + 'EOS': 1, + 'QTUM': 0.01, + 'HSR': 0.001, + 'XRP': 0.1, + 'USDT': '0.1%', + 'QCASH': 5, + 'DASH': 0.002, + 'BCD': 0, + 'UBTC': 0, + 'SBTC': 0, + 'INK': 20, + 'TV': 0.1, + 'BTH': 0, + 'BCX': 0, + 'LBTC': 0, + 'CHAT': 20, + 'bitCNY': 20, + 'HLC': 20, + 'BTP': 0, + 'BCW': 0, + }, + }, + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + }, + 'commonCurrencies': { + 'ENT': 'ENTCash', + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetMarkets (params); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amountScale'), + 'price': this.safeInteger (market, 'priceScale'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetGetAccountInfo (params); + // todo: use this somehow + // let permissions = response['result']['base']; + const balances = this.safeValue (response['result'], 'coins'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + // { enName: "BTC", + // freez: "0.00000000", + // unitDecimal: 8, // always 8 + // cnName: "BTC", + // isCanRecharge: true, // TODO: should use this + // unitTag: "฿", + // isCanWithdraw: true, // TODO: should use this + // available: "0.00000000", + // key: "btc" } + const account = this.account (); + const currencyId = this.safeString (balance, 'key'); + const code = this.safeCurrencyCode (currencyId); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'freez'); + result[code] = account; + } + return this.parseBalance (result); + } + + getMarketFieldName () { + return 'market'; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetGetUserAddress (this.extend (request, params)); + let address = response['message']['datas']['key']; + let tag = undefined; + if (address.indexOf ('_') >= 0) { + const parts = address.split ('_'); + address = parts[0]; // WARNING: MAY BE tag_address INSTEAD OF address_tag FOR SOME CURRENCIES!! + tag = parts[1]; + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketFieldName = this.getMarketFieldName (); + const request = {}; + request[marketFieldName] = market['id']; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetAllTicker (params); + const result = {}; + const anotherMarketsById = {}; + const marketIds = Object.keys (this.marketsById); + for (let i = 0; i < marketIds.length; i++) { + const tickerId = marketIds[i].replace ('_', ''); + anotherMarketsById[tickerId] = this.marketsById[marketIds[i]]; + } + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const market = anotherMarketsById[ids[i]]; + result[market['symbol']] = this.parseTicker (response[ids[i]], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketFieldName = this.getMarketFieldName (); + const request = {}; + request[marketFieldName] = market['id']; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = response['ticker']; + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 1000; + } + const request = { + 'market': market['id'], + 'type': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + request['since'] = since; + } + const response = await this.publicGetKline (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let side = this.safeString (trade, 'trade_type'); + side = (side === 'bid') ? 'buy' : 'sell'; + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketFieldName = this.getMarketFieldName (); + const request = {}; + request[marketFieldName] = market['id']; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new InvalidOrder (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const request = { + 'price': this.priceToPrecision (symbol, price), + 'amount': this.amountToPrecision (symbol, amount), + 'tradeType': (side === 'buy') ? '1' : '0', + 'currency': this.marketId (symbol), + }; + const response = await this.privateGetOrder (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id.toString (), + 'currency': this.marketId (symbol), + }; + return await this.privateGetCancelOrder (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const request = { + 'id': id.toString (), + 'currency': this.marketId (symbol), + }; + const response = await this.privateGetGetOrder (this.extend (request, params)); + // + // { + // 'total_amount': 0.01, + // 'id': '20180910244276459', + // 'price': 180.0, + // 'trade_date': 1536576744960, + // 'status': 2, + // 'trade_money': '1.96742', + // 'trade_amount': 0.01, + // 'type': 0, + // 'currency': 'eth_usdt' + // } + // + return this.parseOrder (response, undefined); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = 50, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + 'fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'pageIndex': 1, // default pageIndex is 1 + 'pageSize': limit, // default pageSize is 50 + }; + let method = 'privateGetGetOrdersIgnoreTradeType'; + // tradeType 交易类型1/0[buy/sell] + if ('tradeType' in params) { + method = 'privateGetGetOrdersNew'; + } + let response = undefined; + try { + response = await this[method] (this.extend (request, params)); + } catch (e) { + if (e instanceof OrderNotFound) { + return []; + } + throw e; + } + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = 10, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + 'fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'pageIndex': 1, // default pageIndex is 1 + 'pageSize': limit, // default pageSize is 10 + }; + let method = 'privateGetGetUnfinishedOrdersIgnoreTradeType'; + // tradeType 交易类型1/0[buy/sell] + if ('tradeType' in params) { + method = 'privateGetGetOrdersNew'; + } + let response = undefined; + try { + response = await this[method] (this.extend (request, params)); + } catch (e) { + if (e instanceof OrderNotFound) { + return []; + } + throw e; + } + return this.parseOrders (response, market, since, limit); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // 'total_amount': 0.01, + // 'id': '20180910244276459', + // 'price': 180.0, + // 'trade_date': 1536576744960, + // 'status': 2, + // 'trade_money': '1.96742', + // 'trade_amount': 0.01, + // 'type': 0, + // 'currency': 'eth_usdt' + // } + // + let side = this.safeInteger (order, 'type'); + side = (side === 1) ? 'buy' : 'sell'; + const type = 'limit'; // market order is not availalbe in ZB + let timestamp = undefined; + const createDateField = this.getCreateDateField (); + if (createDateField in order) { + timestamp = order[createDateField]; + } + const marketId = this.safeString (order, 'currency'); + const symbol = this.safeSymbol (marketId, market, '_'); + const price = this.safeFloat (order, 'price'); + const filled = this.safeFloat (order, 'trade_amount'); + const amount = this.safeFloat (order, 'total_amount'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + const cost = this.safeFloat (order, 'trade_money'); + let average = undefined; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + if ((cost !== undefined) && (filled !== undefined) && (filled > 0)) { + average = cost / filled; + } + const id = this.safeString (order, 'id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'canceled', + '2': 'closed', + '3': 'open', // partial + }; + return this.safeString (statuses, status, status); + } + + getCreateDateField () { + return 'trade_date'; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api === 'public') { + url += '/' + this.version + '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + let query = this.keysort (this.extend ({ + 'method': path, + 'accesskey': this.apiKey, + }, params)); + const nonce = this.nonce (); + query = this.keysort (query); + const auth = this.rawencode (query); + const secret = this.hash (this.encode (this.secret), 'sha1'); + const signature = this.hmac (this.encode (auth), this.encode (secret), 'md5'); + const suffix = 'sign=' + signature + '&reqTime=' + nonce.toString (); + url += '/' + path + '?' + auth + '&' + suffix; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (body[0] === '{') { + const feedback = this.id + ' ' + body; + if ('code' in response) { + const code = this.safeString (response, 'code'); + this.throwExactlyMatchedException (this.exceptions, code, feedback); + if (code !== '1000') { + throw new ExchangeError (feedback); + } + } + // special case for {"result":false,"message":"服务端忙碌"} (a "Busy Server" reply) + const result = this.safeValue (response, 'result'); + if (result !== undefined) { + if (!result) { + const message = this.safeString (response, 'message'); + if (message === '服务端忙碌') { + throw new ExchangeNotAvailable (feedback); + } else { + throw new ExchangeError (feedback); + } + } + } + } + } +}; + +},{"./base/Exchange":5,"./base/errors":7}],180:[function(require,module,exports){ +(function (global){ +'use strict'; + +// compare and isBuffer taken from https://github.com/feross/buffer/blob/680e9e5e488f22aac27599a57dc844a6315928dd/index.js +// original notice: + +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +function compare(a, b) { + if (a === b) { + return 0; + } + + var x = a.length; + var y = b.length; + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i]; + y = b[i]; + break; + } + } + + if (x < y) { + return -1; + } + if (y < x) { + return 1; + } + return 0; +} +function isBuffer(b) { + if (global.Buffer && typeof global.Buffer.isBuffer === 'function') { + return global.Buffer.isBuffer(b); + } + return !!(b != null && b._isBuffer); +} + +// based on node assert, original notice: + +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var util = require('util/'); +var hasOwn = Object.prototype.hasOwnProperty; +var pSlice = Array.prototype.slice; +var functionsHaveNames = (function () { + return function foo() {}.name === 'foo'; +}()); +function pToString (obj) { + return Object.prototype.toString.call(obj); +} +function isView(arrbuf) { + if (isBuffer(arrbuf)) { + return false; + } + if (typeof global.ArrayBuffer !== 'function') { + return false; + } + if (typeof ArrayBuffer.isView === 'function') { + return ArrayBuffer.isView(arrbuf); + } + if (!arrbuf) { + return false; + } + if (arrbuf instanceof DataView) { + return true; + } + if (arrbuf.buffer && arrbuf.buffer instanceof ArrayBuffer) { + return true; + } + return false; +} +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +var regex = /\s*function\s+([^\(\s]*)\s*/; +// based on https://github.com/ljharb/function.prototype.name/blob/adeeeec8bfcc6068b187d7d9fb3d5bb1d3a30899/implementation.js +function getName(func) { + if (!util.isFunction(func)) { + return; + } + if (functionsHaveNames) { + return func.name; + } + var str = func.toString(); + var match = str.match(regex); + return match && match[1]; +} +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = getName(stackStartFunction); + var idx = out.indexOf('\n' + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf('\n', idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function truncate(s, n) { + if (typeof s === 'string') { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} +function inspect(something) { + if (functionsHaveNames || !util.isFunction(something)) { + return util.inspect(something); + } + var rawname = getName(something); + var name = rawname ? ': ' + rawname : ''; + return '[Function' + name + ']'; +} +function getMessage(self) { + return truncate(inspect(self.actual), 128) + ' ' + + self.operator + ' ' + + truncate(inspect(self.expected), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected, false)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) { + if (!_deepEqual(actual, expected, true)) { + fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual); + } +}; + +function _deepEqual(actual, expected, strict, memos) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + } else if (isBuffer(actual) && isBuffer(expected)) { + return compare(actual, expected) === 0; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if ((actual === null || typeof actual !== 'object') && + (expected === null || typeof expected !== 'object')) { + return strict ? actual === expected : actual == expected; + + // If both values are instances of typed arrays, wrap their underlying + // ArrayBuffers in a Buffer each to increase performance + // This optimization requires the arrays to have the same type as checked by + // Object.prototype.toString (aka pToString). Never perform binary + // comparisons for Float*Arrays, though, since e.g. +0 === -0 but their + // bit patterns are not identical. + } else if (isView(actual) && isView(expected) && + pToString(actual) === pToString(expected) && + !(actual instanceof Float32Array || + actual instanceof Float64Array)) { + return compare(new Uint8Array(actual.buffer), + new Uint8Array(expected.buffer)) === 0; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else if (isBuffer(actual) !== isBuffer(expected)) { + return false; + } else { + memos = memos || {actual: [], expected: []}; + + var actualIndex = memos.actual.indexOf(actual); + if (actualIndex !== -1) { + if (actualIndex === memos.expected.indexOf(expected)) { + return true; + } + } + + memos.actual.push(actual); + memos.expected.push(expected); + + return objEquiv(actual, expected, strict, memos); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b, strict, actualVisitedObjects) { + if (a === null || a === undefined || b === null || b === undefined) + return false; + // if one is a primitive, the other must be same + if (util.isPrimitive(a) || util.isPrimitive(b)) + return a === b; + if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) + return false; + var aIsArgs = isArguments(a); + var bIsArgs = isArguments(b); + if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) + return false; + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b, strict); + } + var ka = objectKeys(a); + var kb = objectKeys(b); + var key, i; + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length !== kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] !== kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects)) + return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected, false)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +assert.notDeepStrictEqual = notDeepStrictEqual; +function notDeepStrictEqual(actual, expected, message) { + if (_deepEqual(actual, expected, true)) { + fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual); + } +} + + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } + + try { + if (actual instanceof expected) { + return true; + } + } catch (e) { + // Ignore. The instanceof check doesn't work for arrow functions. + } + + if (Error.isPrototypeOf(expected)) { + return false; + } + + return expected.call({}, actual) === true; +} + +function _tryBlock(block) { + var error; + try { + block(); + } catch (e) { + error = e; + } + return error; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (typeof block !== 'function') { + throw new TypeError('"block" argument must be a function'); + } + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + actual = _tryBlock(block); + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + var userProvidedMessage = typeof message === 'string'; + var isUnwantedException = !shouldThrow && util.isError(actual); + var isUnexpectedException = !shouldThrow && actual && !expected; + + if ((isUnwantedException && + userProvidedMessage && + expectedException(actual, expected)) || + isUnexpectedException) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws(true, block, error, message); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { + _throws(false, block, error, message); +}; + +assert.ifError = function(err) { if (err) throw err; }; + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (hasOwn.call(obj, key)) keys.push(key); + } + return keys; +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"util/":238}],181:[function(require,module,exports){ +'use strict' + +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray + +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} + +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function placeHoldersCount (b64) { + var len = b64.length + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0 +} + +function byteLength (b64) { + // base64 is 4/3 + up to two characters of the original data + return (b64.length * 3 / 4) - placeHoldersCount(b64) +} + +function toByteArray (b64) { + var i, l, tmp, placeHolders, arr + var len = b64.length + placeHolders = placeHoldersCount(b64) + + arr = new Arr((len * 3 / 4) - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? len - 4 : len + + var L = 0 + + for (i = 0; i < l; i += 4) { + tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] + arr[L++] = (tmp >> 16) & 0xFF + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF + } + + if (placeHolders === 2) { + tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[L++] = tmp & 0xFF + } else if (placeHolders === 1) { + tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var output = '' + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + output += lookup[tmp >> 2] + output += lookup[(tmp << 4) & 0x3F] + output += '==' + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + (uint8[len - 1]) + output += lookup[tmp >> 10] + output += lookup[(tmp >> 4) & 0x3F] + output += lookup[(tmp << 2) & 0x3F] + output += '=' + } + + parts.push(output) + + return parts.join('') +} + +},{}],182:[function(require,module,exports){ + +},{}],183:[function(require,module,exports){ +(function (process,Buffer){ +'use strict'; +/* eslint camelcase: "off" */ + +var assert = require('assert'); + +var Zstream = require('pako/lib/zlib/zstream'); +var zlib_deflate = require('pako/lib/zlib/deflate.js'); +var zlib_inflate = require('pako/lib/zlib/inflate.js'); +var constants = require('pako/lib/zlib/constants'); + +for (var key in constants) { + exports[key] = constants[key]; +} + +// zlib modes +exports.NONE = 0; +exports.DEFLATE = 1; +exports.INFLATE = 2; +exports.GZIP = 3; +exports.GUNZIP = 4; +exports.DEFLATERAW = 5; +exports.INFLATERAW = 6; +exports.UNZIP = 7; + +var GZIP_HEADER_ID1 = 0x1f; +var GZIP_HEADER_ID2 = 0x8b; + +/** + * Emulate Node's zlib C++ layer for use by the JS layer in index.js + */ +function Zlib(mode) { + if (typeof mode !== 'number' || mode < exports.DEFLATE || mode > exports.UNZIP) { + throw new TypeError('Bad argument'); + } + + this.dictionary = null; + this.err = 0; + this.flush = 0; + this.init_done = false; + this.level = 0; + this.memLevel = 0; + this.mode = mode; + this.strategy = 0; + this.windowBits = 0; + this.write_in_progress = false; + this.pending_close = false; + this.gzip_id_bytes_read = 0; +} + +Zlib.prototype.close = function () { + if (this.write_in_progress) { + this.pending_close = true; + return; + } + + this.pending_close = false; + + assert(this.init_done, 'close before init'); + assert(this.mode <= exports.UNZIP); + + if (this.mode === exports.DEFLATE || this.mode === exports.GZIP || this.mode === exports.DEFLATERAW) { + zlib_deflate.deflateEnd(this.strm); + } else if (this.mode === exports.INFLATE || this.mode === exports.GUNZIP || this.mode === exports.INFLATERAW || this.mode === exports.UNZIP) { + zlib_inflate.inflateEnd(this.strm); + } + + this.mode = exports.NONE; + + this.dictionary = null; +}; + +Zlib.prototype.write = function (flush, input, in_off, in_len, out, out_off, out_len) { + return this._write(true, flush, input, in_off, in_len, out, out_off, out_len); +}; + +Zlib.prototype.writeSync = function (flush, input, in_off, in_len, out, out_off, out_len) { + return this._write(false, flush, input, in_off, in_len, out, out_off, out_len); +}; + +Zlib.prototype._write = function (async, flush, input, in_off, in_len, out, out_off, out_len) { + assert.equal(arguments.length, 8); + + assert(this.init_done, 'write before init'); + assert(this.mode !== exports.NONE, 'already finalized'); + assert.equal(false, this.write_in_progress, 'write already in progress'); + assert.equal(false, this.pending_close, 'close is pending'); + + this.write_in_progress = true; + + assert.equal(false, flush === undefined, 'must provide flush value'); + + this.write_in_progress = true; + + if (flush !== exports.Z_NO_FLUSH && flush !== exports.Z_PARTIAL_FLUSH && flush !== exports.Z_SYNC_FLUSH && flush !== exports.Z_FULL_FLUSH && flush !== exports.Z_FINISH && flush !== exports.Z_BLOCK) { + throw new Error('Invalid flush value'); + } + + if (input == null) { + input = Buffer.alloc(0); + in_len = 0; + in_off = 0; + } + + this.strm.avail_in = in_len; + this.strm.input = input; + this.strm.next_in = in_off; + this.strm.avail_out = out_len; + this.strm.output = out; + this.strm.next_out = out_off; + this.flush = flush; + + if (!async) { + // sync version + this._process(); + + if (this._checkError()) { + return this._afterSync(); + } + return; + } + + // async version + var self = this; + process.nextTick(function () { + self._process(); + self._after(); + }); + + return this; +}; + +Zlib.prototype._afterSync = function () { + var avail_out = this.strm.avail_out; + var avail_in = this.strm.avail_in; + + this.write_in_progress = false; + + return [avail_in, avail_out]; +}; + +Zlib.prototype._process = function () { + var next_expected_header_byte = null; + + // If the avail_out is left at 0, then it means that it ran out + // of room. If there was avail_out left over, then it means + // that all of the input was consumed. + switch (this.mode) { + case exports.DEFLATE: + case exports.GZIP: + case exports.DEFLATERAW: + this.err = zlib_deflate.deflate(this.strm, this.flush); + break; + case exports.UNZIP: + if (this.strm.avail_in > 0) { + next_expected_header_byte = this.strm.next_in; + } + + switch (this.gzip_id_bytes_read) { + case 0: + if (next_expected_header_byte === null) { + break; + } + + if (this.strm.input[next_expected_header_byte] === GZIP_HEADER_ID1) { + this.gzip_id_bytes_read = 1; + next_expected_header_byte++; + + if (this.strm.avail_in === 1) { + // The only available byte was already read. + break; + } + } else { + this.mode = exports.INFLATE; + break; + } + + // fallthrough + case 1: + if (next_expected_header_byte === null) { + break; + } + + if (this.strm.input[next_expected_header_byte] === GZIP_HEADER_ID2) { + this.gzip_id_bytes_read = 2; + this.mode = exports.GUNZIP; + } else { + // There is no actual difference between INFLATE and INFLATERAW + // (after initialization). + this.mode = exports.INFLATE; + } + + break; + default: + throw new Error('invalid number of gzip magic number bytes read'); + } + + // fallthrough + case exports.INFLATE: + case exports.GUNZIP: + case exports.INFLATERAW: + this.err = zlib_inflate.inflate(this.strm, this.flush + + // If data was encoded with dictionary + );if (this.err === exports.Z_NEED_DICT && this.dictionary) { + // Load it + this.err = zlib_inflate.inflateSetDictionary(this.strm, this.dictionary); + if (this.err === exports.Z_OK) { + // And try to decode again + this.err = zlib_inflate.inflate(this.strm, this.flush); + } else if (this.err === exports.Z_DATA_ERROR) { + // Both inflateSetDictionary() and inflate() return Z_DATA_ERROR. + // Make it possible for After() to tell a bad dictionary from bad + // input. + this.err = exports.Z_NEED_DICT; + } + } + while (this.strm.avail_in > 0 && this.mode === exports.GUNZIP && this.err === exports.Z_STREAM_END && this.strm.next_in[0] !== 0x00) { + // Bytes remain in input buffer. Perhaps this is another compressed + // member in the same archive, or just trailing garbage. + // Trailing zero bytes are okay, though, since they are frequently + // used for padding. + + this.reset(); + this.err = zlib_inflate.inflate(this.strm, this.flush); + } + break; + default: + throw new Error('Unknown mode ' + this.mode); + } +}; + +Zlib.prototype._checkError = function () { + // Acceptable error states depend on the type of zlib stream. + switch (this.err) { + case exports.Z_OK: + case exports.Z_BUF_ERROR: + if (this.strm.avail_out !== 0 && this.flush === exports.Z_FINISH) { + this._error('unexpected end of file'); + return false; + } + break; + case exports.Z_STREAM_END: + // normal statuses, not fatal + break; + case exports.Z_NEED_DICT: + if (this.dictionary == null) { + this._error('Missing dictionary'); + } else { + this._error('Bad dictionary'); + } + return false; + default: + // something else. + this._error('Zlib error'); + return false; + } + + return true; +}; + +Zlib.prototype._after = function () { + if (!this._checkError()) { + return; + } + + var avail_out = this.strm.avail_out; + var avail_in = this.strm.avail_in; + + this.write_in_progress = false; + + // call the write() cb + this.callback(avail_in, avail_out); + + if (this.pending_close) { + this.close(); + } +}; + +Zlib.prototype._error = function (message) { + if (this.strm.msg) { + message = this.strm.msg; + } + this.onerror(message, this.err + + // no hope of rescue. + );this.write_in_progress = false; + if (this.pending_close) { + this.close(); + } +}; + +Zlib.prototype.init = function (windowBits, level, memLevel, strategy, dictionary) { + assert(arguments.length === 4 || arguments.length === 5, 'init(windowBits, level, memLevel, strategy, [dictionary])'); + + assert(windowBits >= 8 && windowBits <= 15, 'invalid windowBits'); + assert(level >= -1 && level <= 9, 'invalid compression level'); + + assert(memLevel >= 1 && memLevel <= 9, 'invalid memlevel'); + + assert(strategy === exports.Z_FILTERED || strategy === exports.Z_HUFFMAN_ONLY || strategy === exports.Z_RLE || strategy === exports.Z_FIXED || strategy === exports.Z_DEFAULT_STRATEGY, 'invalid strategy'); + + this._init(level, windowBits, memLevel, strategy, dictionary); + this._setDictionary(); +}; + +Zlib.prototype.params = function () { + throw new Error('deflateParams Not supported'); +}; + +Zlib.prototype.reset = function () { + this._reset(); + this._setDictionary(); +}; + +Zlib.prototype._init = function (level, windowBits, memLevel, strategy, dictionary) { + this.level = level; + this.windowBits = windowBits; + this.memLevel = memLevel; + this.strategy = strategy; + + this.flush = exports.Z_NO_FLUSH; + + this.err = exports.Z_OK; + + if (this.mode === exports.GZIP || this.mode === exports.GUNZIP) { + this.windowBits += 16; + } + + if (this.mode === exports.UNZIP) { + this.windowBits += 32; + } + + if (this.mode === exports.DEFLATERAW || this.mode === exports.INFLATERAW) { + this.windowBits = -1 * this.windowBits; + } + + this.strm = new Zstream(); + + switch (this.mode) { + case exports.DEFLATE: + case exports.GZIP: + case exports.DEFLATERAW: + this.err = zlib_deflate.deflateInit2(this.strm, this.level, exports.Z_DEFLATED, this.windowBits, this.memLevel, this.strategy); + break; + case exports.INFLATE: + case exports.GUNZIP: + case exports.INFLATERAW: + case exports.UNZIP: + this.err = zlib_inflate.inflateInit2(this.strm, this.windowBits); + break; + default: + throw new Error('Unknown mode ' + this.mode); + } + + if (this.err !== exports.Z_OK) { + this._error('Init error'); + } + + this.dictionary = dictionary; + + this.write_in_progress = false; + this.init_done = true; +}; + +Zlib.prototype._setDictionary = function () { + if (this.dictionary == null) { + return; + } + + this.err = exports.Z_OK; + + switch (this.mode) { + case exports.DEFLATE: + case exports.DEFLATERAW: + this.err = zlib_deflate.deflateSetDictionary(this.strm, this.dictionary); + break; + default: + break; + } + + if (this.err !== exports.Z_OK) { + this._error('Failed to set dictionary'); + } +}; + +Zlib.prototype._reset = function () { + this.err = exports.Z_OK; + + switch (this.mode) { + case exports.DEFLATE: + case exports.DEFLATERAW: + case exports.GZIP: + this.err = zlib_deflate.deflateReset(this.strm); + break; + case exports.INFLATE: + case exports.INFLATERAW: + case exports.GUNZIP: + this.err = zlib_inflate.inflateReset(this.strm); + break; + default: + break; + } + + if (this.err !== exports.Z_OK) { + this._error('Failed to reset stream'); + } +}; + +exports.Zlib = Zlib; +}).call(this,require('_process'),require("buffer").Buffer) +},{"_process":207,"assert":180,"buffer":185,"pako/lib/zlib/constants":197,"pako/lib/zlib/deflate.js":199,"pako/lib/zlib/inflate.js":201,"pako/lib/zlib/zstream":205}],184:[function(require,module,exports){ +(function (process){ +'use strict'; + +var Buffer = require('buffer').Buffer; +var Transform = require('stream').Transform; +var binding = require('./binding'); +var util = require('util'); +var assert = require('assert').ok; +var kMaxLength = require('buffer').kMaxLength; +var kRangeErrorMessage = 'Cannot create final Buffer. It would be larger ' + 'than 0x' + kMaxLength.toString(16) + ' bytes'; + +// zlib doesn't provide these, so kludge them in following the same +// const naming scheme zlib uses. +binding.Z_MIN_WINDOWBITS = 8; +binding.Z_MAX_WINDOWBITS = 15; +binding.Z_DEFAULT_WINDOWBITS = 15; + +// fewer than 64 bytes per chunk is stupid. +// technically it could work with as few as 8, but even 64 bytes +// is absurdly low. Usually a MB or more is best. +binding.Z_MIN_CHUNK = 64; +binding.Z_MAX_CHUNK = Infinity; +binding.Z_DEFAULT_CHUNK = 16 * 1024; + +binding.Z_MIN_MEMLEVEL = 1; +binding.Z_MAX_MEMLEVEL = 9; +binding.Z_DEFAULT_MEMLEVEL = 8; + +binding.Z_MIN_LEVEL = -1; +binding.Z_MAX_LEVEL = 9; +binding.Z_DEFAULT_LEVEL = binding.Z_DEFAULT_COMPRESSION; + +// expose all the zlib constants +var bkeys = Object.keys(binding); +for (var bk = 0; bk < bkeys.length; bk++) { + var bkey = bkeys[bk]; + if (bkey.match(/^Z/)) { + Object.defineProperty(exports, bkey, { + enumerable: true, value: binding[bkey], writable: false + }); + } +} + +// translation table for return codes. +var codes = { + Z_OK: binding.Z_OK, + Z_STREAM_END: binding.Z_STREAM_END, + Z_NEED_DICT: binding.Z_NEED_DICT, + Z_ERRNO: binding.Z_ERRNO, + Z_STREAM_ERROR: binding.Z_STREAM_ERROR, + Z_DATA_ERROR: binding.Z_DATA_ERROR, + Z_MEM_ERROR: binding.Z_MEM_ERROR, + Z_BUF_ERROR: binding.Z_BUF_ERROR, + Z_VERSION_ERROR: binding.Z_VERSION_ERROR +}; + +var ckeys = Object.keys(codes); +for (var ck = 0; ck < ckeys.length; ck++) { + var ckey = ckeys[ck]; + codes[codes[ckey]] = ckey; +} + +Object.defineProperty(exports, 'codes', { + enumerable: true, value: Object.freeze(codes), writable: false +}); + +exports.Deflate = Deflate; +exports.Inflate = Inflate; +exports.Gzip = Gzip; +exports.Gunzip = Gunzip; +exports.DeflateRaw = DeflateRaw; +exports.InflateRaw = InflateRaw; +exports.Unzip = Unzip; + +exports.createDeflate = function (o) { + return new Deflate(o); +}; + +exports.createInflate = function (o) { + return new Inflate(o); +}; + +exports.createDeflateRaw = function (o) { + return new DeflateRaw(o); +}; + +exports.createInflateRaw = function (o) { + return new InflateRaw(o); +}; + +exports.createGzip = function (o) { + return new Gzip(o); +}; + +exports.createGunzip = function (o) { + return new Gunzip(o); +}; + +exports.createUnzip = function (o) { + return new Unzip(o); +}; + +// Convenience methods. +// compress/decompress a string or buffer in one step. +exports.deflate = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new Deflate(opts), buffer, callback); +}; + +exports.deflateSync = function (buffer, opts) { + return zlibBufferSync(new Deflate(opts), buffer); +}; + +exports.gzip = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new Gzip(opts), buffer, callback); +}; + +exports.gzipSync = function (buffer, opts) { + return zlibBufferSync(new Gzip(opts), buffer); +}; + +exports.deflateRaw = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new DeflateRaw(opts), buffer, callback); +}; + +exports.deflateRawSync = function (buffer, opts) { + return zlibBufferSync(new DeflateRaw(opts), buffer); +}; + +exports.unzip = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new Unzip(opts), buffer, callback); +}; + +exports.unzipSync = function (buffer, opts) { + return zlibBufferSync(new Unzip(opts), buffer); +}; + +exports.inflate = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new Inflate(opts), buffer, callback); +}; + +exports.inflateSync = function (buffer, opts) { + return zlibBufferSync(new Inflate(opts), buffer); +}; + +exports.gunzip = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new Gunzip(opts), buffer, callback); +}; + +exports.gunzipSync = function (buffer, opts) { + return zlibBufferSync(new Gunzip(opts), buffer); +}; + +exports.inflateRaw = function (buffer, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + return zlibBuffer(new InflateRaw(opts), buffer, callback); +}; + +exports.inflateRawSync = function (buffer, opts) { + return zlibBufferSync(new InflateRaw(opts), buffer); +}; + +function zlibBuffer(engine, buffer, callback) { + var buffers = []; + var nread = 0; + + engine.on('error', onError); + engine.on('end', onEnd); + + engine.end(buffer); + flow(); + + function flow() { + var chunk; + while (null !== (chunk = engine.read())) { + buffers.push(chunk); + nread += chunk.length; + } + engine.once('readable', flow); + } + + function onError(err) { + engine.removeListener('end', onEnd); + engine.removeListener('readable', flow); + callback(err); + } + + function onEnd() { + var buf; + var err = null; + + if (nread >= kMaxLength) { + err = new RangeError(kRangeErrorMessage); + } else { + buf = Buffer.concat(buffers, nread); + } + + buffers = []; + engine.close(); + callback(err, buf); + } +} + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === 'string') buffer = Buffer.from(buffer); + + if (!Buffer.isBuffer(buffer)) throw new TypeError('Not a string or buffer'); + + var flushFlag = engine._finishFlushFlag; + + return engine._processChunk(buffer, flushFlag); +} + +// generic zlib +// minimal 2-byte header +function Deflate(opts) { + if (!(this instanceof Deflate)) return new Deflate(opts); + Zlib.call(this, opts, binding.DEFLATE); +} + +function Inflate(opts) { + if (!(this instanceof Inflate)) return new Inflate(opts); + Zlib.call(this, opts, binding.INFLATE); +} + +// gzip - bigger header, same deflate compression +function Gzip(opts) { + if (!(this instanceof Gzip)) return new Gzip(opts); + Zlib.call(this, opts, binding.GZIP); +} + +function Gunzip(opts) { + if (!(this instanceof Gunzip)) return new Gunzip(opts); + Zlib.call(this, opts, binding.GUNZIP); +} + +// raw - no header +function DeflateRaw(opts) { + if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts); + Zlib.call(this, opts, binding.DEFLATERAW); +} + +function InflateRaw(opts) { + if (!(this instanceof InflateRaw)) return new InflateRaw(opts); + Zlib.call(this, opts, binding.INFLATERAW); +} + +// auto-detect header. +function Unzip(opts) { + if (!(this instanceof Unzip)) return new Unzip(opts); + Zlib.call(this, opts, binding.UNZIP); +} + +function isValidFlushFlag(flag) { + return flag === binding.Z_NO_FLUSH || flag === binding.Z_PARTIAL_FLUSH || flag === binding.Z_SYNC_FLUSH || flag === binding.Z_FULL_FLUSH || flag === binding.Z_FINISH || flag === binding.Z_BLOCK; +} + +// the Zlib class they all inherit from +// This thing manages the queue of requests, and returns +// true or false if there is anything in the queue when +// you call the .write() method. + +function Zlib(opts, mode) { + var _this = this; + + this._opts = opts = opts || {}; + this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK; + + Transform.call(this, opts); + + if (opts.flush && !isValidFlushFlag(opts.flush)) { + throw new Error('Invalid flush flag: ' + opts.flush); + } + if (opts.finishFlush && !isValidFlushFlag(opts.finishFlush)) { + throw new Error('Invalid flush flag: ' + opts.finishFlush); + } + + this._flushFlag = opts.flush || binding.Z_NO_FLUSH; + this._finishFlushFlag = typeof opts.finishFlush !== 'undefined' ? opts.finishFlush : binding.Z_FINISH; + + if (opts.chunkSize) { + if (opts.chunkSize < exports.Z_MIN_CHUNK || opts.chunkSize > exports.Z_MAX_CHUNK) { + throw new Error('Invalid chunk size: ' + opts.chunkSize); + } + } + + if (opts.windowBits) { + if (opts.windowBits < exports.Z_MIN_WINDOWBITS || opts.windowBits > exports.Z_MAX_WINDOWBITS) { + throw new Error('Invalid windowBits: ' + opts.windowBits); + } + } + + if (opts.level) { + if (opts.level < exports.Z_MIN_LEVEL || opts.level > exports.Z_MAX_LEVEL) { + throw new Error('Invalid compression level: ' + opts.level); + } + } + + if (opts.memLevel) { + if (opts.memLevel < exports.Z_MIN_MEMLEVEL || opts.memLevel > exports.Z_MAX_MEMLEVEL) { + throw new Error('Invalid memLevel: ' + opts.memLevel); + } + } + + if (opts.strategy) { + if (opts.strategy != exports.Z_FILTERED && opts.strategy != exports.Z_HUFFMAN_ONLY && opts.strategy != exports.Z_RLE && opts.strategy != exports.Z_FIXED && opts.strategy != exports.Z_DEFAULT_STRATEGY) { + throw new Error('Invalid strategy: ' + opts.strategy); + } + } + + if (opts.dictionary) { + if (!Buffer.isBuffer(opts.dictionary)) { + throw new Error('Invalid dictionary: it should be a Buffer instance'); + } + } + + this._handle = new binding.Zlib(mode); + + var self = this; + this._hadError = false; + this._handle.onerror = function (message, errno) { + // there is no way to cleanly recover. + // continuing only obscures problems. + _close(self); + self._hadError = true; + + var error = new Error(message); + error.errno = errno; + error.code = exports.codes[errno]; + self.emit('error', error); + }; + + var level = exports.Z_DEFAULT_COMPRESSION; + if (typeof opts.level === 'number') level = opts.level; + + var strategy = exports.Z_DEFAULT_STRATEGY; + if (typeof opts.strategy === 'number') strategy = opts.strategy; + + this._handle.init(opts.windowBits || exports.Z_DEFAULT_WINDOWBITS, level, opts.memLevel || exports.Z_DEFAULT_MEMLEVEL, strategy, opts.dictionary); + + this._buffer = Buffer.allocUnsafe(this._chunkSize); + this._offset = 0; + this._level = level; + this._strategy = strategy; + + this.once('end', this.close); + + Object.defineProperty(this, '_closed', { + get: function () { + return !_this._handle; + }, + configurable: true, + enumerable: true + }); +} + +util.inherits(Zlib, Transform); + +Zlib.prototype.params = function (level, strategy, callback) { + if (level < exports.Z_MIN_LEVEL || level > exports.Z_MAX_LEVEL) { + throw new RangeError('Invalid compression level: ' + level); + } + if (strategy != exports.Z_FILTERED && strategy != exports.Z_HUFFMAN_ONLY && strategy != exports.Z_RLE && strategy != exports.Z_FIXED && strategy != exports.Z_DEFAULT_STRATEGY) { + throw new TypeError('Invalid strategy: ' + strategy); + } + + if (this._level !== level || this._strategy !== strategy) { + var self = this; + this.flush(binding.Z_SYNC_FLUSH, function () { + assert(self._handle, 'zlib binding closed'); + self._handle.params(level, strategy); + if (!self._hadError) { + self._level = level; + self._strategy = strategy; + if (callback) callback(); + } + }); + } else { + process.nextTick(callback); + } +}; + +Zlib.prototype.reset = function () { + assert(this._handle, 'zlib binding closed'); + return this._handle.reset(); +}; + +// This is the _flush function called by the transform class, +// internally, when the last chunk has been written. +Zlib.prototype._flush = function (callback) { + this._transform(Buffer.alloc(0), '', callback); +}; + +Zlib.prototype.flush = function (kind, callback) { + var _this2 = this; + + var ws = this._writableState; + + if (typeof kind === 'function' || kind === undefined && !callback) { + callback = kind; + kind = binding.Z_FULL_FLUSH; + } + + if (ws.ended) { + if (callback) process.nextTick(callback); + } else if (ws.ending) { + if (callback) this.once('end', callback); + } else if (ws.needDrain) { + if (callback) { + this.once('drain', function () { + return _this2.flush(kind, callback); + }); + } + } else { + this._flushFlag = kind; + this.write(Buffer.alloc(0), '', callback); + } +}; + +Zlib.prototype.close = function (callback) { + _close(this, callback); + process.nextTick(emitCloseNT, this); +}; + +function _close(engine, callback) { + if (callback) process.nextTick(callback); + + // Caller may invoke .close after a zlib error (which will null _handle). + if (!engine._handle) return; + + engine._handle.close(); + engine._handle = null; +} + +function emitCloseNT(self) { + self.emit('close'); +} + +Zlib.prototype._transform = function (chunk, encoding, cb) { + var flushFlag; + var ws = this._writableState; + var ending = ws.ending || ws.ended; + var last = ending && (!chunk || ws.length === chunk.length); + + if (chunk !== null && !Buffer.isBuffer(chunk)) return cb(new Error('invalid input')); + + if (!this._handle) return cb(new Error('zlib binding closed')); + + // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag + // (or whatever flag was provided using opts.finishFlush). + // If it's explicitly flushing at some other time, then we use + // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression + // goodness. + if (last) flushFlag = this._finishFlushFlag;else { + flushFlag = this._flushFlag; + // once we've flushed the last of the queue, stop flushing and + // go back to the normal behavior. + if (chunk.length >= ws.length) { + this._flushFlag = this._opts.flush || binding.Z_NO_FLUSH; + } + } + + this._processChunk(chunk, flushFlag, cb); +}; + +Zlib.prototype._processChunk = function (chunk, flushFlag, cb) { + var availInBefore = chunk && chunk.length; + var availOutBefore = this._chunkSize - this._offset; + var inOff = 0; + + var self = this; + + var async = typeof cb === 'function'; + + if (!async) { + var buffers = []; + var nread = 0; + + var error; + this.on('error', function (er) { + error = er; + }); + + assert(this._handle, 'zlib binding closed'); + do { + var res = this._handle.writeSync(flushFlag, chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore); // out_len + } while (!this._hadError && callback(res[0], res[1])); + + if (this._hadError) { + throw error; + } + + if (nread >= kMaxLength) { + _close(this); + throw new RangeError(kRangeErrorMessage); + } + + var buf = Buffer.concat(buffers, nread); + _close(this); + + return buf; + } + + assert(this._handle, 'zlib binding closed'); + var req = this._handle.write(flushFlag, chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore); // out_len + + req.buffer = chunk; + req.callback = callback; + + function callback(availInAfter, availOutAfter) { + // When the callback is used in an async write, the callback's + // context is the `req` object that was created. The req object + // is === this._handle, and that's why it's important to null + // out the values after they are done being used. `this._handle` + // can stay in memory longer than the callback and buffer are needed. + if (this) { + this.buffer = null; + this.callback = null; + } + + if (self._hadError) return; + + var have = availOutBefore - availOutAfter; + assert(have >= 0, 'have should not go down'); + + if (have > 0) { + var out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + // serve some output to the consumer. + if (async) { + self.push(out); + } else { + buffers.push(out); + nread += out.length; + } + } + + // exhausted the output buffer, or used all the input create a new one. + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + // Not actually done. Need to reprocess. + // Also, update the availInBefore to the availInAfter value, + // so that if we have to hit it a third (fourth, etc.) time, + // it'll have the correct byte counts. + inOff += availInBefore - availInAfter; + availInBefore = availInAfter; + + if (!async) return true; + + var newReq = self._handle.write(flushFlag, chunk, inOff, availInBefore, self._buffer, self._offset, self._chunkSize); + newReq.callback = callback; // this same function + newReq.buffer = chunk; + return; + } + + if (!async) return false; + + // finished with the chunk. + cb(); + } +}; + +util.inherits(Deflate, Zlib); +util.inherits(Inflate, Zlib); +util.inherits(Gzip, Zlib); +util.inherits(Gunzip, Zlib); +util.inherits(DeflateRaw, Zlib); +util.inherits(InflateRaw, Zlib); +util.inherits(Unzip, Zlib); +}).call(this,require('_process')) +},{"./binding":183,"_process":207,"assert":180,"buffer":185,"stream":226,"util":238}],185:[function(require,module,exports){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} + return arr.foo() === 42 + } catch (e) { + return false + } +} + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('Invalid typed array length') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } + + if (isArrayBuffer(value)) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + return fromObject(value) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be a number') + } else if (size < 0) { + throw new RangeError('"size" argument must not be negative') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('"encoding" must be a valid string encoding') + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('\'offset\' is out of bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('\'length\' is out of bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj) { + if (isArrayBufferView(obj) || 'length' in obj) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } + } + + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true +} + +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (isArrayBufferView(string) || isArrayBuffer(string)) { + return string.byteLength + } + if (typeof string !== 'string') { + string = '' + string + } + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + case undefined: + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (!Buffer.isBuffer(target)) { + throw new TypeError('Argument must be a Buffer') + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new TypeError('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + var i + + if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else if (len < 1000) { + // ascending copy from start + for (i = 0; i < len; ++i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, start + len), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if (code < 256) { + val = code + } + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : new Buffer(val, encoding) + var len = bytes.length + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +// ArrayBuffers from another context (i.e. an iframe) do not pass the `instanceof` check +// but they should be treated as valid. See: https://github.com/feross/buffer/issues/166 +function isArrayBuffer (obj) { + return obj instanceof ArrayBuffer || + (obj != null && obj.constructor != null && obj.constructor.name === 'ArrayBuffer' && + typeof obj.byteLength === 'number') +} + +// Node 0.10 supports `ArrayBuffer` but lacks `ArrayBuffer.isView` +function isArrayBufferView (obj) { + return (typeof ArrayBuffer.isView === 'function') && ArrayBuffer.isView(obj) +} + +function numberIsNaN (obj) { + return obj !== obj // eslint-disable-line no-self-compare +} + +},{"base64-js":181,"ieee754":191}],186:[function(require,module,exports){ +module.exports = { + "100": "Continue", + "101": "Switching Protocols", + "102": "Processing", + "200": "OK", + "201": "Created", + "202": "Accepted", + "203": "Non-Authoritative Information", + "204": "No Content", + "205": "Reset Content", + "206": "Partial Content", + "207": "Multi-Status", + "208": "Already Reported", + "226": "IM Used", + "300": "Multiple Choices", + "301": "Moved Permanently", + "302": "Found", + "303": "See Other", + "304": "Not Modified", + "305": "Use Proxy", + "307": "Temporary Redirect", + "308": "Permanent Redirect", + "400": "Bad Request", + "401": "Unauthorized", + "402": "Payment Required", + "403": "Forbidden", + "404": "Not Found", + "405": "Method Not Allowed", + "406": "Not Acceptable", + "407": "Proxy Authentication Required", + "408": "Request Timeout", + "409": "Conflict", + "410": "Gone", + "411": "Length Required", + "412": "Precondition Failed", + "413": "Payload Too Large", + "414": "URI Too Long", + "415": "Unsupported Media Type", + "416": "Range Not Satisfiable", + "417": "Expectation Failed", + "418": "I'm a teapot", + "421": "Misdirected Request", + "422": "Unprocessable Entity", + "423": "Locked", + "424": "Failed Dependency", + "425": "Unordered Collection", + "426": "Upgrade Required", + "428": "Precondition Required", + "429": "Too Many Requests", + "431": "Request Header Fields Too Large", + "451": "Unavailable For Legal Reasons", + "500": "Internal Server Error", + "501": "Not Implemented", + "502": "Bad Gateway", + "503": "Service Unavailable", + "504": "Gateway Timeout", + "505": "HTTP Version Not Supported", + "506": "Variant Also Negotiates", + "507": "Insufficient Storage", + "508": "Loop Detected", + "509": "Bandwidth Limit Exceeded", + "510": "Not Extended", + "511": "Network Authentication Required" +} + +},{}],187:[function(require,module,exports){ +module.exports={ + "O_RDONLY": 0, + "O_WRONLY": 1, + "O_RDWR": 2, + "S_IFMT": 61440, + "S_IFREG": 32768, + "S_IFDIR": 16384, + "S_IFCHR": 8192, + "S_IFBLK": 24576, + "S_IFIFO": 4096, + "S_IFLNK": 40960, + "S_IFSOCK": 49152, + "O_CREAT": 512, + "O_EXCL": 2048, + "O_NOCTTY": 131072, + "O_TRUNC": 1024, + "O_APPEND": 8, + "O_DIRECTORY": 1048576, + "O_NOFOLLOW": 256, + "O_SYNC": 128, + "O_SYMLINK": 2097152, + "O_NONBLOCK": 4, + "S_IRWXU": 448, + "S_IRUSR": 256, + "S_IWUSR": 128, + "S_IXUSR": 64, + "S_IRWXG": 56, + "S_IRGRP": 32, + "S_IWGRP": 16, + "S_IXGRP": 8, + "S_IRWXO": 7, + "S_IROTH": 4, + "S_IWOTH": 2, + "S_IXOTH": 1, + "E2BIG": 7, + "EACCES": 13, + "EADDRINUSE": 48, + "EADDRNOTAVAIL": 49, + "EAFNOSUPPORT": 47, + "EAGAIN": 35, + "EALREADY": 37, + "EBADF": 9, + "EBADMSG": 94, + "EBUSY": 16, + "ECANCELED": 89, + "ECHILD": 10, + "ECONNABORTED": 53, + "ECONNREFUSED": 61, + "ECONNRESET": 54, + "EDEADLK": 11, + "EDESTADDRREQ": 39, + "EDOM": 33, + "EDQUOT": 69, + "EEXIST": 17, + "EFAULT": 14, + "EFBIG": 27, + "EHOSTUNREACH": 65, + "EIDRM": 90, + "EILSEQ": 92, + "EINPROGRESS": 36, + "EINTR": 4, + "EINVAL": 22, + "EIO": 5, + "EISCONN": 56, + "EISDIR": 21, + "ELOOP": 62, + "EMFILE": 24, + "EMLINK": 31, + "EMSGSIZE": 40, + "EMULTIHOP": 95, + "ENAMETOOLONG": 63, + "ENETDOWN": 50, + "ENETRESET": 52, + "ENETUNREACH": 51, + "ENFILE": 23, + "ENOBUFS": 55, + "ENODATA": 96, + "ENODEV": 19, + "ENOENT": 2, + "ENOEXEC": 8, + "ENOLCK": 77, + "ENOLINK": 97, + "ENOMEM": 12, + "ENOMSG": 91, + "ENOPROTOOPT": 42, + "ENOSPC": 28, + "ENOSR": 98, + "ENOSTR": 99, + "ENOSYS": 78, + "ENOTCONN": 57, + "ENOTDIR": 20, + "ENOTEMPTY": 66, + "ENOTSOCK": 38, + "ENOTSUP": 45, + "ENOTTY": 25, + "ENXIO": 6, + "EOPNOTSUPP": 102, + "EOVERFLOW": 84, + "EPERM": 1, + "EPIPE": 32, + "EPROTO": 100, + "EPROTONOSUPPORT": 43, + "EPROTOTYPE": 41, + "ERANGE": 34, + "EROFS": 30, + "ESPIPE": 29, + "ESRCH": 3, + "ESTALE": 70, + "ETIME": 101, + "ETIMEDOUT": 60, + "ETXTBSY": 26, + "EWOULDBLOCK": 35, + "EXDEV": 18, + "SIGHUP": 1, + "SIGINT": 2, + "SIGQUIT": 3, + "SIGILL": 4, + "SIGTRAP": 5, + "SIGABRT": 6, + "SIGIOT": 6, + "SIGBUS": 10, + "SIGFPE": 8, + "SIGKILL": 9, + "SIGUSR1": 30, + "SIGSEGV": 11, + "SIGUSR2": 31, + "SIGPIPE": 13, + "SIGALRM": 14, + "SIGTERM": 15, + "SIGCHLD": 20, + "SIGCONT": 19, + "SIGSTOP": 17, + "SIGTSTP": 18, + "SIGTTIN": 21, + "SIGTTOU": 22, + "SIGURG": 16, + "SIGXCPU": 24, + "SIGXFSZ": 25, + "SIGVTALRM": 26, + "SIGPROF": 27, + "SIGWINCH": 28, + "SIGIO": 23, + "SIGSYS": 12, + "SSL_OP_ALL": 2147486719, + "SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION": 262144, + "SSL_OP_CIPHER_SERVER_PREFERENCE": 4194304, + "SSL_OP_CISCO_ANYCONNECT": 32768, + "SSL_OP_COOKIE_EXCHANGE": 8192, + "SSL_OP_CRYPTOPRO_TLSEXT_BUG": 2147483648, + "SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS": 2048, + "SSL_OP_EPHEMERAL_RSA": 0, + "SSL_OP_LEGACY_SERVER_CONNECT": 4, + "SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER": 32, + "SSL_OP_MICROSOFT_SESS_ID_BUG": 1, + "SSL_OP_MSIE_SSLV2_RSA_PADDING": 0, + "SSL_OP_NETSCAPE_CA_DN_BUG": 536870912, + "SSL_OP_NETSCAPE_CHALLENGE_BUG": 2, + "SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG": 1073741824, + "SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG": 8, + "SSL_OP_NO_COMPRESSION": 131072, + "SSL_OP_NO_QUERY_MTU": 4096, + "SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION": 65536, + "SSL_OP_NO_SSLv2": 16777216, + "SSL_OP_NO_SSLv3": 33554432, + "SSL_OP_NO_TICKET": 16384, + "SSL_OP_NO_TLSv1": 67108864, + "SSL_OP_NO_TLSv1_1": 268435456, + "SSL_OP_NO_TLSv1_2": 134217728, + "SSL_OP_PKCS1_CHECK_1": 0, + "SSL_OP_PKCS1_CHECK_2": 0, + "SSL_OP_SINGLE_DH_USE": 1048576, + "SSL_OP_SINGLE_ECDH_USE": 524288, + "SSL_OP_SSLEAY_080_CLIENT_DH_BUG": 128, + "SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG": 0, + "SSL_OP_TLS_BLOCK_PADDING_BUG": 512, + "SSL_OP_TLS_D5_BUG": 256, + "SSL_OP_TLS_ROLLBACK_BUG": 8388608, + "ENGINE_METHOD_DSA": 2, + "ENGINE_METHOD_DH": 4, + "ENGINE_METHOD_RAND": 8, + "ENGINE_METHOD_ECDH": 16, + "ENGINE_METHOD_ECDSA": 32, + "ENGINE_METHOD_CIPHERS": 64, + "ENGINE_METHOD_DIGESTS": 128, + "ENGINE_METHOD_STORE": 256, + "ENGINE_METHOD_PKEY_METHS": 512, + "ENGINE_METHOD_PKEY_ASN1_METHS": 1024, + "ENGINE_METHOD_ALL": 65535, + "ENGINE_METHOD_NONE": 0, + "DH_CHECK_P_NOT_SAFE_PRIME": 2, + "DH_CHECK_P_NOT_PRIME": 1, + "DH_UNABLE_TO_CHECK_GENERATOR": 4, + "DH_NOT_SUITABLE_GENERATOR": 8, + "NPN_ENABLED": 1, + "RSA_PKCS1_PADDING": 1, + "RSA_SSLV23_PADDING": 2, + "RSA_NO_PADDING": 3, + "RSA_PKCS1_OAEP_PADDING": 4, + "RSA_X931_PADDING": 5, + "RSA_PKCS1_PSS_PADDING": 6, + "POINT_CONVERSION_COMPRESSED": 2, + "POINT_CONVERSION_UNCOMPRESSED": 4, + "POINT_CONVERSION_HYBRID": 6, + "F_OK": 0, + "R_OK": 4, + "W_OK": 2, + "X_OK": 1, + "UV_UDP_REUSEADDR": 4 +} + +},{}],188:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}).call(this,{"isBuffer":require("../../is-buffer/index.js")}) +},{"../../is-buffer/index.js":193}],189:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); + err.context = er; + throw err; + } + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.prototype.listenerCount = function(type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) + return 1; + else if (evlistener) + return evlistener.length; + } + return 0; +}; + +EventEmitter.listenerCount = function(emitter, type) { + return emitter.listenerCount(type); +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],190:[function(require,module,exports){ +var http = require('http') +var url = require('url') + +var https = module.exports + +for (var key in http) { + if (http.hasOwnProperty(key)) https[key] = http[key] +} + +https.request = function (params, cb) { + params = validateParams(params) + return http.request.call(this, params, cb) +} + +https.get = function (params, cb) { + params = validateParams(params) + return http.get.call(this, params, cb) +} + +function validateParams (params) { + if (typeof params === 'string') { + params = url.parse(params) + } + if (!params.protocol) { + params.protocol = 'https:' + } + if (params.protocol !== 'https:') { + throw new Error('Protocol "' + params.protocol + '" not supported. Expected "https:"') + } + return params +} + +},{"http":227,"url":233}],191:[function(require,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],192:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],193:[function(require,module,exports){ +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) +} + +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) +} + +},{}],194:[function(require,module,exports){ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; + +},{}],195:[function(require,module,exports){ +'use strict'; + + +var TYPED_OK = (typeof Uint8Array !== 'undefined') && + (typeof Uint16Array !== 'undefined') && + (typeof Int32Array !== 'undefined'); + +function _has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +exports.assign = function (obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + var source = sources.shift(); + if (!source) { continue; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be non-object'); + } + + for (var p in source) { + if (_has(source, p)) { + obj[p] = source[p]; + } + } + } + + return obj; +}; + + +// reduce buffer size, avoiding mem copy +exports.shrinkBuf = function (buf, size) { + if (buf.length === size) { return buf; } + if (buf.subarray) { return buf.subarray(0, size); } + buf.length = size; + return buf; +}; + + +var fnTyped = { + arraySet: function (dest, src, src_offs, len, dest_offs) { + if (src.subarray && dest.subarray) { + dest.set(src.subarray(src_offs, src_offs + len), dest_offs); + return; + } + // Fallback to ordinary array + for (var i = 0; i < len; i++) { + dest[dest_offs + i] = src[src_offs + i]; + } + }, + // Join array of chunks to single array. + flattenChunks: function (chunks) { + var i, l, len, pos, chunk, result; + + // calculate data length + len = 0; + for (i = 0, l = chunks.length; i < l; i++) { + len += chunks[i].length; + } + + // join chunks + result = new Uint8Array(len); + pos = 0; + for (i = 0, l = chunks.length; i < l; i++) { + chunk = chunks[i]; + result.set(chunk, pos); + pos += chunk.length; + } + + return result; + } +}; + +var fnUntyped = { + arraySet: function (dest, src, src_offs, len, dest_offs) { + for (var i = 0; i < len; i++) { + dest[dest_offs + i] = src[src_offs + i]; + } + }, + // Join array of chunks to single array. + flattenChunks: function (chunks) { + return [].concat.apply([], chunks); + } +}; + + +// Enable/Disable typed arrays use, for testing +// +exports.setTyped = function (on) { + if (on) { + exports.Buf8 = Uint8Array; + exports.Buf16 = Uint16Array; + exports.Buf32 = Int32Array; + exports.assign(exports, fnTyped); + } else { + exports.Buf8 = Array; + exports.Buf16 = Array; + exports.Buf32 = Array; + exports.assign(exports, fnUntyped); + } +}; + +exports.setTyped(TYPED_OK); + +},{}],196:[function(require,module,exports){ +'use strict'; + +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It isn't worth it to make additional optimizations as in original. +// Small size is preferable. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function adler32(adler, buf, len, pos) { + var s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) |0; + s2 = (s2 + s1) |0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) |0; +} + + +module.exports = adler32; + +},{}],197:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +module.exports = { + + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + //Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + //Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8 + //Z_NULL: null // Use -1 or null inline, depending on var type +}; + +},{}],198:[function(require,module,exports){ +'use strict'; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// Use ordinary array, since untyped makes no boost here +function makeTable() { + var c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +} + +// Create table on load. Just 255 signed longs. Not a problem. +var crcTable = makeTable(); + + +function crc32(crc, buf, len, pos) { + var t = crcTable, + end = pos + len; + + crc ^= -1; + + for (var i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + + +module.exports = crc32; + +},{}],199:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var utils = require('../utils/common'); +var trees = require('./trees'); +var adler32 = require('./adler32'); +var crc32 = require('./crc32'); +var msg = require('./messages'); + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +var Z_NO_FLUSH = 0; +var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +//var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +//var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +//var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + + +/* compression levels */ +//var Z_NO_COMPRESSION = 0; +//var Z_BEST_SPEED = 1; +//var Z_BEST_COMPRESSION = 9; +var Z_DEFAULT_COMPRESSION = -1; + + +var Z_FILTERED = 1; +var Z_HUFFMAN_ONLY = 2; +var Z_RLE = 3; +var Z_FIXED = 4; +var Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +//var Z_BINARY = 0; +//var Z_TEXT = 1; +//var Z_ASCII = 1; // = Z_TEXT +var Z_UNKNOWN = 2; + + +/* The deflate compression method */ +var Z_DEFLATED = 8; + +/*============================================================================*/ + + +var MAX_MEM_LEVEL = 9; +/* Maximum value for memLevel in deflateInit2 */ +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_MEM_LEVEL = 8; + + +var LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ +var LITERALS = 256; +/* number of literal bytes 0..255 */ +var L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ +var D_CODES = 30; +/* number of distance codes */ +var BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ +var HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ +var MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +var MIN_MATCH = 3; +var MAX_MATCH = 258; +var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); + +var PRESET_DICT = 0x20; + +var INIT_STATE = 42; +var EXTRA_STATE = 69; +var NAME_STATE = 73; +var COMMENT_STATE = 91; +var HCRC_STATE = 103; +var BUSY_STATE = 113; +var FINISH_STATE = 666; + +var BS_NEED_MORE = 1; /* block not completed, need more input or more output */ +var BS_BLOCK_DONE = 2; /* block flush performed */ +var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ +var BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ + +var OS_CODE = 0x03; // Unix :) . Don't detect, use this default. + +function err(strm, errorCode) { + strm.msg = msg[errorCode]; + return errorCode; +} + +function rank(f) { + return ((f) << 1) - ((f) > 4 ? 9 : 0); +} + +function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } + + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->output buffer and copying into it. + * (See also read_buf()). + */ +function flush_pending(strm) { + var s = strm.state; + + //_tr_flush_bits(s); + var len = s.pending; + if (len > strm.avail_out) { + len = strm.avail_out; + } + if (len === 0) { return; } + + utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out); + strm.next_out += len; + s.pending_out += len; + strm.total_out += len; + strm.avail_out -= len; + s.pending -= len; + if (s.pending === 0) { + s.pending_out = 0; + } +} + + +function flush_block_only(s, last) { + trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); + s.block_start = s.strstart; + flush_pending(s.strm); +} + + +function put_byte(s, b) { + s.pending_buf[s.pending++] = b; +} + + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +function putShortMSB(s, b) { +// put_byte(s, (Byte)(b >> 8)); +// put_byte(s, (Byte)(b & 0xff)); + s.pending_buf[s.pending++] = (b >>> 8) & 0xff; + s.pending_buf[s.pending++] = b & 0xff; +} + + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->input buffer and copying from it. + * (See also flush_pending()). + */ +function read_buf(strm, buf, start, size) { + var len = strm.avail_in; + + if (len > size) { len = size; } + if (len === 0) { return 0; } + + strm.avail_in -= len; + + // zmemcpy(buf, strm->next_in, len); + utils.arraySet(buf, strm.input, strm.next_in, len, start); + if (strm.state.wrap === 1) { + strm.adler = adler32(strm.adler, buf, len, start); + } + + else if (strm.state.wrap === 2) { + strm.adler = crc32(strm.adler, buf, len, start); + } + + strm.next_in += len; + strm.total_in += len; + + return len; +} + + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +function longest_match(s, cur_match) { + var chain_length = s.max_chain_length; /* max hash chain length */ + var scan = s.strstart; /* current string */ + var match; /* matched string */ + var len; /* length of current match */ + var best_len = s.prev_length; /* best match length so far */ + var nice_match = s.nice_match; /* stop if match long enough */ + var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? + s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; + + var _win = s.window; // shortcut + + var wmask = s.w_mask; + var prev = s.prev; + + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + + var strend = s.strstart + MAX_MATCH; + var scan_end1 = _win[scan + best_len - 1]; + var scan_end = _win[scan + best_len]; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s.prev_length >= s.good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (nice_match > s.lookahead) { nice_match = s.lookahead; } + + // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + // Assert(cur_match < s->strstart, "no future"); + match = cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ + + if (_win[match + best_len] !== scan_end || + _win[match + best_len - 1] !== scan_end1 || + _win[match] !== _win[scan] || + _win[++match] !== _win[scan + 1]) { + continue; + } + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2; + match++; + // Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + /*jshint noempty:false*/ + } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + scan < strend); + + // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + s.match_start = cur_match; + best_len = len; + if (len >= nice_match) { + break; + } + scan_end1 = _win[scan + best_len - 1]; + scan_end = _win[scan + best_len]; + } + } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); + + if (best_len <= s.lookahead) { + return best_len; + } + return s.lookahead; +} + + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +function fill_window(s) { + var _w_size = s.w_size; + var p, n, m, more, str; + + //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = s.window_size - s.lookahead - s.strstart; + + // JS ints have 32 bit, block below not needed + /* Deal with !@#$% 64K limit: */ + //if (sizeof(int) <= 2) { + // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + // more = wsize; + // + // } else if (more == (unsigned)(-1)) { + // /* Very unlikely, but possible on 16 bit machine if + // * strstart == 0 && lookahead == 1 (input done a byte at time) + // */ + // more--; + // } + //} + + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { + + utils.arraySet(s.window, s.window, _w_size, _w_size, 0); + s.match_start -= _w_size; + s.strstart -= _w_size; + /* we now have strstart >= MAX_DIST */ + s.block_start -= _w_size; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + + n = s.hash_size; + p = n; + do { + m = s.head[--p]; + s.head[p] = (m >= _w_size ? m - _w_size : 0); + } while (--n); + + n = _w_size; + p = n; + do { + m = s.prev[--p]; + s.prev[p] = (m >= _w_size ? m - _w_size : 0); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); + + more += _w_size; + } + if (s.strm.avail_in === 0) { + break; + } + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + //Assert(more >= 2, "more < 2"); + n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); + s.lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s.lookahead + s.insert >= MIN_MATCH) { + str = s.strstart - s.insert; + s.ins_h = s.window[str]; + + /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask; +//#if MIN_MATCH != 3 +// Call update_hash() MIN_MATCH-3 more times +//#endif + while (s.insert) { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = str; + str++; + s.insert--; + if (s.lookahead + s.insert < MIN_MATCH) { + break; + } + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ +// if (s.high_water < s.window_size) { +// var curr = s.strstart + s.lookahead; +// var init = 0; +// +// if (s.high_water < curr) { +// /* Previous high water mark below current data -- zero WIN_INIT +// * bytes or up to end of window, whichever is less. +// */ +// init = s.window_size - curr; +// if (init > WIN_INIT) +// init = WIN_INIT; +// zmemzero(s->window + curr, (unsigned)init); +// s->high_water = curr + init; +// } +// else if (s->high_water < (ulg)curr + WIN_INIT) { +// /* High water mark at or above current data, but below current data +// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up +// * to end of window, whichever is less. +// */ +// init = (ulg)curr + WIN_INIT - s->high_water; +// if (init > s->window_size - s->high_water) +// init = s->window_size - s->high_water; +// zmemzero(s->window + s->high_water, (unsigned)init); +// s->high_water += init; +// } +// } +// +// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, +// "not enough room for search"); +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +function deflate_stored(s, flush) { + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + var max_block_size = 0xffff; + + if (max_block_size > s.pending_buf_size - 5) { + max_block_size = s.pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s.lookahead <= 1) { + + //Assert(s->strstart < s->w_size+MAX_DIST(s) || + // s->block_start >= (long)s->w_size, "slide too late"); +// if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) || +// s.block_start >= s.w_size)) { +// throw new Error("slide too late"); +// } + + fill_window(s); + if (s.lookahead === 0 && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + + if (s.lookahead === 0) { + break; + } + /* flush the current block */ + } + //Assert(s->block_start >= 0L, "block gone"); +// if (s.block_start < 0) throw new Error("block gone"); + + s.strstart += s.lookahead; + s.lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + var max_start = s.block_start + max_block_size; + + if (s.strstart === 0 || s.strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s.lookahead = s.strstart - max_start; + s.strstart = max_start; + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + + + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + + s.insert = 0; + + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + + if (s.strstart > s.block_start) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_NEED_MORE; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +function deflate_fast(s, flush) { + var hash_head; /* head of the hash chain */ + var bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { + break; /* flush the current block */ + } + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + } + if (s.match_length >= MIN_MATCH) { + // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only + + /*** _tr_tally_dist(s, s.strstart - s.match_start, + s.match_length - MIN_MATCH, bflush); ***/ + bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ + if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { + s.match_length--; /* string at strstart already in table */ + do { + s.strstart++; + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s.match_length !== 0); + s.strstart++; + } else + { + s.strstart += s.match_length; + s.match_length = 0; + s.ins_h = s.window[s.strstart]; + /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask; + +//#if MIN_MATCH != 3 +// Call UPDATE_HASH() MIN_MATCH-3 more times +//#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s.window[s.strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +function deflate_slow(s, flush) { + var hash_head; /* head of hash chain */ + var bflush; /* set if current block must be flushed */ + + var max_insert; + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0/*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + */ + s.prev_length = s.match_length; + s.prev_match = s.match_start; + s.match_length = MIN_MATCH - 1; + + if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && + s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + + if (s.match_length <= 5 && + (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s.match_length = MIN_MATCH - 1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { + max_insert = s.strstart + s.lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + //check_match(s, s.strstart-1, s.prev_match, s.prev_length); + + /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, bflush);***/ + bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s.lookahead -= s.prev_length - 1; + s.prev_length -= 2; + do { + if (++s.strstart <= max_insert) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + } while (--s.prev_length !== 0); + s.match_available = 0; + s.match_length = MIN_MATCH - 1; + s.strstart++; + + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + } else if (s.match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); + + if (bflush) { + /*** FLUSH_BLOCK_ONLY(s, 0) ***/ + flush_block_only(s, false); + /***/ + } + s.strstart++; + s.lookahead--; + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s.match_available = 1; + s.strstart++; + s.lookahead--; + } + } + //Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s.match_available) { + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); + + s.match_available = 0; + } + s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_BLOCK_DONE; +} + + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +function deflate_rle(s, flush) { + var bflush; /* set if current block must be flushed */ + var prev; /* byte at distance one to match */ + var scan, strend; /* scan goes up to strend for length of run */ + + var _win = s.window; + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s.lookahead <= MAX_MATCH) { + fill_window(s); + if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { break; } /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s.match_length = 0; + if (s.lookahead >= MIN_MATCH && s.strstart > 0) { + scan = s.strstart - 1; + prev = _win[scan]; + if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { + strend = s.strstart + MAX_MATCH; + do { + /*jshint noempty:false*/ + } while (prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + scan < strend); + s.match_length = MAX_MATCH - (strend - scan); + if (s.match_length > s.lookahead) { + s.match_length = s.lookahead; + } + } + //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s.match_length >= MIN_MATCH) { + //check_match(s, s.strstart, s.strstart - 1, s.match_length); + + /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ + bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + s.strstart += s.match_length; + s.match_length = 0; + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +function deflate_huff(s, flush) { + var bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s.lookahead === 0) { + fill_window(s); + if (s.lookahead === 0) { + if (flush === Z_NO_FLUSH) { + return BS_NEED_MORE; + } + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s.match_length = 0; + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = trees._tr_tally(s, 0, s.window[s.strstart]); + s.lookahead--; + s.strstart++; + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +} + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +function Config(good_length, max_lazy, nice_length, max_chain, func) { + this.good_length = good_length; + this.max_lazy = max_lazy; + this.nice_length = nice_length; + this.max_chain = max_chain; + this.func = func; +} + +var configuration_table; + +configuration_table = [ + /* good lazy nice chain */ + new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ + new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ + new Config(4, 5, 16, 8, deflate_fast), /* 2 */ + new Config(4, 6, 32, 32, deflate_fast), /* 3 */ + + new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ + new Config(8, 16, 32, 32, deflate_slow), /* 5 */ + new Config(8, 16, 128, 128, deflate_slow), /* 6 */ + new Config(8, 32, 128, 256, deflate_slow), /* 7 */ + new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ + new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ +]; + + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +function lm_init(s) { + s.window_size = 2 * s.w_size; + + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + + /* Set the default configuration parameters: + */ + s.max_lazy_match = configuration_table[s.level].max_lazy; + s.good_match = configuration_table[s.level].good_length; + s.nice_match = configuration_table[s.level].nice_length; + s.max_chain_length = configuration_table[s.level].max_chain; + + s.strstart = 0; + s.block_start = 0; + s.lookahead = 0; + s.insert = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + s.ins_h = 0; +} + + +function DeflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.status = 0; /* as the name implies */ + this.pending_buf = null; /* output still pending */ + this.pending_buf_size = 0; /* size of pending_buf */ + this.pending_out = 0; /* next pending byte to output to the stream */ + this.pending = 0; /* nb of bytes in the pending buffer */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.gzhead = null; /* gzip header information to write */ + this.gzindex = 0; /* where in extra, name, or comment */ + this.method = Z_DEFLATED; /* can only be DEFLATED */ + this.last_flush = -1; /* value of flush param for previous deflate call */ + + this.w_size = 0; /* LZ77 window size (32K by default) */ + this.w_bits = 0; /* log2(w_size) (8..16) */ + this.w_mask = 0; /* w_size - 1 */ + + this.window = null; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. + */ + + this.window_size = 0; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + this.prev = null; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + this.head = null; /* Heads of the hash chains or NIL. */ + + this.ins_h = 0; /* hash index of string to be inserted */ + this.hash_size = 0; /* number of elements in hash table */ + this.hash_bits = 0; /* log2(hash_size) */ + this.hash_mask = 0; /* hash_size-1 */ + + this.hash_shift = 0; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + this.block_start = 0; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + this.match_length = 0; /* length of best match */ + this.prev_match = 0; /* previous match */ + this.match_available = 0; /* set if previous match exists */ + this.strstart = 0; /* start of string to insert */ + this.match_start = 0; /* start of matching string */ + this.lookahead = 0; /* number of valid bytes ahead in window */ + + this.prev_length = 0; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + this.max_chain_length = 0; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + this.max_lazy_match = 0; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + // That's alias to max_lazy_match, don't use directly + //this.max_insert_length = 0; + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + this.level = 0; /* compression level (1..9) */ + this.strategy = 0; /* favor or force Huffman coding*/ + + this.good_match = 0; + /* Use a faster search when the previous match is longer than this */ + + this.nice_match = 0; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + + /* Didn't use ct_data typedef below to suppress compiler warning */ + + // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + // Use flat array of DOUBLE size, with interleaved fata, + // because JS does not support effective + this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2); + this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2); + this.bl_tree = new utils.Buf16((2 * BL_CODES + 1) * 2); + zero(this.dyn_ltree); + zero(this.dyn_dtree); + zero(this.bl_tree); + + this.l_desc = null; /* desc. for literal tree */ + this.d_desc = null; /* desc. for distance tree */ + this.bl_desc = null; /* desc. for bit length tree */ + + //ush bl_count[MAX_BITS+1]; + this.bl_count = new utils.Buf16(MAX_BITS + 1); + /* number of codes at each bit length for an optimal tree */ + + //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + this.heap = new utils.Buf16(2 * L_CODES + 1); /* heap used to build the Huffman trees */ + zero(this.heap); + + this.heap_len = 0; /* number of elements in the heap */ + this.heap_max = 0; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; + zero(this.depth); + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + this.l_buf = 0; /* buffer index for literals or lengths */ + + this.lit_bufsize = 0; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + this.last_lit = 0; /* running index in l_buf */ + + this.d_buf = 0; + /* Buffer index for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + this.opt_len = 0; /* bit length of current block with optimal trees */ + this.static_len = 0; /* bit length of current block with static trees */ + this.matches = 0; /* number of string matches in current block */ + this.insert = 0; /* bytes at end of window left to insert */ + + + this.bi_buf = 0; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + this.bi_valid = 0; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + // Used for window memory init. We safely ignore it for JS. That makes + // sense only for pointers and memory check tools. + //this.high_water = 0; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ +} + + +function deflateResetKeep(strm) { + var s; + + if (!strm || !strm.state) { + return err(strm, Z_STREAM_ERROR); + } + + strm.total_in = strm.total_out = 0; + strm.data_type = Z_UNKNOWN; + + s = strm.state; + s.pending = 0; + s.pending_out = 0; + + if (s.wrap < 0) { + s.wrap = -s.wrap; + /* was made negative by deflate(..., Z_FINISH); */ + } + s.status = (s.wrap ? INIT_STATE : BUSY_STATE); + strm.adler = (s.wrap === 2) ? + 0 // crc32(0, Z_NULL, 0) + : + 1; // adler32(0, Z_NULL, 0) + s.last_flush = Z_NO_FLUSH; + trees._tr_init(s); + return Z_OK; +} + + +function deflateReset(strm) { + var ret = deflateResetKeep(strm); + if (ret === Z_OK) { + lm_init(strm.state); + } + return ret; +} + + +function deflateSetHeader(strm, head) { + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; } + strm.state.gzhead = head; + return Z_OK; +} + + +function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { + if (!strm) { // === Z_NULL + return Z_STREAM_ERROR; + } + var wrap = 1; + + if (level === Z_DEFAULT_COMPRESSION) { + level = 6; + } + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } + + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } + + + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return err(strm, Z_STREAM_ERROR); + } + + + if (windowBits === 8) { + windowBits = 9; + } + /* until 256-byte window bug fixed */ + + var s = new DeflateState(); + + strm.state = s; + s.strm = strm; + + s.wrap = wrap; + s.gzhead = null; + s.w_bits = windowBits; + s.w_size = 1 << s.w_bits; + s.w_mask = s.w_size - 1; + + s.hash_bits = memLevel + 7; + s.hash_size = 1 << s.hash_bits; + s.hash_mask = s.hash_size - 1; + s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + s.window = new utils.Buf8(s.w_size * 2); + s.head = new utils.Buf16(s.hash_size); + s.prev = new utils.Buf16(s.w_size); + + // Don't need mem init magic for JS. + //s.high_water = 0; /* nothing written to s->window yet */ + + s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + s.pending_buf_size = s.lit_bufsize * 4; + + //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + //s->pending_buf = (uchf *) overlay; + s.pending_buf = new utils.Buf8(s.pending_buf_size); + + // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) + //s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s.d_buf = 1 * s.lit_bufsize; + + //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + s.l_buf = (1 + 2) * s.lit_bufsize; + + s.level = level; + s.strategy = strategy; + s.method = method; + + return deflateReset(strm); +} + +function deflateInit(strm, level) { + return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); +} + + +function deflate(strm, flush) { + var old_flush, s; + var beg, val; // for gzip header write only + + if (!strm || !strm.state || + flush > Z_BLOCK || flush < 0) { + return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR; + } + + s = strm.state; + + if (!strm.output || + (!strm.input && strm.avail_in !== 0) || + (s.status === FINISH_STATE && flush !== Z_FINISH)) { + return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR); + } + + s.strm = strm; /* just in case */ + old_flush = s.last_flush; + s.last_flush = flush; + + /* Write the header */ + if (s.status === INIT_STATE) { + + if (s.wrap === 2) { // GZIP header + strm.adler = 0; //crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (!s.gzhead) { // s->gzhead == Z_NULL + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s.status = BUSY_STATE; + } + else { + put_byte(s, (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16) + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte(s, s.level === 9 ? 2 : + (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? + 4 : 0)); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; + } + } + else // DEFLATE header + { + var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8; + var level_flags = -1; + + if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { + level_flags = 0; + } else if (s.level < 6) { + level_flags = 1; + } else if (s.level === 6) { + level_flags = 2; + } else { + level_flags = 3; + } + header |= (level_flags << 6); + if (s.strstart !== 0) { header |= PRESET_DICT; } + header += 31 - (header % 31); + + s.status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s.strstart !== 0) { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + strm.adler = 1; // adler32(0L, Z_NULL, 0); + } + } + +//#ifdef GZIP + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + + while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + break; + } + } + put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); + s.gzindex++; + } + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (s.gzindex === s.gzhead.extra.length) { + s.gzindex = 0; + s.status = NAME_STATE; + } + } + else { + s.status = NAME_STATE; + } + } + if (s.status === NAME_STATE) { + if (s.gzhead.name/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.gzindex = 0; + s.status = COMMENT_STATE; + } + } + else { + s.status = COMMENT_STATE; + } + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment/* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.status = HCRC_STATE; + } + } + else { + s.status = HCRC_STATE; + } + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + } + if (s.pending + 2 <= s.pending_buf_size) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + } + } + else { + s.status = BUSY_STATE; + } + } +//#endif + + /* Flush as much pending output as possible */ + if (s.pending !== 0) { + flush_pending(strm); + if (strm.avail_out === 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s.last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && + flush !== Z_FINISH) { + return err(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s.status === FINISH_STATE && strm.avail_in !== 0) { + return err(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm.avail_in !== 0 || s.lookahead !== 0 || + (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) { + var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) : + (s.strategy === Z_RLE ? deflate_rle(s, flush) : + configuration_table[s.level].func(s, flush)); + + if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { + s.status = FINISH_STATE; + } + if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { + if (strm.avail_out === 0) { + s.last_flush = -1; + /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate === BS_BLOCK_DONE) { + if (flush === Z_PARTIAL_FLUSH) { + trees._tr_align(s); + } + else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ + + trees._tr_stored_block(s, 0, 0, false); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush === Z_FULL_FLUSH) { + /*** CLEAR_HASH(s); ***/ /* forget history */ + zero(s.head); // Fill with NIL (= 0); + + if (s.lookahead === 0) { + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + } + } + flush_pending(strm); + if (strm.avail_out === 0) { + s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + //Assert(strm->avail_out > 0, "bug2"); + //if (strm.avail_out <= 0) { throw new Error("bug2");} + + if (flush !== Z_FINISH) { return Z_OK; } + if (s.wrap <= 0) { return Z_STREAM_END; } + + /* Write the trailer */ + if (s.wrap === 2) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + put_byte(s, (strm.adler >> 16) & 0xff); + put_byte(s, (strm.adler >> 24) & 0xff); + put_byte(s, strm.total_in & 0xff); + put_byte(s, (strm.total_in >> 8) & 0xff); + put_byte(s, (strm.total_in >> 16) & 0xff); + put_byte(s, (strm.total_in >> 24) & 0xff); + } + else + { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s.wrap > 0) { s.wrap = -s.wrap; } + /* write the trailer only once! */ + return s.pending !== 0 ? Z_OK : Z_STREAM_END; +} + +function deflateEnd(strm) { + var status; + + if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { + return Z_STREAM_ERROR; + } + + status = strm.state.status; + if (status !== INIT_STATE && + status !== EXTRA_STATE && + status !== NAME_STATE && + status !== COMMENT_STATE && + status !== HCRC_STATE && + status !== BUSY_STATE && + status !== FINISH_STATE + ) { + return err(strm, Z_STREAM_ERROR); + } + + strm.state = null; + + return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; +} + + +/* ========================================================================= + * Initializes the compression dictionary from the given byte + * sequence without producing any compressed output. + */ +function deflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var s; + var str, n; + var wrap; + var avail; + var next; + var input; + var tmpDict; + + if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { + return Z_STREAM_ERROR; + } + + s = strm.state; + wrap = s.wrap; + + if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { + return Z_STREAM_ERROR; + } + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap === 1) { + /* adler32(strm->adler, dictionary, dictLength); */ + strm.adler = adler32(strm.adler, dictionary, dictLength, 0); + } + + s.wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s.w_size) { + if (wrap === 0) { /* already empty otherwise */ + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + /* use the tail */ + // dictionary = dictionary.slice(dictLength - s.w_size); + tmpDict = new utils.Buf8(s.w_size); + utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0); + dictionary = tmpDict; + dictLength = s.w_size; + } + /* insert dictionary into window and hash */ + avail = strm.avail_in; + next = strm.next_in; + input = strm.input; + strm.avail_in = dictLength; + strm.next_in = 0; + strm.input = dictionary; + fill_window(s); + while (s.lookahead >= MIN_MATCH) { + str = s.strstart; + n = s.lookahead - (MIN_MATCH - 1); + do { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + + s.head[s.ins_h] = str; + str++; + } while (--n); + s.strstart = str; + s.lookahead = MIN_MATCH - 1; + fill_window(s); + } + s.strstart += s.lookahead; + s.block_start = s.strstart; + s.insert = s.lookahead; + s.lookahead = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + strm.next_in = next; + strm.input = input; + strm.avail_in = avail; + s.wrap = wrap; + return Z_OK; +} + + +exports.deflateInit = deflateInit; +exports.deflateInit2 = deflateInit2; +exports.deflateReset = deflateReset; +exports.deflateResetKeep = deflateResetKeep; +exports.deflateSetHeader = deflateSetHeader; +exports.deflate = deflate; +exports.deflateEnd = deflateEnd; +exports.deflateSetDictionary = deflateSetDictionary; +exports.deflateInfo = 'pako deflate (from Nodeca project)'; + +/* Not implemented +exports.deflateBound = deflateBound; +exports.deflateCopy = deflateCopy; +exports.deflateParams = deflateParams; +exports.deflatePending = deflatePending; +exports.deflatePrime = deflatePrime; +exports.deflateTune = deflateTune; +*/ + +},{"../utils/common":195,"./adler32":196,"./crc32":198,"./messages":203,"./trees":204}],200:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// See state defs from inflate.js +var BAD = 30; /* got a data error -- remain here until reset */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +module.exports = function inflate_fast(strm, start) { + var state; + var _in; /* local strm.input */ + var last; /* have enough input while in < last */ + var _out; /* local strm.output */ + var beg; /* inflate()'s initial strm.output */ + var end; /* while out < end, enough space available */ +//#ifdef INFLATE_STRICT + var dmax; /* maximum distance from zlib header */ +//#endif + var wsize; /* window size or zero if not using window */ + var whave; /* valid bytes in the window */ + var wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + var s_window; /* allocated sliding window, if wsize != 0 */ + var hold; /* local strm.hold */ + var bits; /* local strm.bits */ + var lcode; /* local strm.lencode */ + var dcode; /* local strm.distcode */ + var lmask; /* mask for first level of length codes */ + var dmask; /* mask for first level of distance codes */ + var here; /* retrieved table entry */ + var op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + var len; /* match length, unused bytes */ + var dist; /* match distance */ + var from; /* where to copy match from */ + var from_source; + + + var input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); +//#ifdef INFLATE_STRICT + dmax = state.dmax; +//#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + if (op === 0) { /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff/*here.val*/; + } + else if (op & 16) { /* length base */ + len = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24/*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff/*here.op*/; + + if (op & 16) { /* distance base */ + dist = here & 0xffff/*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); +//#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } +//#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break top; + } + +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// if (len <= op - whave) { +// do { +// output[_out++] = 0; +// } while (--len); +// continue top; +// } +// len -= op - whave; +// do { +// output[_out++] = 0; +// } while (--op > whave); +// if (op === 0) { +// from = _out - dist; +// do { +// output[_out++] = output[from++]; +// } while (--len); +// continue top; +// } +//#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } + else { + from = _out - dist; /* copy direct from output */ + do { /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } + else if ((op & 64) === 0) { /* 2nd level distance code */ + here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } + else { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } + else if ((op & 64) === 0) { /* 2nd level length code */ + here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } + else if (op & 32) { /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE; + break top; + } + else { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); + strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); + state.hold = hold; + state.bits = bits; + return; +}; + +},{}],201:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var utils = require('../utils/common'); +var adler32 = require('./adler32'); +var crc32 = require('./crc32'); +var inflate_fast = require('./inffast'); +var inflate_table = require('./inftrees'); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +/* Allowed flush values; see deflate() and inflate() below for details */ +//var Z_NO_FLUSH = 0; +//var Z_PARTIAL_FLUSH = 1; +//var Z_SYNC_FLUSH = 2; +//var Z_FULL_FLUSH = 3; +var Z_FINISH = 4; +var Z_BLOCK = 5; +var Z_TREES = 6; + + +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ +var Z_OK = 0; +var Z_STREAM_END = 1; +var Z_NEED_DICT = 2; +//var Z_ERRNO = -1; +var Z_STREAM_ERROR = -2; +var Z_DATA_ERROR = -3; +var Z_MEM_ERROR = -4; +var Z_BUF_ERROR = -5; +//var Z_VERSION_ERROR = -6; + +/* The deflate compression method */ +var Z_DEFLATED = 8; + + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + + +var HEAD = 1; /* i: waiting for magic header */ +var FLAGS = 2; /* i: waiting for method and flags (gzip) */ +var TIME = 3; /* i: waiting for modification time (gzip) */ +var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ +var EXLEN = 5; /* i: waiting for extra length (gzip) */ +var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ +var NAME = 7; /* i: waiting for end of file name (gzip) */ +var COMMENT = 8; /* i: waiting for end of comment (gzip) */ +var HCRC = 9; /* i: waiting for header crc (gzip) */ +var DICTID = 10; /* i: waiting for dictionary check value */ +var DICT = 11; /* waiting for inflateSetDictionary() call */ +var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ +var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ +var STORED = 14; /* i: waiting for stored size (length and complement) */ +var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ +var COPY = 16; /* i/o: waiting for input or output to copy stored block */ +var TABLE = 17; /* i: waiting for dynamic block table lengths */ +var LENLENS = 18; /* i: waiting for code length code lengths */ +var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ +var LEN_ = 20; /* i: same as LEN below, but only first time in */ +var LEN = 21; /* i: waiting for length/lit/eob code */ +var LENEXT = 22; /* i: waiting for length extra bits */ +var DIST = 23; /* i: waiting for distance code */ +var DISTEXT = 24; /* i: waiting for distance extra bits */ +var MATCH = 25; /* o: waiting for output space to copy string */ +var LIT = 26; /* o: waiting for output space to write literal */ +var CHECK = 27; /* i: waiting for 32-bit check value */ +var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ +var DONE = 29; /* finished check, done -- remain here until reset */ +var BAD = 30; /* got a data error -- remain here until reset */ +var MEM = 31; /* got an inflate() memory error -- remain here until reset */ +var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + + + +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var MAX_WBITS = 15; +/* 32K LZ77 window */ +var DEF_WBITS = MAX_WBITS; + + +function zswap32(q) { + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +} + + +function InflateState() { + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib) */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ + this.work = new utils.Buf16(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + +function inflateResetKeep(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ''; /*Z_NULL*/ + if (state.wrap) { /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.dmax = 32768; + state.head = null/*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); + state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +function inflateReset(strm) { + var state; + + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); + +} + +function inflateReset2(strm, windowBits) { + var wrap; + var state; + + /* get the state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +} + +function inflateInit2(strm, windowBits) { + var ret; + var state; + + if (!strm) { return Z_STREAM_ERROR; } + //strm.msg = Z_NULL; /* in case we return an error */ + + state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.window = null/*Z_NULL*/; + ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK) { + strm.state = null/*Z_NULL*/; + } + return ret; +} + +function inflateInit(strm) { + return inflateInit2(strm, DEF_WBITS); +} + + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +var virgin = true; + +var lenfix, distfix; // We have no pointers in JS, so keep tables separate + +function fixedtables(state) { + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + var sym; + + lenfix = new utils.Buf32(512); + distfix = new utils.Buf32(32); + + /* literal/length table */ + sym = 0; + while (sym < 144) { state.lens[sym++] = 8; } + while (sym < 256) { state.lens[sym++] = 9; } + while (sym < 280) { state.lens[sym++] = 7; } + while (sym < 288) { state.lens[sym++] = 8; } + + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); + + /* distance table */ + sym = 0; + while (sym < 32) { state.lens[sym++] = 5; } + + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +} + + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +function updatewindow(strm, src, end, copy) { + var dist; + var state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new utils.Buf8(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0); + state.wnext = 0; + state.whave = state.wsize; + } + else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + utils.arraySet(state.window, src, end - copy, dist, state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + utils.arraySet(state.window, src, end - copy, copy, 0); + state.wnext = copy; + state.whave = state.wsize; + } + else { + state.wnext += dist; + if (state.wnext === state.wsize) { state.wnext = 0; } + if (state.whave < state.wsize) { state.whave += dist; } + } + } + return 0; +} + +function inflate(strm, flush) { + var state; + var input, output; // input/output buffers + var next; /* next input INDEX */ + var put; /* next output INDEX */ + var have, left; /* available input and output */ + var hold; /* bit buffer */ + var bits; /* bits in bit buffer */ + var _in, _out; /* save starting available input and output */ + var copy; /* number of stored or match bytes to copy */ + var from; /* where to copy match bytes from */ + var from_source; + var here = 0; /* current decoding table entry */ + var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //var last; /* parent table entry */ + var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + var len; /* length to copy for repeats, bits to drop */ + var ret; /* return code */ + var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ + var opts; + + var n; // temporary var for NEED_BITS + + var order = /* permutation of code lengths */ + [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; + + + if (!strm || !strm.state || !strm.output || + (!strm.input && strm.avail_in !== 0)) { + return Z_STREAM_ERROR; + } + + state = strm.state; + if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ + + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK; + + inf_leave: // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ + state.check = 0/*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + state.flags = 0; /* expect zlib header */ + if (state.head) { + state.head.done = false; + } + if (!(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { + strm.msg = 'incorrect header check'; + state.mode = BAD; + break; + } + if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f)/*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } + else if (len > state.wbits) { + strm.msg = 'invalid window size'; + state.mode = BAD; + break; + } + state.dmax = 1 << len; + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = 'unknown compression method'; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = 'unknown header flags set'; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = ((hold >> 8) & 1); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if (state.flags & 0x0200) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = (hold & 0xff); + state.head.os = (hold >> 8); + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + else if (state.head) { + state.head.extra = null/*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) { copy = have; } + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more convenient processing later + state.head.extra = new Array(state.head.extra_len); + } + utils.arraySet( + state.head.extra, + input, + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + copy, + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) { break inf_leave; } + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.name_max*/)) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) { break inf_leave; } + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if (state.head && len && + (state.length < 65536 /*state.head.comm_max*/)) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if (state.flags & 0x0200) { + state.check = crc32(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) { break inf_leave; } + } + else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.check & 0xffff)) { + strm.msg = 'header crc mismatch'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = ((state.flags >> 9) & 1); + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT; + } + strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = (hold & 0x01)/*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03)/*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = 'invalid block type'; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = 'invalid stored block lengths'; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) { copy = have; } + if (copy > left) { copy = left; } + if (copy === 0) { break inf_leave; } + //--- zmemcpy(put, next, copy); --- + utils.arraySet(output, input, next, copy, put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// +//#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = 'too many length or distance symbols'; + state.mode = BAD; + break; + } +//#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = 'invalid code lengths set'; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } + else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03);//BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } + else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07);//BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f);//BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = 'invalid bit length repeat'; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) { break; } + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = 'invalid code -- missing end-of-block'; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = 'invalid literal/lengths set'; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = 'invalid distances set'; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) { break inf_leave; } + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inflate_fast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = 'invalid literal/length code'; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[last_val + + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) { break; } + //--- PULLBYTE() ---// + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = 'invalid distance code'; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } +//#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +//#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) { break inf_leave; } + copy = _out - left; + if (state.offset > copy) { /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = 'invalid distance too far back'; + state.mode = BAD; + break; + } +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility +//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR +// Trace((stderr, "inflate.c too far\n")); +// copy -= state.whave; +// if (copy > state.length) { copy = state.length; } +// if (copy > left) { copy = left; } +// left -= copy; +// state.length -= copy; +// do { +// output[put++] = 0; +// } while (--copy); +// if (state.length === 0) { state.mode = LEN; } +// break; +//#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } + else { + from = state.wnext - copy; + } + if (copy > state.length) { copy = state.length; } + from_source = state.window; + } + else { /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) { copy = left; } + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) { state.mode = LEN; } + break; + case LIT: + if (left === 0) { break inf_leave; } + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + // Use '|' instead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if (_out) { + strm.adler = state.check = + /*UPDATE(state.check, put - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); + + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = 'incorrect data check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) { break inf_leave; } + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.total & 0xffffffff)) { + strm.msg = 'incorrect length check'; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR; + break inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH))) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) { + state.mode = MEM; + return Z_MEM_ERROR; + } + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if (state.wrap && _out) { + strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ + (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { + ret = Z_BUF_ERROR; + } + return ret; +} + +function inflateEnd(strm) { + + if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { + return Z_STREAM_ERROR; + } + + var state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK; +} + +function inflateGetHeader(strm, head) { + var state; + + /* check state */ + if (!strm || !strm.state) { return Z_STREAM_ERROR; } + state = strm.state; + if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK; +} + +function inflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var state; + var dictid; + var ret; + + /* check state */ + if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +exports.inflateReset = inflateReset; +exports.inflateReset2 = inflateReset2; +exports.inflateResetKeep = inflateResetKeep; +exports.inflateInit = inflateInit; +exports.inflateInit2 = inflateInit2; +exports.inflate = inflate; +exports.inflateEnd = inflateEnd; +exports.inflateGetHeader = inflateGetHeader; +exports.inflateSetDictionary = inflateSetDictionary; +exports.inflateInfo = 'pako inflate (from Nodeca project)'; + +/* Not implemented +exports.inflateCopy = inflateCopy; +exports.inflateGetDictionary = inflateGetDictionary; +exports.inflateMark = inflateMark; +exports.inflatePrime = inflatePrime; +exports.inflateSync = inflateSync; +exports.inflateSyncPoint = inflateSyncPoint; +exports.inflateUndermine = inflateUndermine; +*/ + +},{"../utils/common":195,"./adler32":196,"./crc32":198,"./inffast":200,"./inftrees":202}],202:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var utils = require('../utils/common'); + +var MAXBITS = 15; +var ENOUGH_LENS = 852; +var ENOUGH_DISTS = 592; +//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +var CODES = 0; +var LENS = 1; +var DISTS = 2; + +var lbase = [ /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +]; + +var lext = [ /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 +]; + +var dbase = [ /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0 +]; + +var dext = [ /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64 +]; + +module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) +{ + var bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + var len = 0; /* a code's length in bits */ + var sym = 0; /* index of code symbols */ + var min = 0, max = 0; /* minimum and maximum code lengths */ + var root = 0; /* number of index bits for root table */ + var curr = 0; /* number of index bits for current table */ + var drop = 0; /* code bits to drop for sub-table */ + var left = 0; /* number of prefix codes available */ + var used = 0; /* code entries in table used */ + var huff = 0; /* Huffman code */ + var incr; /* for incrementing code, index */ + var fill; /* index for replicating entries */ + var low; /* low bits for current root entry */ + var mask; /* mask for low root bits */ + var next; /* next available space in table */ + var base = null; /* base value table to use */ + var base_index = 0; +// var shoextra; /* extra bits table to use */ + var end; /* use base and extra for symbol > end */ + var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + var extra = null; + var extra_index = 0; + + var here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) { break; } + } + if (root > max) { + root = max; + } + if (max === 0) { /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) { break; } + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES) { + base = extra = work; /* dummy value--not used */ + end = 19; + + } else if (type === LENS) { + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + + } else { /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] < end) { + here_op = 0; + here_val = work[sym]; + } + else if (work[sym] > end) { + here_op = extra[extra_index + work[sym]]; + here_val = base[base_index + work[sym]]; + } + else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) { break; } + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) { break; } + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ((type === LENS && used > ENOUGH_LENS) || + (type === DISTS && used > ENOUGH_DISTS)) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + +},{"../utils/common":195}],203:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +module.exports = { + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ + '-1': 'file error', /* Z_ERRNO (-1) */ + '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ + '-3': 'data error', /* Z_DATA_ERROR (-3) */ + '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ + '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ + '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ +}; + +},{}],204:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var utils = require('../utils/common'); + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + + +//var Z_FILTERED = 1; +//var Z_HUFFMAN_ONLY = 2; +//var Z_RLE = 3; +var Z_FIXED = 4; +//var Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +var Z_BINARY = 0; +var Z_TEXT = 1; +//var Z_ASCII = 1; // = Z_TEXT +var Z_UNKNOWN = 2; + +/*============================================================================*/ + + +function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } + +// From zutil.h + +var STORED_BLOCK = 0; +var STATIC_TREES = 1; +var DYN_TREES = 2; +/* The three kinds of block type */ + +var MIN_MATCH = 3; +var MAX_MATCH = 258; +/* The minimum and maximum match lengths */ + +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ + +var LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ + +var LITERALS = 256; +/* number of literal bytes 0..255 */ + +var L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ + +var D_CODES = 30; +/* number of distance codes */ + +var BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ + +var HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ + +var MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +var Buf_size = 16; +/* size of bit buffer in bi_buf */ + + +/* =========================================================================== + * Constants + */ + +var MAX_BL_BITS = 7; +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +var END_BLOCK = 256; +/* end of block literal code */ + +var REP_3_6 = 16; +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +var REPZ_3_10 = 17; +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +var REPZ_11_138 = 18; +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* eslint-disable comma-spacing,array-bracket-spacing */ +var extra_lbits = /* extra bits for each length code */ + [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; + +var extra_dbits = /* extra bits for each distance code */ + [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; + +var extra_blbits = /* extra bits for each bit length code */ + [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]; + +var bl_order = + [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]; +/* eslint-enable comma-spacing,array-bracket-spacing */ + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +// We pre-fill arrays with 0 to avoid uninitialized gaps + +var DIST_CODE_LEN = 512; /* see definition of array dist_code below */ + +// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 +var static_ltree = new Array((L_CODES + 2) * 2); +zero(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +var static_dtree = new Array(D_CODES * 2); +zero(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +var _dist_code = new Array(DIST_CODE_LEN); +zero(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1); +zero(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ + +var base_length = new Array(LENGTH_CODES); +zero(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ + +var base_dist = new Array(D_CODES); +zero(base_dist); +/* First normalized distance for each code (0 = distance of 1) */ + + +function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { + + this.static_tree = static_tree; /* static tree or NULL */ + this.extra_bits = extra_bits; /* extra bits for each code or NULL */ + this.extra_base = extra_base; /* base index for extra_bits */ + this.elems = elems; /* max number of elements in the tree */ + this.max_length = max_length; /* max bit length for the codes */ + + // show if `static_tree` has data or dummy - needed for monomorphic objects + this.has_stree = static_tree && static_tree.length; +} + + +var static_l_desc; +var static_d_desc; +var static_bl_desc; + + +function TreeDesc(dyn_tree, stat_desc) { + this.dyn_tree = dyn_tree; /* the dynamic tree */ + this.max_code = 0; /* largest code with non zero frequency */ + this.stat_desc = stat_desc; /* the corresponding static tree */ +} + + + +function d_code(dist) { + return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; +} + + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +function put_short(s, w) { +// put_byte(s, (uch)((w) & 0xff)); +// put_byte(s, (uch)((ush)(w) >> 8)); + s.pending_buf[s.pending++] = (w) & 0xff; + s.pending_buf[s.pending++] = (w >>> 8) & 0xff; +} + + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +function send_bits(s, value, length) { + if (s.bi_valid > (Buf_size - length)) { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + put_short(s, s.bi_buf); + s.bi_buf = value >> (Buf_size - s.bi_valid); + s.bi_valid += length - Buf_size; + } else { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + s.bi_valid += length; + } +} + + +function send_code(s, c, tree) { + send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); +} + + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +function bi_reverse(code, len) { + var res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; +} + + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +function bi_flush(s) { + if (s.bi_valid === 16) { + put_short(s, s.bi_buf); + s.bi_buf = 0; + s.bi_valid = 0; + + } else if (s.bi_valid >= 8) { + s.pending_buf[s.pending++] = s.bi_buf & 0xff; + s.bi_buf >>= 8; + s.bi_valid -= 8; + } +} + + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +function gen_bitlen(s, desc) +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + var tree = desc.dyn_tree; + var max_code = desc.max_code; + var stree = desc.stat_desc.static_tree; + var has_stree = desc.stat_desc.has_stree; + var extra = desc.stat_desc.extra_bits; + var base = desc.stat_desc.extra_base; + var max_length = desc.stat_desc.max_length; + var h; /* heap index */ + var n, m; /* iterate over the tree elements */ + var bits; /* bit length */ + var xbits; /* extra bits */ + var f; /* frequency */ + var overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) { + s.bl_count[bits] = 0; + } + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ + + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n * 2 + 1]/*.Len*/ = bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) { continue; } /* not a leaf node */ + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) { + xbits = extra[n - base]; + } + f = tree[n * 2]/*.Freq*/; + s.opt_len += f * (bits + xbits); + if (has_stree) { + s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); + } + } + if (overflow === 0) { return; } + + // Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (s.bl_count[bits] === 0) { bits--; } + s.bl_count[bits]--; /* move one leaf down the tree */ + s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + s.bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > max_code) { continue; } + if (tree[m * 2 + 1]/*.Len*/ !== bits) { + // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; + tree[m * 2 + 1]/*.Len*/ = bits; + } + n--; + } + } +} + + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +function gen_codes(tree, max_code, bl_count) +// ct_data *tree; /* the tree to decorate */ +// int max_code; /* largest code with non zero frequency */ +// ushf *bl_count; /* number of codes at each bit length */ +{ + var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */ + var code = 0; /* running code value */ + var bits; /* bit index */ + var n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits - 1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + //Assert (code + bl_count[MAX_BITS]-1 == (1< length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + _length_code[length++] = code; + } + } + //Assert (length == 256, "tr_static_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + _length_code[length - 1] = code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + _dist_code[dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + _dist_code[256 + dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) { + bl_count[bits] = 0; + } + + n = 0; + while (n <= 143) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + while (n <= 255) { + static_ltree[n * 2 + 1]/*.Len*/ = 9; + n++; + bl_count[9]++; + } + while (n <= 279) { + static_ltree[n * 2 + 1]/*.Len*/ = 7; + n++; + bl_count[7]++; + } + while (n <= 287) { + static_ltree[n * 2 + 1]/*.Len*/ = 8; + n++; + bl_count[8]++; + } + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes(static_ltree, L_CODES + 1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n * 2 + 1]/*.Len*/ = 5; + static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); + } + + // Now data ready and we can init static trees + static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS); + static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); + static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); + + //static_init_done = true; +} + + +/* =========================================================================== + * Initialize a new block. + */ +function init_block(s) { + var n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } + + s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; + s.opt_len = s.static_len = 0; + s.last_lit = s.matches = 0; +} + + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +function bi_windup(s) +{ + if (s.bi_valid > 8) { + put_short(s, s.bi_buf); + } else if (s.bi_valid > 0) { + //put_byte(s, (Byte)s->bi_buf); + s.pending_buf[s.pending++] = s.bi_buf; + } + s.bi_buf = 0; + s.bi_valid = 0; +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +function copy_block(s, buf, len, header) +//DeflateState *s; +//charf *buf; /* the input data */ +//unsigned len; /* its length */ +//int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + + if (header) { + put_short(s, len); + put_short(s, ~len); + } +// while (len--) { +// put_byte(s, *buf++); +// } + utils.arraySet(s.pending_buf, s.window, buf, len, s.pending); + s.pending += len; +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +function smaller(tree, n, m, depth) { + var _n2 = n * 2; + var _m2 = m * 2; + return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || + (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); +} + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +function pqdownheap(s, tree, k) +// deflate_state *s; +// ct_data *tree; /* the tree to restore */ +// int k; /* node to move down */ +{ + var v = s.heap[k]; + var j = k << 1; /* left son of k */ + while (j <= s.heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s.heap_len && + smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s.heap[j], s.depth)) { break; } + + /* Exchange v with the smallest son */ + s.heap[k] = s.heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s.heap[k] = v; +} + + +// inlined manually +// var SMALLEST = 1; + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +function compress_block(s, ltree, dtree) +// deflate_state *s; +// const ct_data *ltree; /* literal tree */ +// const ct_data *dtree; /* distance tree */ +{ + var dist; /* distance of matched string */ + var lc; /* match length or unmatched char (if dist == 0) */ + var lx = 0; /* running index in l_buf */ + var code; /* the code to send */ + var extra; /* number of extra bits to send */ + + if (s.last_lit !== 0) { + do { + dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]); + lc = s.pending_buf[s.l_buf + lx]; + lx++; + + if (dist === 0) { + send_code(s, lc, ltree); /* send a literal byte */ + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra !== 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra !== 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + // "pendingBuf overflow"); + + } while (lx < s.last_lit); + } + + send_code(s, END_BLOCK, ltree); +} + + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +function build_tree(s, desc) +// deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + var tree = desc.dyn_tree; + var stree = desc.stat_desc.static_tree; + var has_stree = desc.stat_desc.has_stree; + var elems = desc.stat_desc.elems; + var n, m; /* iterate over heap elements */ + var max_code = -1; /* largest code with non zero frequency */ + var node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s.heap_len = 0; + s.heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n * 2]/*.Freq*/ !== 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + + } else { + tree[n * 2 + 1]/*.Len*/ = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); + tree[node * 2]/*.Freq*/ = 1; + s.depth[node] = 0; + s.opt_len--; + + if (has_stree) { + s.static_len -= stree[node * 2 + 1]/*.Len*/; + } + /* node is 0 or 1 so it does not have extra bits */ + } + desc.max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + //pqremove(s, tree, n); /* n = node of least frequency */ + /*** pqremove ***/ + n = s.heap[1/*SMALLEST*/]; + s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; + pqdownheap(s, tree, 1/*SMALLEST*/); + /***/ + + m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ + + s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ + s.heap[--s.heap_max] = m; + + /* Create a new node father of n and m */ + tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; + s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; + tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; + + /* and insert the new node in the heap */ + s.heap[1/*SMALLEST*/] = node++; + pqdownheap(s, tree, 1/*SMALLEST*/); + + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes(tree, max_code, s.bl_count); +} + + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +function scan_tree(s, tree, max_code) +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + var n; /* iterates over all tree elements */ + var prevlen = -1; /* last emitted length */ + var curlen; /* length of current code */ + + var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + var count = 0; /* repeat count of the current code */ + var max_count = 7; /* max repeat count */ + var min_count = 4; /* min repeat count */ + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + s.bl_tree[curlen * 2]/*.Freq*/ += count; + + } else if (curlen !== 0) { + + if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } + s.bl_tree[REP_3_6 * 2]/*.Freq*/++; + + } else if (count <= 10) { + s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; + + } else { + s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; + } + + count = 0; + prevlen = curlen; + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +} + + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +function send_tree(s, tree, max_code) +// deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + var n; /* iterates over all tree elements */ + var prevlen = -1; /* last emitted length */ + var curlen; /* length of current code */ + + var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ + + var count = 0; /* repeat count of the current code */ + var max_count = 7; /* max repeat count */ + var min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + + } else if (count < min_count) { + do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); + + } else if (curlen !== 0) { + if (curlen !== prevlen) { + send_code(s, curlen, s.bl_tree); + count--; + } + //Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); + send_bits(s, count - 3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s.bl_tree); + send_bits(s, count - 3, 3); + + } else { + send_code(s, REPZ_11_138, s.bl_tree); + send_bits(s, count - 11, 7); + } + + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + + } else { + max_count = 7; + min_count = 4; + } + } +} + + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +function build_bl_tree(s) { + var max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, s.bl_desc); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { + break; + } + } + /* Update opt_len to include the bit length tree and counts */ + s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + // s->opt_len, s->static_len)); + + return max_blindex; +} + + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +function send_all_trees(s, lcodes, dcodes, blcodes) +// deflate_state *s; +// int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + var rank; /* index in bl_order */ + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + // "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); + } + //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ + //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ + //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +function detect_data_type(s) { + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + var black_mask = 0xf3ffc07f; + var n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>>= 1) { + if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { + return Z_BINARY; + } + } + + /* Check for textual ("white-listed") bytes. */ + if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || + s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + for (n = 32; n < LITERALS; n++) { + if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { + return Z_TEXT; + } + } + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + + +var static_init_done = false; + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +function _tr_init(s) +{ + + if (!static_init_done) { + tr_static_init(); + static_init_done = true; + } + + s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); + s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); + s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); + + s.bi_buf = 0; + s.bi_valid = 0; + + /* Initialize the first block of the first file: */ + init_block(s); +} + + +/* =========================================================================== + * Send a stored block + */ +function _tr_stored_block(s, buf, stored_len, last) +//DeflateState *s; +//charf *buf; /* input block */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ + copy_block(s, buf, stored_len, true); /* with header */ +} + + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +function _tr_align(s) { + send_bits(s, STATIC_TREES << 1, 3); + send_code(s, END_BLOCK, static_ltree); + bi_flush(s); +} + + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +function _tr_flush_block(s, buf, stored_len, last) +//DeflateState *s; +//charf *buf; /* input block, or NULL if too old */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + var opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + var max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s.level > 0) { + + /* Check if the file is binary or text */ + if (s.strm.data_type === Z_UNKNOWN) { + s.strm.data_type = detect_data_type(s); + } + + /* Construct the literal and distance trees */ + build_tree(s, s.l_desc); + // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + + build_tree(s, s.d_desc); + // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s.opt_len + 3 + 7) >>> 3; + static_lenb = (s.static_len + 3 + 7) >>> 3; + + // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + // s->last_lit)); + + if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } + + } else { + // Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + + if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { + /* 4: two words for the lengths */ + + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, last); + + } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { + + send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); + compress_block(s, static_ltree, static_dtree); + + } else { + send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); + send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); + } + // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + // s->compressed_len-7*last)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +function _tr_tally(s, dist, lc) +// deflate_state *s; +// unsigned dist; /* distance of matched string */ +// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + //var out_length, in_length, dcode; + + s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff; + s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff; + + s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff; + s.last_lit++; + + if (dist === 0) { + /* lc is the unmatched char */ + s.dyn_ltree[lc * 2]/*.Freq*/++; + } else { + s.matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + //Assert((ush)dist < (ush)MAX_DIST(s) && + // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++; + s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; + } + +// (!) This block is disabled in zlib defaults, +// don't enable it for binary compatibility + +//#ifdef TRUNCATE_BLOCK +// /* Try to guess if it is profitable to stop the current block here */ +// if ((s.last_lit & 0x1fff) === 0 && s.level > 2) { +// /* Compute an upper bound for the compressed length */ +// out_length = s.last_lit*8; +// in_length = s.strstart - s.block_start; +// +// for (dcode = 0; dcode < D_CODES; dcode++) { +// out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]); +// } +// out_length >>>= 3; +// //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", +// // s->last_lit, in_length, out_length, +// // 100L - out_length*100L/in_length)); +// if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) { +// return true; +// } +// } +//#endif + + return (s.last_lit === s.lit_bufsize - 1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +exports._tr_init = _tr_init; +exports._tr_stored_block = _tr_stored_block; +exports._tr_flush_block = _tr_flush_block; +exports._tr_tally = _tr_tally; +exports._tr_align = _tr_align; + +},{"../utils/common":195}],205:[function(require,module,exports){ +'use strict'; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = ''/*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2/*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +module.exports = ZStream; + +},{}],206:[function(require,module,exports){ +(function (process){ +'use strict'; + +if (!process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = nextTick; +} else { + module.exports = process.nextTick; +} + +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} + +}).call(this,require('_process')) +},{"_process":207}],207:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],208:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.4.1 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],209:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +module.exports = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + + if (typeof qs !== 'string' || qs.length === 0) { + return obj; + } + + var regexp = /\+/g; + qs = qs.split(sep); + + var maxKeys = 1000; + if (options && typeof options.maxKeys === 'number') { + maxKeys = options.maxKeys; + } + + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } + + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; + + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); + } else { + kstr = x; + vstr = ''; + } + + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } + } + + return obj; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +},{}],210:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +var stringifyPrimitive = function(v) { + switch (typeof v) { + case 'string': + return v; + + case 'boolean': + return v ? 'true' : 'false'; + + case 'number': + return isFinite(v) ? v : ''; + + default: + return ''; + } +}; + +module.exports = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (obj === null) { + obj = undefined; + } + + if (typeof obj === 'object') { + return map(objectKeys(obj), function(k) { + var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; + if (isArray(obj[k])) { + return map(obj[k], function(v) { + return ks + encodeURIComponent(stringifyPrimitive(v)); + }).join(sep); + } else { + return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + } + }).join(sep); + + } + + if (!name) return ''; + return encodeURIComponent(stringifyPrimitive(name)) + eq + + encodeURIComponent(stringifyPrimitive(obj)); +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +function map (xs, f) { + if (xs.map) return xs.map(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + res.push(f(xs[i], i)); + } + return res; +} + +var objectKeys = Object.keys || function (obj) { + var res = []; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; +}; + +},{}],211:[function(require,module,exports){ +'use strict'; + +exports.decode = exports.parse = require('./decode'); +exports.encode = exports.stringify = require('./encode'); + +},{"./decode":209,"./encode":210}],212:[function(require,module,exports){ +module.exports = require('./lib/_stream_duplex.js'); + +},{"./lib/_stream_duplex.js":213}],213:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +'use strict'; + +/**/ + +var processNextTick = require('process-nextick-args'); +/**/ + +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +module.exports = Duplex; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +var keys = objectKeys(Writable.prototype); +for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) this.readable = false; + + if (options && options.writable === false) this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; + + this.once('end', onend); +} + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; + + // no more data can be written. + // But allow more writes to happen in this tick. + processNextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); + +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); + + processNextTick(cb, err); +}; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} +},{"./_stream_readable":215,"./_stream_writable":217,"core-util-is":188,"inherits":192,"process-nextick-args":206}],214:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; +},{"./_stream_transform":216,"core-util-is":188,"inherits":192}],215:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var processNextTick = require('process-nextick-args'); +/**/ + +module.exports = Readable; + +/**/ +var isArray = require('isarray'); +/**/ + +/**/ +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; + +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +// TODO(bmeurer): Change this back to const once hole checks are +// properly optimized away early in Ignition+TurboFan. +/**/ +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; + +util.inherits(Readable, Stream); + +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') { + return emitter.prependListener(event, fn); + } else { + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; + } +} + +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.readableObjectMode; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + + // has it been destroyed + this.destroyed = false; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + if (!(this instanceof Readable)) return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + } +}); + +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + } + } + + return needMoreData(state); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; + +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} + +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + + if (n !== 0) state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) processNextTick(emitReadable_, stream);else emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + processNextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) processNextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + + cleanedUp = true; + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', src._readableState.awaitDrain); + src._readableState.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } + + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + + if (!dest) dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, unpipeInfo); + }return this; + } + + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + + dest.emit('unpipe', this, unpipeInfo); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + processNextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + processNextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var state = this._readableState; + var paused = false; + + var self = this; + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) self.push(chunk); + } + + self.push(null); + }); + + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); + + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = self.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } + + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], self.emit.bind(self, kProxyEvents[n])); + } + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + self._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return self; +}; + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } + + return ret; +} + +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} + +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + processNextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./_stream_duplex":213,"./internal/streams/BufferList":218,"./internal/streams/destroy":219,"./internal/streams/stream":220,"_process":207,"core-util-is":188,"events":189,"inherits":192,"isarray":194,"process-nextick-args":206,"safe-buffer":225,"string_decoder/":231,"util":182}],216:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +'use strict'; + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + +function TransformState(stream) { + this.afterTransform = function (er, data) { + return afterTransform(stream, er, data); + }; + + this.needTransform = false; + this.transforming = false; + this.writecb = null; + this.writechunk = null; + this.writeencoding = null; +} + +function afterTransform(stream, er, data) { + var ts = stream._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) { + return stream.emit('error', new Error('write callback called multiple times')); + } + + ts.writechunk = null; + ts.writecb = null; + + if (data !== null && data !== undefined) stream.push(data); + + cb(er); + + var rs = stream._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + stream._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + + Duplex.call(this, options); + + this._transformState = new TransformState(this); + + var stream = this; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + + if (typeof options.flush === 'function') this._flush = options.flush; + } + + // When the writable side finishes, then flush out anything remaining. + this.once('prefinish', function () { + if (typeof this._flush === 'function') this._flush(function (er, data) { + done(stream, er, data); + });else done(stream); + }); +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +Transform.prototype._destroy = function (err, cb) { + var _this = this; + + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this.emit('close'); + }); +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + + if (data !== null && data !== undefined) stream.push(data); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + var ws = stream._writableState; + var ts = stream._transformState; + + if (ws.length) throw new Error('Calling transform done when ws.length != 0'); + + if (ts.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} +},{"./_stream_duplex":213,"core-util-is":188,"inherits":192}],217:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. + +'use strict'; + +/**/ + +var processNextTick = require('process-nextick-args'); +/**/ + +module.exports = Writable; + +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} + +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick; +/**/ + +/**/ +var Duplex; +/**/ + +Writable.WritableState = WritableState; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ + +var destroyImpl = require('./internal/streams/destroy'); + +util.inherits(Writable, Stream); + +function nop() {} + +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // if _final has been called + this.finalCalled = false; + + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // has it been destroyed + this.destroyed = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.bufferedRequest = null; + this.lastBufferedRequest = null; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; + + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); + +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + + if (typeof options.writev === 'function') this._writev = options.writev; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; + +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + processNextTick(cb, er); +} + +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; + + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + processNextTick(cb, er); + valid = false; + } + return valid; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = _isUint8Array(chunk) && !state.objectMode; + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + + if (typeof cb !== 'function') cb = nop; + + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function () { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + return chunk; +} + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + processNextTick(cb, er); + // this can emit finish, and it will always happen + // after error + processNextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; + + doWrite(stream, state, true, state.length, buffer, '', holder.finish); + + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequestCount = 0; + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + processNextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + } + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) processNextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = corkReq; + } else { + state.corkedRequestsFree = corkReq; + } +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } +}); + +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./_stream_duplex":213,"./internal/streams/destroy":219,"./internal/streams/stream":220,"_process":207,"core-util-is":188,"inherits":192,"process-nextick-args":206,"safe-buffer":225,"util-deprecate":235}],218:[function(require,module,exports){ +'use strict'; + +/**/ + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Buffer = require('safe-buffer').Buffer; +/**/ + +function copyBuffer(src, target, offset) { + src.copy(target, offset); +} + +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); + + this.head = null; + this.tail = null; + this.length = 0; + } + + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; + + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; + + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; + + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; + + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; + + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + if (this.length === 1) return this.head.data; + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; + + return BufferList; +}(); +},{"safe-buffer":225}],219:[function(require,module,exports){ +'use strict'; + +/**/ + +var processNextTick = require('process-nextick-args'); +/**/ + +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { + processNextTick(emitErrorNT, this, err); + } + return; + } + + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; + } + + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + processNextTick(emitErrorNT, _this, err); + if (_this._writableState) { + _this._writableState.errorEmitted = true; + } + } else if (cb) { + cb(err); + } + }); +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy +}; +},{"process-nextick-args":206}],220:[function(require,module,exports){ +module.exports = require('events').EventEmitter; + +},{"events":189}],221:[function(require,module,exports){ +module.exports = require('./readable').PassThrough + +},{"./readable":222}],222:[function(require,module,exports){ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); + +},{"./lib/_stream_duplex.js":213,"./lib/_stream_passthrough.js":214,"./lib/_stream_readable.js":215,"./lib/_stream_transform.js":216,"./lib/_stream_writable.js":217}],223:[function(require,module,exports){ +module.exports = require('./readable').Transform + +},{"./readable":222}],224:[function(require,module,exports){ +module.exports = require('./lib/_stream_writable.js'); + +},{"./lib/_stream_writable.js":217}],225:[function(require,module,exports){ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} + +},{"buffer":185}],226:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +module.exports = Stream; + +var EE = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(Stream, EE); +Stream.Readable = require('readable-stream/readable.js'); +Stream.Writable = require('readable-stream/writable.js'); +Stream.Duplex = require('readable-stream/duplex.js'); +Stream.Transform = require('readable-stream/transform.js'); +Stream.PassThrough = require('readable-stream/passthrough.js'); + +// Backwards-compat with node 0.4.x +Stream.Stream = Stream; + + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + +Stream.prototype.pipe = function(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === 'function') dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, 'error') === 0) { + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +},{"events":189,"inherits":192,"readable-stream/duplex.js":212,"readable-stream/passthrough.js":221,"readable-stream/readable.js":222,"readable-stream/transform.js":223,"readable-stream/writable.js":224}],227:[function(require,module,exports){ +(function (global){ +var ClientRequest = require('./lib/request') +var extend = require('xtend') +var statusCodes = require('builtin-status-codes') +var url = require('url') + +var http = exports + +http.request = function (opts, cb) { + if (typeof opts === 'string') + opts = url.parse(opts) + else + opts = extend(opts) + + // Normally, the page is loaded from http or https, so not specifying a protocol + // will result in a (valid) protocol-relative url. However, this won't work if + // the protocol is something else, like 'file:' + var defaultProtocol = global.location.protocol.search(/^https?:$/) === -1 ? 'http:' : '' + + var protocol = opts.protocol || defaultProtocol + var host = opts.hostname || opts.host + var port = opts.port + var path = opts.path || '/' + + // Necessary for IPv6 addresses + if (host && host.indexOf(':') !== -1) + host = '[' + host + ']' + + // This may be a relative url. The browser should always be able to interpret it correctly. + opts.url = (host ? (protocol + '//' + host) : '') + (port ? ':' + port : '') + path + opts.method = (opts.method || 'GET').toUpperCase() + opts.headers = opts.headers || {} + + // Also valid opts.auth, opts.mode + + var req = new ClientRequest(opts) + if (cb) + req.on('response', cb) + return req +} + +http.get = function get (opts, cb) { + var req = http.request(opts, cb) + req.end() + return req +} + +http.Agent = function () {} +http.Agent.defaultMaxSockets = 4 + +http.STATUS_CODES = statusCodes + +http.METHODS = [ + 'CHECKOUT', + 'CONNECT', + 'COPY', + 'DELETE', + 'GET', + 'HEAD', + 'LOCK', + 'M-SEARCH', + 'MERGE', + 'MKACTIVITY', + 'MKCOL', + 'MOVE', + 'NOTIFY', + 'OPTIONS', + 'PATCH', + 'POST', + 'PROPFIND', + 'PROPPATCH', + 'PURGE', + 'PUT', + 'REPORT', + 'SEARCH', + 'SUBSCRIBE', + 'TRACE', + 'UNLOCK', + 'UNSUBSCRIBE' +] +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./lib/request":229,"builtin-status-codes":186,"url":233,"xtend":239}],228:[function(require,module,exports){ +(function (global){ +exports.fetch = isFunction(global.fetch) && isFunction(global.ReadableStream) + +exports.blobConstructor = false +try { + new Blob([new ArrayBuffer(1)]) + exports.blobConstructor = true +} catch (e) {} + +// The xhr request to example.com may violate some restrictive CSP configurations, +// so if we're running in a browser that supports `fetch`, avoid calling getXHR() +// and assume support for certain features below. +var xhr +function getXHR () { + // Cache the xhr value + if (xhr !== undefined) return xhr + + if (global.XMLHttpRequest) { + xhr = new global.XMLHttpRequest() + // If XDomainRequest is available (ie only, where xhr might not work + // cross domain), use the page location. Otherwise use example.com + // Note: this doesn't actually make an http request. + try { + xhr.open('GET', global.XDomainRequest ? '/' : 'https://example.com') + } catch(e) { + xhr = null + } + } else { + // Service workers don't have XHR + xhr = null + } + return xhr +} + +function checkTypeSupport (type) { + var xhr = getXHR() + if (!xhr) return false + try { + xhr.responseType = type + return xhr.responseType === type + } catch (e) {} + return false +} + +// For some strange reason, Safari 7.0 reports typeof global.ArrayBuffer === 'object'. +// Safari 7.1 appears to have fixed this bug. +var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined' +var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice) + +// If fetch is supported, then arraybuffer will be supported too. Skip calling +// checkTypeSupport(), since that calls getXHR(). +exports.arraybuffer = exports.fetch || (haveArrayBuffer && checkTypeSupport('arraybuffer')) + +// These next two tests unavoidably show warnings in Chrome. Since fetch will always +// be used if it's available, just return false for these to avoid the warnings. +exports.msstream = !exports.fetch && haveSlice && checkTypeSupport('ms-stream') +exports.mozchunkedarraybuffer = !exports.fetch && haveArrayBuffer && + checkTypeSupport('moz-chunked-arraybuffer') + +// If fetch is supported, then overrideMimeType will be supported too. Skip calling +// getXHR(). +exports.overrideMimeType = exports.fetch || (getXHR() ? isFunction(getXHR().overrideMimeType) : false) + +exports.vbArray = isFunction(global.VBArray) + +function isFunction (value) { + return typeof value === 'function' +} + +xhr = null // Help gc + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],229:[function(require,module,exports){ +(function (process,global,Buffer){ +var capability = require('./capability') +var inherits = require('inherits') +var response = require('./response') +var stream = require('readable-stream') +var toArrayBuffer = require('to-arraybuffer') + +var IncomingMessage = response.IncomingMessage +var rStates = response.readyStates + +function decideMode (preferBinary, useFetch) { + if (capability.fetch && useFetch) { + return 'fetch' + } else if (capability.mozchunkedarraybuffer) { + return 'moz-chunked-arraybuffer' + } else if (capability.msstream) { + return 'ms-stream' + } else if (capability.arraybuffer && preferBinary) { + return 'arraybuffer' + } else if (capability.vbArray && preferBinary) { + return 'text:vbarray' + } else { + return 'text' + } +} + +var ClientRequest = module.exports = function (opts) { + var self = this + stream.Writable.call(self) + + self._opts = opts + self._body = [] + self._headers = {} + if (opts.auth) + self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64')) + Object.keys(opts.headers).forEach(function (name) { + self.setHeader(name, opts.headers[name]) + }) + + var preferBinary + var useFetch = true + if (opts.mode === 'disable-fetch' || 'timeout' in opts) { + // If the use of XHR should be preferred and includes preserving the 'content-type' header. + // Force XHR to be used since the Fetch API does not yet support timeouts. + useFetch = false + preferBinary = true + } else if (opts.mode === 'prefer-streaming') { + // If streaming is a high priority but binary compatibility and + // the accuracy of the 'content-type' header aren't + preferBinary = false + } else if (opts.mode === 'allow-wrong-content-type') { + // If streaming is more important than preserving the 'content-type' header + preferBinary = !capability.overrideMimeType + } else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') { + // Use binary if text streaming may corrupt data or the content-type header, or for speed + preferBinary = true + } else { + throw new Error('Invalid value for opts.mode') + } + self._mode = decideMode(preferBinary, useFetch) + + self.on('finish', function () { + self._onFinish() + }) +} + +inherits(ClientRequest, stream.Writable) + +ClientRequest.prototype.setHeader = function (name, value) { + var self = this + var lowerName = name.toLowerCase() + // This check is not necessary, but it prevents warnings from browsers about setting unsafe + // headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but + // http-browserify did it, so I will too. + if (unsafeHeaders.indexOf(lowerName) !== -1) + return + + self._headers[lowerName] = { + name: name, + value: value + } +} + +ClientRequest.prototype.getHeader = function (name) { + var header = this._headers[name.toLowerCase()] + if (header) + return header.value + return null +} + +ClientRequest.prototype.removeHeader = function (name) { + var self = this + delete self._headers[name.toLowerCase()] +} + +ClientRequest.prototype._onFinish = function () { + var self = this + + if (self._destroyed) + return + var opts = self._opts + + var headersObj = self._headers + var body = null + if (opts.method !== 'GET' && opts.method !== 'HEAD') { + if (capability.blobConstructor) { + body = new global.Blob(self._body.map(function (buffer) { + return toArrayBuffer(buffer) + }), { + type: (headersObj['content-type'] || {}).value || '' + }) + } else { + // get utf8 string + body = Buffer.concat(self._body).toString() + } + } + + // create flattened list of headers + var headersList = [] + Object.keys(headersObj).forEach(function (keyName) { + var name = headersObj[keyName].name + var value = headersObj[keyName].value + if (Array.isArray(value)) { + value.forEach(function (v) { + headersList.push([name, v]) + }) + } else { + headersList.push([name, value]) + } + }) + + if (self._mode === 'fetch') { + global.fetch(self._opts.url, { + method: self._opts.method, + headers: headersList, + body: body || undefined, + mode: 'cors', + credentials: opts.withCredentials ? 'include' : 'same-origin' + }).then(function (response) { + self._fetchResponse = response + self._connect() + }, function (reason) { + self.emit('error', reason) + }) + } else { + var xhr = self._xhr = new global.XMLHttpRequest() + try { + xhr.open(self._opts.method, self._opts.url, true) + } catch (err) { + process.nextTick(function () { + self.emit('error', err) + }) + return + } + + // Can't set responseType on really old browsers + if ('responseType' in xhr) + xhr.responseType = self._mode.split(':')[0] + + if ('withCredentials' in xhr) + xhr.withCredentials = !!opts.withCredentials + + if (self._mode === 'text' && 'overrideMimeType' in xhr) + xhr.overrideMimeType('text/plain; charset=x-user-defined') + + if ('timeout' in opts) { + xhr.timeout = opts.timeout + xhr.ontimeout = function () { + self.emit('timeout') + } + } + + headersList.forEach(function (header) { + xhr.setRequestHeader(header[0], header[1]) + }) + + self._response = null + xhr.onreadystatechange = function () { + switch (xhr.readyState) { + case rStates.LOADING: + case rStates.DONE: + self._onXHRProgress() + break + } + } + // Necessary for streaming in Firefox, since xhr.response is ONLY defined + // in onprogress, not in onreadystatechange with xhr.readyState = 3 + if (self._mode === 'moz-chunked-arraybuffer') { + xhr.onprogress = function () { + self._onXHRProgress() + } + } + + xhr.onerror = function () { + if (self._destroyed) + return + self.emit('error', new Error('XHR error')) + } + + try { + xhr.send(body) + } catch (err) { + process.nextTick(function () { + self.emit('error', err) + }) + return + } + } +} + +/** + * Checks if xhr.status is readable and non-zero, indicating no error. + * Even though the spec says it should be available in readyState 3, + * accessing it throws an exception in IE8 + */ +function statusValid (xhr) { + try { + var status = xhr.status + return (status !== null && status !== 0) + } catch (e) { + return false + } +} + +ClientRequest.prototype._onXHRProgress = function () { + var self = this + + if (!statusValid(self._xhr) || self._destroyed) + return + + if (!self._response) + self._connect() + + self._response._onXHRProgress() +} + +ClientRequest.prototype._connect = function () { + var self = this + + if (self._destroyed) + return + + self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode) + self._response.on('error', function(err) { + self.emit('error', err) + }) + + self.emit('response', self._response) +} + +ClientRequest.prototype._write = function (chunk, encoding, cb) { + var self = this + + self._body.push(chunk) + cb() +} + +ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function () { + var self = this + self._destroyed = true + if (self._response) + self._response._destroyed = true + if (self._xhr) + self._xhr.abort() + // Currently, there isn't a way to truly abort a fetch. + // If you like bikeshedding, see https://github.com/whatwg/fetch/issues/27 +} + +ClientRequest.prototype.end = function (data, encoding, cb) { + var self = this + if (typeof data === 'function') { + cb = data + data = undefined + } + + stream.Writable.prototype.end.call(self, data, encoding, cb) +} + +ClientRequest.prototype.flushHeaders = function () {} +ClientRequest.prototype.setTimeout = function () {} +ClientRequest.prototype.setNoDelay = function () {} +ClientRequest.prototype.setSocketKeepAlive = function () {} + +// Taken from http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method +var unsafeHeaders = [ + 'accept-charset', + 'accept-encoding', + 'access-control-request-headers', + 'access-control-request-method', + 'connection', + 'content-length', + 'cookie', + 'cookie2', + 'date', + 'dnt', + 'expect', + 'host', + 'keep-alive', + 'origin', + 'referer', + 'te', + 'trailer', + 'transfer-encoding', + 'upgrade', + 'user-agent', + 'via' +] + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) +},{"./capability":228,"./response":230,"_process":207,"buffer":185,"inherits":192,"readable-stream":222,"to-arraybuffer":232}],230:[function(require,module,exports){ +(function (process,global,Buffer){ +var capability = require('./capability') +var inherits = require('inherits') +var stream = require('readable-stream') + +var rStates = exports.readyStates = { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4 +} + +var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode) { + var self = this + stream.Readable.call(self) + + self._mode = mode + self.headers = {} + self.rawHeaders = [] + self.trailers = {} + self.rawTrailers = [] + + // Fake the 'close' event, but only once 'end' fires + self.on('end', function () { + // The nextTick is necessary to prevent the 'request' module from causing an infinite loop + process.nextTick(function () { + self.emit('close') + }) + }) + + if (mode === 'fetch') { + self._fetchResponse = response + + self.url = response.url + self.statusCode = response.status + self.statusMessage = response.statusText + + response.headers.forEach(function(header, key){ + self.headers[key.toLowerCase()] = header + self.rawHeaders.push(key, header) + }) + + + // TODO: this doesn't respect backpressure. Once WritableStream is available, this can be fixed + var reader = response.body.getReader() + function read () { + reader.read().then(function (result) { + if (self._destroyed) + return + if (result.done) { + self.push(null) + return + } + self.push(new Buffer(result.value)) + read() + }).catch(function(err) { + self.emit('error', err) + }) + } + read() + + } else { + self._xhr = xhr + self._pos = 0 + + self.url = xhr.responseURL + self.statusCode = xhr.status + self.statusMessage = xhr.statusText + var headers = xhr.getAllResponseHeaders().split(/\r?\n/) + headers.forEach(function (header) { + var matches = header.match(/^([^:]+):\s*(.*)/) + if (matches) { + var key = matches[1].toLowerCase() + if (key === 'set-cookie') { + if (self.headers[key] === undefined) { + self.headers[key] = [] + } + self.headers[key].push(matches[2]) + } else if (self.headers[key] !== undefined) { + self.headers[key] += ', ' + matches[2] + } else { + self.headers[key] = matches[2] + } + self.rawHeaders.push(matches[1], matches[2]) + } + }) + + self._charset = 'x-user-defined' + if (!capability.overrideMimeType) { + var mimeType = self.rawHeaders['mime-type'] + if (mimeType) { + var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/) + if (charsetMatch) { + self._charset = charsetMatch[1].toLowerCase() + } + } + if (!self._charset) + self._charset = 'utf-8' // best guess + } + } +} + +inherits(IncomingMessage, stream.Readable) + +IncomingMessage.prototype._read = function () {} + +IncomingMessage.prototype._onXHRProgress = function () { + var self = this + + var xhr = self._xhr + + var response = null + switch (self._mode) { + case 'text:vbarray': // For IE9 + if (xhr.readyState !== rStates.DONE) + break + try { + // This fails in IE8 + response = new global.VBArray(xhr.responseBody).toArray() + } catch (e) {} + if (response !== null) { + self.push(new Buffer(response)) + break + } + // Falls through in IE8 + case 'text': + try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4 + response = xhr.responseText + } catch (e) { + self._mode = 'text:vbarray' + break + } + if (response.length > self._pos) { + var newData = response.substr(self._pos) + if (self._charset === 'x-user-defined') { + var buffer = new Buffer(newData.length) + for (var i = 0; i < newData.length; i++) + buffer[i] = newData.charCodeAt(i) & 0xff + + self.push(buffer) + } else { + self.push(newData, self._charset) + } + self._pos = response.length + } + break + case 'arraybuffer': + if (xhr.readyState !== rStates.DONE || !xhr.response) + break + response = xhr.response + self.push(new Buffer(new Uint8Array(response))) + break + case 'moz-chunked-arraybuffer': // take whole + response = xhr.response + if (xhr.readyState !== rStates.LOADING || !response) + break + self.push(new Buffer(new Uint8Array(response))) + break + case 'ms-stream': + response = xhr.response + if (xhr.readyState !== rStates.LOADING) + break + var reader = new global.MSStreamReader() + reader.onprogress = function () { + if (reader.result.byteLength > self._pos) { + self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos)))) + self._pos = reader.result.byteLength + } + } + reader.onload = function () { + self.push(null) + } + // reader.onerror = ??? // TODO: this + reader.readAsArrayBuffer(response) + break + } + + // The ms-stream case handles end separately in reader.onload() + if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') { + self.push(null) + } +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) +},{"./capability":228,"_process":207,"buffer":185,"inherits":192,"readable-stream":222}],231:[function(require,module,exports){ +'use strict'; + +var Buffer = require('safe-buffer').Buffer; + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return -1; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// UTF-8 replacement characters ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'.repeat(p); + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'.repeat(p + 1); + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'.repeat(p + 2); + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character for each buffered byte of a (partial) +// character needs to be added to the output. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'.repeat(this.lastTotal - this.lastNeed); + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} +},{"safe-buffer":225}],232:[function(require,module,exports){ +var Buffer = require('buffer').Buffer + +module.exports = function (buf) { + // If the buffer is backed by a Uint8Array, a faster version will work + if (buf instanceof Uint8Array) { + // If the buffer isn't a subarray, return the underlying ArrayBuffer + if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) { + return buf.buffer + } else if (typeof buf.buffer.slice === 'function') { + // Otherwise we need to get a proper copy + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) + } + } + + if (Buffer.isBuffer(buf)) { + // This is the slow version that will work with any Buffer + // implementation (even in old browsers) + var arrayCopy = new Uint8Array(buf.length) + var len = buf.length + for (var i = 0; i < len; i++) { + arrayCopy[i] = buf[i] + } + return arrayCopy.buffer + } else { + throw new Error('Argument must be a Buffer') + } +} + +},{"buffer":185}],233:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +var punycode = require('punycode'); +var util = require('./util'); + +exports.parse = urlParse; +exports.resolve = urlResolve; +exports.resolveObject = urlResolveObject; +exports.format = urlFormat; + +exports.Url = Url; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; +} + +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], + + // RFC 2396: characters not allowed for various reasons. + unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), + + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = ['\''].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), + hostEndingChars = ['/', '?', '#'], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + unsafeProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }, + querystring = require('querystring'); + +function urlParse(url, parseQueryString, slashesDenoteHost) { + if (url && util.isObject(url) && url instanceof Url) return url; + + var u = new Url; + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} + +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { + if (!util.isString(url)) { + throw new TypeError("Parameter 'url' must be a string, not " + typeof url); + } + + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + var queryIndex = url.indexOf('?'), + splitter = + (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', + uSplit = url.split(splitter), + slashRegex = /\\/g; + uSplit[0] = uSplit[0].replace(slashRegex, '/'); + url = uSplit.join(splitter); + + var rest = url; + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.path = rest; + this.href = rest; + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + if (parseQueryString) { + this.query = querystring.parse(this.search.substr(1)); + } else { + this.query = this.search.substr(1); + } + } else if (parseQueryString) { + this.search = ''; + this.query = {}; + } + return this; + } + } + + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.substr(proto.length); + } + + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + var slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } + + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { + + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) + hostEnd = rest.length; + + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + // pull out port. + this.parseHost(); + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (var i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) continue; + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = '/' + notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + // IDNA Support: Returns a punycoded representation of "domain". + // It only converts parts of the domain name that + // have non-ASCII characters, i.e. it doesn't matter if + // you call it with a domain that already is ASCII-only. + this.hostname = punycode.toASCII(this.hostname); + } + + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + this.href += this.host; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } + } + + // now rest is set to the post-host stuff. + // chop off any delim chars. + if (!unsafeProtocol[lowerProto]) { + + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + for (var i = 0, l = autoEscape.length; i < l; i++) { + var ae = autoEscape[i]; + if (rest.indexOf(ae) === -1) + continue; + var esc = encodeURIComponent(ae); + if (esc === ae) { + esc = escape(ae); + } + rest = rest.split(ae).join(esc); + } + } + + + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + rest = rest.slice(0, qm); + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ''; + this.query = {}; + } + if (rest) this.pathname = rest; + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = '/'; + } + + //to support http.request + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; + } + + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; +}; + +// format a parsed object into a url string +function urlFormat(obj) { + // ensure it's an object, and not a string url. + // If it's an obj, this is a no-op. + // this way, you can call url_format() on strings + // to clean up potentially wonky urls. + if (util.isString(obj)) obj = urlParse(obj); + if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + return obj.format(); +} + +Url.prototype.format = function() { + var auth = this.auth || ''; + if (auth) { + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ':'); + auth += '@'; + } + + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', + host = false, + query = ''; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; + } + } + + if (this.query && + util.isObject(this.query) && + Object.keys(this.query).length) { + query = querystring.stringify(this.query); + } + + var search = this.search || (query && ('?' + query)) || ''; + + if (protocol && protocol.substr(-1) !== ':') protocol += ':'; + + // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || + (!protocol || slashedProtocol[protocol]) && host !== false) { + host = '//' + (host || ''); + if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; + } else if (!host) { + host = ''; + } + + if (hash && hash.charAt(0) !== '#') hash = '#' + hash; + if (search && search.charAt(0) !== '?') search = '?' + search; + + pathname = pathname.replace(/[?#]/g, function(match) { + return encodeURIComponent(match); + }); + search = search.replace('#', '%23'); + + return protocol + host + pathname + search + hash; +}; + +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); +} + +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; + +function urlResolveObject(source, relative) { + if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); +} + +Url.prototype.resolveObject = function(relative) { + if (util.isString(relative)) { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } + + var result = new Url(); + var tkeys = Object.keys(this); + for (var tk = 0; tk < tkeys.length; tk++) { + var tkey = tkeys[tk]; + result[tkey] = this[tkey]; + } + + // hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; + + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === '') { + result.href = result.format(); + return result; + } + + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + var rkeys = Object.keys(relative); + for (var rk = 0; rk < rkeys.length; rk++) { + var rkey = rkeys[rk]; + if (rkey !== 'protocol') + result[rkey] = relative[rkey]; + } + + //urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; + } + + result.href = result.format(); + return result; + } + + if (relative.protocol && relative.protocol !== result.protocol) { + // if it's a known url protocol, then changing + // the protocol does weird things + // first, if it's not file:, then we MUST have a host, + // and if there was a path + // to begin with, then we MUST have a path. + // if it is file:, then the host is dropped, + // because that's known to be hostless. + // anything else is assumed to be absolute. + if (!slashedProtocol[relative.protocol]) { + var keys = Object.keys(relative); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + result[k] = relative[k]; + } + result.href = result.format(); + return result; + } + + result.protocol = relative.protocol; + if (!relative.host && !hostlessProtocol[relative.protocol]) { + var relPath = (relative.pathname || '').split('/'); + while (relPath.length && !(relative.host = relPath.shift())); + if (!relative.host) relative.host = ''; + if (!relative.hostname) relative.hostname = ''; + if (relPath[0] !== '') relPath.unshift(''); + if (relPath.length < 2) relPath.unshift(''); + result.pathname = relPath.join('/'); + } else { + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; + } + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), + isRelAbs = ( + relative.host || + relative.pathname && relative.pathname.charAt(0) === '/' + ), + mustEndAbs = (isRelAbs || isSourceAbs || + (result.host && relative.pathname)), + removeAllDots = mustEndAbs, + srcPath = result.pathname && result.pathname.split('/') || [], + relPath = relative.pathname && relative.pathname.split('/') || [], + psychotic = result.protocol && !slashedProtocol[result.protocol]; + + // if the url is a non-slashed url, then relative + // links like ../.. should be able + // to crawl up to the hostname, as well. This is strange. + // result.protocol has already been set by now. + // Later on, put the first path part into the host field. + if (psychotic) { + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); + } + result.host = ''; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === '') relPath[0] = relative.host; + else relPath.unshift(relative.host); + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); + } + + if (isRelAbs) { + // it's absolute. + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // fall through to the dot-handling below. + } else if (relPath.length) { + // it's relative + // throw away the existing file, and take the new path instead. + if (!srcPath) srcPath = []; + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (!util.isNullOrUndefined(relative.search)) { + // just pull out the search. + // like href='?foo'. + // Put this after the other two cases because it simplifies the booleans + if (psychotic) { + result.hostname = result.host = srcPath.shift(); + //occationaly the auth can get stuck only in host + //this especially happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + result.search = relative.search; + result.query = relative.query; + //to support http.request + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.href = result.format(); + return result; + } + + if (!srcPath.length) { + // no path at all. easy. + // we've already handled the other stuff above. + result.pathname = null; + //to support http.request + if (result.search) { + result.path = '/' + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } + + // if a url ENDs in . or .., then it must get a trailing slash. + // however, if it ends in anything else non-slashy, + // then it must NOT get a trailing slash. + var last = srcPath.slice(-1)[0]; + var hasTrailingSlash = ( + (result.host || relative.host || srcPath.length > 1) && + (last === '.' || last === '..') || last === ''); + + // strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = srcPath.length; i >= 0; i--) { + last = srcPath[i]; + if (last === '.') { + srcPath.splice(i, 1); + } else if (last === '..') { + srcPath.splice(i, 1); + up++; + } else if (up) { + srcPath.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + for (; up--; up) { + srcPath.unshift('..'); + } + } + + if (mustEndAbs && srcPath[0] !== '' && + (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { + srcPath.unshift(''); + } + + if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { + srcPath.push(''); + } + + var isAbsolute = srcPath[0] === '' || + (srcPath[0] && srcPath[0].charAt(0) === '/'); + + // put the host back + if (psychotic) { + result.hostname = result.host = isAbsolute ? '' : + srcPath.length ? srcPath.shift() : ''; + //occationaly the auth can get stuck only in host + //this especially happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + + mustEndAbs = mustEndAbs || (result.host && srcPath.length); + + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(''); + } + + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); + } + + //to support request.http + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +Url.prototype.parseHost = function() { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); + } + if (host) this.hostname = host; +}; + +},{"./util":234,"punycode":208,"querystring":211}],234:[function(require,module,exports){ +'use strict'; + +module.exports = { + isString: function(arg) { + return typeof(arg) === 'string'; + }, + isObject: function(arg) { + return typeof(arg) === 'object' && arg !== null; + }, + isNull: function(arg) { + return arg === null; + }, + isNullOrUndefined: function(arg) { + return arg == null; + } +}; + +},{}],235:[function(require,module,exports){ +(function (global){ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],236:[function(require,module,exports){ +arguments[4][192][0].apply(exports,arguments) +},{"dup":192}],237:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],238:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":237,"_process":207,"inherits":236}],239:[function(require,module,exports){ +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} + +},{}]},{},[1]); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/.eslintrc b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/.eslintrc new file mode 100644 index 000000000..4a873d0f0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/.eslintrc @@ -0,0 +1,84 @@ +{ + "env": { + "node": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "script" + }, + "extends": ["eslint:recommended", "airbnb-base"], + "rules": { + "strict": "off", + "semi": "error", + "indent": ["error", 4], + "init-declarations": "error", + "no-undef-init": "off", + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], + "brace-style": ["error", "1tbs"], + "multiline-comment-style": ["error", "separate-lines"], + "dot-notation": "off", + "quote-props": ["error", "always"], + "no-multi-spaces": ["error", { "ignoreEOLComments": true }], + "no-whitespace-before-property": "error", + "space-before-blocks": ["error", "always"], + "space-before-function-paren": ["error", "always"], + "no-spaced-func": "off", + "func-call-spacing": ["error", "always"], + "block-spacing": ["error", "always"], + "keyword-spacing": ["error", { "before": true, "after": true }], + "object-curly-spacing": ["error", "always", { "objectsInObjects": false }], + "object-curly-newline": ["error", { "consistent": true }], + "space-infix-ops": "error", + "space-unary-ops": "error", + "space-in-parens": "error", + "no-nested-ternary": "error", + "eqeqeq": "error", + "quotes": ["error", "single", { "avoidEscape": true }], + "no-unused-vars": ["error", { "argsIgnorePattern": "^(symbol|price|tag|since|limit|params|market|timeframe|api|path|code|currency|response|requestHeaders|requestBody|bidsKey|asksKey)" }], + "new-parens": "error", + "new-cap": ["error"], + "no-var": "error", + "prefer-const": ["error", { + "destructuring": "any", + "ignoreReadBeforeAssign": false + }], + "no-warning-comments": ["warn", { "terms": ["fixme"] }], + "padded-blocks": ["error", "never"], + "lines-between-class-members": "error", + "no-multiple-empty-lines": ["error", { "max": 1 }], + "padding-line-between-statements": ["error", + { "blankLine": "never", "prev":"*", "next": "*" }, + { "blankLine": "always", "prev":"directive", "next": "*" }, + { "blankLine": "always", "prev":"*", "next": "cjs-export" } + ], + "prefer-template": "off", + "curly": "error", + "no-plusplus": "off", + "no-restricted-properties": "off", + "prefer-destructuring": "off", + "class-methods-use-this": "off", + "no-param-reassign": "off", + "max-len": "off", + "no-return-await": "off", + "array-bracket-spacing": "off", + "radix": "off", + "camelcase": "off", + "no-lonely-if": "off", + "no-mixed-operators": "off", + "no-shadow": "off", + "no-useless-concat": "off", + "no-continue": "off", + "no-else-return": "off", + "no-unneeded-ternary": "off", + "operator-assignment": "off", + "no-underscore-dangle": "off", + "consistent-return": "off", + "no-await-in-loop": "off" + } +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/acx.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/acx.js new file mode 100644 index 000000000..011921fa4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/acx.js @@ -0,0 +1,486 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InsufficientFunds, OrderNotFound } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class acx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'acx', + 'name': 'ACX', + 'countries': [ 'AU' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '12h': '720', + '1d': '1440', + '3d': '4320', + '1w': '10080', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30247614-1fe61c74-9621-11e7-9e8c-f1a627afa279.jpg', + 'extension': '.json', + 'api': 'https://acx.io/api', + 'www': 'https://acx.io', + 'doc': 'https://acx.io/documents/api_v2', + }, + 'api': { + 'public': { + 'get': [ + 'depth', // Get depth or specified market Both asks and bids are sorted from highest price to lowest. + 'k_with_pending_trades', // Get K data with pending trades, which are the trades not included in K data yet, because there's delay between trade generated and processed by K data generator + 'k', // Get OHLC(k line) of specific market + 'markets', // Get all available markets + 'order_book', // Get the order book of specified market + 'order_book/{market}', + 'tickers', // Get ticker of all markets + 'tickers/{market}', // Get ticker of specific market + 'timestamp', // Get server current time, in seconds since Unix epoch + 'trades', // Get recent trades on market, each trade is included only once Trades are sorted in reverse creation order. + 'trades/{market}', + ], + }, + 'private': { + 'get': [ + 'members/me', // Get your profile and accounts info + 'deposits', // Get your deposits history + 'deposit', // Get details of specific deposit + 'deposit_address', // Where to deposit The address field could be empty when a new address is generating (e.g. for bitcoin), you should try again later in that case. + 'orders', // Get your orders, results is paginated + 'order', // Get information of specified order + 'trades/my', // Get your executed trades Trades are sorted in reverse creation order. + 'withdraws', // Get your cryptocurrency withdraws + 'withdraw', // Get your cryptocurrency withdraw + ], + 'post': [ + 'orders', // Create a Sell/Buy order + 'orders/multi', // Create multiple sell/buy orders + 'orders/clear', // Cancel all my orders + 'order/delete', // Cancel an order + 'withdraw', // Create a withdraw + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': true, + 'withdraw': {}, // There is only 1% fee on withdrawals to your bank account. + }, + }, + 'commonCurrencies': { + 'PLA': 'Plair', + }, + 'exceptions': { + '2002': InsufficientFunds, + '2003': OrderNotFound, + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = market['id']; + const symbol = market['name']; + let baseId = this.safeString (market, 'base_unit'); + let quoteId = this.safeString (market, 'quote_unit'); + if ((baseId === undefined) || (quoteId === undefined)) { + const ids = symbol.split ('/'); + baseId = ids[0].toLowerCase (); + quoteId = ids[1].toLowerCase (); + } + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + // todo: find out their undocumented precision and limits + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'info': market, + 'active': undefined, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetMembersMe (params); + const balances = this.safeValue (response, 'accounts'); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 300 + } + const orderbook = await this.publicGetDepth (this.extend (request, params)); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'at'); + ticker = ticker['ticker']; + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let market = undefined; + let symbol = id; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } else { + let base = id.slice (0, 3); + let quote = id.slice (3, 6); + base = base.toUpperCase (); + quote = quote.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + symbol = base + '/' + quote; + } + result[symbol] = this.parseTicker (response[id], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTickersMarket (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const id = this.safeString (trade, 'tid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': this.safeFloat (trade, 'price'), + 'amount': this.safeFloat (trade, 'volume'), + 'cost': this.safeFloat (trade, 'funds'), + 'fee': undefined, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTimestamp (params); + // + // 1594911427 + // + return response * 1000; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 500; // default is 30 + } + const request = { + 'market': market['id'], + 'period': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.publicGetK (this.extend (request, params)); + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'done': 'closed', + 'wait': 'open', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'amount': this.safeFloat (order, 'volume'), + 'filled': this.safeFloat (order, 'executed_volume'), + 'remaining': this.safeFloat (order, 'remaining_volume'), + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'cost': undefined, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': parseInt (id), + }; + const response = await this.privateGetOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'side': side, + 'volume': amount.toString (), + 'ord_type': type, + }; + if (type === 'limit') { + request['price'] = price.toString (); + } + const response = await this.privatePostOrders (this.extend (request, params)); + const marketId = this.safeValue (response, 'market'); + const market = this.safeValue (this.markets_by_id, marketId); + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrderDelete (this.extend (request, params)); + const order = this.parseOrder (response); + const status = order['status']; + if (status === 'closed' || status === 'canceled') { + throw new OrderNotFound (this.id + ' ' + this.json (order)); + } + return order; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // they have XRP but no docs on memo/tag + const request = { + 'currency': currency['id'], + 'sum': amount, + 'address': address, + }; + const response = await this.privatePostWithdraw (this.extend (request, params)); + // withdrawal response is undocumented + return { + 'info': response, + 'id': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + encodeParams (params) { + if ('orders' in params) { + const orders = params['orders']; + let query = this.urlencode (this.keysort (this.omit (params, 'orders'))); + for (let i = 0; i < orders.length; i++) { + const order = orders[i]; + const keys = Object.keys (order); + for (let k = 0; k < keys.length; k++) { + const key = keys[k]; + const value = order[key]; + query += '&orders%5B%5D%5B' + key + '%5D=' + value.toString (); + } + } + return query; + } + return this.urlencode (this.keysort (params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/api/' + this.version + '/' + this.implodeParams (path, params); + if ('extension' in this.urls) { + request += this.urls['extension']; + } + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + request; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const query = this.encodeParams (this.extend ({ + 'access_key': this.apiKey, + 'tonce': nonce, + }, params)); + const auth = method + '|' + request + '|' + query; + const signed = this.hmac (this.encode (auth), this.encode (this.secret)); + const suffix = query + '&signature=' + signed; + if (method === 'GET') { + url += '?' + suffix; + } else { + body = suffix; + headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code === 400) { + const error = this.safeValue (response, 'error'); + const errorCode = this.safeString (error, 'code'); + const feedback = this.id + ' ' + this.json (response); + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + // fallback to default error handler + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/aofex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/aofex.js new file mode 100644 index 000000000..fb5e7f4a3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/aofex.js @@ -0,0 +1,1096 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, BadSymbol, InvalidOrder, PermissionDenied, InvalidAddress, AuthenticationError, InvalidNonce, BadRequest, InsufficientFunds, OrderNotFound } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class aofex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'aofex', + 'name': 'AOFEX', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'has': { + 'fetchMarkets': true, + 'fetchCurrencies': false, + 'fetchOrderBook': true, + 'fetchTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOHLCV': true, + 'fetchBalance': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchClosedOrder': true, + 'fetchOrderTrades': true, + 'fetchTradingFee': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '6h': '6hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/77670271-056d1080-6f97-11ea-9ac2-4268e9ed0c1f.jpg', + 'api': { + 'public': 'https://openapi.aofex.com/openApi', + 'private': 'https://openapi.aofex.com/openApi', + }, + 'www': 'https://aofex.com', + 'doc': 'https://aofex.zendesk.com/hc/en-us/sections/360005576574-API', + 'fees': 'https://aofex.zendesk.com/hc/en-us/articles/360025814934-Fees-on-AOFEX', + 'referral': 'https://aofex.com/#/register?key=9763840', + }, + 'api': { + 'public': { + 'get': [ + 'market/symbols', + 'market/trade', + 'market/depth', + 'market/kline', + 'market/precision', + 'market/24kline', + 'market/gears_depth', + 'market/detail', + ], + }, + 'private': { + 'get': [ + 'entrust/currentList', + 'entrust/historyList', + 'entrust/rate', + 'wallet/list', + 'entrust/detail', + ], + 'post': [ + 'entrust/add', + 'entrust/cancel', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.0019, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + '20001': ExchangeError, // request failure + '20401': PermissionDenied, // no permission + '20500': ExchangeError, // system error + '20501': BadSymbol, // base symbol error + '20502': ExchangeError, // base currency error + '20503': ExchangeError, // base date error + '20504': InsufficientFunds, // account frozen balance insufficient error + '20505': BadRequest, // bad argument + '20506': AuthenticationError, // api signature not valid + '20507': ExchangeError, // gateway internal error + '20508': InvalidAddress, // ad ethereum addresss + '20509': InsufficientFunds, // order accountbalance error + '20510': InvalidOrder, // order limitorder price error + '20511': InvalidOrder, // order limitorder amount error + '20512': InvalidOrder, // order orderprice precision error + '20513': InvalidOrder, // order orderamount precision error + '20514': InvalidOrder, // order marketorder amount error + '20515': InvalidOrder, // order queryorder invalid + '20516': InvalidOrder, // order orderstate error + '20517': InvalidOrder, // order datelimit error + '50518': InvalidOrder, // order update error + '20519': InvalidNonce, // the nonce has been used + '20520': InvalidNonce, // nonce expires, please verify server time + '20521': BadRequest, // incomplete header parameters + '20522': ExchangeError, // not getting the current user + '20523': AuthenticationError, // please authenticate + '20524': PermissionDenied, // btc account lockout + '20525': AuthenticationError, // get API Key error + '20526': PermissionDenied, // no query permission + '20527': PermissionDenied, // no deal permission + '20528': PermissionDenied, // no withdrawal permission + '20529': AuthenticationError, // API Key expired + '20530': PermissionDenied, // no permission + }, + 'broad': { + }, + }, + 'options': { + 'fetchBalance': { + 'show_all': '0', // '1' to show zero balances + }, + }, + }); + } + + async fetchMarkets (params = {}) { + let markets = await this.publicGetMarketSymbols (params); + // + // { + // errno: 0, + // errmsg: 'success', + // result: [ + // { + // id: 2, + // symbol: 'BTC-USDT', + // base_currency: 'BTC', + // quote_currency: 'USDT', + // min_size: 0.00008, + // max_size: 1300, + // min_price: 1000, + // max_price: 110000, + // maker_fee: 1, + // taker_fee: 1, + // isHot: null, + // isNew: null, + // crown: null + // }, + // ] + // } + // + let precisions = await this.publicGetMarketPrecision (); + // + // { + // errno: 0, + // errmsg: 'success', + // result: { + // 'MANA-USDT': { + // amount: '2', + // minQuantity: '32', + // maxQuantity: '46000000', + // price: '4', + // minPrice: '0.003', + // maxPrice: '0.35' + // }, + // } + // } + // + precisions = this.safeValue (precisions, 'result', {}); + markets = this.safeValue (markets, 'result', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const numericId = this.safeInteger (market, 'id'); + const precision = this.safeValue (precisions, id, {}); + const makerFee = this.safeFloat (market, 'maker_fee'); + const takerFee = this.safeFloat (market, 'taker_fee'); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': undefined, + 'maker': makerFee / 1000, + 'taker': takerFee / 1000, + 'precision': { + 'amount': this.safeInteger (precision, 'amount'), + 'price': this.safeInteger (precision, 'price'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_size'), + 'max': this.safeFloat (market, 'max_size'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // id: 1584950100, + // amount: "329.196", + // count: 81, + // open: "0.021155", + // close: "0.021158", + // low: "0.021144", + // high: "0.021161", + // vol: "6.963557767" + // } + // + return [ + this.safeTimestamp (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'amount'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 150; // default 150, max 2000 + } + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + 'size': limit, // default 150, max 2000 + }; + const response = await this.publicGetMarketKline (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: { + // ts: 1584950139003, + // symbol: "ETH-BTC", + // period: "1min", + // data: [ + // { + // id: 1584950100, + // amount: "329.196", + // count: 81, + // open: "0.021155", + // close: "0.021158", + // low: "0.021144", + // high: "0.021161", + // vol: "6.963557767" + // }, + // { + // id: 1584950040, + // amount: "513.265", + // count: 151, + // open: "0.021165", + // close: "0.021155", + // low: "0.021151", + // high: "0.02118", + // vol: "10.862806573" + // }, + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseOHLCVs (data, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchBalance', {}); + const showAll = this.safeValue (options, 'show_all', '0'); + const request = { + // 'currency': 'BTC', + 'show_all': showAll, // required to show zero balances + }; + const response = await this.privateGetWalletList (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": [ + // { "available": "0", "frozen": "0", "currency": "BTC" } + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'result', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'frozen'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privateGetEntrustRate (this.extend (request, params)); + // + // { + // "errno":0, + // "errmsg":"success", + // "result": { + // "toFee":"0.002","fromFee":"0.002" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return { + 'info': response, + 'symbol': symbol, + 'maker': this.safeFloat (result, 'fromFee'), + 'taker': this.safeFloat (result, 'toFee'), + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetMarketDepth (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: { + // buyType: 1, + // sellType: 1, + // ts: 1584950701050, + // symbol: "ETH-BTC", + // asks: [ + // ["0.021227", "0.182"], + // ["0.021249", "0.035"], + // ["0.021253", "0.058"], + // ], + // bids: [ + // ["0.021207", "0.039"], + // ["0.021203", "0.051"], + // ["0.02117", "2.326"], + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const timestamp = this.safeInteger (result, 'ts'); + return this.parseOrderBook (result, timestamp); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // id: 1584890087, + // amount: '150032.919', + // count: 134538, + // open: '0.021394', + // close: '0.021177', + // low: '0.021053', + // high: '0.021595', + // vol: '3201.72451442' + // } + // + const timestamp = this.safeTimestamp (ticker, 'id'); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const open = this.safeFloat (ticker, 'open'); + const last = this.safeFloat (ticker, 'close'); + let change = undefined; + if (symbol !== undefined) { + change = parseFloat (this.priceToPrecision (symbol, last - open)); + } else { + change = last - open; + } + const average = this.sum (last, open) / 2; + const percentage = change / open * 100; + const baseVolume = this.safeFloat (ticker, 'amount'); + const quoteVolume = this.safeFloat (ticker, 'vol'); + let vwap = this.vwap (baseVolume, quoteVolume); + if (vwap !== undefined) { + vwap = parseFloat (this.priceToPrecision (symbol, vwap)); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['symbol'] = ids.join (','); + } + const response = await this.publicGetMarket24kline (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: [ + // { + // symbol: "HB-AQ", + // data: { + // id: 1584893403, + // amount: "4753751.243400354852648809", + // count: 4724, + // open: "6.3497", + // close: "6.3318", + // low: "6.011", + // high: "6.5", + // vol: "29538384.7873528796542891343493" + // } + // }, + // ] + // } + // + const tickers = this.safeValue (response, 'result', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const marketId = this.safeString (tickers[i], 'symbol'); + const market = this.safeMarket (marketId, undefined, '-'); + const symbol = market['symbol']; + const data = this.safeValue (tickers[i], 'data', {}); + result[symbol] = this.parseTicker (data, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketDetail (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: 'success', + // result: { + // id: 1584890087, + // amount: '150032.919', + // count: 134538, + // open: '0.021394', + // close: '0.021177', + // low: '0.021053', + // high: '0.021595', + // vol: '3201.72451442' + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTicker (result, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // id: 1584948803298490, + // amount: "2.737", + // price: "0.021209", + // direction: "sell", + // ts: 1584948803 + // } + // + // fetchOrder trades + // + // { + // "id":null, + // "ctime":"2020-03-23 20:07:17", + // "price":"123.9", + // "number":"0.010688626311541565", + // "total_price":"1.324320799999999903", + // "fee":"0.000021377252623083" + // } + // + const id = this.safeString (trade, 'id'); + const ctime = this.parse8601 (this.safeString (trade, 'ctime')); + const timestamp = this.safeTimestamp (trade, 'ts', ctime) - 28800000; // 8 hours, adjust to UTC; + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'direction'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'amount', 'number'); + let cost = this.safeFloat (trade, 'total_price'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrencyCode = undefined; + if (market !== undefined) { + if (side === 'buy') { + feeCurrencyCode = market['base']; + } else if (side === 'sell') { + feeCurrencyCode = market['quote']; + } + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketTrade (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: "success", + // result: { + // symbol: "ETH-BTC", + // ts: 1584948805439, + // data: [ + // { + // id: 1584948803300883, + // amount: "0.583", + // price: "0.021209", + // direction: "buy", + // ts: 1584948803 + // }, + // { + // id: 1584948803298490, + // amount: "2.737", + // price: "0.021209", + // direction: "sell", + // ts: 1584948803 + // }, + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + '1': 'open', + '2': 'open', // partially filled + '3': 'closed', + '4': 'canceled', // canceling + '5': 'canceled', // partially canceled + '6': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { order_sn: 'BM7442641584965237751ZMAKJ5' } + // + // fetchOpenOrders, fetchClosedOrders + // + // { + // "order_sn": "BL74426415849672087836G48N1", + // "symbol": "ETH-USDT", + // "ctime": "2020-03-23 20:40:08", + // "type": 2, + // "side": "buy", + // "price": "90", // undefined for market orders + // "number": "0.1", + // "total_price": "9.0", // 0 for market orders + // "deal_number": null, + // "deal_price": null, + // "status": 1, + // } + // + // fetchOrder + // + // { + // order_sn: 'BM7442641584965237751ZMAKJ5', + // symbol: 'ETH-USDT', + // ctime: '2020-03-23 20:07:17', + // type: 1, + // side: 'buy', + // price: '0', + // number: '10', + // total_price: '0', + // deal_number: '0.080718626311541565', + // deal_price: '123.890000000000000000', + // status: 3, + // // the trades field is injected by fetchOrder + // trades: [ + // { + // id: null, + // ctime: '2020-03-23 20:07:17', + // price: '123.9', + // number: '0.010688626311541565', + // total_price: '1.324320799999999903', + // fee: '0.000021377252623083' + // } + // ] + // } + // + const id = this.safeString (order, 'order_sn'); + const orderStatus = this.safeString (order, 'status'); + const status = this.parseOrderStatus (orderStatus); + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market, '-'); + let timestamp = this.parse8601 (this.safeString (order, 'ctime')); + if (timestamp !== undefined) { + timestamp -= 28800000; // 8 hours, adjust to UTC + } + const orderType = this.safeString (order, 'type'); + const type = (orderType === '2') ? 'limit' : 'market'; + const side = this.safeString (order, 'side'); + // const amount = this.safeFloat (order, 'number'); + // const price = this.safeFloat (order, 'price'); + let cost = undefined; + let price = undefined; + let amount = undefined; + let average = undefined; + const number = this.safeFloat (order, 'number'); + const totalPrice = this.safeFloat (order, 'total_price'); + if (type === 'limit') { + amount = number; + price = this.safeFloat (order, 'price'); + } else { + average = this.safeFloat (order, 'deal_price'); + if (side === 'buy') { + amount = this.safeFloat (order, 'deal_number'); + } else { + amount = number; + } + } + let fee = undefined; + let trades = undefined; + let filled = undefined; + let feeCost = undefined; + let remaining = undefined; + let lastTradeTimestamp = undefined; + // all orders except new orders and canceled orders + if ((orderStatus !== '1') && (orderStatus !== '6')) { + const rawTrades = this.safeValue (order, 'trades'); + if (rawTrades !== undefined) { + for (let i = 0; i < rawTrades.length; i++) { + rawTrades[i]['direction'] = side; + } + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'symbol': market['symbol'], + 'order': id, + 'side': side, + 'type': type, + }); + const tradesLength = trades.length; + if (tradesLength > 0) { + const firstTrade = trades[0]; + feeCost = firstTrade['fee']['cost']; + lastTradeTimestamp = firstTrade['timestamp']; + filled = firstTrade['amount']; + cost = firstTrade['cost']; + for (let i = 1; i < trades.length; i++) { + const trade = trades[i]; + feeCost = this.sum (feeCost, trade['fee']['cost']); + filled = this.sum (filled, trade['amount']); + cost = this.sum (cost, trade['cost']); + lastTradeTimestamp = Math.max (lastTradeTimestamp, trade['timestamp']); + } + if (amount !== undefined) { + filled = Math.min (amount, filled); + } + if (filled > 0) { + average = cost / filled; + } + } + if (feeCost !== undefined) { + const feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + } + } else { + filled = 0; + cost = 0; + } + if (cost === undefined) { + if (type === 'limit') { + cost = totalPrice; + } else if (side === 'buy') { + cost = number; + } + } + if (filled === undefined) { + if ((type === 'limit') && (orderStatus === '3')) { + filled = amount; + } + } + if (filled !== undefined) { + if (amount !== undefined) { + remaining = Math.max (amount - filled, 0); + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + }; + } + + async fetchClosedOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_sn': id, + }; + const response = await this.privateGetEntrustDetail (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": { + // "trades": [ + // { + // "id":null, + // "ctime":"2020-03-23 20:07:17", + // "price":"123.9", + // "number":"0.010688626311541565", + // "total_price":"1.324320799999999903", + // "fee":"0.000021377252623083" + // }, + // ], + // "entrust":{ + // "order_sn":"BM7442641584965237751ZMAKJ5", + // "symbol":"ETH-USDT", + // "ctime":"2020-03-23 20:07:17", + // "type":1, + // "side":"buy", + // "price":"0", + // "number":"10", + // "total_price":"0", + // "deal_number":"0.080718626311541565", + // "deal_price":"123.890000000000000000", + // "status":3 + // } + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + const order = this.safeValue (result, 'entrust', {}); + order['trades'] = trades; + return this.parseOrder (order); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.fetchClosedOrder (id, symbol, params); + return this.safeValue (response, 'trades', []); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'from': 'BM7442641584965237751ZMAKJ5', // query start order_sn + 'direct': 'prev', // next + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 100 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": [ + // { + // "order_sn": "BL74426415849672087836G48N1", + // "symbol": "ETH-USDT", + // "ctime": "2020-03-23 20:40:08", + // "type": 2, + // "side": "buy", + // "price": "90", + // "number": "0.1", + // "total_price": "9.0", + // "deal_number": null, + // "deal_price": null, + // "status": 1, + // } + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetEntrustCurrentList', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetEntrustHistoryList', symbol, since, limit, params); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderType = side + '-' + type; + const request = { + 'symbol': market['id'], + 'type': orderType, + }; + if (type === 'limit') { + request['amount'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + let cost = amount; + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + cost = amount * price; + } else { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument"); + } + } + const precision = market['precision']['price']; + request['amount'] = this.decimalToPrecision (cost, TRUNCATE, precision, this.precisionMode); + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this.privatePostEntrustAdd (this.extend (request, params)); + // + // { + // errno: 0, + // errmsg: 'success', + // result: { order_sn: 'BM7442641584965237751ZMAKJ5' } + // } + // + const result = this.safeValue (response, 'result', {}); + const order = this.parseOrder (result, market); + const timestamp = this.milliseconds (); + return this.extend (order, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'amount': amount, + 'price': price, + 'type': type, + 'side': side, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_ids': id, + }; + const response = await this.privatePostEntrustCancel (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": { + // "success": [ "avl12121", "bl3123123" ], + // "failed": [ "sd24564", "sdf6564564" ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const success = this.safeValue (result, 'success', []); + if (!this.inArray (id, success)) { + throw new OrderNotFound (this.id + ' order id ' + id + ' not found in successfully canceled orders: ' + this.json (response)); + } + const timestamp = undefined; + return { + 'info': response, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': 'canceled', + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'cost': undefined, + 'average': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + }; + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privatePostEntrustCancel (this.extend (request, params)); + // + // { + // "errno": 0, + // "errmsg": "success", + // "result": { + // "success": [ "avl12121", "bl3123123" ], + // "failed": [ "sd24564", "sdf6564564" ] + // } + // } + // + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + path; + let keys = Object.keys (params); + const keysLength = keys.length; + if (api === 'public') { + if (keysLength > 0) { + url += '?' + this.urlencode (params); + } + } else { + const nonce = this.nonce ().toString (); + const uuid = this.uuid (); + const randomString = uuid.slice (0, 5); + const nonceString = nonce + '_' + randomString; + const auth = {}; + auth[this.apiKey] = this.apiKey; + auth[this.secret] = this.secret; + auth[nonceString] = nonceString; + for (let i = 0; i < keysLength; i++) { + const key = keys[i]; + auth[key] = key + '=' + params[key]; + } + const keysorted = this.keysort (auth); + let stringToSign = ''; + keys = Object.keys (keysorted); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + stringToSign += keysorted[key]; + } + const signature = this.hash (this.encode (stringToSign), 'sha1'); + headers = { + 'Nonce': nonceString, + 'Token': this.apiKey, + 'Signature': signature, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + if (keysLength > 0) { + body = this.urlencode (params); + } + } else { + if (keysLength > 0) { + url += '?' + this.urlencode (params); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"errno":20501,"errmsg":"base symbol error"} + // + const error = this.safeString (response, 'errno'); + if ((error !== undefined) && (error !== '0')) { + const message = this.safeString (response, 'errmsg'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/.eslintrc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/.eslintrc.js new file mode 100644 index 000000000..c441debac --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/.eslintrc.js @@ -0,0 +1,43 @@ +'use strict'; + +module.exports = { + "env": { + "node": true, + "es6": true, + "browser": true, + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "script", + }, + "rules": { + "semi": "off", + "no-unused-vars": ["off"], + "quotes": ["off", "single"], + "quote-props": "off", + "func-call-spacing": ["error", "always"], + "one-var": "off", + "indent": ["warn", 4, { "ignoredNodes": ["ObjectPattern"] }], + "comma-style": "off", + "no-multi-spaces": "off", + "comma-dangle": "off", + "spaced-comment": "off", + "camelcase": "off", + "padded-blocks": "off", + "multiline-comment-style": "off", + "curly": "off", + "rest-spread-spacing": "off", + "no-multiple-empty-lines": "off", + "no-undef-init": "off", + "no-useless-return": "off", + "no-console": "off", + "operator-linebreak": "off", + "key-spacing": "off", + "brace-style": "off", + "padding-line-between-statements": ["off", + {"blankLine": "never", "prev":"*", "next": "*" }, + {"blankLine": "always", "prev":"directive", "next": "*" }, + {"blankLine": "always", "prev":"*", "next": "cjs-export" }, + ], + }, +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/Exchange.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/Exchange.js new file mode 100644 index 000000000..f231581ed --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/Exchange.js @@ -0,0 +1,1400 @@ +"use strict"; + +// ---------------------------------------------------------------------------- + +const functions = require ('./functions') + +const { + isNode + , keys + , values + , deepExtend + , extend + , clone + , flatten + , unique + , indexBy + , sortBy + , groupBy + , aggregate + , uuid + , unCamelCase + , precisionFromString + , throttle + , capitalize + , now + , timeout + , TimedOut + , buildOHLCVC + , decimalToPrecision + , defaultFetch +} = functions + +const { // eslint-disable-line object-curly-newline + ExchangeError + , BadSymbol + , InvalidAddress + , NotSupported + , AuthenticationError + , DDoSProtection + , RequestTimeout + , ExchangeNotAvailable + , RateLimitExceeded } = require ('./errors') + +const { TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING } = functions.precisionConstants + +const BN = require ('../static_dependencies/BN/bn') + +// ---------------------------------------------------------------------------- + +module.exports = class Exchange { + + describe () { + return { + 'id': undefined, + 'name': undefined, + 'countries': undefined, + 'enableRateLimit': false, + 'rateLimit': 2000, // milliseconds = seconds * 1000 + 'certified': false, + 'pro': false, + 'has': { + 'loadMarkets': true, + 'cancelAllOrders': false, + 'cancelOrder': true, + 'cancelOrders': false, + 'CORS': false, + 'createDepositAddress': false, + 'createLimitOrder': true, + 'createMarketOrder': true, + 'createOrder': true, + 'deposit': false, + 'editOrder': 'emulated', + 'fetchBalance': true, + 'fetchBidsAsks': false, + 'fetchClosedOrders': false, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchDeposits': false, + 'fetchFundingFees': false, + 'fetchL2OrderBook': true, + 'fetchLedger': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': 'emulated', + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': true, + 'fetchOrderBooks': false, + 'fetchOrders': false, + 'fetchOrderTrades': false, + 'fetchStatus': 'emulated', + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTime': false, + 'fetchTrades': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTradingLimits': false, + 'fetchTransactions': false, + 'fetchWithdrawals': false, + 'privateAPI': true, + 'publicAPI': true, + 'signIn': false, + 'withdraw': false, + }, + 'urls': { + 'logo': undefined, + 'api': undefined, + 'www': undefined, + 'doc': undefined, + 'fees': undefined, + }, + 'api': undefined, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': false, + 'login': false, + 'password': false, + 'twofa': false, // 2-factor authentication (one-time password key) + 'privateKey': false, // a "0x"-prefixed hexstring private key for a wallet + 'walletAddress': false, // the wallet address "0x"-prefixed hexstring + 'token': false, // reserved for HTTP auth in some cases + }, + 'markets': undefined, // to be filled manually or by fetchMarkets + 'currencies': {}, // to be filled manually or by fetchMarkets + 'timeframes': undefined, // redefine if the exchange has.fetchOHLCV + 'fees': { + 'trading': { + 'tierBased': undefined, + 'percentage': undefined, + 'taker': undefined, + 'maker': undefined, + }, + 'funding': { + 'tierBased': undefined, + 'percentage': undefined, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'status': { + 'status': 'ok', + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + }, + 'exceptions': undefined, + 'httpExceptions': { + '422': ExchangeError, + '418': DDoSProtection, + '429': RateLimitExceeded, + '404': ExchangeNotAvailable, + '409': ExchangeNotAvailable, + '410': ExchangeNotAvailable, + '500': ExchangeNotAvailable, + '501': ExchangeNotAvailable, + '502': ExchangeNotAvailable, + '520': ExchangeNotAvailable, + '521': ExchangeNotAvailable, + '522': ExchangeNotAvailable, + '525': ExchangeNotAvailable, + '526': ExchangeNotAvailable, + '400': ExchangeNotAvailable, + '403': ExchangeNotAvailable, + '405': ExchangeNotAvailable, + '503': ExchangeNotAvailable, + '530': ExchangeNotAvailable, + '408': RequestTimeout, + '504': RequestTimeout, + '401': AuthenticationError, + '511': AuthenticationError, + }, + // some exchanges report only 'free' on `fetchBlance` call (i.e. report no 'used' funds) + // in this case ccxt will try to infer 'used' funds from open order cache, which might be stale + // still, some exchanges report number of open orders together with balance + // if you set the following flag to 'true' ccxt will leave 'used' funds undefined in case of discrepancy + 'dontGetUsedBalanceFromStaleCache': false, + 'commonCurrencies': { // gets extended/overwritten in subclasses + 'XBT': 'BTC', + 'BCC': 'BCH', + 'DRK': 'DASH', + 'BCHABC': 'BCH', + 'BCHSV': 'BSV', + }, + 'precisionMode': DECIMAL_PLACES, + 'paddingMode': NO_PADDING, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + } // return + } // describe () + + constructor (userConfig = {}) { + Object.assign (this, functions) + // if (isNode) { + // this.nodeVersion = process.version.match (/\d+\.\d+\.\d+/)[0] + // this.userAgent = { + // 'User-Agent': 'ccxt/' + Exchange.ccxtVersion + + // ' (+https://github.com/ccxt/ccxt)' + + // ' Node.js/' + this.nodeVersion + ' (JavaScript)' + // } + // } + + this.options = {} // exchange-specific options, if any + + // fetch implementation options (JS only) + this.fetchOptions = { + // keepalive: true, // does not work in Chrome, https://github.com/ccxt/ccxt/issues/6368 + } + + this.userAgents = { + 'chrome': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', + 'chrome39': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', + } + + this.headers = {} + + // prepended to URL, like https://proxy.com/https://exchange.com/api... + this.proxy = '' + this.origin = '*' // CORS origin + + this.minFundingAddressLength = 1 // used in checkAddress + this.substituteCommonCurrencyCodes = true // reserved + + // do not delete this line, it is needed for users to be able to define their own fetchImplementation + this.fetchImplementation = defaultFetch + + this.timeout = 10000 // milliseconds + this.verbose = false + this.debug = false + this.userAgent = undefined + this.twofa = undefined // two-factor authentication (2FA) + + this.apiKey = undefined + this.secret = undefined + this.uid = undefined + this.login = undefined + this.password = undefined + this.privateKey = undefined // a "0x"-prefixed hexstring private key for a wallet + this.walletAddress = undefined // a wallet address "0x"-prefixed hexstring + this.token = undefined // reserved for HTTP auth in some cases + + this.balance = {} + this.orderbooks = {} + this.tickers = {} + this.orders = undefined + this.trades = {} + this.transactions = {} + this.ohlcvs = {} + this.myTrades = undefined + + this.requiresWeb3 = false + this.requiresEddsa = false + this.precision = {} + + this.enableLastJsonResponse = true + this.enableLastHttpResponse = true + this.enableLastResponseHeaders = true + this.last_http_response = undefined + this.last_json_response = undefined + this.last_response_headers = undefined + + const unCamelCaseProperties = (obj = this) => { + if (obj !== null) { + const ownPropertyNames = Object.getOwnPropertyNames (obj) + for (let i = 0; i < ownPropertyNames.length; i++) { + const k = ownPropertyNames[i] + this[unCamelCase (k)] = this[k] + } + unCamelCaseProperties (Object.getPrototypeOf (obj)) + } + } + unCamelCaseProperties () + + // merge to this + const configEntries = Object.entries (this.describe ()).concat (Object.entries (userConfig)) + for (let i = 0; i < configEntries.length; i++) { + const [property, value] = configEntries[i] + if (value && Object.getPrototypeOf (value) === Object.prototype) { + this[property] = deepExtend (this[property], value) + } else { + this[property] = value + } + } + + const agentOptions = { + 'keepAlive': true, + } + + if (!this.httpAgent && defaultFetch.http && isNode) { + this.httpAgent = new defaultFetch.http.Agent (agentOptions) + } + + if (!this.httpsAgent && defaultFetch.https && isNode) { + this.httpsAgent = new defaultFetch.https.Agent (agentOptions) + } + + // generate old metainfo interface + const hasKeys = Object.keys (this.has) + for (let i = 0; i < hasKeys.length; i++) { + const k = hasKeys[i] + this['has' + capitalize (k)] = !!this.has[k] // converts 'emulated' to true + } + + if (this.api) { + this.defineRestApi (this.api, 'request') + } + + this.initRestRateLimiter () + + if (this.markets) { + this.setMarkets (this.markets) + } + } + + defaults () { + return { /* override me */ } + } + + nonce () { + return this.seconds () + } + + encodeURIComponent (...args) { + return encodeURIComponent (...args) + } + + checkRequiredCredentials (error = true) { + const keys = Object.keys (this.requiredCredentials) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (this.requiredCredentials[key] && !this[key]) { + if (error) { + throw new AuthenticationError (this.id + ' requires `' + key + '` credential') + } else { + return error + } + } + } + return true + } + + checkAddress (address) { + + if (address === undefined) { + throw new InvalidAddress (this.id + ' address is undefined') + } + + // check the address is not the same letter like 'aaaaa' nor too short nor has a space + if ((unique (address).length === 1) || address.length < this.minFundingAddressLength || address.includes (' ')) { + throw new InvalidAddress (this.id + ' address is invalid or has less than ' + this.minFundingAddressLength.toString () + ' characters: "' + this.json (address) + '"') + } + + return address + } + + initRestRateLimiter () { + + if (this.rateLimit === undefined) { + throw new Error (this.id + '.rateLimit property is not configured') + } + + this.tokenBucket = this.extend ({ + delay: 1, + capacity: 1, + defaultCost: 1, + maxCapacity: 1000, + }, this.tokenBucket) + + this.throttle = throttle (this.tokenBucket) + + this.executeRestRequest = (url, method = 'GET', headers = undefined, body = undefined) => { + + // fetchImplementation cannot be called on this. in browsers: + // TypeError Failed to execute 'fetch' on 'Window': Illegal invocation + const fetchImplementation = this.fetchImplementation + + const params = { method, headers, body, timeout: this.timeout } + + if (this.agent) { + params['agent'] = this.agent + } else if (this.httpAgent && url.indexOf ('http://') === 0) { + params['agent'] = this.httpAgent + } else if (this.httpsAgent && url.indexOf ('https://') === 0) { + params['agent'] = this.httpsAgent + } + + const promise = + fetchImplementation (url, this.extend (params, this.fetchOptions)) + .catch ((e) => { + if (isNode) { + throw new ExchangeNotAvailable ([ this.id, method, url, e.type, e.message ].join (' ')) + } + throw e // rethrow all unknown errors + }) + .then ((response) => this.handleRestResponse (response, url, method, headers, body)) + + return timeout (this.timeout, promise).catch ((e) => { + if (e instanceof TimedOut) { + throw new RequestTimeout (this.id + ' ' + method + ' ' + url + ' request timed out (' + this.timeout + ' ms)') + } + throw e + }) + } + } + + setSandboxMode (enabled) { + if (!!enabled) { // eslint-disable-line no-extra-boolean-cast + if ('test' in this.urls) { + if (typeof this.urls['api'] === 'string') { + this.urls['api_backup'] = this.urls['api'] + this.urls['api'] = this.urls['test'] + } else { + this.urls['api_backup'] = clone (this.urls['api']) + this.urls['api'] = clone (this.urls['test']) + } + } else { + throw new NotSupported (this.id + ' does not have a sandbox URL') + } + } else if ('api_backup' in this.urls) { + if (typeof this.urls['api'] === 'string') { + this.urls['api'] = this.urls['api_backup'] + } else { + this.urls['api'] = clone (this.urls['api_backup']) + } + } + } + + defineRestApi (api, methodName, paths = []) { + const keys = Object.keys (api) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const value = api[key] + if (Array.isArray (value)) { + const uppercaseMethod = key.toUpperCase () + const lowercaseMethod = key.toLowerCase () + const camelcaseMethod = this.capitalize (lowercaseMethod) + for (let k = 0; k < value.length; k++) { + const path = value[k].trim () + const splitPath = path.split (/[^a-zA-Z0-9]/) + const camelcaseSuffix = splitPath.map (this.capitalize).join ('') + const underscoreSuffix = splitPath.map ((x) => x.trim ().toLowerCase ()).filter ((x) => x.length > 0).join ('_') + const camelcasePrefix = [ paths[0] ].concat (paths.slice (1).map (this.capitalize)).join ('') + const underscorePrefix = [ paths[0] ].concat (paths.slice (1).map ((x) => x.trim ()).filter ((x) => x.length > 0)).join ('_') + const camelcase = camelcasePrefix + camelcaseMethod + this.capitalize (camelcaseSuffix) + const underscore = underscorePrefix + '_' + lowercaseMethod + '_' + underscoreSuffix + const typeArgument = (paths.length > 1) ? paths : paths[0] + const partial = async (params) => this[methodName] (path, typeArgument, uppercaseMethod, params || {}) + this[camelcase] = partial + this[underscore] = partial + } + } else { + this.defineRestApi (value, methodName, paths.concat ([ key ])) + } + } + } + + print (... args) { + console.log (... args) + } + + setHeaders (headers) { + return headers; + } + + fetch (url, method = 'GET', headers = undefined, body = undefined) { + + if (isNode && this.userAgent) { + if (typeof this.userAgent === 'string') { + headers = extend ({ 'User-Agent': this.userAgent }, headers) + } else if ((typeof this.userAgent === 'object') && ('User-Agent' in this.userAgent)) { + headers = extend (this.userAgent, headers) + } + } + + if (typeof this.proxy === 'function') { + + url = this.proxy (url) + if (isNode) { + headers = extend ({ 'Origin': this.origin }, headers) + } + + } else if (typeof this.proxy === 'string') { + + if (this.proxy.length && isNode) { + headers = extend ({ 'Origin': this.origin }, headers) + } + + url = this.proxy + url + } + + headers = extend (this.headers, headers) + headers = this.setHeaders (headers) + + if (this.verbose) { + this.print ("fetch:\n", this.id, method, url, "\nRequest:\n", headers, "\n", body, "\n") + } + + return this.executeRestRequest (url, method, headers, body) + } + + async fetch2 (path, type = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + + if (this.enableRateLimit) { + await this.throttle (this.rateLimit) + } + + const request = this.sign (path, type, method, params, headers, body) + return this.fetch (request.url, request.method, request.headers, request.body) + } + + request (path, type = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + return this.fetch2 (path, type, method, params, headers, body) + } + + parseJson (jsonString) { + try { + if (this.isJsonEncodedObject (jsonString)) { + return JSON.parse (jsonString) + } + } catch (e) { + // SyntaxError + return undefined + } + } + + throwExactlyMatchedException (exact, string, message) { + if (string in exact) { + throw new exact[string] (message) + } + } + + throwBroadlyMatchedException (broad, string, message) { + const broadKey = this.findBroadlyMatchedKey (broad, string) + if (broadKey !== undefined) { + throw new broad[broadKey] (message) + } + } + + // a helper for matching error strings exactly vs broadly + findBroadlyMatchedKey (broad, string) { + const keys = Object.keys (broad) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (string.indexOf (key) >= 0) { + return key + } + } + return undefined + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + // override me + } + + handleHttpStatusCode (code, reason, url, method, body) { + const codeAsString = code.toString () + if (codeAsString in this.httpExceptions) { + const ErrorClass = this.httpExceptions[codeAsString] + throw new ErrorClass ([ this.id, method, url, code, reason, body ].join (' ')) + } + } + + getResponseHeaders (response) { + const result = {} + response.headers.forEach ((value, key) => { + key = key.split ('-').map ((word) => capitalize (word)).join ('-') + result[key] = value + }) + return result + } + + handleRestResponse (response, url, method = 'GET', requestHeaders = undefined, requestBody = undefined) { + + return response.text ().then ((responseBody) => { + + const json = this.parseJson (responseBody) + + const responseHeaders = this.getResponseHeaders (response) + + if (this.enableLastResponseHeaders) { + this.last_response_headers = responseHeaders + } + + if (this.enableLastHttpResponse) { + this.last_http_response = responseBody + } + + if (this.enableLastJsonResponse) { + this.last_json_response = json + } + + if (this.verbose) { + this.print ("handleRestResponse:\n", this.id, method, url, response.status, response.statusText, "\nResponse:\n", responseHeaders, "\n", responseBody, "\n") + } + + this.handleErrors (response.status, response.statusText, url, method, responseHeaders, responseBody, json, requestHeaders, requestBody) + this.handleHttpStatusCode (response.status, response.statusText, url, method, responseBody) + + return json || responseBody + }) + } + + setMarkets (markets, currencies = undefined) { + const values = Object.values (markets).map ((market) => deepExtend ({ + 'limits': this.limits, + 'precision': this.precision, + }, this.fees['trading'], market)) + this.markets = indexBy (values, 'symbol') + this.marketsById = indexBy (markets, 'id') + this.markets_by_id = this.marketsById + this.symbols = Object.keys (this.markets).sort () + this.ids = Object.keys (this.markets_by_id).sort () + if (currencies) { + this.currencies = deepExtend (currencies, this.currencies) + } else { + let baseCurrencies = + values.filter ((market) => 'base' in market) + .map ((market) => ({ + id: market.baseId || market.base, + numericId: (market.baseNumericId !== undefined) ? market.baseNumericId : undefined, + code: market.base, + precision: market.precision ? (market.precision.base || market.precision.amount) : 8, + })) + let quoteCurrencies = + values.filter ((market) => 'quote' in market) + .map ((market) => ({ + id: market.quoteId || market.quote, + numericId: (market.quoteNumericId !== undefined) ? market.quoteNumericId : undefined, + code: market.quote, + precision: market.precision ? (market.precision.quote || market.precision.price) : 8, + })) + baseCurrencies = sortBy (baseCurrencies, 'code') + quoteCurrencies = sortBy (quoteCurrencies, 'code') + this.baseCurrencies = indexBy (baseCurrencies, 'code') + this.quoteCurrencies = indexBy (quoteCurrencies, 'code') + const allCurrencies = baseCurrencies.concat (quoteCurrencies) + const groupedCurrencies = groupBy (allCurrencies, 'code') + const currencies = Object.keys (groupedCurrencies).map ((code) => + groupedCurrencies[code].reduce ((previous, current) => // eslint-disable-line implicit-arrow-linebreak + ((previous.precision > current.precision) ? previous : current), groupedCurrencies[code][0])) // eslint-disable-line implicit-arrow-linebreak + const sortedCurrencies = sortBy (flatten (currencies), 'code') + this.currencies = deepExtend (indexBy (sortedCurrencies, 'code'), this.currencies) + } + this.currencies_by_id = indexBy (this.currencies, 'id') + return this.markets + } + + async loadMarketsHelper (reload = false, params = {}) { + if (!reload && this.markets) { + if (!this.markets_by_id) { + return this.setMarkets (this.markets) + } + return this.markets + } + let currencies = undefined + if (this.has.fetchCurrencies) { + currencies = await this.fetchCurrencies () + } + const markets = await this.fetchMarkets (params) + return this.setMarkets (markets, currencies) + } + + // is async (returns a promise) + loadMarkets (reload = false, params = {}) { + if ((reload && !this.reloadingMarkets) || !this.marketsLoading) { + this.reloadingMarkets = true + this.marketsLoading = this.loadMarketsHelper (reload, params).then ((resolved) => { + this.reloadingMarkets = false + return resolved + }, (error) => { + this.reloadingMarkets = false + throw error + }) + } + return this.marketsLoading + } + + async loadAccounts (reload = false, params = {}) { + if (reload) { + this.accounts = await this.fetchAccounts (params) + } else { + if (this.accounts) { + return this.accounts + } else { + this.accounts = await this.fetchAccounts (params) + } + } + this.accountsById = this.indexBy (this.accounts, 'id') + return this.accounts + } + + fetchBidsAsks (symbols = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchBidsAsks not supported yet') + } + + async fetchOHLCVC (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + if (!this.has['fetchTrades']) { + throw new NotSupported (this.id + ' fetchOHLCV() not supported yet') + } + await this.loadMarkets () + const trades = await this.fetchTrades (symbol, since, limit, params) + const ohlcvc = buildOHLCVC (trades, timeframe, since, limit) + return ohlcvc + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + if (!this.has['fetchTrades']) { + throw new NotSupported (this.id + ' fetchOHLCV() not supported yet') + } + await this.loadMarkets () + const trades = await this.fetchTrades (symbol, since, limit, params) + const ohlcvc = buildOHLCVC (trades, timeframe, since, limit) + return ohlcvc.map ((c) => c.slice (0, -1)) + } + + parseTradingViewOHLCV (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { + const result = this.convertTradingViewToOHLCV (ohlcvs) + return this.parseOHLCVs (result, market, timeframe, since, limit) + } + + convertTradingViewToOHLCV (ohlcvs, t = 't', o = 'o', h = 'h', l = 'l', c = 'c', v = 'v', ms = false) { + const result = []; + for (let i = 0; i < ohlcvs[t].length; i++) { + result.push ([ + ms ? ohlcvs[t][i] : (ohlcvs[t][i] * 1000), + ohlcvs[o][i], + ohlcvs[h][i], + ohlcvs[l][i], + ohlcvs[c][i], + ohlcvs[v][i], + ]) + } + return result + } + + convertOHLCVToTradingView (ohlcvs, t = 't', o = 'o', h = 'h', l = 'l', c = 'c', v = 'v', ms = false) { + const result = {} + result[t] = [] + result[o] = [] + result[h] = [] + result[l] = [] + result[c] = [] + result[v] = [] + for (let i = 0; i < ohlcvs.length; i++) { + result[t].push (ms ? ohlcvs[i][0] : parseInt (ohlcvs[i][0] / 1000)) + result[o].push (ohlcvs[i][1]) + result[h].push (ohlcvs[i][2]) + result[l].push (ohlcvs[i][3]) + result[c].push (ohlcvs[i][4]) + result[v].push (ohlcvs[i][5]) + } + return result + } + + fetchTicker (symbol, params = {}) { + throw new NotSupported (this.id + ' fetchTicker not supported yet') + } + + fetchTickers (symbols = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchTickers not supported yet') + } + + fetchOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOrder not supported yet'); + } + + fetchUnifiedOrder (order, params = {}) { + return this.fetchOrder (this.safeValue (order, 'id'), this.safeValue (order, 'symbol'), params); + } + + createOrder (symbol, type, side, amount, price = undefined, params = {}) { + throw new NotSupported (this.id + ' createOrder not supported yet'); + } + + cancelOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' cancelOrder not supported yet'); + } + + cancelUnifiedOrder (order, params = {}) { + return this.cancelOrder (this.safeValue (order, 'id'), this.safeValue (order, 'symbol'), params); + } + + fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOrders not supported yet'); + } + + fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOpenOrders not supported yet'); + } + + fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchClosedOrders not supported yet'); + } + + fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchMyTrades not supported yet'); + } + + fetchTransactions (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchTransactions not supported yet'); + } + + fetchDeposits (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchDeposits not supported yet'); + } + + fetchWithdrawals (symbol = undefined, since = undefined, limit = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchWithdrawals not supported yet'); + } + + fetchCurrencies (params = {}) { + // markets are returned as a list + // currencies are returned as a dict + // this is for historical reasons + // and may be changed for consistency later + return new Promise ((resolve, reject) => resolve (this.currencies)); + } + + fetchMarkets (params = {}) { + // markets are returned as a list + // currencies are returned as a dict + // this is for historical reasons + // and may be changed for consistency later + return new Promise ((resolve, reject) => resolve (Object.values (this.markets))) + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + const order = await this.fetchOrder (id, symbol, params); + return order['status']; + } + + account () { + return { + 'free': undefined, + 'used': undefined, + 'total': undefined, + } + } + + commonCurrencyCode (currency) { + if (!this.substituteCommonCurrencyCodes) { + return currency + } + return this.safeString (this.commonCurrencies, currency, currency) + } + + currencyId (commonCode) { + + if (this.currencies === undefined) { + throw new ExchangeError (this.id + ' currencies not loaded') + } + + if (commonCode in this.currencies) { + return this.currencies[commonCode]['id']; + } + + const currencyIds = {} + const distinct = Object.keys (this.commonCurrencies) + for (let i = 0; i < distinct.length; i++) { + const k = distinct[i] + currencyIds[this.commonCurrencies[k]] = k + } + + return this.safeString (currencyIds, commonCode, commonCode) + } + + currency (code) { + + if (this.currencies === undefined) { + throw new ExchangeError (this.id + ' currencies not loaded') + } + + if ((typeof code === 'string') && (code in this.currencies)) { + return this.currencies[code] + } + + throw new ExchangeError (this.id + ' does not have currency code ' + code) + } + + market (symbol) { + + if (this.markets === undefined) { + throw new ExchangeError (this.id + ' markets not loaded') + } + + if ((typeof symbol === 'string') && (symbol in this.markets)) { + return this.markets[symbol] + } + + throw new BadSymbol (this.id + ' does not have market symbol ' + symbol) + } + + marketId (symbol) { + const market = this.market (symbol) + return (market !== undefined ? market['id'] : symbol) + } + + marketIds (symbols) { + return symbols.map ((symbol) => this.marketId (symbol)) + } + + symbol (symbol) { + return this.market (symbol).symbol || symbol + } + + url (path, params = {}) { + let result = this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)) + if (Object.keys (query).length) { + result += '?' + this.urlencode (query) + } + return result + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + const price = parseFloat (bidask[priceKey]) + const amount = parseFloat (bidask[amountKey]) + return [ price, amount ] + } + + parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) { + return Object.values (bidasks || []).map ((bidask) => this.parseBidAsk (bidask, priceKey, amountKey)) + } + + async fetchL2OrderBook (symbol, limit = undefined, params = {}) { + const orderbook = await this.fetchOrderBook (symbol, limit, params) + return extend (orderbook, { + 'bids': sortBy (aggregate (orderbook.bids), 0, true), + 'asks': sortBy (aggregate (orderbook.asks), 0), + }) + } + + parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1) { + return { + 'bids': sortBy ((bidsKey in orderbook) ? this.parseBidsAsks (orderbook[bidsKey], priceKey, amountKey) : [], 0, true), + 'asks': sortBy ((asksKey in orderbook) ? this.parseBidsAsks (orderbook[asksKey], priceKey, amountKey) : [], 0), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + } + } + + parseBalance (balance) { + + const codes = Object.keys (this.omit (balance, [ 'info', 'free', 'used', 'total' ])); + + balance['free'] = {} + balance['used'] = {} + balance['total'] = {} + + for (let i = 0; i < codes.length; i++) { + const code = codes[i] + if (balance[code].total === undefined) { + if (balance[code].free !== undefined && balance[code].used !== undefined) { + balance[code].total = this.sum (balance[code].free, balance[code].used) + } + } + if (balance[code].free === undefined) { + if (balance[code].total !== undefined && balance[code].used !== undefined) { + balance[code].free = this.sum (balance[code].total, -balance[code].used) + } + } + if (balance[code].used === undefined) { + if (balance[code].total !== undefined && balance[code].free !== undefined) { + balance[code].used = this.sum (balance[code].total, -balance[code].free) + } + } + + balance.free[code] = balance[code].free + balance.used[code] = balance[code].used + balance.total[code] = balance[code].total + } + + return balance + } + + async fetchPartialBalance (part, params = {}) { + const balance = await this.fetchBalance (params) + return balance[part] + } + + fetchFreeBalance (params = {}) { + return this.fetchPartialBalance ('free', params) + } + + fetchUsedBalance (params = {}) { + return this.fetchPartialBalance ('used', params) + } + + fetchTotalBalance (params = {}) { + return this.fetchPartialBalance ('total', params) + } + + async fetchStatus (params = {}) { + if (this.has['fetchTime']) { + const time = await this.fetchTime (params) + this.status = this.extend (this.status, { + 'updated': time, + }) + } + return this.status + } + + async fetchTradingFees (params = {}) { + throw new NotSupported (this.id + ' fetchTradingFees not supported yet') + } + + async fetchTradingFee (symbol, params = {}) { + if (!this.has['fetchTradingFees']) { + throw new NotSupported (this.id + ' fetchTradingFee not supported yet') + } + return await this.fetchTradingFees (params) + } + + async loadTradingLimits (symbols = undefined, reload = false, params = {}) { + if (this.has['fetchTradingLimits']) { + if (reload || !('limitsLoaded' in this.options)) { + const response = await this.fetchTradingLimits (symbols); + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + this.markets[symbol] = this.deepExtend (this.markets[symbol], response[symbol]); + } + this.options['limitsLoaded'] = this.milliseconds (); + } + } + return this.markets; + } + + filterBySinceLimit (array, since = undefined, limit = undefined, key = 'timestamp', tail = false) { + const sinceIsDefined = (since !== undefined && since !== null) + if (sinceIsDefined) { + array = array.filter ((entry) => entry[key] >= since) + } + if (limit !== undefined && limit !== null) { + array = (tail && !sinceIsDefined) ? array.slice (-limit) : array.slice (0, limit) + } + return array + } + + filterByValueSinceLimit (array, field, value = undefined, since = undefined, limit = undefined, key = 'timestamp', tail = false) { + + const valueIsDefined = value !== undefined && value !== null + const sinceIsDefined = since !== undefined && since !== null + + // single-pass filter for both symbol and since + if (valueIsDefined || sinceIsDefined) { + array = array.filter ((entry) => + ((valueIsDefined ? (entry[field] === value) : true) && + (sinceIsDefined ? (entry[key] >= since) : true))) + } + + if (limit !== undefined && limit !== null) { + array = (tail && !sinceIsDefined) ? array.slice (-limit) : array.slice (0, limit) + } + + return array + } + + filterBySymbolSinceLimit (array, symbol = undefined, since = undefined, limit = undefined) { + return this.filterByValueSinceLimit (array, 'symbol', symbol, since, limit) + } + + filterByCurrencySinceLimit (array, code = undefined, since = undefined, limit = undefined) { + return this.filterByValueSinceLimit (array, 'currency', code, since, limit) + } + + filterByArray (objects, key, values = undefined, indexed = true) { + + objects = Object.values (objects) + + // return all of them if no values were passed + if (values === undefined || values === null) { + return indexed ? indexBy (objects, key) : objects + } + + const result = [] + for (let i = 0; i < objects.length; i++) { + if (values.includes (objects[i][key])) { + result.push (objects[i]) + } + } + + return indexed ? indexBy (result, key) : result + } + + parseTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { + // this code is commented out temporarily to catch for exchange-specific errors + // if (!this.isArray (trades)) { + // throw new ExchangeError (this.id + ' parseTrades expected an array in the trades argument, but got ' + typeof trades); + // } + let result = Object.values (trades || []).map ((trade) => this.extend (this.parseTrade (trade, market), params)) + result = sortBy (result, 'timestamp') + const symbol = (market !== undefined) ? market['symbol'] : undefined + return this.filterBySymbolSinceLimit (result, symbol, since, limit) + } + + parseTransactions (transactions, currency = undefined, since = undefined, limit = undefined, params = {}) { + // this code is commented out temporarily to catch for exchange-specific errors + // if (!this.isArray (transactions)) { + // throw new ExchangeError (this.id + ' parseTransactions expected an array in the transactions argument, but got ' + typeof transactions); + // } + let result = Object.values (transactions || []).map ((transaction) => this.extend (this.parseTransaction (transaction, currency), params)) + result = this.sortBy (result, 'timestamp'); + const code = (currency !== undefined) ? currency['code'] : undefined; + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + parseLedger (data, currency = undefined, since = undefined, limit = undefined, params = {}) { + let result = []; + const array = Object.values (data || []); + for (let i = 0; i < array.length; i++) { + const itemOrItems = this.parseLedgerEntry (array[i], currency); + if (Array.isArray (itemOrItems)) { + for (let j = 0; j < itemOrItems.length; j++) { + result.push (this.extend (itemOrItems[j], params)); + } + } else { + result.push (this.extend (itemOrItems, params)); + } + } + result = this.sortBy (result, 'timestamp'); + const code = (currency !== undefined) ? currency['code'] : undefined; + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { + // + // the value of orders is either a dict or a list + // + // dict + // + // { + // 'id1': { ... }, + // 'id2': { ... }, + // 'id3': { ... }, + // ... + // } + // + // list + // + // [ + // { 'id': 'id1', ... }, + // { 'id': 'id2', ... }, + // { 'id': 'id3', ... }, + // ... + // ] + // + let result = Array.isArray (orders) ? + Object.values (orders).map ((order) => this.extend (this.parseOrder (order, market), params)) : + Object.entries (orders).map (([ id, order ]) => this.extend (this.parseOrder (this.extend ({ 'id': id }, order), market), params)) + result = sortBy (result, 'timestamp') + const symbol = (market !== undefined) ? market['symbol'] : undefined + return this.filterBySymbolSinceLimit (result, symbol, since, limit) + } + + safeCurrency (currencyId, currency = undefined) { + if ((currencyId === undefined) && (currency !== undefined)) { + return currency + } + if ((this.currencies_by_id !== undefined) && (currencyId in this.currencies_by_id)) { + return this.currencies_by_id[currencyId] + } + return { + 'id': currencyId, + 'code': (currencyId === undefined) ? currencyId : this.commonCurrencyCode (currencyId.toUpperCase ()), + } + } + + safeCurrencyCode (currencyId, currency = undefined) { + currency = this.safeCurrency (currencyId, currency) + return currency['code'] + } + + safeMarket (marketId, market = undefined, delimiter = undefined) { + if (marketId !== undefined) { + if (this.markets_by_id !== undefined && marketId in this.markets_by_id) { + market = this.markets_by_id[marketId] + } else if (delimiter !== undefined) { + const [ baseId, quoteId ] = marketId.split (delimiter) + const base = this.safeCurrencyCode (baseId) + const quote = this.safeCurrencyCode (quoteId) + const symbol = base + '/' + quote + return { + 'id': marketId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + } + } + } + if (market !== undefined) { + return market + } + return { + 'id': marketId, + 'symbol': marketId, + 'base': undefined, + 'quote': undefined, + 'baseId': undefined, + 'quoteId': undefined, + } + } + + safeSymbol (marketId, market = undefined, delimiter = undefined) { + market = this.safeMarket (marketId, market, delimiter) + return market['symbol']; + } + + filterBySymbol (array, symbol = undefined) { + return ((symbol !== undefined) ? array.filter ((entry) => entry.symbol === symbol) : array) + } + + parseOHLCV (ohlcv, market = undefined) { + return Array.isArray (ohlcv) ? ohlcv.slice (0, 6) : ohlcv + } + + parseOHLCVs (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { + // this code is commented out temporarily to catch for exchange-specific errors + // if (!this.isArray (ohlcvs)) { + // throw new ExchangeError (this.id + ' parseOHLCVs expected an array in the ohlcvs argument, but got ' + typeof ohlcvs); + // } + ohlcvs = Object.values (ohlcvs || []) + const result = [] + for (let i = 0; i < ohlcvs.length; i++) { + if (limit && (result.length >= limit)) { + break; + } + const ohlcv = this.parseOHLCV (ohlcvs[i], market) + if (since && (ohlcv[0] < since)) { + continue + } + result.push (ohlcv) + } + return this.sortBy (result, 0) + } + + editLimitBuyOrder (id, symbol, ...args) { + return this.editLimitOrder (id, symbol, 'buy', ...args) + } + + editLimitSellOrder (id, symbol, ...args) { + return this.editLimitOrder (id, symbol, 'sell', ...args) + } + + editLimitOrder (id, symbol, ...args) { + return this.editOrder (id, symbol, 'limit', ...args) + } + + async editOrder (id, symbol, ...args) { + if (!this.enableRateLimit) { + throw new ExchangeError (this.id + ' editOrder() requires enableRateLimit = true') + } + await this.cancelOrder (id, symbol); + return this.createOrder (symbol, ...args) + } + + createLimitOrder (symbol, ...args) { + return this.createOrder (symbol, 'limit', ...args) + } + + createMarketOrder (symbol, ...args) { + return this.createOrder (symbol, 'market', ...args) + } + + createLimitBuyOrder (symbol, ...args) { + return this.createOrder (symbol, 'limit', 'buy', ...args) + } + + createLimitSellOrder (symbol, ...args) { + return this.createOrder (symbol, 'limit', 'sell', ...args) + } + + createMarketBuyOrder (symbol, amount, params = {}) { + return this.createOrder (symbol, 'market', 'buy', amount, undefined, params) + } + + createMarketSellOrder (symbol, amount, params = {}) { + return this.createOrder (symbol, 'market', 'sell', amount, undefined, params) + } + + costToPrecision (symbol, cost) { + return decimalToPrecision (cost, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode) + } + + priceToPrecision (symbol, price) { + return decimalToPrecision (price, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode) + } + + amountToPrecision (symbol, amount) { + return decimalToPrecision (amount, TRUNCATE, this.markets[symbol].precision.amount, this.precisionMode, this.paddingMode) + } + + feeToPrecision (symbol, fee) { + return decimalToPrecision (fee, ROUND, this.markets[symbol].precision.price, this.precisionMode, this.paddingMode) + } + + currencyToPrecision (currency, fee) { + return decimalToPrecision (fee, ROUND, this.currencies[currency]['precision'], this.precisionMode, this.paddingMode); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol] + const rate = market[takerOrMaker] + const cost = parseFloat (this.costToPrecision (symbol, amount * price)) + return { + 'type': takerOrMaker, + 'currency': market['quote'], + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, rate * cost)), + } + } + + checkRequiredDependencies () { + return + } + + soliditySha3 (array) { + // we only support address, uint256, and string solidity types + const encoded = [] + for (let i = 0; i < array.length; i++) { + const value = array[i] + if (Number.isInteger (value) || value.match (/^[0-9]+$/)) { + encoded.push (this.numberToBE (this.numberToString (value), 32)) + } else { + const noPrefix = this.remove0xPrefix (value) + if (noPrefix.length === 40 && noPrefix.toLowerCase ().match (/^[0-9a-f]+$/)) { // check if it is an address + encoded.push (this.base16ToBinary (noPrefix)) + } else { + encoded.push (this.stringToBinary (noPrefix)) + } + } + } + const concated = this.binaryConcatArray (encoded) + return '0x' + this.hash (concated, 'keccak', 'hex') + } + + remove0xPrefix (hexData) { + if (hexData.slice (0, 2) === '0x') { + return hexData.slice (2) + } else { + return hexData + } + } + + hashMessage (message) { + // takes a hex encoded message + const binaryMessage = this.base16ToBinary (this.remove0xPrefix (message)) + const prefix = this.stringToBinary ('\x19Ethereum Signed Message:\n' + binaryMessage.sigBytes) + return '0x' + this.hash (this.binaryConcat (prefix, binaryMessage), 'keccak', 'hex') + } + + signHash (hash, privateKey) { + const signature = this.ecdsa (hash.slice (-64), privateKey.slice (-64), 'secp256k1', undefined) + return { + 'r': '0x' + signature['r'], + 's': '0x' + signature['s'], + 'v': 27 + signature['v'], + } + } + + signMessage (message, privateKey) { + return this.signHash (this.hashMessage (message), privateKey.slice (-64)) + } + + signMessageString (message, privateKey) { + // still takes the input as a hex string + // same as above but returns a string instead of an object + const signature = this.signMessage (message, privateKey) + return signature['r'] + this.remove0xPrefix (signature['s']) + this.binaryToBase16 (this.numberToBE (signature['v'])); + } + + oath () { + if (typeof this.twofa !== 'undefined') { + return this.totp (this.twofa) + } else { + throw new ExchangeError (this.id + ' this.twofa has not been set') + } + } + + // the following functions take and return numbers represented as strings + // this is useful for arbitrary precision maths that floats lack + integerDivide (a, b) { + return new BN (a).div (new BN (b)) + } + + integerModulo (a, b) { + return new BN (a).mod (new BN (b)) + } + + integerPow (a, b) { + return new BN (a).pow (new BN (b)) + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/errorHierarchy.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/errorHierarchy.js new file mode 100644 index 000000000..5f30b1796 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/errorHierarchy.js @@ -0,0 +1,44 @@ +'use strict'; + +const errorHierarchy = { + 'BaseError': { + 'ExchangeError': { + 'AuthenticationError': { + 'PermissionDenied': {}, + 'AccountSuspended': {}, + }, + 'ArgumentsRequired': {}, + 'BadRequest': { + 'BadSymbol': {}, + }, + 'BadResponse': { + 'NullResponse': {}, + }, + 'InsufficientFunds': {}, + 'InvalidAddress': { + 'AddressPending': {}, + }, + 'InvalidOrder': { + 'OrderNotFound': {}, + 'OrderNotCached': {}, + 'CancelPending': {}, + 'OrderImmediatelyFillable': {}, + 'OrderNotFillable': {}, + 'DuplicateOrderId': {}, + }, + 'NotSupported': {}, + }, + 'NetworkError': { + 'DDoSProtection': { + 'RateLimitExceeded': {}, + }, + 'ExchangeNotAvailable': { + 'OnMaintenance': {}, + }, + 'InvalidNonce': {}, + 'RequestTimeout': {}, + }, + }, +}; + +module.exports = errorHierarchy; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/errors.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/errors.js new file mode 100644 index 000000000..25a28580a --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/errors.js @@ -0,0 +1,51 @@ +'use strict'; + +const errorHierarchy = require ('./errorHierarchy.js') + +/* ------------------------------------------------------------------------ */ + +function subclass (BaseClass, classes, namespace = {}) { + + for (const [className, subclasses] of Object.entries (classes)) { + + const Class = Object.assign (namespace, { + + /* By creating a named property, we trick compiler to assign our class constructor function a name. + Otherwise, all our error constructors would be shown as [Function: Error] in the debugger! And + the super-useful `e.constructor.name` magic wouldn't work — we then would have no chance to + obtain a error type string from an error instance programmatically! */ + + [className]: class extends BaseClass { + + constructor (message) { + + super (message) + + /* A workaround to make `instanceof` work on custom Error classes in transpiled ES5. + See my blog post for the explanation of this hack: + + https://medium.com/@xpl/javascript-deriving-from-error-properly-8d2f8f315801 */ + + this.constructor = Class + this.__proto__ = Class.prototype + this.name = className + this.message = message + } + } + + })[className] + + subclass (Class, subclasses, namespace) + } + + return namespace +} + +/* ------------------------------------------------------------------------ */ + +module.exports = subclass ( + // Root class + Error, + // Derived class hierarchy + errorHierarchy +) diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions.js new file mode 100644 index 000000000..d03d5feb2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions.js @@ -0,0 +1,28 @@ +'use strict'; + +/* ------------------------------------------------------------------------ */ + +const { unCamelCase } = require ('./functions/string') + +const unCamelCasePropertyNames = x => { + for (const k in x) x[unCamelCase (k)] = x[k] // camel_case_method = camelCaseMethod + return x +} + +/* ------------------------------------------------------------------------ */ + +module.exports = unCamelCasePropertyNames (Object.assign ({} + + , require ('./functions/platform') + , require ('./functions/generic') + , require ('./functions/string') + , require ('./functions/type') + , require ('./functions/number') + , require ('./functions/encode') + , require ('./functions/crypto') + , require ('./functions/time') + , require ('./functions/throttle') + , require ('./functions/misc') +)) + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/crypto.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/crypto.js new file mode 100644 index 000000000..3cdd9a932 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/crypto.js @@ -0,0 +1,158 @@ +'use strict' + +/* ------------------------------------------------------------------------ */ + +const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js') +const { capitalize } = require ('./string') +const { stringToBase64, urlencodeBase64 } = require ('./encode') +const NodeRSA = require ('./../../static_dependencies/node-rsa/NodeRSA') +const { binaryToBase58, byteArrayToWordArray } = require ('./encode') +const elliptic = require ('./../../static_dependencies/elliptic/lib/elliptic') +const EC = elliptic.ec +const EDDSA = elliptic.eddsa +const { ArgumentsRequired } = require ('./../errors') +const BN = require ('../../static_dependencies/BN/bn.js') + +/* ------------------------------------------------------------------------ */ + +const hash = (request, hash = 'md5', digest = 'hex') => { + const options = {} + if (hash === 'keccak') { + hash = 'SHA3' + options['outputLength'] = 256 + } + const result = CryptoJS[hash.toUpperCase ()] (request, options) + return (digest === 'binary') ? result : result.toString (CryptoJS.enc[capitalize (digest)]) +} + +/* ............................................. */ + +const hmac = (request, secret, hash = 'sha256', digest = 'hex') => { + const result = CryptoJS['Hmac' + hash.toUpperCase ()] (request, secret) + if (digest) { + const encoding = (digest === 'binary') ? 'Latin1' : capitalize (digest) + return result.toString (CryptoJS.enc[capitalize (encoding)]) + } + return result +} + +/* ............................................. */ + +function rsa (request, secret, alg = 'RS256') { + const algos = { + 'RS256': 'pkcs1-sha256', + 'RS512': 'pkcs1-sha512', + } + if (!(alg in algos)) { + throw new ExchangeError (alg + ' is not a supported rsa signing algorithm.') + } + const algorithm = algos[alg] + let key = new NodeRSA (secret, { + 'environment': 'browser', + 'signingScheme': algorithm, + }) + return key.sign (request, 'base64', 'binary') +} + + +/** + * @return {string} + */ +function jwt (request, secret, alg = 'HS256') { + const algos = { + 'HS256': 'sha256', + 'HS384': 'sha384', + 'HS512': 'sha512', + }; + const encodedHeader = urlencodeBase64 (stringToBase64 (JSON.stringify ({ 'alg': alg, 'typ': 'JWT' }))) + const encodedData = urlencodeBase64 (stringToBase64 (JSON.stringify (request))) + const token = [ encodedHeader, encodedData ].join ('.') + const algoType = alg.slice (0, 2); + const algorithm = algos[alg] + let signature = undefined + if (algoType === 'HS') { + signature = urlencodeBase64 (hmac (token, secret, algorithm, 'base64')) + } else if (algoType === 'RS') { + signature = urlencodeBase64 (rsa (token, secret, alg)) + } + return [ token, signature ].join ('.') +} + +function ecdsa (request, secret, algorithm = 'p256', hashFunction = undefined, fixedLength = false) { + let digest = request + if (hashFunction !== undefined) { + digest = hash (request, hashFunction, 'hex') + } + const curve = new EC (algorithm) + let signature = curve.sign (digest, secret, 'hex', { 'canonical': true }) + let counter = new BN ('0') + const minimum_size = new BN ('1').shln (8 * 31).sub (new BN ('1')) + while (fixedLength && (signature.r.gt (curve.nh) || signature.r.lte (minimum_size) || signature.s.lte (minimum_size))) { + signature = curve.sign (digest, secret, 'hex', { 'canonical': true, 'extraEntropy': counter.toArray ('le', 32)}) + counter = counter.add (new BN ('1')) + } + return { + 'r': signature.r.toString (16).padStart (64, '0'), + 's': signature.s.toString (16).padStart (64, '0'), + 'v': signature.recoveryParam, + } +} + + +function eddsa (request, secret, algorithm = 'ed25519') { + // used for waves.exchange (that's why the output is base58) + const curve = new EDDSA (algorithm) + const signature = curve.signModified (request, secret) + return binaryToBase58 (byteArrayToWordArray (signature.toBytes ())) +} + +/* ------------------------------------------------------------------------ */ + +const totp = (secret) => { + + const dec2hex = s => ((s < 15.5 ? '0' : '') + Math.round (s).toString (16)) + , hex2dec = s => parseInt (s, 16) + , leftpad = (s, p) => (p + s).slice (-p.length) // both s and p are short strings + + const base32tohex = (base32) => { + let base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' + let bits = '' + let hex = '' + for (let i = 0; i < base32.length; i++) { + let val = base32chars.indexOf (base32.charAt (i).toUpperCase ()) + bits += leftpad (val.toString (2), '00000') + } + for (let i = 0; i + 4 <= bits.length; i += 4) { + let chunk = bits.substr (i, 4) + hex = hex + parseInt (chunk, 2).toString (16) + } + return hex + } + + const getOTP = (secret) => { + secret = secret.replace (' ', '') // support 2fa-secrets with spaces like "4TDV WOGO" → "4TDVWOGO" + let epoch = Math.round (new Date ().getTime () / 1000.0) + let time = leftpad (dec2hex (Math.floor (epoch / 30)), '0000000000000000') + let hmacRes = hmac (CryptoJS.enc.Hex.parse (time), CryptoJS.enc.Hex.parse (base32tohex (secret)), 'sha1', 'hex') + let offset = hex2dec (hmacRes.substring (hmacRes.length - 1)) + let otp = (hex2dec (hmacRes.substr (offset * 2, 8)) & hex2dec ('7fffffff')) + '' + otp = (otp).substr (otp.length - 6, 6) + return otp + } + + return getOTP (secret) +} + +/* ------------------------------------------------------------------------ */ + +module.exports = { + hash, + hmac, + jwt, + totp, + rsa, + ecdsa, + eddsa, +} + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/encode.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/encode.js new file mode 100644 index 000000000..0b4331ccf --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/encode.js @@ -0,0 +1,118 @@ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const CryptoJS = require ('../../static_dependencies/crypto-js/crypto-js') +const qs = require ('../../static_dependencies/qs/index') +const BN = require ('../../static_dependencies/BN/bn') + + +/* ------------------------------------------------------------------------ */ + +// global vars for base58 encoding +const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +let base58Decoder = null +let base58Encoder = null + +/* ------------------------------------------------------------------------ */ + +module.exports = + + { json: (data, params = undefined) => JSON.stringify (data) + , unjson: JSON.parse + + , isJsonEncodedObject: object => ( + (typeof object === 'string') && + (object.length >= 2) && + ((object[0] === '{') || (object[0] === '[')) + ) + + , stringToBinary: string => CryptoJS.enc.Latin1.parse (string) + , stringToBase64: string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64) + , base64ToBinary: string => CryptoJS.enc.Base64.parse (string) + , base64ToString: string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8) + , binaryToBase64: binary => binary.toString (CryptoJS.enc.Base64) + , base16ToBinary: string => CryptoJS.enc.Hex.parse (string) + , binaryToBase16: binary => binary.toString (CryptoJS.enc.Hex) + , binaryConcat: (...args) => args.reduce ((a, b) => a.concat (b)) + , binaryConcatArray: (arr) => arr.reduce ((a, b) => a.concat (b)) + + , urlencode: object => qs.stringify (object) + , urlencodeWithArrayRepeat: object => qs.stringify (object, { arrayFormat: 'repeat' }) + , rawencode: object => qs.stringify (object, { encode: false }) + , encode: x => x + , decode: x => x + + // Url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores + + , urlencodeBase64: base64string => base64string.replace (/[=]+$/, '') + .replace (/\+/g, '-') + .replace (/\//g, '_') + + , numberToLE: (n, padding) => { + const hexArray = new BN (n).toArray ('le', padding) + return byteArrayToWordArray (hexArray) + } + + , numberToBE: (n, padding) => { + const hexArray = new BN (n).toArray ('be', padding) + return byteArrayToWordArray (hexArray) + } + + , base58ToBinary: (string) => { + if (!base58Decoder) { + base58Decoder = {} + base58Encoder = {} + for (let i = 0; i < 58; i++) { + const c = base58Alphabet[i] + const bigNum = new BN (i) + base58Decoder[c] = bigNum + base58Encoder[bigNum] = c + } + } + let result = new BN (0) + const base = new BN (58) + for (let i = 0; i < string.length; i++) { + const character = string[i] + result.imul (base) + result.iadd (base58Decoder[character]) + } + return byteArrayToWordArray (result.toArray ('be')) + } + + , binaryToBase58: (wordArray) => { + if (!base58Encoder) { + base58Decoder = {} + base58Encoder = {} + for (let i = 0; i < 58; i++) { + const c = base58Alphabet[i] + const bigNum = new BN (i) + base58Decoder[c] = bigNum + base58Encoder[bigNum] = c + } + } + const base = new BN (58) + // hex is only compatible encoding between cryptojs and BN + const hexString = wordArray.toString (CryptoJS.enc.Hex) + let result = new BN (hexString, 16) + let string = [] + while (!result.isZero ()) { + const { div, mod } = result.divmod (base) + result = div + string.push (base58Encoder[mod]) + } + return string.reverse ().join ('') + } +} + +function byteArrayToWordArray (ba) { + const wa = [] + for (let i = 0; i < ba.length; i++) { + wa[(i / 4) | 0] |= ba[i] << (24 - 8 * i) + } + return CryptoJS.lib.WordArray.create (wa, ba.length) +} + +module.exports['byteArrayToWordArray'] = byteArrayToWordArray + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/generic.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/generic.js new file mode 100644 index 000000000..be2e7e9ba --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/generic.js @@ -0,0 +1,236 @@ +"use strict"; + +// ---------------------------------------------------------------------------- + +const { isObject, isNumber, isDictionary, isArray } = require ('./type') + +// ---------------------------------------------------------------------------- + +const keys = Object.keys + , values = (x) => ((!isArray (x)) ? Object.values (x) : x) // don't copy arrays if they're already arrays + , index = (x) => new Set (values (x)) + , extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free + , clone = (x) => (isArray (x) ? Array.from (x) : extend (x)) // clone arrays or objects + +// ---------------------------------------------------------------------------- + +module.exports = { + keys + , values + , extend + , clone + , index + , ordered: (x) => x // a stub to keep assoc keys in order (in JS it does nothing, it's mostly for Python) + , unique: (x) => Array.from (index (x)) + , arrayConcat: (a, b) => a.concat (b) + + // ------------------------------------------------------------------------ + + , inArray (needle, haystack) { + + return haystack.includes (needle) + } + + , toArray (object) { + + return Object.values (object) + } + + , isEmpty (object) { + if (!object) { + return true + } + return (Array.isArray (object) ? object : Object.keys (object)).length < 1; + } + + // ------------------------------------------------------------------------ + + , keysort (x, out = {}) { + + for (const k of keys (x).sort ()) + out[k] = x[k] + + return out + } + + // ------------------------------------------------------------------------ + + /* + Accepts a map/array of objects and a key name to be used as an index: + array = [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value2', anotherKey: 'anotherValue2' }, + { someKey: 'value3', anotherKey: 'anotherValue3' }, + ] + key = 'someKey' + + Returns a map: + { + value1: { someKey: 'value1', anotherKey: 'anotherValue1' }, + value2: { someKey: 'value2', anotherKey: 'anotherValue2' }, + value3: { someKey: 'value3', anotherKey: 'anotherValue3' }, + } + */ + + , indexBy (x, k, out = {}) { + + for (const v of values (x)) { + if (k in v) { + out[v[k]] = v + } + } + + return out + } + + // ------------------------------------------------------------------------ + + /* + Accepts a map/array of objects and a key name to be used as a grouping parameter: + array = [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value1', anotherKey: 'anotherValue2' }, + { someKey: 'value3', anotherKey: 'anotherValue3' }, + ] + key = 'someKey' + + Returns a map: + { + value1: [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value1', anotherKey: 'anotherValue2' }, + ] + value3: [ + { someKey: 'value3', anotherKey: 'anotherValue3' } + ], + } + */ + + , groupBy (x, k, out = {}) { + + for (const v of values (x)) { + if (k in v) { + const p = v[k] + out[p] = out[p] || [] + out[p].push (v) + } + } + return out + } + + // ------------------------------------------------------------------------ + + /* + Accepts a map/array of objects, a key name and a key value to be used as a filter: + array = [ + { someKey: 'value1', anotherKey: 'anotherValue1' }, + { someKey: 'value2', anotherKey: 'anotherValue2' }, + { someKey: 'value3', anotherKey: 'anotherValue3' }, + ] + key = 'someKey' + value = 'value1' + + Returns an array: + [ + value1: { someKey: 'value1', anotherKey: 'anotherValue1' }, + ] + */ + + , filterBy (x, k, value = undefined, out = []) { + + for (const v of values (x)) { + if (v[k] === value) { + out.push (v) + } + } + + return out + } + + // ------------------------------------------------------------------------ + // NB: MUTATES ARRAY! + + , sortBy: (array, key, descending = false, direction = descending ? -1 : 1) => array.sort ((a, b) => { + if (a[key] < b[key]) { + return -direction + } else if (a[key] > b[key]) { + return direction + } else { + return 0 + } + }) + + // ------------------------------------------------------------------------ + + , flatten: function flatten (x, out = []) { + + for (const v of x) { + if (isArray (v)) { + flatten (v, out) + } else { + out.push (v) + } + } + + return out + } + + // ------------------------------------------------------------------------ + + , pluck: (x, k) => values (x).filter ((v) => k in v).map ((v) => v[k]) + + // ------------------------------------------------------------------------ + + , omit (x, ...args) { + + if (!Array.isArray (x)) { + + const out = clone (x) + + for (const k of args) { + if (isArray (k)) { // omit (x, ['a', 'b']) + for (const kk of k) { + delete out[kk] + } + } else { + delete out[k] // omit (x, 'a', 'b') + } + } + + return out + } + + return x + } + + // ------------------------------------------------------------------------ + + , sum (...xs) { + + const ns = xs.filter (isNumber) // leave only numbers + + return (ns.length > 0) ? ns.reduce ((a, b) => a + b, 0) : undefined + } + + // ------------------------------------------------------------------------ + + , deepExtend: function deepExtend (...xs) { + let out = undefined + for (const x of xs) { + if (isDictionary (x)) { + if (!isDictionary (out)) { + out = {} + } + for (const k in x) { + out[k] = deepExtend (out[k], x[k]) + } + } else { + out = x + } + } + return out + } + +// ---------------------------------------------------------------------------- + +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/misc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/misc.js new file mode 100644 index 000000000..7c12a1191 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/misc.js @@ -0,0 +1,138 @@ +'use strict'; + +const { ROUND_UP, ROUND_DOWN } = require ('./number') +const { NotSupported } = require ('../errors') + +//------------------------------------------------------------------------- +// converts timeframe to seconds +const parseTimeframe = (timeframe) => { + + const amount = timeframe.slice (0, -1) + const unit = timeframe.slice (-1) + let scale = undefined; + + if (unit === 'y') { + scale = 60 * 60 * 24 * 365 + } else if (unit === 'M') { + scale = 60 * 60 * 24 * 30 + } else if (unit === 'w') { + scale = 60 * 60 * 24 * 7 + } else if (unit === 'd') { + scale = 60 * 60 * 24 + } else if (unit === 'h') { + scale = 60 * 60 + } else if (unit === 'm') { + scale = 60 + } else if (unit === 's') { + scale = 1 + } else { + throw new NotSupported ('timeframe unit ' + unit + ' is not supported') + } + + return amount * scale +} + +const roundTimeframe = (timeframe, timestamp, direction = ROUND_DOWN) => { + const ms = parseTimeframe (timeframe) * 1000 + // Get offset based on timeframe in milliseconds + const offset = timestamp % ms + return timestamp - offset + ((direction === ROUND_UP) ? ms : 0); +} + +// given a sorted arrays of trades (recent last) and a timeframe builds an array of OHLCV candles +const buildOHLCVC = (trades, timeframe = '1m', since = -Infinity, limit = Infinity) => { + const ms = parseTimeframe (timeframe) * 1000; + const ohlcvs = []; + const [ timestamp, /* open */, high, low, close, volume, count ] = [ 0, 1, 2, 3, 4, 5, 6 ]; + const oldest = Math.min (trades.length - 1, limit); + + for (let i = 0; i <= oldest; i++) { + const trade = trades[i]; + if (trade.timestamp < since) { + continue; + } + const openingTime = Math.floor (trade.timestamp / ms) * ms; // shift to the edge of m/h/d (but not M) + const candle = ohlcvs.length - 1; + + if (candle === -1 || openingTime >= ohlcvs[candle][timestamp] + ms) { + // moved to a new timeframe -> create a new candle from opening trade + ohlcvs.push ([ + openingTime, // timestamp + trade.price, // O + trade.price, // H + trade.price, // L + trade.price, // C + trade.amount, // V + 1, // count + ]); + } else { + // still processing the same timeframe -> update opening trade + ohlcvs[candle][high] = Math.max (ohlcvs[candle][high], trade.price); + ohlcvs[candle][low] = Math.min (ohlcvs[candle][low], trade.price); + ohlcvs[candle][close] = trade.price; + ohlcvs[candle][volume] += trade.amount; + ohlcvs[candle][count]++; + } // if + } // for + return ohlcvs; +} + +const extractParams = (string) => { + const re = /{([\w-]+)}/g + const matches = [] + let match = re.exec (string) + while (match) { + matches.push (match[1]) + match = re.exec (string) + } + return matches +} + +const implodeParams = (string, params) => { + if (!Array.isArray (params)) { + const keys = Object.keys (params) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (!Array.isArray (params[key])) { + string = string.replace ('{' + key + '}', params[key]) + } + } + } + return string +} + +function vwap (baseVolume, quoteVolume) { + return ((baseVolume !== undefined) && (quoteVolume !== undefined) && (baseVolume > 0)) ? (quoteVolume / baseVolume) : undefined +} + +/* ------------------------------------------------------------------------ */ + +module.exports = { + + aggregate (bidasks) { + + const result = {} + + for (let i = 0; i < bidasks.length; i++) { + const [ price, volume ] = bidasks[i]; + if (volume > 0) { + result[price] = (result[price] || 0) + volume + } + } + + return Object.keys (result).map ((price) => [parseFloat (price), parseFloat (result[price])]) + }, + + parseTimeframe, + roundTimeframe, + buildOHLCVC, + ROUND_UP, + ROUND_DOWN, + + implodeParams, + extractParams, + + vwap, +} + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/number.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/number.js new file mode 100644 index 000000000..cfd6e7db3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/number.js @@ -0,0 +1,339 @@ +'use strict' + +/* ------------------------------------------------------------------------ + + NB: initially, I used objects for options passing: + + decimalToPrecision ('123.456', { digits: 2, round: true, afterPoint: true }) + + ...but it turns out it's hard to port that across different languages and it is also + probably has a performance penalty -- while it's a performance critical code! So + I switched to using named constants instead, as it is actually more readable and + succinct, and surely doesn't come with any inherent performance downside: + + decimalToPrecision ('123.456', ROUND, 2, DECIMAL_PLACES) */ + +const ROUND = 0 // rounding mode + , TRUNCATE = 1 + , ROUND_UP = 2 + , ROUND_DOWN = 3 + +const DECIMAL_PLACES = 0 // digits counting mode + , SIGNIFICANT_DIGITS = 1 + , TICK_SIZE = 2 + +const NO_PADDING = 0 // zero-padding mode + , PAD_WITH_ZERO = 1 + +const precisionConstants = { + ROUND, + TRUNCATE, + ROUND_UP, + ROUND_DOWN, + DECIMAL_PLACES, + SIGNIFICANT_DIGITS, + TICK_SIZE, + NO_PADDING, + PAD_WITH_ZERO, +} + +/* ------------------------------------------------------------------------ */ + +// See https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript for discussion + +function numberToString (x) { // avoids scientific notation for too large and too small numbers + + if (typeof x === 'string') return x + + const s = x.toString () + if (Math.abs (x) < 1.0) { + const n_e = s.split ('e-') + const n = n_e[0].replace ('.', '') + const e = parseInt (n_e[1]) + const neg = (s[0] === '-') + if (e) { + x = (neg ? '-' : '') + '0.' + (new Array (e)).join ('0') + n.substring (neg) + return x + } + } else { + const parts = s.split ('e') + if (parts[1]) { + let e = parseInt (parts[1]) + const m = parts[0].split ('.') + if (m[1]) { + e -= m[1].length + } + return m[0] + m[1] + (new Array (e + 1)).join ('0') + } + } + return s +} + +//----------------------------------------------------------------------------- +// expects non-scientific notation + +const truncate_regExpCache = [] + , truncate_to_string = (num, precision = 0) => { + num = numberToString (num) + if (precision > 0) { + const re = truncate_regExpCache[precision] || (truncate_regExpCache[precision] = new RegExp ("([-]*\\d+\\.\\d{" + precision + "})(\\d)")) + const [ , result] = num.toString ().match (re) || [null, num] + return result.toString () + } + return parseInt (num).toString () + } + , truncate = (num, precision = 0) => parseFloat (truncate_to_string (num, precision)) + +function precisionFromString (string) { + const split = string.replace (/0+$/g, '').split ('.') + return (split.length > 1) ? (split[1].length) : 0 +} + +/* ------------------------------------------------------------------------ */ + +const decimalToPrecision = (x, roundingMode + , numPrecisionDigits + , countingMode = DECIMAL_PLACES + , paddingMode = NO_PADDING) => { + + if (countingMode === TICK_SIZE) { + if (numPrecisionDigits <= 0) { + throw new Error ('TICK_SIZE cant be used with negative or zero numPrecisionDigits') + } + } + + if (numPrecisionDigits < 0) { + const toNearest = Math.pow (10, -numPrecisionDigits) + if (roundingMode === ROUND) { + return (toNearest * decimalToPrecision (x / toNearest, roundingMode, 0, countingMode, paddingMode)).toString () + } + if (roundingMode === TRUNCATE) { + return (x - (x % toNearest)).toString () + } + } + +/* handle tick size */ + if (countingMode === TICK_SIZE) { + const precisionDigitsString = decimalToPrecision (numPrecisionDigits, ROUND, 22, DECIMAL_PLACES, NO_PADDING) + const newNumPrecisionDigits = precisionFromString (precisionDigitsString) + let missing = x % numPrecisionDigits + // See: https://github.com/ccxt/ccxt/pull/6486 + missing = Number (decimalToPrecision (missing, ROUND, 8, DECIMAL_PLACES, NO_PADDING)); + const fpError = decimalToPrecision (missing / numPrecisionDigits, ROUND, Math.max (newNumPrecisionDigits, 8), DECIMAL_PLACES, NO_PADDING) + if (precisionFromString (fpError) !== 0) { + if (roundingMode === ROUND) { + if (x > 0) { + if (missing >= numPrecisionDigits / 2) { + x = x - missing + numPrecisionDigits + } else { + x = x - missing + } + } else { + if (missing >= numPrecisionDigits / 2) { + x = Number (x) - missing + } else { + x = Number (x) - missing - numPrecisionDigits + } + } + } else if (roundingMode === TRUNCATE) { + x = x - missing + } + } + return decimalToPrecision (x, ROUND, newNumPrecisionDigits, DECIMAL_PLACES, paddingMode); + } + +/* Convert to a string (if needed), skip leading minus sign (if any) */ + + const str = numberToString (x) + , isNegative = str[0] === '-' + , strStart = isNegative ? 1 : 0 + , strEnd = str.length + +/* Find the dot position in the source buffer */ + + for (var strDot = 0; strDot < strEnd; strDot++) + if (str[strDot] === '.') + break + + const hasDot = strDot < str.length + +/* Char code constants */ + + const MINUS = 45 + , DOT = 46 + , ZERO = 48 + , ONE = (ZERO + 1) + , FIVE = (ZERO + 5) + , NINE = (ZERO + 9) + +/* For -123.4567 the `chars` array will hold 01234567 (leading zero is reserved for rounding cases when 099 → 100) */ + + const chars = new Uint8Array ((strEnd - strStart) + (hasDot ? 0 : 1)) + chars[0] = ZERO + +/* Validate & copy digits, determine certain locations in the resulting buffer */ + + let afterDot = chars.length + , digitsStart = -1 // significant digits + , digitsEnd = -1 + + for (var i = 1, j = strStart; j < strEnd; j++, i++) { + + const c = str.charCodeAt (j) + + if (c === DOT) { + afterDot = i-- + + } else if ((c < ZERO) || (c > NINE)) { + throw new Error (`${str}: invalid number (contains an illegal character '${str[i - 1]}')`) + + } else { + chars[i] = c + if ((c !== ZERO) && (digitsStart < 0)) digitsStart = i + } + } + + if (digitsStart < 0) digitsStart = 1 + +/* Determine the range to cut */ + + let precisionStart = (countingMode === DECIMAL_PLACES) ? afterDot // 0.(0)001234567 + : digitsStart // 0.00(1)234567 + , precisionEnd = precisionStart + + numPrecisionDigits + +/* Reset the last significant digit index, as it will change during the rounding/truncation. */ + + digitsEnd = -1 + +/* Perform rounding/truncation per digit, from digitsEnd to digitsStart, by using the following + algorithm (rounding 999 → 1000, as an example): + + step = i=3 i=2 i=1 i=0 + + chars = 0999 0999 0900 1000 + memo = ---0 --1- -1-- 0--- */ + + let allZeros = true; + let signNeeded = isNegative; + + for (let i = chars.length - 1, memo = 0; i >= 0; i--) { + + let c = chars[i] + + if (i !== 0) { + c += memo + + if (i >= (precisionStart + numPrecisionDigits)) { + + const ceil = (roundingMode === ROUND) && + (c >= FIVE) && + !((c === FIVE) && memo) // prevents rounding of 1.45 to 2 + + c = ceil ? (NINE + 1) : ZERO + } + if (c > NINE) { c = ZERO; memo = 1; } + else memo = 0 + + } else if (memo) c = ONE // leading extra digit (0900 → 1000) + + chars[i] = c + + if (c !== ZERO) { + allZeros = false + digitsStart = i + digitsEnd = (digitsEnd < 0) ? (i + 1) : digitsEnd + } + } + +/* Update the precision range, as `digitsStart` may have changed... & the need for a negative sign if it is only 0 */ + + if (countingMode === SIGNIFICANT_DIGITS) { + precisionStart = digitsStart + precisionEnd = precisionStart + numPrecisionDigits + } + + if (allZeros) { + signNeeded = false + } + +/* Determine the input character range */ + + const readStart = ((digitsStart >= afterDot) || allZeros) ? (afterDot - 1) : digitsStart // 0.000(1)234 ----> (0).0001234 + , readEnd = (digitsEnd < afterDot) ? (afterDot ) : digitsEnd // 12(3)000 ----> 123000( ) + +/* Compute various sub-ranges */ + + const nSign = (signNeeded ? 1 : 0) // (-)123.456 + , nBeforeDot = (nSign + (afterDot - readStart)) // (-123).456 + , nAfterDot = Math.max (readEnd - afterDot, 0) // -123.(456) + , actualLength = (readEnd - readStart) // -(123.456) + , desiredLength = (paddingMode === NO_PADDING) + ? (actualLength) // -(123.456) + : (precisionEnd - readStart) // -(123.456 ) + + , pad = Math.max (desiredLength - actualLength, 0) // -123.456( ) + , padStart = (nBeforeDot + 1 + nAfterDot) // -123.456( ) + , padEnd = (padStart + pad) // -123.456 ( ) + , isInteger = (nAfterDot + pad) === 0 // -123 + +/* Fill the output buffer with characters */ + + const out = new Uint8Array (nBeforeDot + (isInteger ? 0 : 1) + nAfterDot + pad) + // ------------------------------------------------------------------------------------------ // --------------------- + if (signNeeded) out[0] = MINUS // - minus sign + for (i = nSign, j = readStart; i < nBeforeDot; i++, j++) out[i] = chars[j] // 123 before dot + if (!isInteger) out[nBeforeDot] = DOT // . dot + for (i = nBeforeDot + 1, j = afterDot; i < padStart; i++, j++) out[i] = chars[j] // 456 after dot + for (i = padStart; i < padEnd; i++) out[i] = ZERO // 000 padding + +/* Build a string from the output buffer */ + + return String.fromCharCode (...out) +} + +// toWei / fromWei + +function fromWei (amount, decimals = 18) { + if (amount === undefined) { + return amount + } + const exponential = Math.floor (amount).toExponential () // wei must be whole numbers + const [ n, exponent ] = exponential.split ('e') + const newExponent = parseInt (exponent) - decimals + return parseFloat (n + 'e' + newExponent) +} + +function toWei (amount, decimals = 18) { + if (amount === undefined) { + return amount + } + const exponential = parseFloat (amount).toExponential () + const [ n, exponent ] = exponential.split ('e') + const newExponent = parseInt (exponent) + decimals + return numberToString (Math.floor (parseFloat (n + 'e' + newExponent))) // wei must be whole numbers +} + +/* ------------------------------------------------------------------------ */ + +module.exports = { + toWei, + fromWei, + numberToString, + precisionFromString, + decimalToPrecision, + truncate_to_string, + truncate, + precisionConstants, + ROUND, + TRUNCATE, + ROUND_UP, + ROUND_DOWN, + DECIMAL_PLACES, + SIGNIFICANT_DIGITS, + TICK_SIZE, + NO_PADDING, + PAD_WITH_ZERO, +} + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/platform.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/platform.js new file mode 100644 index 000000000..de3b1874a --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/platform.js @@ -0,0 +1,38 @@ +"use strict"; + +// ---------------------------------------------------------------------------- +// There's been a lot of messing with this code... +// The problem is to satisfy the following requirements: +// - properly detect isNode == true on server side and isNode == false in the browser (on client side) +// - make sure create-react-app, react-starter-kit and other react frameworks work +// - make sure it does not break the browserified version (when linked into a html from a cdn) +// - make sure it does not break the webpacking and babel-transpiled scripts +// - make sure it works in Electron +// - make sure it works with Angular.js +// - make sure it does not break other possible usage scenarios + +const isBrowser = typeof window !== 'undefined' + +const isElectron = typeof process !== 'undefined' && + typeof process.versions !== 'undefined' && + typeof process.versions.electron !== 'undefined' + +const isWebWorker = typeof WorkerGlobalScope !== 'undefined' && (self instanceof WorkerGlobalScope) + +const isWindows = typeof process !== 'undefined' && process.platform === "win32" + +const isNode = !(isBrowser || isWebWorker) + +const defaultFetch = typeof (fetch) === "undefined" ? require ('../../static_dependencies/fetch-ponyfill/fetch-node') ().fetch : fetch + +// ---------------------------------------------------------------------------- + +module.exports = { + + isBrowser, + isElectron, + isWebWorker, + isNode, + isWindows, + defaultFetch, +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/string.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/string.js new file mode 100644 index 000000000..cabc63b48 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/string.js @@ -0,0 +1,21 @@ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const uuid = a => a ? (a ^ Math.random () * 16 >> a / 4).toString (16) + : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace (/[018]/g, uuid) + +module.exports = + + { uuid + + // hasFetchOHLCV → has_fetch_ohlcv; parseHTTPResponse → parse_http_response + , unCamelCase: s => s.match (/^[A-Z0-9_]+$/) ? s : (s.replace (/[a-z0-9][A-Z]/g, x => x[0] + '_' + x[1]).replace(/[A-Z0-9][A-Z0-9][a-z]/g, x => x[0] + '_' + x[1] + x[2]).toLowerCase ()) + + , capitalize: s => s.length + ? (s.charAt (0).toUpperCase () + s.slice (1)) + : s + , strip: s => s.replace(/^\s+|\s+$/g, '') + } + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/throttle.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/throttle.js new file mode 100644 index 000000000..e117291ff --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/throttle.js @@ -0,0 +1,64 @@ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const { sleep + , now } = require ('./time') + +/* ------------------------------------------------------------------------ */ + +module.exports = { + + throttle: function throttle (cfg) { + + let lastTimestamp = now () + , numTokens = (cfg.numTokens !== undefined) ? cfg.numTokens : cfg.capacity + , running = false + , counter = 0 + + const queue = [] + + return Object.assign ((rateLimit, cost) => { + + if (queue.length > cfg.maxCapacity) + throw new Error ('Backlog is over max capacity of ' + cfg.maxCapacity) + + return new Promise (async (resolve, reject) => { + + try { + queue.push ({ cost, resolve, reject }) + + if (!running) { + running = true + while (queue.length > 0) { + const hasEnoughTokens = cfg.capacity ? (numTokens > 0) : (numTokens >= 0) + if (hasEnoughTokens) { + if (queue.length > 0) { + let { cost, resolve, reject } = queue[0] + cost = (cost || cfg.defaultCost) + if (numTokens >= Math.min (cost, cfg.capacity)) { + numTokens -= cost + queue.shift () + resolve () + } + } + } + const t = now () + , elapsed = t - lastTimestamp + lastTimestamp = t + numTokens = Math.min (cfg.capacity, numTokens + elapsed * (1 / rateLimit)) + await sleep (cfg.delay) + } + running = false + } + + } catch (e) { + reject (e) + } + }) + + }, cfg, { configure: newCfg => throttle (Object.assign ({}, cfg, newCfg)) }) + } +} + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/time.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/time.js new file mode 100644 index 000000000..a27c2ff25 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/time.js @@ -0,0 +1,202 @@ +'use strict'; + +/* ------------------------------------------------------------------------ */ + +const now = Date.now // TODO: figure out how to utilize performance.now () properly – it's not as easy as it does not return a unix timestamp... +const microseconds = () => now () * 1000 // TODO: utilize performance.now for that purpose +const milliseconds = now +const seconds = () => Math.floor (now () / 1000) +const uuidv1 = () => { + const biasSeconds = 12219292800 // seconds from 15th Oct 1572 to Jan 1st 1970 + const bias = biasSeconds * 10000000 // in hundreds of nanoseconds + const time = microseconds () * 10 + bias + const timeHex = time.toString (16) + const arranged = timeHex.slice (7, 15) + timeHex.slice (3, 7) + '1' + timeHex.slice (0, 3) + // these should be random, but we're not making more than 10 requests per microsecond so who cares + const clockId = '9696' // a 14 bit number + const macAddress = 'ff'.repeat (6) + return arranged + clockId + macAddress +} + +/* ------------------------------------------------------------------------ */ + +const setTimeout_original = setTimeout +const setTimeout_safe = (done, ms, setTimeout = setTimeout_original /* overrideable for mocking purposes */, targetTime = now () + ms) => { + +/* The built-in setTimeout function can fire its callback earlier than specified, so we + need to ensure that it does not happen: sleep recursively until `targetTime` is reached... */ + + let clearInnerTimeout = () => {} + let active = true + + const id = setTimeout (() => { + active = true + const rest = targetTime - now () + if (rest > 0) { + clearInnerTimeout = setTimeout_safe (done, rest, setTimeout, targetTime) // try sleep more + } else { + done () + } + }, ms) + + return function clear () { + if (active) { + active = false // dunno if IDs are unique on various platforms, so it's better to rely on this flag to exclude the possible cancellation of the wrong timer (if called after completion) + clearTimeout (id) + } + clearInnerTimeout () + } +} + +/* ------------------------------------------------------------------------ */ + +class TimedOut extends Error { + + constructor () { + const message = 'timed out' + super (message) + this.constructor = TimedOut + this.__proto__ = TimedOut.prototype + this.message = message + } +} + +/* ------------------------------------------------------------------------ */ + +const iso8601 = (timestamp) => { + let _timestampNumber = undefined; + if (typeof timestamp === 'number') { + _timestampNumber = Math.floor (timestamp); + } else { + _timestampNumber = parseInt (timestamp, 10); + } + + // undefined, null and lots of nasty non-numeric values yield NaN + if (Number.isNaN (_timestampNumber) || _timestampNumber < 0) { + return undefined; + } + + // last line of defence + try { + return new Date (_timestampNumber).toISOString (); + } catch (e) { + return undefined; + } +} + +const parse8601 = (x) => { + if (typeof x !== 'string' || !x) { + return undefined; + } + + if (x.match (/^[0-9]+$/)) { + // a valid number in a string, not a date. + return undefined; + } + + if (x.indexOf ('-') < 0 || x.indexOf (':') < 0) { // no date can be without a dash and a colon + return undefined; + } + + // last line of defence + try { + const candidate = Date.parse (((x.indexOf ('+') >= 0) || (x.slice (-1) === 'Z')) ? x : (x + 'Z').replace (/\s(\d\d):/, 'T$1:')); + if (Number.isNaN (candidate)) { + return undefined; + } + return candidate; + } catch (e) { + return undefined; + } +} + +const parseDate = (x) => { + if (typeof x !== 'string' || !x) { + return undefined; + } + + if (x.indexOf ('GMT') >= 0) { + try { + return Date.parse (x); + } catch (e) { + return undefined; + } + } + + return parse8601 (x); +} + +const rfc2616 = (timestamp = undefined) => { + return new Date (timestamp).toUTCString (); +} + +const mdy = (timestamp, infix = '-') => { + infix = infix || '' + const date = new Date (timestamp) + const Y = date.getUTCFullYear ().toString () + let m = date.getUTCMonth () + 1 + let d = date.getUTCDate () + m = m < 10 ? ('0' + m) : m.toString () + d = d < 10 ? ('0' + d) : d.toString () + return m + infix + d + infix + Y +} + +const ymd = (timestamp, infix = '-') => { + infix = infix || '' + const date = new Date (timestamp) + const Y = date.getUTCFullYear ().toString () + let m = date.getUTCMonth () + 1 + let d = date.getUTCDate () + m = m < 10 ? ('0' + m) : m.toString () + d = d < 10 ? ('0' + d) : d.toString () + return Y + infix + m + infix + d +} + +const ymdhms = (timestamp, infix = ' ') => { + const date = new Date (timestamp) + const Y = date.getUTCFullYear () + let m = date.getUTCMonth () + 1 + let d = date.getUTCDate () + let H = date.getUTCHours () + let M = date.getUTCMinutes () + let S = date.getUTCSeconds () + m = m < 10 ? ('0' + m) : m + d = d < 10 ? ('0' + d) : d + H = H < 10 ? ('0' + H) : H + M = M < 10 ? ('0' + M) : M + S = S < 10 ? ('0' + S) : S + return Y + '-' + m + '-' + d + infix + H + ':' + M + ':' + S +} + +module.exports = + + { + now + , microseconds + , milliseconds + , seconds + , iso8601 + , parse8601 + , rfc2616 + , uuidv1 + , parseDate + , mdy + , ymd + , ymdhms + , setTimeout_safe + , sleep: ms => new Promise (resolve => setTimeout_safe (resolve, ms)) + , TimedOut + , timeout: async (ms, promise) => { + + let clear = () => {} + const expires = new Promise (resolve => (clear = setTimeout_safe (resolve, ms))) + + try { + return await Promise.race ([promise, expires.then (() => { throw new TimedOut () })]) + } finally { + clear () // fixes https://github.com/ccxt/ccxt/issues/749 + } + } +} + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/type.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/type.js new file mode 100644 index 000000000..dec3acf7a --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/base/functions/type.js @@ -0,0 +1,66 @@ +"use strict"; + +/* ------------------------------------------------------------------------ */ + +const isNumber = Number.isFinite + , isInteger = Number.isInteger + , isArray = Array.isArray + , hasProps = o => ((o !== undefined) && (o !== null)) + , isString = s => (typeof s === 'string') + , isObject = o => ((o !== null) && (typeof o === 'object')) + , isRegExp = o => (o instanceof RegExp) + , isDictionary = o => (isObject (o) && (Object.getPrototypeOf (o) === Object.prototype) && !isArray (o) && !isRegExp (o)) + , isStringCoercible = x => ((hasProps (x) && x.toString) || isNumber (x)) + +/* ............................................. */ + +const prop = (o, k) => (isObject (o) ? o[k] : undefined) + , prop2 = (o, k1, k2) => (!isObject (o) ? undefined : (((k1 in o) && (o[k1] !== null)) ? o[k1] : o[k2])) + +/* ............................................. */ + +const asFloat = x => ((isNumber (x) || isString (x)) ? parseFloat (x) : NaN) + , asInteger = x => ((isNumber (x) || isString (x)) ? parseInt (x, 10) : NaN) + +/* ............................................. */ + +module.exports = { + + isNumber + , isInteger + , isArray + , isObject + , isString + , isStringCoercible + , isDictionary + + , hasProps + , prop + + , asFloat + , asInteger + + , safeFloat: (o, k, $default, n = asFloat (prop (o, k))) => (isNumber (n) ? n : $default) + , safeInteger: (o, k, $default, n = asInteger (prop (o, k))) => (isNumber (n) ? n : $default) + , safeIntegerProduct: (o, k, $factor, $default, n = asInteger (prop (o, k))) => (isNumber (n) ? parseInt (n * $factor) : $default) + , safeTimestamp: (o, k, $default, n = asFloat (prop (o, k))) => (isNumber (n) ? parseInt (n * 1000) : $default) + , safeValue: (o, k, $default, x = prop (o, k)) => (hasProps (x) ? x : $default) + , safeString: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x) : $default) + , safeStringLower: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x).toLowerCase () : $default) + , safeStringUpper: (o, k, $default, x = prop (o, k)) => (isStringCoercible (x) ? String (x).toUpperCase () : $default) + + // not using safeFloats with an array argument as we're trying to save some cycles here + // we're not using safeFloat3 either because those cases are too rare to deserve their own optimization + + , safeFloat2: (o, k1, k2, $default, n = asFloat (prop2 (o, k1, k2))) => (isNumber (n) ? n : $default) + , safeInteger2: (o, k1, k2, $default, n = asInteger (prop2 (o, k1, k2))) => (isNumber (n) ? n : $default) + , safeIntegerProduct2: (o, k1, k2, $factor, $default, n = asInteger (prop2 (o, k1, k2))) => (isNumber (n) ? parseInt (n * $factor) : $default) + , safeTimestamp2: (o, k1, k2, $default, n = asFloat (prop2 (o, k1, k2))) => (isNumber (n) ? parseInt (n * 1000) : $default) + , safeValue2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (hasProps (x) ? x : $default) + , safeString2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x) : $default) + , safeStringLower2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x).toLowerCase () : $default) + , safeStringUpper2: (o, k1, k2, $default, x = prop2 (o, k1, k2)) => (isStringCoercible (x) ? String (x).toUpperCase () : $default) + +} + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bcex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bcex.js new file mode 100644 index 000000000..22eb628a3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bcex.js @@ -0,0 +1,687 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, OrderNotFound } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bcex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bcex', + 'name': 'BCEX', + 'countries': [ 'CN', 'HK' ], + 'version': '1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': 'emulated', + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + 'fetchTradingLimits': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/77231516-851c6900-6bac-11ea-8fd6-ee5c23eddbd4.jpg', + 'api': 'https://www.bcex.top', + 'www': 'https://www.bcex.top', + 'doc': 'https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Interface', + 'fees': 'https://bcex.udesk.cn/hc/articles/57085', + 'referral': 'https://www.bcex.top/register?invite_code=758978&lang=en', + }, + 'status': { + 'status': 'error', + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + }, + 'api': { + 'public': { + 'get': [ + 'Api_Market/getPriceList', // tickers + 'Api_Order/ticker', // last ohlcv candle (ticker) + 'Api_Order/depth', // orderbook + 'Api_Market/getCoinTrade', // ticker + 'Api_Order/marketOrder', // trades... + ], + 'post': [ + 'Api_Market/getPriceList', // tickers + 'Api_Order/ticker', // last ohlcv candle (ticker) + 'Api_Order/depth', // orderbook + 'Api_Market/getCoinTrade', // ticker + 'Api_Order/marketOrder', // trades... + ], + }, + 'private': { + 'post': [ + 'Api_Order/cancel', + 'Api_Order/coinTrust', // limit order + 'Api_Order/orderList', // open / all orders (my trades?) + 'Api_Order/orderInfo', + 'Api_Order/tradeList', // open / all orders + 'Api_Order/trustList', // ? + 'Api_User/userBalance', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'ckusd': 0.0, + 'other': 0.05 / 100, + }, + 'deposit': {}, + }, + }, + 'exceptions': { + '该币不存在,非法操作': ExchangeError, // { code: 1, msg: "该币不存在,非法操作" } - returned when a required symbol parameter is missing in the request (also, maybe on other types of errors as well) + '公钥不合法': AuthenticationError, // { code: 1, msg: '公钥不合法' } - wrong public key + '您的可用余额不足': InsufficientFunds, // { code: 1, msg: '您的可用余额不足' } - your available balance is insufficient + '您的btc不足': InsufficientFunds, // { code: 1, msg: '您的btc不足' } - your btc is insufficient + '参数非法': InvalidOrder, // {'code': 1, 'msg': '参数非法'} - 'Parameter illegal' + '订单信息不存在': OrderNotFound, // {'code': 1, 'msg': '订单信息不存在'} - 'Order information does not exist' + }, + 'commonCurrencies': { + 'UNI': 'UNI COIN', + 'PNT': 'Penta', + }, + 'options': { + 'limits': { + // hardcoding is deprecated, using these predefined values is not recommended, use loadTradingLimits instead + 'AFC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 6, 'max': 120000 }}}, + 'AFC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 6, 'max': 120000 }}}, + 'AFT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 300000 }}}, + 'AICC/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 5, 'max': 50000 }}}, + 'AIDOC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 100000 }}}, + 'AISI/ETH': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.001, 'max': 500 }}}, + 'AIT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 400000 }}}, + 'ANS/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 500 }}}, + 'ANS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.1, 'max': 1000 }}}, + 'ARC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 60, 'max': 600000 }}}, + 'AXF/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 100, 'max': 1000000 }}}, + 'BASH/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 250, 'max': 3000000 }}}, + 'BATT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 60, 'max': 1500000 }}}, + 'BCD/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.3, 'max': 7000 }}}, + 'BHPC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 70000 }}}, + 'BHPC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 60000 }}}, + 'BOPO/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 2000000 }}}, + 'BOPO/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 100, 'max': 10000000 }}}, + 'BTC/CKUSD': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.001, 'max': 10 }}}, + 'BTC/CNET': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.0005, 'max': 5 }}}, + 'BTC/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.0002, 'max': 4 }}}, + 'BTE/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 25, 'max': 250000 }}}, + 'BU/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 400000 }}}, + 'CIC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 3000, 'max': 30000000 }}}, + 'CIT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 4, 'max': 40000 }}}, + 'CIT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 4, 'max': 40000 }}}, + 'CMT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 2500000 }}}, + 'CNET/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 12, 'max': 120000 }}}, + 'CNMC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 4, 'max': 50000 }}}, + 'CTC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 5, 'max': 550000 }}}, + 'CZR/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 12, 'max': 500000 }}}, + 'DCON/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 8, 'max': 300000 }}}, + 'DCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 40000 }}}, + 'DCT/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 2, 'max': 2000 }}}, + 'DOGE/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 3000, 'max': 14000000 }}}, + 'DOGE/CKUSD': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 500, 'max': 2000000 }}}, + 'DRCT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 16, 'max': 190000 }}}, + 'ELA/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.02, 'max': 500 }}}, + 'ELF/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 100000 }}}, + 'ELF/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.01, 'max': 100000 }}}, + 'EOS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.5, 'max': 5000 }}}, + 'EOS/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 2.5, 'max': 30000 }}}, + 'EOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.18, 'max': 1800 }}}, + 'ETC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2500 }}}, + 'ETC/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2500 }}}, + 'ETF/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 7, 'max': 150000 }}}, + 'ETH/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.015, 'max': 100 }}}, + 'ETH/CKUSD': { 'precision': { 'amount': 4, 'price': 4 }, 'limits': { 'amount': { 'min': 0.005, 'max': 100 }}}, + 'ETH/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.005, 'max': 100 }}}, + 'FCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.24, 'max': 1000 }}}, + 'FCT/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.24, 'max': 1000 }}}, + 'GAME/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 1, 'max': 10000 }}}, + 'GOOC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 200, 'max': 2000000 }}}, + 'GP/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 600, 'max': 6000000 }}}, + 'HSC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 1000, 'max': 20000000 }}}, + 'IFISH/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 300, 'max': 8000000 }}}, + 'IIC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 50, 'max': 4000000 }}}, + 'IMOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 300000 }}}, + 'JC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 300, 'max': 3000000 }}}, + 'LBTC/BTC': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 0.1, 'max': 3000 }}}, + 'LEC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 500, 'max': 5000000 }}}, + 'LKY/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 10, 'max': 70000 }}}, + 'LKY/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}}, + 'LMC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 25, 'max': 250000 }}}, + 'LSK/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.3, 'max': 3000 }}}, + 'LTC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.01, 'max': 500 }}}, + 'LTC/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.01, 'max': 500 }}}, + 'LTC/USDT': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.02, 'max': 450 }}}, + 'MC/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 10000, 'max': 100000000 }}}, + 'MCC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 30, 'max': 350000 }}}, + 'MOC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 25, 'max': 600000 }}}, + 'MRYC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 300, 'max': 3000000 }}}, + 'MT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 200, 'max': 6000000 }}}, + 'MXI/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 5000, 'max': 60000000 }}}, + 'NAI/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}}, + 'NAS/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.2, 'max': 15000 }}}, + 'NAS/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.5, 'max': 5000 }}}, + 'NEWOS/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 65, 'max': 700000 }}}, + 'NKN/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 3, 'max': 350000 }}}, + 'NTK/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 2, 'max': 30000 }}}, + 'ONT/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.2, 'max': 2000 }}}, + 'ONT/ETH': { 'precision': { 'amount': 3, 'price': 8 }, 'limits': { 'amount': { 'min': 0.01, 'max': 1000 }}}, + 'PNT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 80, 'max': 800000 }}}, + 'PST/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 5, 'max': 100000 }}}, + 'PTT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 450, 'max': 10000000 }}}, + 'QTUM/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.4, 'max': 2800 }}}, + 'QTUM/CKUSD': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 0.1, 'max': 1000 }}}, + 'RATING/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 500, 'max': 10000000 }}}, + 'RHC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 1000, 'max': 10000000 }}}, + 'SDA/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 20, 'max': 500000 }}}, + 'SDD/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 10, 'max': 100000 }}}, + 'SHC/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 250, 'max': 2500000 }}}, + 'SHE/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 5000000 }}}, + 'SMC/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 1000, 'max': 10000000 }}}, + 'SOP/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 50, 'max': 1000000 }}}, + 'TAC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 35, 'max': 800000 }}}, + 'TIP/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 7, 'max': 200000 }}}, + 'TKT/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 40, 'max': 400000 }}}, + 'TLC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 500, 'max': 10000000 }}}, + 'TNC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 110000 }}}, + 'TUB/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 200, 'max': 8000000 }}}, + 'UC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 3000000 }}}, + 'UDB/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 2000, 'max': 40000000 }}}, + 'UIC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 150000 }}}, + 'VAAC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 250000 }}}, + 'VPN/CNET': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 200, 'max': 2000000 }}}, + 'VSC/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 30, 'max': 650000 }}}, + 'WAVES/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 0.15, 'max': 1500 }}}, + 'WDNA/ETH': { 'precision': { 'amount': 2, 'price': 8 }, 'limits': { 'amount': { 'min': 100, 'max': 250000 }}}, + 'WIC/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 3, 'max': 30000 }}}, + 'XAS/CNET': { 'precision': { 'amount': 2, 'price': 2 }, 'limits': { 'amount': { 'min': 2.5, 'max': 25000 }}}, + 'XLM/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 10, 'max': 300000 }}}, + 'XLM/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 1, 'max': 300000 }}}, + 'XLM/USDT': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 5, 'max': 150000 }}}, + 'XRP/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 24, 'max': 100000 }}}, + 'XRP/CKUSD': { 'precision': { 'amount': 2, 'price': 3 }, 'limits': { 'amount': { 'min': 5, 'max': 50000 }}}, + 'YBCT/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 15, 'max': 200000 }}}, + 'YBCT/CKUSD': { 'precision': { 'amount': 2, 'price': 4 }, 'limits': { 'amount': { 'min': 10, 'max': 200000 }}}, + 'YBY/CNET': { 'precision': { 'amount': 2, 'price': 6 }, 'limits': { 'amount': { 'min': 25000, 'max': 250000000 }}}, + 'ZEC/BTC': { 'precision': { 'amount': 4, 'price': 8 }, 'limits': { 'amount': { 'min': 0.02, 'max': 100 }}}, + 'ZEC/CKUSD': { 'precision': { 'amount': 4, 'price': 2 }, 'limits': { 'amount': { 'min': 0.02, 'max': 100 }}}, + }, + }, + }); + } + + async fetchTradingLimits (symbols = undefined, params = {}) { + // this method should not be called directly, use loadTradingLimits () instead + // by default it will try load withdrawal fees of all currencies (with separate requests, sequentially) + // however if you define symbols = [ 'ETH/BTC', 'LTC/BTC' ] in args it will only load those + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const result = {}; + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + result[symbol] = await this.fetchTradingLimitsById (this.marketId (symbol), params); + } + return result; + } + + async fetchTradingLimitsById (id, params = {}) { + const request = { + 'symbol': id, + }; + const response = await this.publicPostApiOrderTicker (this.extend (request, params)); + // + // { code: 0, + // msg: "获取牌价信息成功", + // data: { high: 0.03721392, + // low: 0.03335362, + // buy: "0.03525757", + // sell: "0.03531160", + // last: 0.0352634, + // vol: "184742.4176", + // min_trade: "0.01500000", + // max_trade: "100.00000000", + // number_float: "4", + // price_float: "8" } } } + // + return this.parseTradingLimits (this.safeValue (response, 'data', {})); + } + + parseTradingLimits (limits, symbol = undefined, params = {}) { + // + // { high: 0.03721392, + // low: 0.03335362, + // buy: "0.03525757", + // sell: "0.03531160", + // last: 0.0352634, + // vol: "184742.4176", + // min_trade: "0.01500000", + // max_trade: "100.00000000", + // number_float: "4", + // price_float: "8" } + // + return { + 'info': limits, + 'precision': { + 'amount': this.safeInteger (limits, 'number_float'), + 'price': this.safeInteger (limits, 'price_float'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (limits, 'min_trade'), + 'max': this.safeFloat (limits, 'max_trade'), + }, + }, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetApiMarketGetPriceList (params); + const result = []; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const currentMarketId = keys[i]; + const currentMarkets = response[currentMarketId]; + for (let j = 0; j < currentMarkets.length; j++) { + const market = currentMarkets[j]; + const baseId = this.safeString (market, 'coin_from'); + const quoteId = this.safeString (market, 'coin_to'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const id = baseId + '2' + quoteId; + const symbol = base + '/' + quote; + const active = true; + const defaults = this.safeValue (this.options['limits'], symbol, {}); + result.push (this.extend ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + // overrided by defaults from this.options['limits'] + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + // overrided by defaults from this.options['limits'] + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + 'info': market, + }, defaults)); + } + } + return result; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp2 (trade, 'date', 'created'); + const id = this.safeString (trade, 'tid'); + const orderId = this.safeString (trade, 'order_id'); + const amount = this.safeFloat2 (trade, 'number', 'amount'); + const price = this.safeFloat (trade, 'price'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + let side = this.safeString (trade, 'side'); + if (side === 'sale') { + side = 'sell'; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'order': orderId, + 'fee': undefined, + 'takerOrMaker': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const market = this.market (symbol); + const response = await this.publicPostApiOrderMarketOrder (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostApiUserUserBalance (params); + const data = this.safeValue (response, 'data'); + let keys = Object.keys (data); + const result = { }; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const amount = this.safeFloat (data, key); + const parts = key.split ('_'); + const currencyId = parts[0]; + const lockOrOver = parts[1]; + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + result[code] = this.account (); + } + if (lockOrOver === 'lock') { + result[code]['used'] = parseFloat (amount); + } else { + result[code]['free'] = parseFloat (amount); + } + } + keys = Object.keys (result); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const total = this.sum (result[key]['used'], result[key]['free']); + result[key]['total'] = total; + } + result['info'] = data; + return this.parseBalance (result); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.markets[symbol]; + const request = { + 'part': market['quoteId'], + 'coin': market['baseId'], + }; + const response = await this.publicPostApiMarketGetCoinTrade (this.extend (request, params)); + const timestamp = this.milliseconds (); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (response, 'max'), + 'low': this.safeFloat (response, 'min'), + 'bid': this.safeFloat (response, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (response, 'sale'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': this.safeFloat (response, 'price'), + 'last': this.safeFloat (response, 'price'), + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (response, 'change_24h'), + 'average': undefined, + 'baseVolume': this.safeFloat (response, 'volume_24h'), + 'quoteVolume': undefined, + 'info': response, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + const response = await this.publicPostApiOrderDepth (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const timestamp = this.safeTimestamp (data, 'date'); + return this.parseOrderBook (data, timestamp); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privatePostApiOrderOrderList (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', // partially filled + '2': 'closed', + '3': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a `symbol` argument'); + } + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'trust_id': id, + }; + const response = await this.privatePostApiOrderOrderInfo (this.extend (request, params)); + const order = this.safeValue (response, 'data'); + const timestamp = this.safeTimestamp (order, 'created'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let side = this.safeString (order, 'flag'); + if (side === 'sale') { + side = 'sell'; + } + // Can't use parseOrder because the data format is different btw endpoint for fetchOrder and fetchOrders + return { + 'info': order, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': this.safeFloat (order, 'price'), + 'cost': undefined, + 'average': this.safeFloat (order, 'avg_price'), + 'amount': this.safeFloat (order, 'number'), + 'filled': this.safeFloat (order, 'numberdeal'), + 'remaining': this.safeFloat (order, 'numberover'), + 'status': status, + 'fee': undefined, + 'clientOrderId': undefined, + 'trades': undefined, + }; + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const timestamp = this.safeTimestamp (order, 'datetime'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const type = undefined; + let side = this.safeString (order, 'type'); + if (side === 'sale') { + side = 'sell'; + } + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'avg_price'); + const amount = this.safeFloat (order, 'amount'); + const remaining = this.safeFloat (order, 'amount_outstanding'); + const filled = amount - remaining; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const cost = filled * price; + const fee = undefined; + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + return result; + } + + async fetchOrdersByType (type, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'type': type, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privatePostApiOrderTradeList (this.extend (request, params)); + if ('data' in response) { + return this.parseOrders (response['data'], market, since, limit); + } + return []; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByType ('open', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByType ('all', symbol, since, limit, params); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'type': side, + 'price': this.priceToPrecision (symbol, price), + 'number': this.amountToPrecision (symbol, amount), + }; + const response = await this.privatePostApiOrderCoinTrust (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const id = this.safeString (data, 'order_id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument'); + } + await this.loadMarkets (); + const request = {}; + if (symbol !== undefined) { + request['symbol'] = this.marketId (symbol); + } + if (id !== undefined) { + request['order_id'] = id; + } + return await this.privatePostApiOrderCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + let payload = this.urlencode ({ 'api_key': this.apiKey }); + if (Object.keys (query).length) { + payload += '&' + this.urlencode (this.keysort (query)); + } + const auth = payload + '&secret_key=' + this.secret; + const signature = this.hash (this.encode (auth)); + body = payload + '&sign=' + signature; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const errorCode = this.safeValue (response, 'code'); + if (errorCode !== undefined) { + if (errorCode !== 0) { + // + // { code: 1, msg: "该币不存在,非法操作" } - returned when a required symbol parameter is missing in the request (also, maybe on other types of errors as well) + // { code: 1, msg: '公钥不合法' } - wrong public key + // { code: 1, msg: '价格输入有误,请检查你的数值精度' } - 'The price input is incorrect, please check your numerical accuracy' + // { code: 1, msg: '单笔最小交易数量不能小于0.00100000,请您重新挂单'} - + // 'The minimum number of single transactions cannot be less than 0.00100000. Please re-post the order' + // + const message = this.safeString (response, 'msg'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + if (message.indexOf ('请您重新挂单') >= 0) { // minimum limit + throw new InvalidOrder (feedback); + } else { + throw new ExchangeError (feedback); + } + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bequant.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bequant.js new file mode 100644 index 000000000..a2d9348e1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bequant.js @@ -0,0 +1,32 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const hitbtc = require ('./hitbtc'); +// --------------------------------------------------------------------------- + +module.exports = class bequant extends hitbtc { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bequant', + 'name': 'Bequant', + 'countries': [ 'MT' ], // Malta + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/55248342-a75dfe00-525a-11e9-8aa2-05e9dca943c6.jpg', + 'api': { + 'public': 'https://api.bequant.io', + 'private': 'https://api.bequant.io', + }, + 'www': 'https://bequant.io', + 'doc': [ + 'https://api.bequant.io/', + ], + 'fees': [ + 'https://bequant.io/fees-and-limits', + ], + 'referral': 'https://bequant.io', + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bibox.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bibox.js new file mode 100644 index 000000000..5f1e30c04 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bibox.js @@ -0,0 +1,1049 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AccountSuspended, ArgumentsRequired, AuthenticationError, DDoSProtection, ExchangeNotAvailable, InvalidOrder, OrderNotFound, PermissionDenied, InsufficientFunds, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bibox extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bibox', + 'name': 'Bibox', + 'countries': [ 'CN', 'US', 'KR' ], + 'version': 'v1', + 'hostname': 'bibox365.com', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, // or they will return https://github.com/ccxt/ccxt/issues/2338 + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDeposits': true, + 'fetchDepositAddress': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'publicAPI': false, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '12h': '12hour', + '1d': 'day', + '1w': 'week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg', + 'api': 'https://api.{hostname}', + 'www': 'https://www.bibox365.com', + 'doc': [ + 'https://biboxcom.github.io/en/', + ], + 'fees': 'https://bibox.zendesk.com/hc/en-us/articles/360002336133', + 'referral': 'https://w2.bibox365.com/login/register?invite_code=05Kj3I', + }, + 'api': { + 'public': { + 'post': [ + // TODO: rework for full endpoint/cmd paths here + 'mdata', + ], + 'get': [ + 'cquery', + 'mdata', + ], + }, + 'private': { + 'post': [ + 'cquery', + 'ctrade', + 'user', + 'orderpending', + 'transfer', + ], + }, + 'v2private': { + 'post': [ + 'assets/transfer/spot', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.0008, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'exceptions': { + '2011': AccountSuspended, // Account is locked + '2015': AuthenticationError, // Google authenticator is wrong + '2021': InsufficientFunds, // Insufficient balance available for withdrawal + '2027': InsufficientFunds, // Insufficient balance available (for trade) + '2033': OrderNotFound, // operation failed! Orders have been completed or revoked + '2067': InvalidOrder, // Does not support market orders + '2068': InvalidOrder, // The number of orders can not be less than + '2085': InvalidOrder, // Order quantity is too small + '3012': AuthenticationError, // invalid apiKey + '3016': BadSymbol, // Trading pair error + '3024': PermissionDenied, // wrong apikey permissions + '3025': AuthenticationError, // signature failed + '4000': ExchangeNotAvailable, // current network is unstable + '4003': DDoSProtection, // server busy please try again later + }, + 'commonCurrencies': { + 'BOX': 'DefiBox', + 'BPT': 'BlockPool Token', + 'KEY': 'Bihu', + 'MTC': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025 + 'PAI': 'PCHAIN', + }, + }); + } + + async fetchMarkets (params = {}) { + const request = { + 'cmd': 'marketAll', + }; + const response = await this.publicGetMdata (this.extend (request, params)); + // + // { + // "result": [ + // { + // "is_hide":0, + // "high_cny":"1.9478", + // "amount":"272.41", + // "coin_symbol":"BIX", + // "last":"0.00002487", + // "currency_symbol":"BTC", + // "change":"+0.00000073", + // "low_cny":"1.7408", + // "base_last_cny":"1.84538041", + // "area_id":7, + // "percent":"+3.02%", + // "last_cny":"1.8454", + // "high":"0.00002625", + // "low":"0.00002346", + // "pair_type":0, + // "last_usd":"0.2686", + // "vol24H":"10940613", + // "id":1, + // "high_usd":"0.2835", + // "low_usd":"0.2534" + // } + // ], + // "cmd":"marketAll", + // "ver":"1.1" + // } + // + const markets = this.safeValue (response, 'result'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const numericId = this.safeInteger (market, 'id'); + const baseId = this.safeString (market, 'coin_symbol'); + const quoteId = this.safeString (market, 'currency_symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = baseId + '_' + quoteId; + const precision = { + 'amount': 4, + 'price': 8, + }; + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // we don't set values that are not defined by the exchange + const timestamp = this.safeInteger (ticker, 'timestamp'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } else { + const baseId = this.safeString (ticker, 'coin_symbol'); + const quoteId = this.safeString (ticker, 'currency_symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const last = this.safeFloat (ticker, 'last'); + const change = this.safeFloat (ticker, 'change'); + const baseVolume = this.safeFloat2 (ticker, 'vol', 'vol24H'); + let open = undefined; + if ((last !== undefined) && (change !== undefined)) { + open = last - change; + } + let percentage = this.safeString (ticker, 'percent'); + if (percentage !== undefined) { + percentage = percentage.replace ('%', ''); + percentage = parseFloat (percentage); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': this.safeFloat (ticker, 'amount'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'ticker', + 'pair': market['id'], + }; + const response = await this.publicGetMdata (this.extend (request, params)); + return this.parseTicker (response['result'], market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + const ticker = this.parseTicker (rawTickers[i]); + if ((symbols === undefined) || (this.inArray (ticker['symbol'], symbols))) { + tickers.push (ticker); + } + } + return tickers; + } + + async fetchTickers (symbols = undefined, params = {}) { + const request = { + 'cmd': 'marketAll', + }; + const response = await this.publicGetMdata (this.extend (request, params)); + const tickers = this.parseTickers (response['result'], symbols); + const result = this.indexBy (tickers, 'symbol'); + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeInteger2 (trade, 'time', 'createdAt'); + let side = this.safeInteger2 (trade, 'side', 'order_side'); + side = (side === 1) ? 'buy' : 'sell'; + let symbol = undefined; + if (market === undefined) { + let marketId = this.safeString (trade, 'pair'); + if (marketId === undefined) { + const baseId = this.safeString (trade, 'coin_symbol'); + const quoteId = this.safeString (trade, 'currency_symbol'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + marketId = baseId + '_' + quoteId; + } + } + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + let feeCurrency = this.safeString (trade, 'fee_symbol'); + if (feeCurrency !== undefined) { + if (feeCurrency in this.currencies_by_id) { + feeCurrency = this.currencies_by_id[feeCurrency]['code']; + } else { + feeCurrency = this.safeCurrencyCode (feeCurrency); + } + } + const feeRate = undefined; // todo: deduce from market if market is defined + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined && amount !== undefined) { + cost = price * amount; + } + if (feeCost !== undefined) { + fee = { + 'cost': -feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }; + } + const id = this.safeString (trade, 'id'); + return { + 'info': trade, + 'id': id, + 'order': undefined, // Bibox does not have it (documented) yet + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': 'limit', + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'deals', + 'pair': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; // default = 200 + } + const response = await this.publicGetMdata (this.extend (request, params)); + return this.parseTrades (response['result'], market, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'depth', + 'pair': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; // default = 200 + } + const response = await this.publicGetMdata (this.extend (request, params)); + return this.parseOrderBook (response['result'], this.safeFloat (response['result'], 'update_time'), 'bids', 'asks', 'price', 'volume'); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":1591448220000, + // "open":"0.02507029", + // "high":"0.02507029", + // "low":"0.02506349", + // "close":"0.02506349", + // "vol":"5.92000000" + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'vol'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'kline', + 'pair': market['id'], + 'period': this.timeframes[timeframe], + 'size': limit, + }; + const response = await this.publicGetMdata (this.extend (request, params)); + // + // { + // "result":[ + // {"time":1591448220000,"open":"0.02507029","high":"0.02507029","low":"0.02506349","close":"0.02506349","vol":"5.92000000"}, + // {"time":1591448280000,"open":"0.02506449","high":"0.02506975","low":"0.02506108","close":"0.02506843","vol":"5.72000000"}, + // {"time":1591448340000,"open":"0.02506698","high":"0.02506698","low":"0.02506452","close":"0.02506519","vol":"4.86000000"}, + // ], + // "cmd":"kline", + // "ver":"1.1" + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + async fetchCurrencies (params = {}) { + if (!this.apiKey || !this.secret) { + throw new AuthenticationError (this.id + " fetchCurrencies is an authenticated endpoint, therefore it requires 'apiKey' and 'secret' credentials. If you don't need currency details, set exchange.has['fetchCurrencies'] = false before calling its methods."); + } + const request = { + 'cmd': 'transfer/coinList', + 'body': {}, + }; + const response = await this.privatePostTransfer (this.extend (request, params)); + // + // { + // "result":[ + // { + // "result":[ + // { + // "totalBalance":"14.57582269", + // "balance":"14.57582269", + // "freeze":"0.00000000", + // "id":60, + // "symbol":"USDT", + // "icon_url":"/appimg/USDT_icon.png", + // "describe_url":"[{\"lang\":\"zh-cn\",\"link\":\"https://bibox.zendesk.com/hc/zh-cn/articles/115004798234\"},{\"lang\":\"en-ww\",\"link\":\"https://bibox.zendesk.com/hc/en-us/articles/115004798234\"}]", + // "name":"USDT", + // "enable_withdraw":1, + // "enable_deposit":1, + // "enable_transfer":1, + // "confirm_count":2, + // "is_erc20":1, + // "forbid_info":null, + // "describe_summary":"[{\"lang\":\"zh-cn\",\"text\":\"USDT 是 Tether 公司推出的基于稳定价值货币美元(USD)的代币 Tether USD(简称USDT),1USDT=1美元,用户可以随时使用 USDT 与 USD 进行1:1的兑换。\"},{\"lang\":\"en-ww\",\"text\":\"USDT is a cryptocurrency asset issued on the Bitcoin blockchain via the Omni Layer Protocol. Each USDT unit is backed by a U.S Dollar held in the reserves of the Tether Limited and can be redeemed through the Tether Platform.\"}]", + // "total_amount":4776930644, + // "supply_amount":4642367414, + // "price":"--", + // "contract_father":"OMNI", + // "supply_time":"--", + // "comment":null, + // "contract":"31", + // "original_decimals":8, + // "deposit_type":0, + // "hasCobo":0, + // "BTCValue":"0.00126358", + // "CNYValue":"100.93381445", + // "USDValue":"14.57524654", + // "children":[ + // {"type":"OMNI","symbol":"USDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":2}, + // {"type":"TRC20","symbol":"tUSDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":20}, + // {"type":"ERC20","symbol":"eUSDT","enable_deposit":1,"enable_withdraw":1,"confirm_count":25} + // ] + // }, + // ], + // "cmd":"transfer/coinList" + // } + // ] + // } + // + const currencies = this.safeValue (response, 'result'); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'symbol'); + const name = currency['name']; // contains hieroglyphs causing python ASCII bug + const code = this.safeCurrencyCode (id); + const precision = 8; + const deposit = this.safeValue (currency, 'enable_deposit'); + const withdraw = this.safeValue (currency, 'enable_withdraw'); + const active = (deposit && withdraw); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const type = this.safeString (params, 'type', 'assets'); + params = this.omit (params, 'type'); + const request = { + 'cmd': 'transfer/' + type, // assets, mainAssets + 'body': this.extend ({ + 'select': 1, // return full info + }, params), + }; + const response = await this.privatePostTransfer (request); + const balances = this.safeValue (response, 'result'); + const result = { 'info': balances }; + let indexed = undefined; + if ('assets_list' in balances) { + indexed = this.indexBy (balances['assets_list'], 'coin_symbol'); + } else { + indexed = balances; + } + const keys = Object.keys (indexed); + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + let code = id.toUpperCase (); + if (code.indexOf ('TOTAL_') >= 0) { + code = code.slice (6); + } + if (code in this.currencies_by_id) { + code = this.currencies_by_id[code]['code']; + } + const account = this.account (); + let balance = indexed[id]; + if (typeof balance === 'string') { + balance = parseFloat (balance); + account['free'] = balance; + account['used'] = 0.0; + account['total'] = balance; + } else { + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'freeze'); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + 'page': 1, + }; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } else { + request['size'] = 100; + } + const response = await this.privatePostTransfer ({ + 'cmd': 'transfer/transferInList', + 'body': this.extend (request, params), + }); + const deposits = this.safeValue (response['result'], 'items', []); + for (let i = 0; i < deposits.length; i++) { + deposits[i]['type'] = 'deposit'; + } + return this.parseTransactions (deposits, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + 'page': 1, + }; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } else { + request['size'] = 100; + } + const response = await this.privatePostTransfer ({ + 'cmd': 'transfer/transferOutList', + 'body': this.extend (request, params), + }); + const withdrawals = this.safeValue (response['result'], 'items', []); + for (let i = 0; i < withdrawals.length; i++) { + withdrawals[i]['type'] = 'withdrawal'; + } + return this.parseTransactions (withdrawals, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // 'id': 1023291, + // 'coin_symbol': 'ETH', + // 'to_address': '0x7263....', + // 'amount': '0.49170000', + // 'confirmCount': '16', + // 'createdAt': 1553123867000, + // 'status': 2 + // } + // + // fetchWithdrawals + // + // { + // 'id': 521844, + // 'coin_symbol': 'ETH', + // 'to_address': '0xfd4e....', + // 'addr_remark': '', + // 'amount': '0.39452750', + // 'fee': '0.00600000', + // 'createdAt': 1553226906000, + // 'memo': '', + // 'status': 3 + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'to_address'); + const currencyId = this.safeString (transaction, 'coin_symbol'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeString (transaction, 'createdAt'); + let tag = this.safeString (transaction, 'addr_remark'); + const type = this.safeString (transaction, 'type'); + const status = this.parseTransactionStatusByType (this.safeString (transaction, 'status'), type); + const amount = this.safeFloat (transaction, 'amount'); + let feeCost = this.safeFloat (transaction, 'fee'); + if (type === 'deposit') { + feeCost = 0; + tag = undefined; + } + const fee = { + 'cost': feeCost, + 'currency': code, + }; + return { + 'info': transaction, + 'id': id, + 'txid': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTransactionStatusByType (status, type = undefined) { + const statuses = { + 'deposit': { + '1': 'pending', + '2': 'ok', + }, + 'withdrawal': { + '0': 'pending', + '3': 'ok', + }, + }; + return this.safeString (this.safeValue (statuses, type, {}), status, status); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderType = (type === 'limit') ? 2 : 1; + const orderSide = (side === 'buy') ? 1 : 2; + const request = { + 'cmd': 'orderpending/trade', + 'body': this.extend ({ + 'pair': market['id'], + 'account_type': 0, + 'order_type': orderType, + 'order_side': orderSide, + 'pay_bix': 0, + 'amount': amount, + 'price': price, + }, params), + }; + const response = await this.privatePostOrderpending (request); + return { + 'info': response, + 'id': this.safeString (response, 'result'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'cmd': 'orderpending/cancelTrade', + 'body': this.extend ({ + 'orders_id': id, + }, params), + }; + const response = await this.privatePostOrderpending (request); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'cmd': 'orderpending/order', + 'body': this.extend ({ + 'id': id.toString (), + 'account_type': 0, // 0 = spot account + }, params), + }; + const response = await this.privatePostOrderpending (request); + const order = this.safeValue (response, 'result'); + if (this.isEmpty (order)) { + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + return this.parseOrder (order); + } + + parseOrder (order, market = undefined) { + let symbol = undefined; + if (market === undefined) { + let marketId = undefined; + const baseId = this.safeString (order, 'coin_symbol'); + const quoteId = this.safeString (order, 'currency_symbol'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + marketId = baseId + '_' + quoteId; + } + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + const type = (order['order_type'] === 1) ? 'market' : 'limit'; + const timestamp = order['createdAt']; + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'deal_price'); + const filled = this.safeFloat (order, 'deal_amount'); + const amount = this.safeFloat (order, 'amount'); + let cost = this.safeFloat2 (order, 'deal_money', 'money'); + let remaining = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (cost === undefined) { + cost = price * filled; + } + } + const side = (order['order_side'] === 1) ? 'buy' : 'sell'; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString (order, 'id'); + const feeCost = this.safeFloat (order, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + cost = cost ? cost : (parseFloat (price) * filled); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + // original comments from bibox: + '1': 'open', // pending + '2': 'open', // part completed + '3': 'closed', // completed + '4': 'canceled', // part canceled + '5': 'canceled', // canceled + '6': 'canceled', // canceling + }; + return this.safeString (statuses, status, status); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let pair = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + pair = market['id']; + } + const size = limit ? limit : 200; + const request = { + 'cmd': 'orderpending/orderPendingList', + 'body': this.extend ({ + 'pair': pair, + 'account_type': 0, // 0 - regular, 1 - margin + 'page': 1, + 'size': size, + }, params), + }; + const response = await this.privatePostOrderpending (request); + const orders = this.safeValue (response['result'], 'items', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = 200, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cmd': 'orderpending/pendingHistoryList', + 'body': this.extend ({ + 'pair': market['id'], + 'account_type': 0, // 0 - regular, 1 - margin + 'page': 1, + 'size': limit, + }, params), + }; + const response = await this.privatePostOrderpending (request); + const orders = this.safeValue (response['result'], 'items', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const size = limit ? limit : 200; + const request = { + 'cmd': 'orderpending/orderHistoryList', + 'body': this.extend ({ + 'pair': market['id'], + 'account_type': 0, // 0 - regular, 1 - margin + 'page': 1, + 'size': size, + 'coin_symbol': market['baseId'], + 'currency_symbol': market['quoteId'], + }, params), + }; + const response = await this.privatePostOrderpending (request); + const trades = this.safeValue (response['result'], 'items', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'cmd': 'transfer/transferIn', + 'body': this.extend ({ + 'coin_symbol': currency['id'], + }, params), + }; + const response = await this.privatePostTransfer (request); + // + // { + // "result":"3Jx6RZ9TNMsAoy9NUzBwZf68QBppDruSKW","cmd":"transfer/transferIn" + // } + // + // { + // "result":"{\"account\":\"PERSONALLY OMITTED\",\"memo\":\"PERSONALLY OMITTED\"}","cmd":"transfer/transferIn" + // } + // + const result = this.safeString (response, 'result'); + let address = result; + let tag = undefined; + if (this.isJsonEncodedObject (result)) { + const parsed = JSON.parse (result); + address = this.safeString (parsed, 'account'); + tag = this.safeString (parsed, 'memo'); + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (this.password === undefined) { + if (!('trade_pwd' in params)) { + throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a trade_pwd parameter'); + } + } + if (!('totp_code' in params)) { + throw new ExchangeError (this.id + ' withdraw() requires a totp_code parameter for 2FA authentication'); + } + const request = { + 'trade_pwd': this.password, + 'coin_symbol': currency['id'], + 'amount': amount, + 'addr': address, + }; + if (tag !== undefined) { + request['address_remark'] = tag; + } + const response = await this.privatePostTransfer ({ + 'cmd': 'transfer/transferOut', + 'body': this.extend (request, params), + }); + return { + 'info': response, + 'id': undefined, + }; + } + + async fetchFundingFees (codes = undefined, params = {}) { + // by default it will try load withdrawal fees of all currencies (with separate requests) + // however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those + await this.loadMarkets (); + const withdrawFees = {}; + const info = {}; + if (codes === undefined) { + codes = Object.keys (this.currencies); + } + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const request = { + 'cmd': 'transfer/coinConfig', + 'body': this.extend ({ + 'coin_symbol': currency['id'], + }, params), + }; + const response = await this.privatePostTransfer (request); + info[code] = response; + withdrawFees[code] = this.safeFloat (response['result'], 'withdraw_fee'); + } + return { + 'info': info, + 'withdraw': withdrawFees, + 'deposit': {}, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }) + '/' + this.version + '/' + path; + const cmds = this.json ([ params ]); + if (api === 'public') { + if (method !== 'GET') { + body = { 'cmds': cmds }; + } else if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else if (api === 'v2private') { + this.checkRequiredCredentials (); + url = this.urls['api'] + '/v2/' + path; + const json_params = this.json (params); + body = { + 'body': json_params, + 'apikey': this.apiKey, + 'sign': this.hmac (this.encode (json_params), this.encode (this.secret), 'md5'), + }; + } else { + this.checkRequiredCredentials (); + body = { + 'cmds': cmds, + 'apikey': this.apiKey, + 'sign': this.hmac (this.encode (cmds), this.encode (this.secret), 'md5'), + }; + } + if (body !== undefined) { + body = this.json (body, { 'convertArraysToObjects': true }); + } + headers = { 'Content-Type': 'application/json' }; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if ('error' in response) { + if ('code' in response['error']) { + const code = this.safeString (response['error'], 'code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } + throw new ExchangeError (this.id + ' ' + body); + } + if (!('result' in response)) { + throw new ExchangeError (this.id + ' ' + body); + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (method === 'GET') { + return response; + } else { + return response['result'][0]; + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bigone.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bigone.js new file mode 100644 index 000000000..b589aceeb --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bigone.js @@ -0,0 +1,1267 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, OrderNotFound, InsufficientFunds, PermissionDenied, BadRequest, RateLimitExceeded, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bigone extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bigone', + 'name': 'BigONE', + 'countries': [ 'CN' ], + 'version': 'v3', + 'rateLimit': 1200, // 500 request per 10 minutes + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'min1', + '5m': 'min5', + '15m': 'min15', + '30m': 'min30', + '1h': 'hour1', + '3h': 'hour3', + '4h': 'hour4', + '6h': 'hour6', + '12h': 'hour12', + '1d': 'day1', + '1w': 'week1', + '1M': 'month1', + }, + 'hostname': 'big.one', // or 'bigone.com' + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/69354403-1d532180-0c91-11ea-88ed-44c06cefdf87.jpg', + 'api': { + 'public': 'https://{hostname}/api/v3', + 'private': 'https://{hostname}/api/v3/viewer', + }, + 'www': 'https://big.one', + 'doc': 'https://open.big.one/docs/api.html', + 'fees': 'https://bigone.zendesk.com/hc/en-us/articles/115001933374-BigONE-Fee-Policy', + 'referral': 'https://b1.run/users/new?code=D3LLBVFT', + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'asset_pairs', + 'asset_pairs/{asset_pair_name}/depth', + 'asset_pairs/{asset_pair_name}/trades', + 'asset_pairs/{asset_pair_name}/ticker', + 'asset_pairs/{asset_pair_name}/candles', + 'asset_pairs/tickers', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'assets/{asset_symbol}/address', + 'orders', + 'orders/{id}', + 'orders/multi', + 'trades', + 'withdrawals', + 'deposits', + ], + 'post': [ + 'orders', + 'orders/{id}/cancel', + 'orders/cancel', + 'withdrawals', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + // HARDCODING IS DEPRECATED THE FEES BELOW ARE TO BE REMOVED SOON + 'withdraw': { + 'BTC': 0.001, + 'ETH': 0.005, + 'EOS': 0.01, + 'ZEC': 0.003, + 'LTC': 0.01, + 'QTUM': 0.01, + // 'INK': 0.01 QTUM, + // 'BOT': 0.01 QTUM, + 'ETC': 0.01, + 'GAS': 0.0, + 'BTS': 1.0, + 'GXS': 0.1, + 'BITCNY': 19.0, + }, + }, + }, + 'exceptions': { + 'exact': { + '10001': BadRequest, // syntax error + '10005': ExchangeError, // internal error + "Amount's scale must greater than AssetPair's base scale": InvalidOrder, + '10007': BadRequest, // parameter error, {"code":10007,"message":"Amount's scale must greater than AssetPair's base scale"} + '10011': ExchangeError, // system error + '10013': OrderNotFound, // {"code":10013,"message":"Resource not found"} + '10014': InsufficientFunds, // {"code":10014,"message":"Insufficient funds"} + '10403': PermissionDenied, // permission denied + '10429': RateLimitExceeded, // too many requests + '40004': AuthenticationError, // {"code":40004,"message":"invalid jwt"} + '40103': AuthenticationError, // invalid otp code + '40104': AuthenticationError, // invalid asset pin code + '40301': PermissionDenied, // {"code":40301,"message":"Permission denied withdrawal create"} + '40302': ExchangeError, // already requested + '40601': ExchangeError, // resource is locked + '40602': ExchangeError, // resource is depleted + '40603': InsufficientFunds, // insufficient resource + '40120': InvalidOrder, // Order is in trading + '40121': InvalidOrder, // Order is already cancelled or filled + }, + 'broad': { + }, + }, + 'commonCurrencies': { + 'MBN': 'Mobilian Coin', + 'ONE': 'BigONE Token', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAssetPairs (params); + // + // { + // "code":0, + // "data":[ + // { + // "id":"01e48809-b42f-4a38-96b1-c4c547365db1", + // "name":"PCX-BTC", + // "quote_scale":7, + // "quote_asset":{ + // "id":"0df9c3c3-255a-46d7-ab82-dedae169fba9", + // "symbol":"BTC", + // "name":"Bitcoin", + // }, + // "base_asset":{ + // "id":"405484f7-4b03-4378-a9c1-2bd718ecab51", + // "symbol":"PCX", + // "name":"ChainX", + // }, + // "base_scale":3, + // "min_quote_value":"0.0001", + // }, + // ] + // } + // + const markets = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const uuid = this.safeString (market, 'id'); + const baseAsset = this.safeValue (market, 'base_asset', {}); + const quoteAsset = this.safeValue (market, 'quote_asset', {}); + const baseId = this.safeString (baseAsset, 'symbol'); + const quoteId = this.safeString (quoteAsset, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'base_scale'), + 'price': this.safeInteger (market, 'quote_scale'), + }; + const minCost = this.safeInteger (market, 'min_quote_value'); + const entry = { + 'id': id, + 'uuid': uuid, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }, + 'info': market, + }; + result.push (entry); + } + return result; + } + + async loadMarkets (reload = false, params = {}) { + const markets = await super.loadMarkets (reload, params); + let marketsByUuid = this.safeValue (this.options, 'marketsByUuid'); + if ((marketsByUuid === undefined) || reload) { + marketsByUuid = {}; + for (let i = 0; i < this.symbols.length; i++) { + const symbol = this.symbols[i]; + const market = this.markets[symbol]; + const uuid = this.safeString (market, 'uuid'); + marketsByUuid[uuid] = market; + } + this.options['marketsByUuid'] = marketsByUuid; + } + return markets; + } + + parseTicker (ticker, market = undefined) { + // + // { + // "asset_pair_name":"ETH-BTC", + // "bid":{"price":"0.021593","order_count":1,"quantity":"0.20936"}, + // "ask":{"price":"0.021613","order_count":1,"quantity":"2.87064"}, + // "open":"0.021795", + // "high":"0.021795", + // "low":"0.021471", + // "close":"0.021613", + // "volume":"117078.90431", + // "daily_change":"-0.000182" + // } + // + const marketId = this.safeString (ticker, 'asset_pair_name'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = undefined; + const close = this.safeFloat (ticker, 'close'); + const bid = this.safeValue (ticker, 'bid', {}); + const ask = this.safeValue (ticker, 'ask', {}); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (bid, 'price'), + 'bidVolume': this.safeFloat (bid, 'quantity'), + 'ask': this.safeFloat (ask, 'price'), + 'askVolume': this.safeFloat (ask, 'quantity'), + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'daily_change'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + const response = await this.publicGetAssetPairsAssetPairNameTicker (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "asset_pair_name":"ETH-BTC", + // "bid":{"price":"0.021593","order_count":1,"quantity":"0.20936"}, + // "ask":{"price":"0.021613","order_count":1,"quantity":"2.87064"}, + // "open":"0.021795", + // "high":"0.021795", + // "low":"0.021471", + // "close":"0.021613", + // "volume":"117078.90431", + // "daily_change":"-0.000182" + // } + // } + // + const ticker = this.safeValue (response, 'data', {}); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['pair_names'] = ids.join (','); + } + const response = await this.publicGetAssetPairsTickers (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // { + // "asset_pair_name":"PCX-BTC", + // "bid":{"price":"0.000234","order_count":1,"quantity":"0.518"}, + // "ask":{"price":"0.0002348","order_count":1,"quantity":"2.348"}, + // "open":"0.0002343", + // "high":"0.0002348", + // "low":"0.0002162", + // "close":"0.0002348", + // "volume":"12887.016", + // "daily_change":"0.0000005" + // }, + // { + // "asset_pair_name":"GXC-USDT", + // "bid":{"price":"0.5054","order_count":1,"quantity":"40.53"}, + // "ask":{"price":"0.5055","order_count":1,"quantity":"38.53"}, + // "open":"0.5262", + // "high":"0.5323", + // "low":"0.5055", + // "close":"0.5055", + // "volume":"603963.05", + // "daily_change":"-0.0207" + // } + // ] + // } + // + const tickers = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { + // "data": { + // "timestamp": 1527665262168391000 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'timestamp'); + return parseInt (timestamp / 1000000); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 200 + } + const response = await this.publicGetAssetPairsAssetPairNameDepth (this.extend (request, params)); + // + // { + // "code":0, + // "data": { + // "asset_pair_name": "EOS-BTC", + // "bids": [ + // { "price": "42", "order_count": 4, "quantity": "23.33363711" } + // ], + // "asks": [ + // { "price": "45", "order_count": 2, "quantity": "4193.3283464" } + // ] + // } + // } + // + const orderbook = this.safeValue (response, 'data', {}); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'quantity'); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id": 38199941, + // "price": "3378.67", + // "amount": "0.019812", + // "taker_side": "ASK", + // "created_at": "2019-01-29T06:05:56Z" + // } + // + // fetchMyTrades (private) + // + // { + // "id": 10854280, + // "asset_pair_name": "XIN-USDT", + // "price": "70", + // "amount": "1", + // "taker_side": "ASK", + // "maker_order_id": 58284908, + // "taker_order_id": 58284909, + // "maker_fee": "0.0008", + // "taker_fee": "0.07", + // "side": "SELF_TRADING", + // "inserted_at": "2019-04-16T12:00:01Z" + // }, + // + // { + // "id": 10854263, + // "asset_pair_name": "XIN-USDT", + // "price": "75.7", + // "amount": "12.743149", + // "taker_side": "BID", + // "maker_order_id": null, + // "taker_order_id": 58284888, + // "maker_fee": null, + // "taker_fee": "0.0025486298", + // "side": "BID", + // "inserted_at": "2019-04-15T06:20:57Z" + // } + // + const timestamp = this.parse8601 (this.safeString2 (trade, 'created_at', 'inserted_at')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const marketId = this.safeString (trade, 'asset_pair_name'); + const symbol = this.safeSymbol (marketId, market, '-'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = this.costToPrecision (symbol, price * amount); + } + } + let side = this.safeString (trade, 'side'); + const takerSide = this.safeString (trade, 'taker_side'); + let takerOrMaker = undefined; + if ((takerSide !== undefined) && (side !== undefined) && (side !== 'SELF_TRADING')) { + takerOrMaker = (takerSide === side) ? 'taker' : 'maker'; + } + if (side === undefined) { + // taker side is not related to buy/sell side + // the following code is probably a mistake + side = (takerSide === 'ASK') ? 'sell' : 'buy'; + } else { + if (side === 'BID') { + side = 'buy'; + } else if (side === 'ASK') { + side = 'sell'; + } + } + const makerOrderId = this.safeString (trade, 'maker_order_id'); + const takerOrderId = this.safeString (trade, 'taker_order_id'); + let orderId = undefined; + if (makerOrderId !== undefined) { + if (takerOrderId !== undefined) { + orderId = [ makerOrderId, takerOrderId ]; + } else { + orderId = makerOrderId; + } + } else if (takerOrderId !== undefined) { + orderId = takerOrderId; + } + const id = this.safeString (trade, 'id'); + const result = { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': parseFloat (cost), + 'info': trade, + }; + let makerCurrencyCode = undefined; + let takerCurrencyCode = undefined; + if ((market !== undefined) && (takerOrMaker !== undefined)) { + if (side === 'buy') { + if (takerOrMaker === 'maker') { + makerCurrencyCode = market['base']; + takerCurrencyCode = market['quote']; + } else { + makerCurrencyCode = market['quote']; + takerCurrencyCode = market['base']; + } + } else { + if (takerOrMaker === 'maker') { + makerCurrencyCode = market['quote']; + takerCurrencyCode = market['base']; + } else { + makerCurrencyCode = market['base']; + takerCurrencyCode = market['quote']; + } + } + } else if (side === 'SELF_TRADING') { + if (takerSide === 'BID') { + makerCurrencyCode = market['quote']; + takerCurrencyCode = market['base']; + } else if (takerSide === 'ASK') { + makerCurrencyCode = market['base']; + takerCurrencyCode = market['quote']; + } + } + const makerFeeCost = this.safeFloat (trade, 'maker_fee'); + const takerFeeCost = this.safeFloat (trade, 'taker_fee'); + if (makerFeeCost !== undefined) { + if (takerFeeCost !== undefined) { + result['fees'] = [ + { 'cost': makerFeeCost, 'currency': makerCurrencyCode }, + { 'cost': takerFeeCost, 'currency': takerCurrencyCode }, + ]; + } else { + result['fee'] = { 'cost': makerFeeCost, 'currency': makerCurrencyCode }; + } + } else if (takerFeeCost !== undefined) { + result['fee'] = { 'cost': takerFeeCost, 'currency': takerCurrencyCode }; + } else { + result['fee'] = undefined; + } + return result; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + const response = await this.publicGetAssetPairsAssetPairNameTrades (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "id": 38199941, + // "price": "3378.67", + // "amount": "0.019812", + // "taker_side": "ASK", + // "created_at": "2019-01-29T06:05:56Z" + // }, + // { + // "id": 38199934, + // "price": "3376.14", + // "amount": "0.019384", + // "taker_side": "ASK", + // "created_at": "2019-01-29T06:05:40Z" + // } + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // close: '0.021562', + // high: '0.021563', + // low: '0.02156', + // open: '0.021563', + // time: '2019-11-21T07:54:00Z', + // volume: '59.84376' + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'time')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; // default 100, max 500 + } + const request = { + 'asset_pair_name': market['id'], + 'period': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + // const start = parseInt (since / 1000); + const end = this.sum (since, limit * this.parseTimeframe (timeframe) * 1000); + request['time'] = this.iso8601 (end); + } + const response = await this.publicGetAssetPairsAssetPairNameCandles (this.extend (request, params)); + // + // { + // code: 0, + // data: [ + // { + // close: '0.021656', + // high: '0.021658', + // low: '0.021652', + // open: '0.021652', + // time: '2019-11-21T09:30:00Z', + // volume: '53.08664' + // }, + // { + // close: '0.021652', + // high: '0.021656', + // low: '0.021652', + // open: '0.021656', + // time: '2019-11-21T09:29:00Z', + // volume: '88.39861' + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + // + // { + // "code":0, + // "data":[ + // {"asset_symbol":"NKC","balance":"0","locked_balance":"0"}, + // {"asset_symbol":"UBTC","balance":"0","locked_balance":"0"}, + // {"asset_symbol":"READ","balance":"0","locked_balance":"0"}, + // ], + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const symbol = this.safeString (balance, 'asset_symbol'); + const code = this.safeCurrencyCode (symbol); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": 10, + // "asset_pair_name": "EOS-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "FILLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z", + // } + // + const id = this.safeString (order, 'id'); + const marketId = this.safeString (order, 'asset_pair_name'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled_amount'); + let remaining = undefined; + if (amount !== undefined && filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + const status = this.parseOrderStatus (this.safeString (order, 'state')); + let side = this.safeString (order, 'side'); + if (side === 'BID') { + side = 'buy'; + } else { + side = 'sell'; + } + let cost = undefined; + if (filled !== undefined) { + if (price !== undefined) { + cost = filled * price; + } + } + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'updated_at')); + const average = this.safeFloat (order, 'avg_deal_price'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + side = (side === 'buy') ? 'BID' : 'ASK'; + const uppercaseType = type.toUpperCase (); + const request = { + 'asset_pair_name': market['id'], // asset pair name BTC-USDT, required + 'side': side, // order side one of "ASK"/"BID", required + 'amount': this.amountToPrecision (symbol, amount), // order amount, string, required + // 'price': this.priceToPrecision (symbol, price), // order price, string, required + 'type': uppercaseType, + // 'operator': 'GTE', // stop orders only, GTE greater than and equal, LTE less than and equal + // 'immediate_or_cancel': false, // limit orders only, must be false when post_only is true + // 'post_only': false, // limit orders only, must be false when immediate_or_cancel is true + }; + if (uppercaseType === 'LIMIT') { + request['price'] = this.priceToPrecision (symbol, price); + } else { + const isStopLimit = (uppercaseType === 'STOP_LIMIT'); + const isStopMarket = (uppercaseType === 'STOP_MARKET'); + if (isStopLimit || isStopMarket) { + const stopPrice = this.safeFloat2 (params, 'stop_price', 'stopPrice'); + if (stopPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a stop_price parameter'); + } + request['stop_price'] = this.priceToPrecision (symbol, stopPrice); + params = this.omit (params, [ 'stop_price', 'stopPrice' ]); + } + if (isStopLimit) { + request['price'] = this.priceToPrecision (symbol, price); + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "id": 10, + // "asset_pair_name": "EOS-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "FILLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z" + // } + // + const order = this.safeValue (response, 'data'); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'id': id }; + const response = await this.privatePostOrdersIdCancel (this.extend (request, params)); + // { + // "id": 10, + // "asset_pair_name": "EOS-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "CANCELLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z" + // } + const order = this.safeValue (response, 'data'); + return this.parseOrder (order); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + }; + const response = await this.privatePostOrdersCancel (this.extend (request, params)); + // + // { + // "code":0, + // "data": { + // "cancelled":[ + // 58272370, + // 58272377 + // ], + // "failed": [] + // } + // } + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'id': id }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + const order = this.safeValue (response, 'data', {}); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + // 'page_token': 'dxzef', // request page after this page token + // 'side': 'ASK', // 'ASK' or 'BID', optional + // 'state': 'FILLED', // 'CANCELLED', 'FILLED', 'PENDING' + // 'limit' 20, // default 20, max 200 + }; + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 200 + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // "code":0, + // "data": [ + // { + // "id": 10, + // "asset_pair_name": "ETH-BTC", + // "price": "10.00", + // "amount": "10.00", + // "filled_amount": "9.0", + // "avg_deal_price": "12.0", + // "side": "ASK", + // "state": "FILLED", + // "created_at":"2019-01-29T06:05:56Z", + // "updated_at":"2019-01-29T06:05:56Z", + // }, + // ], + // "page_token":"dxzef", + // } + // + const orders = this.safeValue (response, 'data', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument'); + } + const market = this.market (symbol); + const request = { + 'asset_pair_name': market['id'], + // 'page_token': 'dxzef', // request page after this page token + }; + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 200 + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "id": 10854280, + // "asset_pair_name": "XIN-USDT", + // "price": "70", + // "amount": "1", + // "taker_side": "ASK", + // "maker_order_id": 58284908, + // "taker_order_id": 58284909, + // "maker_fee": "0.0008", + // "taker_fee": "0.07", + // "side": "SELF_TRADING", + // "inserted_at": "2019-04-16T12:00:01Z" + // }, + // { + // "id": 10854263, + // "asset_pair_name": "XIN-USDT", + // "price": "75.7", + // "amount": "12.743149", + // "taker_side": "BID", + // "maker_order_id": null, + // "taker_order_id": 58284888, + // "maker_fee": null, + // "taker_fee": "0.0025486298", + // "side": "BID", + // "inserted_at": "2019-04-15T06:20:57Z" + // } + // ], + // "page_token":"dxfv" + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'PENDING': 'open', + 'FILLED': 'closed', + 'CANCELLED': 'canceled', + }; + return this.safeString (statuses, status); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'PENDING', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'FILLED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + nonce () { + return this.microseconds () * 1000; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + const baseUrl = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }); + let url = baseUrl + '/' + this.implodeParams (path, params); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const request = { + 'type': 'OpenAPIV2', + 'sub': this.apiKey, + 'nonce': nonce, + // 'recv_window': '30', // default 30 + }; + const jwt = this.jwt (request, this.encode (this.secret)); + headers = { + 'Authorization': 'Bearer ' + jwt, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset_symbol': currency['id'], + }; + const response = await this.privateGetAssetsAssetSymbolAddress (this.extend (request, params)); + // + // the actual response format is not the same as the documented one + // the data key contains an array in the actual response + // + // { + // "code":0, + // "message":"", + // "data":[ + // { + // "id":5521878, + // "chain":"Bitcoin", + // "value":"1GbmyKoikhpiQVZ1C9sbF17mTyvBjeobVe", + // "memo":"" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const dataLength = data.length; + if (dataLength < 1) { + throw new ExchangeError (this.id + 'fetchDepositAddress() returned empty address response'); + } + const firstElement = data[0]; + const address = this.safeString (firstElement, 'value'); + const tag = this.safeString (firstElement, 'memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + // what are other statuses here? + 'WITHHOLD': 'ok', // deposits + 'UNCONFIRMED': 'pending', + 'CONFIRMED': 'ok', // withdrawals + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "amount": "25.0", + // "asset_symbol": "BTS" + // "confirms": 100, + // "id": 5, + // "inserted_at": "2018-02-16T11:39:58.000Z", + // "is_internal": false, + // "kind": "default", + // "memo": "", + // "state": "WITHHOLD", + // "txid": "72e03037d144dae3d32b68b5045462b1049a0755", + // "updated_at": "2018-11-09T10:20:09.000Z", + // } + // + // fetchWithdrawals + // + // { + // "amount": "5", + // "asset_symbol": "ETH", + // "completed_at": "2018-03-15T16:13:45.610463Z", + // "customer_id": "10", + // "id": 10, + // "inserted_at": "2018-03-15T16:13:45.610463Z", + // "is_internal": true, + // "note": "2018-03-15T16:13:45.610463Z", + // "state": "CONFIRMED", + // "target_address": "0x4643bb6b393ac20a6175c713175734a72517c63d6f7" + // "txid": "0x4643bb6b393ac20a6175c713175734a72517c63d6f73a3ca90a15356f2e967da0", + // } + // + // withdraw + // + // { + // "id":1077391, + // "customer_id":1082679, + // "amount":"21.9000000000000000", + // "txid":"", + // "is_internal":false, + // "kind":"on_chain", + // "state":"PENDING", + // "inserted_at":"2020-06-03T00:50:57+00:00", + // "updated_at":"2020-06-03T00:50:57+00:00", + // "memo":"", + // "target_address":"rDYtYT3dBeuw376rvHqoZBKW3UmvguoBAf", + // "fee":"0.1000000000000000", + // "asset_symbol":"XRP" + // } + // + const currencyId = this.safeString (transaction, 'asset_symbol'); + const code = this.safeCurrencyCode (currencyId); + const id = this.safeInteger (transaction, 'id'); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const timestamp = this.parse8601 (this.safeString (transaction, 'inserted_at')); + const updated = this.parse8601 (this.safeString2 (transaction, 'updated_at', 'completed_at')); + const txid = this.safeString (transaction, 'txid'); + const address = this.safeString (transaction, 'target_address'); + const tag = this.safeString (transaction, 'memo'); + const type = ('customer_id' in transaction) ? 'deposit' : 'withdrawal'; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': undefined, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': undefined, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page_token': 'dxzef', // request page after this page token + // 'limit': 50, // optional, default 50 + // 'kind': 'string', // optional - air_drop, big_holder_dividend, default, eosc_to_eos, internal, equally_airdrop, referral_mining, one_holder_dividend, single_customer, snapshotted_airdrop, trade_mining + // 'asset_symbol': 'BTC', // optional + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset_symbol'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50 + } + const response = await this.privateGetDeposits (this.extend (request, params)); + // + // { + // "code": 0, + // "page_token": "NQ==", + // "data": [ + // { + // "id": 5, + // "amount": "25.0", + // "confirms": 100, + // "txid": "72e03037d144dae3d32b68b5045462b1049a0755", + // "is_internal": false, + // "inserted_at": "2018-02-16T11:39:58.000Z", + // "updated_at": "2018-11-09T10:20:09.000Z", + // "kind": "default", + // "memo": "", + // "state": "WITHHOLD", + // "asset_symbol": "BTS" + // } + // ] + // } + // + const deposits = this.safeValue (response, 'data', []); + return this.parseTransactions (deposits, code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page_token': 'dxzef', // request page after this page token + // 'limit': 50, // optional, default 50 + // 'kind': 'string', // optional - air_drop, big_holder_dividend, default, eosc_to_eos, internal, equally_airdrop, referral_mining, one_holder_dividend, single_customer, snapshotted_airdrop, trade_mining + // 'asset_symbol': 'BTC', // optional + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset_symbol'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50 + } + const response = await this.privateGetWithdrawals (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "id": 10, + // "customer_id": "10", + // "asset_symbol": "ETH", + // "amount": "5", + // "state": "CONFIRMED", + // "note": "2018-03-15T16:13:45.610463Z", + // "txid": "0x4643bb6b393ac20a6175c713175734a72517c63d6f73a3ca90a15356f2e967da0", + // "completed_at": "2018-03-15T16:13:45.610463Z", + // "inserted_at": "2018-03-15T16:13:45.610463Z", + // "is_internal": true, + // "target_address": "0x4643bb6b393ac20a6175c713175734a72517c63d6f7" + // } + // ], + // "page_token":"dxvf" + // } + // + const withdrawals = this.safeValue (response, 'data', []); + return this.parseTransactions (withdrawals, code, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'symbol': currency['id'], + 'target_address': address, + 'amount': this.currencyToPrecision (code, amount), + }; + if (tag !== undefined) { + request['memo'] = tag; + } + // requires write permission on the wallet + const response = await this.privatePostWithdrawals (this.extend (request, params)); + // + // { + // "code":0, + // "message":"", + // "data":{ + // "id":1077391, + // "customer_id":1082679, + // "amount":"21.9000000000000000", + // "txid":"", + // "is_internal":false, + // "kind":"on_chain", + // "state":"PENDING", + // "inserted_at":"2020-06-03T00:50:57+00:00", + // "updated_at":"2020-06-03T00:50:57+00:00", + // "memo":"", + // "target_address":"rDYtYT3dBeuw376rvHqoZBKW3UmvguoBAf", + // "fee":"0.1000000000000000", + // "asset_symbol":"XRP" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransaction (data, currency); + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"code":10013,"message":"Resource not found"} + // {"code":40004,"message":"invalid jwt"} + // + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + if (code !== '0') { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/binance.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/binance.js new file mode 100644 index 000000000..c9e459dfb --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/binance.js @@ -0,0 +1,2627 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, InvalidAddress, RateLimitExceeded, PermissionDenied, NotSupported, BadRequest, BadSymbol, AccountSuspended, OrderImmediatelyFillable } = require ('./base/errors'); +const { ROUND, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class binance extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'binance', + 'name': 'Binance', + 'countries': [ 'JP', 'MT' ], // Japan, Malta + 'rateLimit': 500, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': 'emulated', + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg', + 'test': { + 'dapiPublic': 'https://testnet.binancefuture.com/dapi/v1', + 'dapiPrivate': 'https://testnet.binancefuture.com/dapi/v1', + 'fapiPublic': 'https://testnet.binancefuture.com/fapi/v1', + 'fapiPrivate': 'https://testnet.binancefuture.com/fapi/v1', + 'fapiPrivateV2': 'https://testnet.binancefuture.com/fapi/v2', + 'public': 'https://testnet.binance.vision/api/v3', + 'private': 'https://testnet.binance.vision/api/v3', + 'v3': 'https://testnet.binance.vision/api/v3', + 'v1': 'https://testnet.binance.vision/api/v1', + }, + 'api': { + 'wapi': 'https://api.binance.com/wapi/v3', + 'sapi': 'https://api.binance.com/sapi/v1', + 'dapiPublic': 'https://dapi.binance.com/dapi/v1', + 'dapiPrivate': 'https://dapi.binance.com/dapi/v1', + 'dapiData': 'https://dapi.binance.com/futures/data', + 'fapiPublic': 'https://fapi.binance.com/fapi/v1', + 'fapiPrivate': 'https://fapi.binance.com/fapi/v1', + 'fapiData': 'https://fapi.binance.com/futures/data', + 'fapiPrivateV2': 'https://fapi.binance.com/fapi/v2', + 'public': 'https://api.binance.com/api/v3', + 'private': 'https://api.binance.com/api/v3', + 'v3': 'https://api.binance.com/api/v3', + 'v1': 'https://api.binance.com/api/v1', + }, + 'www': 'https://www.binance.com', + 'referral': 'https://www.binance.com/?ref=10205187', + 'doc': [ + 'https://binance-docs.github.io/apidocs/spot/en', + ], + 'api_management': 'https://www.binance.com/en/usercenter/settings/api-management', + 'fees': 'https://www.binance.com/en/fee/schedule', + }, + 'api': { + // the API structure below will need 3-layer apidefs + 'sapi': { + 'get': [ + 'accountSnapshot', + // these endpoints require this.apiKey + 'margin/asset', + 'margin/pair', + 'margin/allAssets', + 'margin/allPairs', + 'margin/priceIndex', + // these endpoints require this.apiKey + this.secret + 'asset/assetDividend', + 'margin/loan', + 'margin/repay', + 'margin/account', + 'margin/transfer', + 'margin/interestHistory', + 'margin/forceLiquidationRec', + 'margin/order', + 'margin/openOrders', + 'margin/allOrders', + 'margin/myTrades', + 'margin/maxBorrowable', + 'margin/maxTransferable', + 'margin/isolated/transfer', + 'margin/isolated/account', + 'margin/isolated/pair', + 'margin/isolated/allPairs', + 'futures/transfer', + 'futures/loan/borrow/history', + 'futures/loan/repay/history', + 'futures/loan/wallet', + 'futures/loan/configs', + 'futures/loan/calcAdjustLevel', + 'futures/loan/calcMaxAdjustAmount', + 'futures/loan/adjustCollateral/history', + 'futures/loan/liquidationHistory', + // https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi + 'capital/config/getall', // get networks for withdrawing USDT ERC20 vs USDT Omni + 'capital/deposit/address', + 'capital/deposit/hisrec', + 'capital/deposit/subAddress', + 'capital/deposit/subHisrec', + 'capital/withdraw/history', + 'sub-account/futures/account', + 'sub-account/futures/accountSummary', + 'sub-account/futures/positionRisk', + 'sub-account/futures/internalTransfer', + 'sub-account/margin/account', + 'sub-account/margin/accountSummary', + 'sub-account/spotSummary', + 'sub-account/status', + 'sub-account/transfer/subUserHistory', + // lending endpoints + 'lending/daily/product/list', + 'lending/daily/userLeftQuota', + 'lending/daily/userRedemptionQuota', + 'lending/daily/token/position', + 'lending/union/account', + 'lending/union/purchaseRecord', + 'lending/union/redemptionRecord', + 'lending/union/interestHistory', + 'lending/project/list', + 'lending/project/position/list', + // mining endpoints + 'mining/pub/algoList', + 'mining/pub/coinList', + 'mining/worker/detail', + 'mining/worker/list', + 'mining/payment/list', + 'mining/statistics/user/status', + 'mining/statistics/user/list', + // liquid swap endpoints + 'bswap/pools', + 'bswap/liquidity', + 'bswap/liquidityOps', + 'bswap/quote', + 'bswap/swap', + ], + 'post': [ + 'asset/dust', + 'account/disableFastWithdrawSwitch', + 'account/enableFastWithdrawSwitch', + 'capital/withdraw/apply', + 'margin/transfer', + 'margin/loan', + 'margin/repay', + 'margin/order', + 'margin/isolated/create', + 'margin/isolated/transfer', + 'sub-account/margin/transfer', + 'sub-account/margin/enable', + 'sub-account/margin/enable', + 'sub-account/futures/enable', + 'sub-account/futures/transfer', + 'sub-account/futures/internalTransfer', + 'sub-account/transfer/subToSub', + 'sub-account/transfer/subToMaster', + 'userDataStream', + 'userDataStream/isolated', + 'futures/transfer', + 'futures/loan/borrow', + 'futures/loan/repay', + 'futures/loan/adjustCollateral', + // lending + 'lending/customizedFixed/purchase', + 'lending/daily/purchase', + 'lending/daily/redeem', + // liquid swap endpoints + 'bswap/liquidityAdd', + 'bswap/liquidityRemove', + 'bswap/swap', + ], + 'put': [ + 'userDataStream', + 'userDataStream/isolated', + ], + 'delete': [ + 'margin/order', + 'userDataStream', + 'userDataStream/isolated', + ], + }, + 'wapi': { + 'post': [ + 'withdraw', + 'sub-account/transfer', + ], + 'get': [ + 'depositHistory', + 'withdrawHistory', + 'depositAddress', + 'accountStatus', + 'systemStatus', + 'apiTradingStatus', + 'userAssetDribbletLog', + 'tradeFee', + 'assetDetail', + 'sub-account/list', + 'sub-account/transfer/history', + 'sub-account/assets', + ], + }, + 'dapiPublic': { + 'get': [ + 'ping', + 'time', + 'exchangeInfo', + 'depth', + 'trades', + 'historicalTrades', + 'aggTrades', + 'premiumIndex', + 'fundingRate', + 'klines', + 'continuousKlines', + 'indexPriceKlines', + 'markPriceKlines', + 'ticker/24hr', + 'ticker/price', + 'ticker/bookTicker', + 'allForceOrders', + 'openInterest', + ], + }, + 'dapiData': { + 'get': [ + 'openInterestHist', + 'topLongShortAccountRatio', + 'topLongShortPositionRatio', + 'globalLongShortAccountRatio', + 'takerBuySellVol', + 'basis', + ], + }, + 'dapiPrivate': { + 'get': [ + 'positionSide/dual', + 'order', + 'openOrder', + 'openOrders', + 'allOrders', + 'balance', + 'account', + 'positionMargin/history', + 'positionRisk', + 'userTrades', + 'income', + 'leverageBracket', + 'forceOrders', + 'adlQuantile', + ], + 'post': [ + 'positionSide/dual', + 'order', + 'batchOrders', + 'countdownCancelAll', + 'leverage', + 'marginType', + 'positionMargin', + 'listenKey', + ], + 'put': [ + 'listenKey', + ], + 'delete': [ + 'order', + 'allOpenOrders', + 'batchOrders', + 'listenKey', + ], + }, + 'fapiPublic': { + 'get': [ + 'ping', + 'time', + 'exchangeInfo', + 'depth', + 'trades', + 'historicalTrades', + 'aggTrades', + 'klines', + 'fundingRate', + 'premiumIndex', + 'ticker/24hr', + 'ticker/price', + 'ticker/bookTicker', + 'allForceOrders', + 'openInterest', + 'indexInfo', + ], + }, + 'fapiData': { + 'get': [ + 'openInterestHist', + 'topLongShortAccountRatio', + 'topLongShortPositionRatio', + 'globalLongShortAccountRatio', + 'takerlongshortRatio', + ], + }, + 'fapiPrivate': { + 'get': [ + 'allForceOrders', + 'allOrders', + 'openOrder', + 'openOrders', + 'order', + 'account', + 'balance', + 'leverageBracket', + 'positionMargin/history', + 'positionRisk', + 'positionSide/dual', + 'userTrades', + 'income', + ], + 'post': [ + 'batchOrders', + 'positionSide/dual', + 'positionMargin', + 'marginType', + 'order', + 'leverage', + 'listenKey', + 'countdownCancelAll', + ], + 'put': [ + 'listenKey', + ], + 'delete': [ + 'batchOrders', + 'order', + 'allOpenOrders', + 'listenKey', + ], + }, + 'fapiPrivateV2': { + 'get': [ + 'account', + 'balance', + 'positionRisk', + ], + }, + 'v3': { + 'get': [ + 'ticker/price', + 'ticker/bookTicker', + ], + }, + 'public': { + 'get': [ + 'ping', + 'time', + 'depth', + 'trades', + 'aggTrades', + 'historicalTrades', + 'klines', + 'ticker/24hr', + 'ticker/price', + 'ticker/bookTicker', + 'exchangeInfo', + ], + 'put': [ 'userDataStream' ], + 'post': [ 'userDataStream' ], + 'delete': [ 'userDataStream' ], + }, + 'private': { + 'get': [ + 'allOrderList', // oco + 'openOrderList', // oco + 'orderList', // oco + 'order', + 'openOrders', + 'allOrders', + 'account', + 'myTrades', + ], + 'post': [ + 'order/oco', + 'order', + 'order/test', + ], + 'delete': [ + 'openOrders', // added on 2020-04-25 for canceling all open orders per symbol + 'orderList', // oco + 'order', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + }, + 'commonCurrencies': { + 'BCC': 'BCC', // kept for backward-compatibility https://github.com/ccxt/ccxt/issues/4848 + 'YOYO': 'YOYOW', + }, + // exchange-specific options + 'options': { + // 'fetchTradesMethod': 'publicGetAggTrades', // publicGetTrades, publicGetHistoricalTrades + 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel + 'defaultType': 'spot', // 'spot', 'future', 'margin', 'delivery' + 'hasAlreadyAuthenticatedSuccessfully': false, + 'warnOnFetchOpenOrdersWithoutSymbol': true, + 'recvWindow': 5 * 1000, // 5 sec, binance default + 'timeDifference': 0, // the difference between system clock and Binance clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision + 'newOrderRespType': { + 'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + 'limit': 'RESULT', // we change it from 'ACK' by default to 'RESULT' + }, + 'quoteOrderQty': true, // whether market orders support amounts in quote currency + }, + // https://binance-docs.github.io/apidocs/spot/en/#error-codes-2 + 'exceptions': { + 'API key does not exist': AuthenticationError, + 'Order would trigger immediately.': OrderImmediatelyFillable, + 'Order would immediately match and take.': OrderImmediatelyFillable, // {"code":-2010,"msg":"Order would immediately match and take."} + 'Account has insufficient balance for requested action.': InsufficientFunds, + 'Rest API trading is not enabled.': ExchangeNotAvailable, + "You don't have permission.": PermissionDenied, // {"msg":"You don't have permission.","success":false} + 'Market is closed.': ExchangeNotAvailable, // {"code":-1013,"msg":"Market is closed."} + 'Too many requests.': DDoSProtection, // {"msg":"Too many requests. Please try again later.","success":false} + '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} + '-1001': ExchangeNotAvailable, // 'Internal error; unable to process your request. Please try again.' + '-1002': AuthenticationError, // 'You are not authorized to execute this request.' + '-1003': RateLimitExceeded, // {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."} + '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL + '-1015': RateLimitExceeded, // 'Too many new orders; current limit is %s orders per %s.' + '-1016': ExchangeNotAvailable, // 'This service is no longer available.', + '-1020': BadRequest, // 'This operation is not supported.' + '-1021': InvalidNonce, // 'your time is ahead of server' + '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."} + '-1100': BadRequest, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price' + '-1101': BadRequest, // Too many parameters; expected %s and received %s. + '-1102': BadRequest, // Param %s or %s must be sent, but both were empty + '-1103': BadRequest, // An unknown parameter was sent. + '-1104': BadRequest, // Not all sent parameters were read, read 8 parameters but was sent 9 + '-1105': BadRequest, // Parameter %s was empty. + '-1106': BadRequest, // Parameter %s sent when not required. + '-1111': BadRequest, // Precision is over the maximum defined for this asset. + '-1112': InvalidOrder, // No orders on book for symbol. + '-1114': BadRequest, // TimeInForce parameter sent when not required. + '-1115': BadRequest, // Invalid timeInForce. + '-1116': BadRequest, // Invalid orderType. + '-1117': BadRequest, // Invalid side. + '-1118': BadRequest, // New client order ID was empty. + '-1119': BadRequest, // Original client order ID was empty. + '-1120': BadRequest, // Invalid interval. + '-1121': BadSymbol, // Invalid symbol. + '-1125': AuthenticationError, // This listenKey does not exist. + '-1127': BadRequest, // More than %s hours between startTime and endTime. + '-1128': BadRequest, // {"code":-1128,"msg":"Combination of optional parameters invalid."} + '-1130': BadRequest, // Data sent for paramter %s is not valid. + '-1131': BadRequest, // recvWindow must be less than 60000 + '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc... + '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER' + '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist' + '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." } + '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action." + '-3005': InsufficientFunds, // {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."} + '-3008': InsufficientFunds, // {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."} + '-3010': ExchangeError, // {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."} + '-3022': AccountSuspended, // You account's trading is banned. + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const type = this.safeString2 (this.options, 'fetchTime', 'defaultType', 'spot'); + let method = 'publicGetTime'; + if (type === 'future') { + method = 'fapiPublicGetTime'; + } else if (type === 'delivery') { + method = 'dapiPublicGetTime'; + } + const response = await this[method] (params); + return this.safeInteger (response, 'serverTime'); + } + + async loadTimeDifference (params = {}) { + const serverTime = await this.fetchTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = after - serverTime; + return this.options['timeDifference']; + } + + async fetchMarkets (params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchMarkets', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + if ((type !== 'spot') && (type !== 'future') && (type !== 'margin') && (type !== 'delivery')) { + throw new ExchangeError (this.id + " does not support '" + type + "' type, set exchange.options['defaultType'] to 'spot', 'margin', 'delivery' or 'future'"); // eslint-disable-line quotes + } + let method = 'publicGetExchangeInfo'; + if (type === 'future') { + method = 'fapiPublicGetExchangeInfo'; + } else if (type === 'delivery') { + method = 'dapiPublicGetExchangeInfo'; + } + const response = await this[method] (query); + // + // spot / margin + // + // { + // "timezone":"UTC", + // "serverTime":1575416692969, + // "rateLimits":[ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, + // {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":100}, + // {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":200000} + // ], + // "exchangeFilters":[], + // "symbols":[ + // { + // "symbol":"ETHBTC", + // "status":"TRADING", + // "baseAsset":"ETH", + // "baseAssetPrecision":8, + // "quoteAsset":"BTC", + // "quotePrecision":8, + // "baseCommissionPrecision":8, + // "quoteCommissionPrecision":8, + // "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"], + // "icebergAllowed":true, + // "ocoAllowed":true, + // "quoteOrderQtyMarketAllowed":true, + // "isSpotTradingAllowed":true, + // "isMarginTradingAllowed":true, + // "filters":[ + // {"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"}, + // {"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5}, + // {"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"}, + // {"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5}, + // {"filterType":"ICEBERG_PARTS","limit":10}, + // {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"63100.00000000","stepSize":"0.00000000"}, + // {"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5} + // ] + // }, + // ], + // } + // + // futures/usdt-margined (fapi) + // + // { + // "timezone":"UTC", + // "serverTime":1575417244353, + // "rateLimits":[ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, + // {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200} + // ], + // "exchangeFilters":[], + // "symbols":[ + // { + // "symbol":"BTCUSDT", + // "status":"TRADING", + // "maintMarginPercent":"2.5000", + // "requiredMarginPercent":"5.0000", + // "baseAsset":"BTC", + // "quoteAsset":"USDT", + // "pricePrecision":2, + // "quantityPrecision":3, + // "baseAssetPrecision":8, + // "quotePrecision":8, + // "filters":[ + // {"minPrice":"0.01","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.01"}, + // {"stepSize":"0.001","filterType":"LOT_SIZE","maxQty":"1000","minQty":"0.001"}, + // {"stepSize":"0.001","filterType":"MARKET_LOT_SIZE","maxQty":"1000","minQty":"0.001"}, + // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, + // {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} + // ], + // "orderTypes":["LIMIT","MARKET","STOP"], + // "timeInForce":["GTC","IOC","FOK","GTX"] + // } + // ] + // } + // + // delivery/coin-margined (dapi) + // + // { + // "timezone": "UTC", + // "serverTime": 1597667052958, + // "rateLimits": [ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000}, + // {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":6000} + // ], + // "exchangeFilters": [], + // "symbols": [ + // { + // "symbol": "BTCUSD_200925", + // "pair": "BTCUSD", + // "contractType": "CURRENT_QUARTER", + // "deliveryDate": 1601020800000, + // "onboardDate": 1590739200000, + // "contractStatus": "TRADING", + // "contractSize": 100, + // "marginAsset": "BTC", + // "maintMarginPercent": "2.5000", + // "requiredMarginPercent": "5.0000", + // "baseAsset": "BTC", + // "quoteAsset": "USD", + // "pricePrecision": 1, + // "quantityPrecision": 0, + // "baseAssetPrecision": 8, + // "quotePrecision": 8, + // "equalQtyPrecision": 4, + // "filters": [ + // {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"}, + // {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"stepSize":"0","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, + // {"multiplierDown":"0.9500","multiplierUp":"1.0500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} + // ], + // "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"], + // "timeInForce": ["GTC","IOC","FOK","GTX"] + // }, + // { + // "symbol": "BTCUSD_PERP", + // "pair": "BTCUSD", + // "contractType": "PERPETUAL", + // "deliveryDate": 4133404800000, + // "onboardDate": 1596006000000, + // "contractStatus": "TRADING", + // "contractSize": 100, + // "marginAsset": "BTC", + // "maintMarginPercent": "2.5000", + // "requiredMarginPercent": "5.0000", + // "baseAsset": "BTC", + // "quoteAsset": "USD", + // "pricePrecision": 1, + // "quantityPrecision": 0, + // "baseAssetPrecision": 8, + // "quotePrecision": 8, + // "equalQtyPrecision": 4, + // "filters": [ + // {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"}, + // {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"stepSize":"1","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"}, + // {"limit":200,"filterType":"MAX_NUM_ORDERS"}, + // {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"} + // ], + // "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"], + // "timeInForce": ["GTC","IOC","FOK","GTX"] + // } + // ] + // } + // + if (this.options['adjustForTimeDifference']) { + await this.loadTimeDifference (); + } + const markets = this.safeValue (response, 'symbols'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const spot = (type === 'spot'); + const future = (type === 'future'); + const delivery = (type === 'delivery'); + const id = this.safeString (market, 'symbol'); + const lowercaseId = this.safeStringLower (market, 'symbol'); + const baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const parts = id.split ('_'); + const lastPart = this.safeString (parts, 1); + const idSymbol = (delivery) && (lastPart !== 'PERP'); + const symbol = idSymbol ? id : (base + '/' + quote); + const filters = this.safeValue (market, 'filters', []); + const filtersByType = this.indexBy (filters, 'filterType'); + const precision = { + 'base': this.safeInteger (market, 'baseAssetPrecision'), + 'quote': this.safeInteger (market, 'quotePrecision'), + 'amount': this.safeInteger (market, 'baseAssetPrecision'), + 'price': this.safeInteger (market, 'quotePrecision'), + }; + const status = this.safeString2 (market, 'status', 'contractStatus'); + const active = (status === 'TRADING'); + const margin = this.safeValue (market, 'isMarginTradingAllowed', future || delivery); + const entry = { + 'id': id, + 'lowercaseId': lowercaseId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'margin': margin, + 'future': future, + 'delivery': delivery, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + if ('PRICE_FILTER' in filtersByType) { + const filter = this.safeValue (filtersByType, 'PRICE_FILTER', {}); + // PRICE_FILTER reports zero values for maxPrice + // since they updated filter types in November 2018 + // https://github.com/ccxt/ccxt/issues/4286 + // therefore limits['price']['max'] doesn't have any meaningful value except undefined + entry['limits']['price'] = { + 'min': this.safeFloat (filter, 'minPrice'), + 'max': undefined, + }; + const maxPrice = this.safeFloat (filter, 'maxPrice'); + if ((maxPrice !== undefined) && (maxPrice > 0)) { + entry['limits']['price']['max'] = maxPrice; + } + entry['precision']['price'] = this.precisionFromString (filter['tickSize']); + } + if ('LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'LOT_SIZE', {}); + const stepSize = this.safeString (filter, 'stepSize'); + entry['precision']['amount'] = this.precisionFromString (stepSize); + entry['limits']['amount'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MARKET_LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MARKET_LOT_SIZE', {}); + entry['limits']['market'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MIN_NOTIONAL' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MIN_NOTIONAL', {}); + entry['limits']['cost']['min'] = this.safeFloat (filter, 'minNotional'); + } + result.push (entry); + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetAccount'; + if (type === 'future') { + const options = this.safeValue (this.options, 'future', {}); + const fetchBalanceOptions = this.safeValue (options, 'fetchBalance', {}); + method = this.safeString (fetchBalanceOptions, 'method', 'fapiPrivateV2GetAccount'); + } else if (type === 'delivery') { + const options = this.safeValue (this.options, 'delivery', {}); + const fetchBalanceOptions = this.safeValue (options, 'fetchBalance', {}); + method = this.safeString (fetchBalanceOptions, 'method', 'dapiPrivateGetAccount'); + } else if (type === 'margin') { + method = 'sapiGetMarginAccount'; + } + const query = this.omit (params, 'type'); + const response = await this[method] (query); + // + // spot + // + // { + // makerCommission: 10, + // takerCommission: 10, + // buyerCommission: 0, + // sellerCommission: 0, + // canTrade: true, + // canWithdraw: true, + // canDeposit: true, + // updateTime: 1575357359602, + // accountType: "MARGIN", + // balances: [ + // { asset: "BTC", free: "0.00219821", locked: "0.00000000" }, + // ] + // } + // + // margin + // + // { + // "borrowEnabled":true, + // "marginLevel":"999.00000000", + // "totalAssetOfBtc":"0.00000000", + // "totalLiabilityOfBtc":"0.00000000", + // "totalNetAssetOfBtc":"0.00000000", + // "tradeEnabled":true, + // "transferEnabled":true, + // "userAssets":[ + // {"asset":"MATIC","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}, + // {"asset":"VET","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}, + // {"asset":"USDT","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"} + // ], + // } + // + // futures (fapi) + // + // fapiPrivateGetAccount + // + // { + // "feeTier":0, + // "canTrade":true, + // "canDeposit":true, + // "canWithdraw":true, + // "updateTime":0, + // "totalInitialMargin":"0.00000000", + // "totalMaintMargin":"0.00000000", + // "totalWalletBalance":"4.54000000", + // "totalUnrealizedProfit":"0.00000000", + // "totalMarginBalance":"4.54000000", + // "totalPositionInitialMargin":"0.00000000", + // "totalOpenOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"4.54000000", + // "assets":[ + // { + // "asset":"USDT", + // "walletBalance":"4.54000000", + // "unrealizedProfit":"0.00000000", + // "marginBalance":"4.54000000", + // "maintMargin":"0.00000000", + // "initialMargin":"0.00000000", + // "positionInitialMargin":"0.00000000", + // "openOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"4.54000000" + // } + // ], + // "positions":[ + // { + // "symbol":"BTCUSDT", + // "initialMargin":"0.00000", + // "maintMargin":"0.00000", + // "unrealizedProfit":"0.00000000", + // "positionInitialMargin":"0.00000", + // "openOrderInitialMargin":"0.00000" + // } + // ] + // } + // + // fapiPrivateV2GetAccount + // + // { + // "feeTier":0, + // "canTrade":true, + // "canDeposit":true, + // "canWithdraw":true, + // "updateTime":0, + // "totalInitialMargin":"0.00000000", + // "totalMaintMargin":"0.00000000", + // "totalWalletBalance":"0.00000000", + // "totalUnrealizedProfit":"0.00000000", + // "totalMarginBalance":"0.00000000", + // "totalPositionInitialMargin":"0.00000000", + // "totalOpenOrderInitialMargin":"0.00000000", + // "totalCrossWalletBalance":"0.00000000", + // "totalCrossUnPnl":"0.00000000", + // "availableBalance":"0.00000000", + // "maxWithdrawAmount":"0.00000000", + // "assets":[ + // { + // "asset":"BNB", + // "walletBalance":"0.01000000", + // "unrealizedProfit":"0.00000000", + // "marginBalance":"0.01000000", + // "maintMargin":"0.00000000", + // "initialMargin":"0.00000000", + // "positionInitialMargin":"0.00000000", + // "openOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"0.01000000", + // "crossWalletBalance":"0.01000000", + // "crossUnPnl":"0.00000000", + // "availableBalance":"0.01000000" + // } + // ], + // "positions":[ + // { + // "symbol":"BTCUSDT", + // "initialMargin":"0", + // "maintMargin":"0", + // "unrealizedProfit":"0.00000000", + // "positionInitialMargin":"0", + // "openOrderInitialMargin":"0", + // "leverage":"20", + // "isolated":false, + // "entryPrice":"0.00000", + // "maxNotional":"5000000", + // "positionSide":"BOTH" + // }, + // ] + // } + // + // fapiPrivateV2GetBalance + // + // [ + // { + // "accountAlias":"FzFzXquXXqoC", + // "asset":"BNB", + // "balance":"0.01000000", + // "crossWalletBalance":"0.01000000", + // "crossUnPnl":"0.00000000", + // "availableBalance":"0.01000000", + // "maxWithdrawAmount":"0.01000000" + // } + // ] + // + const result = { 'info': response }; + if ((type === 'spot') || (type === 'margin')) { + const balances = this.safeValue2 (response, 'balances', 'userAssets', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + } else { + let balances = response; + if (!Array.isArray (response)) { + balances = this.safeValue (response, 'assets', []); + } + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableBalance'); + account['used'] = this.safeFloat (balance, 'initialMargin'); + account['total'] = this.safeFloat2 (balance, 'marginBalance', 'balance'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 5000, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book + } + let method = 'publicGetDepth'; + if (market['future']) { + method = 'fapiPublicGetDepth'; + } else if (market['delivery']) { + method = 'dapiPublicGetDepth'; + } + const response = await this[method] (this.extend (request, params)); + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // { + // symbol: 'ETHBTC', + // priceChange: '0.00068700', + // priceChangePercent: '2.075', + // weightedAvgPrice: '0.03342681', + // prevClosePrice: '0.03310300', + // lastPrice: '0.03378900', + // lastQty: '0.07700000', + // bidPrice: '0.03378900', + // bidQty: '7.16800000', + // askPrice: '0.03379000', + // askQty: '24.00000000', + // openPrice: '0.03310200', + // highPrice: '0.03388900', + // lowPrice: '0.03306900', + // volume: '205478.41000000', + // quoteVolume: '6868.48826294', + // openTime: 1601469986932, + // closeTime: 1601556386932, + // firstId: 196098772, + // lastId: 196186315, + // count: 87544 + // } + // + const timestamp = this.safeInteger (ticker, 'closeTime'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'lastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), + 'open': this.safeFloat (ticker, 'openPrice'), + 'close': last, + 'last': last, + 'previousClose': this.safeFloat (ticker, 'prevClosePrice'), // previous day close + 'change': this.safeFloat (ticker, 'priceChange'), + 'percentage': this.safeFloat (ticker, 'priceChangePercent'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchStatus (params = {}) { + const response = await this.wapiGetSystemStatus (params); + let status = this.safeValue (response, 'status'); + if (status !== undefined) { + status = (status === 0) ? 'ok' : 'maintenance'; + this.status = this.extend (this.status, { + 'status': status, + 'updated': this.milliseconds (), + }); + } + return this.status; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = 'publicGetTicker24hr'; + if (market['future']) { + method = 'fapiPublicGetTicker24hr'; + } else if (market['delivery']) { + method = 'dapiPublicGetTicker24hr'; + } + const response = await this[method] (this.extend (request, params)); + if (Array.isArray (response)) { + const firstTicker = this.safeValue (response, 0, {}); + return this.parseTicker (firstTicker, market); + } + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchBidsAsks (symbols = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchBidsAsks', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = undefined; + if (type === 'future') { + method = 'fapiPublicGetTickerBookTicker'; + } else if (type === 'delivery') { + method = 'dapiPublicGetTickerBookTicker'; + } else { + method = 'publicGetTickerBookTicker'; + } + const response = await this[method] (query); + return this.parseTickers (response, symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let defaultMethod = undefined; + if (type === 'future') { + defaultMethod = 'fapiPublicGetTicker24hr'; + } else if (type === 'delivery') { + defaultMethod = 'dapiPublicGetTicker24hr'; + } else { + defaultMethod = 'publicGetTicker24hr'; + } + const method = this.safeString (this.options, 'fetchTickersMethod', defaultMethod); + const response = await this[method] (query); + return this.parseTickers (response, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591478520000, + // "0.02501300", + // "0.02501800", + // "0.02500000", + // "0.02500000", + // "22.19000000", + // 1591478579999, + // "0.55490906", + // 40, + // "10.92900000", + // "0.27336462", + // "0" + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default == max == 500 + } + let method = 'publicGetKlines'; + if (market['future']) { + method = 'fapiPublicGetKlines'; + } else if (market['delivery']) { + method = 'dapiPublicGetKlines'; + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"], + // [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"], + // [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + if ('isDustTrade' in trade) { + return this.parseDustTrade (trade, market); + } + // + // aggregate trades + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list + // + // { + // "a": 26129, // Aggregate tradeId + // "p": "0.01633102", // Price + // "q": "4.70443515", // Quantity + // "f": 27781, // First tradeId + // "l": 27781, // Last tradeId + // "T": 1498793709153, // Timestamp + // "m": true, // Was the buyer the maker? + // "M": true // Was the trade the best price match? + // } + // + // recent public trades and old public trades + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data + // + // { + // "id": 28457, + // "price": "4.00000100", + // "qty": "12.00000000", + // "time": 1499865549590, + // "isBuyerMaker": true, + // "isBestMatch": true + // } + // + // private trades + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data + // + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true + // } + // + // futures trades + // https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data + // + // { + // "accountId": 20, + // "buyer": False, + // "commission": "-0.07819010", + // "commissionAsset": "USDT", + // "counterPartyId": 653, + // "id": 698759, + // "maker": False, + // "orderId": 25851813, + // "price": "7819.01", + // "qty": "0.002", + // "quoteQty": "0.01563", + // "realizedPnl": "-0.91539999", + // "side": "SELL", + // "symbol": "BTCUSDT", + // "time": 1569514978020 + // } + // + const timestamp = this.safeInteger2 (trade, 'T', 'time'); + const price = this.safeFloat2 (trade, 'p', 'price'); + const amount = this.safeFloat2 (trade, 'q', 'qty'); + const id = this.safeString2 (trade, 'a', 'id'); + let side = undefined; + const orderId = this.safeString (trade, 'orderId'); + if ('m' in trade) { + side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally + } else if ('isBuyerMaker' in trade) { + side = trade['isBuyerMaker'] ? 'sell' : 'buy'; + } else if ('side' in trade) { + side = this.safeStringLower (trade, 'side'); + } else { + if ('isBuyer' in trade) { + side = trade['isBuyer'] ? 'buy' : 'sell'; // this is a true side + } + } + let fee = undefined; + if ('commission' in trade) { + fee = { + 'cost': this.safeFloat (trade, 'commission'), + 'currency': this.safeCurrencyCode (this.safeString (trade, 'commissionAsset')), + }; + } + let takerOrMaker = undefined; + if ('isMaker' in trade) { + takerOrMaker = trade['isMaker'] ? 'maker' : 'taker'; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'fromId': 123, // ID to get aggregate trades from INCLUSIVE. + // 'startTime': 456, // Timestamp in ms to get aggregate trades from INCLUSIVE. + // 'endTime': 789, // Timestamp in ms to get aggregate trades until INCLUSIVE. + // 'limit': 500, // default = 500, maximum = 1000 + }; + const defaultType = this.safeString2 (this.options, 'fetchTrades', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let defaultMethod = undefined; + if (type === 'future') { + defaultMethod = 'fapiPublicGetAggTrades'; + } else if (type === 'delivery') { + defaultMethod = 'dapiPublicGetAggTrades'; + } else { + defaultMethod = 'publicGetAggTrades'; + } + let method = this.safeString (this.options, 'fetchTradesMethod', defaultMethod); + if (method === 'publicGetAggTrades') { + if (since !== undefined) { + request['startTime'] = since; + // https://github.com/ccxt/ccxt/issues/6400 + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list + request['endTime'] = this.sum (since, 3600000); + } + if (type === 'future') { + method = 'fapiPublicGetAggTrades'; + } else if (type === 'delivery') { + method = 'dapiPublicGetAggTrades'; + } + } else if (method === 'publicGetHistoricalTrades') { + if (type === 'future') { + method = 'fapiPublicGetHistoricalTrades'; + } else if (type === 'delivery') { + method = 'dapiPublicGetHistoricalTrades'; + } + } + if (limit !== undefined) { + request['limit'] = limit; // default = 500, maximum = 1000 + } + // + // Caveats: + // - default limit (500) applies only if no other parameters set, trades up + // to the maximum limit may be returned to satisfy other parameters + // - if both limit and time window is set and time window contains more + // trades than the limit then the last trades from the window are returned + // - 'tradeId' accepted and returned by this method is "aggregate" trade id + // which is different from actual trade id + // - setting both fromId and time window results in error + const response = await this[method] (this.extend (request, query)); + // + // aggregate trades + // + // [ + // { + // "a": 26129, // Aggregate tradeId + // "p": "0.01633102", // Price + // "q": "4.70443515", // Quantity + // "f": 27781, // First tradeId + // "l": 27781, // Last tradeId + // "T": 1498793709153, // Timestamp + // "m": true, // Was the buyer the maker? + // "M": true // Was the trade the best price match? + // } + // ] + // + // recent public trades and historical public trades + // + // [ + // { + // "id": 28457, + // "price": "4.00000100", + // "qty": "12.00000000", + // "time": 1499865549590, + // "isBuyerMaker": true, + // "isBestMatch": true + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'PARTIALLY_FILLED': 'open', + 'FILLED': 'closed', + 'CANCELED': 'canceled', + 'PENDING_CANCEL': 'canceling', // currently unused + 'REJECTED': 'rejected', + 'EXPIRED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // spot + // + // { + // "symbol": "LTCBTC", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "0.0", + // "cummulativeQuoteQty": "0.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "icebergQty": "0.0", + // "time": 1499827319559, + // "updateTime": 1499827319559, + // "isWorking": true + // } + // + // futures + // + // { + // "symbol": "BTCUSDT", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "1.0", + // "cumQuote": "10.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "updateTime": 1499827319559 + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = undefined; + if ('time' in order) { + timestamp = this.safeInteger (order, 'time'); + } else if ('transactTime' in order) { + timestamp = this.safeInteger (order, 'transactTime'); + } + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'origQty'); + const filled = this.safeFloat (order, 'executedQty'); + let remaining = undefined; + // - Spot/Margin market: cummulativeQuoteQty + // - Futures market: cumQuote. + // Note this is not the actual cost, since Binance futures uses leverage to calculate margins. + let cost = this.safeFloat2 (order, 'cummulativeQuoteQty', 'cumQuote'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + if (this.options['parseOrderToPrecision']) { + remaining = parseFloat (this.amountToPrecision (symbol, remaining)); + } + remaining = Math.max (remaining, 0.0); + } + if (price !== undefined) { + if (cost === undefined) { + cost = price * filled; + } + } + } + const id = this.safeString (order, 'orderId'); + let type = this.safeStringLower (order, 'type'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + if (this.options['parseOrderToPrecision']) { + price = parseFloat (this.priceToPrecision (symbol, price)); + } + } + } + } + } else if (type === 'limit_maker') { + type = 'limit'; + } + const side = this.safeStringLower (order, 'side'); + let fee = undefined; + let trades = undefined; + const fills = this.safeValue (order, 'fills'); + if (fills !== undefined) { + trades = this.parseTrades (fills, market); + const numTrades = trades.length; + if (numTrades > 0) { + cost = trades[0]['cost']; + fee = { + 'cost': trades[0]['fee']['cost'], + 'currency': trades[0]['fee']['currency'], + }; + for (let i = 1; i < trades.length; i++) { + cost = this.sum (cost, trades[i]['cost']); + fee['cost'] = this.sum (fee['cost'], trades[i]['fee']['cost']); + } + } + } + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + if (this.options['parseOrderToPrecision']) { + average = parseFloat (this.priceToPrecision (symbol, average)); + } + } + if (this.options['parseOrderToPrecision']) { + cost = parseFloat (this.costToPrecision (symbol, cost)); + } + } + const clientOrderId = this.safeString (order, 'clientOrderId'); + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'createOrder', 'defaultType', market['type']); + const orderType = this.safeString (params, 'type', defaultType); + const clientOrderId = this.safeString2 (params, 'newClientOrderId', 'clientOrderId'); + params = this.omit (params, [ 'type', 'newClientOrderId', 'clientOrderId' ]); + let method = 'privatePostOrder'; + if (orderType === 'future') { + method = 'fapiPrivatePostOrder'; + } else if (orderType === 'delivery') { + method = 'dapiPrivatePostOrder'; + } else if (orderType === 'margin') { + method = 'sapiPostMarginOrder'; + } + // the next 5 lines are added to support for testing orders + if (market['spot']) { + const test = this.safeValue (params, 'test', false); + if (test) { + method += 'Test'; + } + params = this.omit (params, 'test'); + } + const uppercaseType = type.toUpperCase (); + const validOrderTypes = this.safeValue (market['info'], 'orderTypes'); + if (!this.inArray (uppercaseType, validOrderTypes)) { + throw new InvalidOrder (this.id + ' ' + type + ' is not a valid order type in ' + market['type'] + ' market ' + symbol); + } + const request = { + 'symbol': market['id'], + 'type': uppercaseType, + 'side': side.toUpperCase (), + }; + if (clientOrderId !== undefined) { + request['newClientOrderId'] = clientOrderId; + } + if (market['spot']) { + request['newOrderRespType'] = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + } + // additional required fields depending on the order type + let timeInForceIsRequired = false; + let priceIsRequired = false; + let stopPriceIsRequired = false; + let quantityIsRequired = false; + // + // spot/margin + // + // LIMIT timeInForce, quantity, price + // MARKET quantity or quoteOrderQty + // STOP_LOSS quantity, stopPrice + // STOP_LOSS_LIMIT timeInForce, quantity, price, stopPrice + // TAKE_PROFIT quantity, stopPrice + // TAKE_PROFIT_LIMIT timeInForce, quantity, price, stopPrice + // LIMIT_MAKER quantity, price + // + // futures + // + // LIMIT timeInForce, quantity, price + // MARKET quantity + // STOP/TAKE_PROFIT quantity, price, stopPrice + // STOP_MARKET stopPrice + // TAKE_PROFIT_MARKET stopPrice + // TRAILING_STOP_MARKET callbackRate + // + if (uppercaseType === 'MARKET') { + const quoteOrderQty = this.safeValue (this.options, 'quoteOrderQty', false); + if (quoteOrderQty) { + const quoteOrderQty = this.safeFloat (params, 'quoteOrderQty'); + const precision = market['precision']['price']; + if (quoteOrderQty !== undefined) { + request['quoteOrderQty'] = this.decimalToPrecision (quoteOrderQty, TRUNCATE, precision, this.precisionMode); + params = this.omit (params, 'quoteOrderQty'); + } else if (price !== undefined) { + request['quoteOrderQty'] = this.decimalToPrecision (amount * price, TRUNCATE, precision, this.precisionMode); + } else { + quantityIsRequired = true; + } + } else { + quantityIsRequired = true; + } + } else if (uppercaseType === 'LIMIT') { + priceIsRequired = true; + timeInForceIsRequired = true; + quantityIsRequired = true; + } else if ((uppercaseType === 'STOP_LOSS') || (uppercaseType === 'TAKE_PROFIT')) { + stopPriceIsRequired = true; + quantityIsRequired = true; + if (market['future']) { + priceIsRequired = true; + } + } else if ((uppercaseType === 'STOP_LOSS_LIMIT') || (uppercaseType === 'TAKE_PROFIT_LIMIT')) { + quantityIsRequired = true; + stopPriceIsRequired = true; + priceIsRequired = true; + timeInForceIsRequired = true; + } else if (uppercaseType === 'LIMIT_MAKER') { + priceIsRequired = true; + quantityIsRequired = true; + } else if (uppercaseType === 'STOP') { + quantityIsRequired = true; + stopPriceIsRequired = true; + priceIsRequired = true; + } else if ((uppercaseType === 'STOP_MARKET') || (uppercaseType === 'TAKE_PROFIT_MARKET')) { + const closePosition = this.safeValue (params, 'closePosition'); + if (closePosition === undefined) { + quantityIsRequired = true; + } + stopPriceIsRequired = true; + } else if (uppercaseType === 'TRAILING_STOP_MARKET') { + quantityIsRequired = true; + const callbackRate = this.safeFloat (params, 'callbackRate'); + if (callbackRate === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a callbackRate extra param for a ' + type + ' order'); + } + } + if (quantityIsRequired) { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (priceIsRequired) { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a price argument for a ' + type + ' order'); + } + request['price'] = this.priceToPrecision (symbol, price); + } + if (timeInForceIsRequired) { + request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel + } + if (stopPriceIsRequired) { + const stopPrice = this.safeFloat (params, 'stopPrice'); + if (stopPrice === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order'); + } else { + params = this.omit (params, 'stopPrice'); + request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); + } + } + const response = await this[method] (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetOrder'; + if (type === 'future') { + method = 'fapiPrivateGetOrder'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetOrder'; + } else if (type === 'margin') { + method = 'sapiGetMarginOrder'; + } + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['origClientOrderId'] = clientOrderId; + } else { + request['orderId'] = parseInt (id); + } + const query = this.omit (params, [ 'type', 'clientOrderId', 'origClientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + return this.parseOrder (response, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOrders', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetAllOrders'; + if (type === 'future') { + method = 'fapiPrivateGetAllOrders'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetAllOrders'; + } else if (type === 'margin') { + method = 'sapiGetMarginAllOrders'; + } + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // "symbol": "LTCBTC", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "0.0", + // "cummulativeQuoteQty": "0.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "icebergQty": "0.0", + // "time": 1499827319559, + // "updateTime": 1499827319559, + // "isWorking": true + // } + // ] + // + // futures + // + // [ + // { + // "symbol": "BTCUSDT", + // "orderId": 1, + // "clientOrderId": "myOrder1", + // "price": "0.1", + // "origQty": "1.0", + // "executedQty": "1.0", + // "cumQuote": "10.0", + // "status": "NEW", + // "timeInForce": "GTC", + // "type": "LIMIT", + // "side": "BUY", + // "stopPrice": "0.0", + // "updateTime": 1499827319559 + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let query = undefined; + let type = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', market['type']); + type = this.safeString (params, 'type', defaultType); + query = this.omit (params, 'type'); + } else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) { + const symbols = this.symbols; + const numSymbols = symbols.length; + const fetchOpenOrdersRateLimit = parseInt (numSymbols / 2); + throw new ExchangeError (this.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.'); + } else { + const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', 'spot'); + type = this.safeString (params, 'type', defaultType); + query = this.omit (params, 'type'); + } + let method = 'privateGetOpenOrders'; + if (type === 'future') { + method = 'fapiPrivateGetOpenOrders'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetOpenOrders'; + } else if (type === 'margin') { + method = 'sapiGetMarginOpenOrders'; + } + const response = await this[method] (this.extend (request, query)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOpenOrders', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + // https://github.com/ccxt/ccxt/issues/6507 + const origClientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + const request = { + 'symbol': market['id'], + // 'orderId': parseInt (id), + // 'origClientOrderId': id, + }; + if (origClientOrderId === undefined) { + request['orderId'] = parseInt (id); + } else { + request['origClientOrderId'] = origClientOrderId; + } + let method = 'privateDeleteOrder'; + if (type === 'future') { + method = 'fapiPrivateDeleteOrder'; + } else if (type === 'delivery') { + method = 'dapiPrivateDeleteOrder'; + } else if (type === 'margin') { + method = 'sapiDeleteMarginOrder'; + } + const query = this.omit (params, [ 'type', 'origClientOrderId', 'clientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const defaultType = this.safeString2 (this.options, 'cancelAllOrders', 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = 'privateDeleteOpenOrders'; + if (type === 'future') { + method = 'fapiPrivateDeleteAllOpenOrders'; + } else if (type === 'delivery') { + method = 'dapiPrivateDeleteAllOpenOrders'; + } + const response = await this[method] (this.extend (request, query)); + if (Array.isArray (response)) { + return this.parseOrders (response, market); + } else { + return response; + } + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.fetchBalance (params); + const info = this.safeValue (response, 'info', {}); + // + // futures, delivery + // + // { + // "feeTier":0, + // "canTrade":true, + // "canDeposit":true, + // "canWithdraw":true, + // "updateTime":0, + // "assets":[ + // { + // "asset":"ETH", + // "walletBalance":"0.09886711", + // "unrealizedProfit":"0.00000000", + // "marginBalance":"0.09886711", + // "maintMargin":"0.00000000", + // "initialMargin":"0.00000000", + // "positionInitialMargin":"0.00000000", + // "openOrderInitialMargin":"0.00000000", + // "maxWithdrawAmount":"0.09886711", + // "crossWalletBalance":"0.09886711", + // "crossUnPnl":"0.00000000", + // "availableBalance":"0.09886711" + // } + // ], + // "positions":[ + // { + // "symbol":"BTCUSD_201225", + // "initialMargin":"0", + // "maintMargin":"0", + // "unrealizedProfit":"0.00000000", + // "positionInitialMargin":"0", + // "openOrderInitialMargin":"0", + // "leverage":"20", + // "isolated":false, + // "positionSide":"BOTH", + // "entryPrice":"0.00000000", + // "maxQty":"250", // "maxNotional" on futures + // }, + // ] + // } + // + const positions = this.safeValue2 (info, 'positions', 'userAssets', []); + // todo unify parsePosition/parsePositions + return positions; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeValue (params, 'type', market['type']); + let method = undefined; + if (type === 'spot') { + method = 'privateGetMyTrades'; + } else if (type === 'future') { + method = 'fapiPrivateGetUserTrades'; + } else if (type === 'delivery') { + method = 'dapiPrivateGetUserTrades'; + } + params = this.omit (params, 'type'); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot trade + // + // [ + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true, + // } + // ] + // + // futures trade + // + // [ + // { + // "accountId": 20, + // "buyer": False, + // "commission": "-0.07819010", + // "commissionAsset": "USDT", + // "counterPartyId": 653, + // "id": 698759, + // "maker": False, + // "orderId": 25851813, + // "price": "7819.01", + // "qty": "0.002", + // "quoteQty": "0.01563", + // "realizedPnl": "-0.91539999", + // "side": "SELL", + // "symbol": "BTCUSDT", + // "time": 1569514978020 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchMyDustTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // + // Binance provides an opportunity to trade insignificant (i.e. non-tradable and non-withdrawable) + // token leftovers (of any asset) into `BNB` coin which in turn can be used to pay trading fees with it. + // The corresponding trades history is called the `Dust Log` and can be requested via the following end-point: + // https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data + // + await this.loadMarkets (); + const response = await this.wapiGetUserAssetDribbletLog (params); + // { success: true, + // results: { total: 1, + // rows: [ { transfered_total: "1.06468458", + // service_charge_total: "0.02172826", + // tran_id: 2701371634, + // logs: [ { tranId: 2701371634, + // serviceChargeAmount: "0.00012819", + // uid: "35103861", + // amount: "0.8012", + // operateTime: "2018-10-07 17:56:07", + // transferedAmount: "0.00628141", + // fromAsset: "ADA" } ], + // operate_time: "2018-10-07 17:56:06" } ] } } + const results = this.safeValue (response, 'results', {}); + const rows = this.safeValue (results, 'rows', []); + const data = []; + for (let i = 0; i < rows.length; i++) { + const logs = rows[i]['logs']; + for (let j = 0; j < logs.length; j++) { + logs[j]['isDustTrade'] = true; + data.push (logs[j]); + } + } + const trades = this.parseTrades (data, undefined, since, limit); + return this.filterBySinceLimit (trades, since, limit); + } + + parseDustTrade (trade, market = undefined) { + // { tranId: 2701371634, + // serviceChargeAmount: "0.00012819", + // uid: "35103861", + // amount: "0.8012", + // operateTime: "2018-10-07 17:56:07", + // transferedAmount: "0.00628141", + // fromAsset: "ADA" }, + const orderId = this.safeString (trade, 'tranId'); + const timestamp = this.parse8601 (this.safeString (trade, 'operateTime')); + const tradedCurrency = this.safeCurrencyCode (this.safeString (trade, 'fromAsset')); + const earnedCurrency = this.currency ('BNB')['code']; + const applicantSymbol = earnedCurrency + '/' + tradedCurrency; + let tradedCurrencyIsQuote = false; + if (applicantSymbol in this.markets) { + tradedCurrencyIsQuote = true; + } + // + // Warning + // Binance dust trade `fee` is already excluded from the `BNB` earning reported in the `Dust Log`. + // So the parser should either set the `fee.cost` to `0` or add it on top of the earned + // BNB `amount` (or `cost` depending on the trade `side`). The second of the above options + // is much more illustrative and therefore preferable. + // + const fee = { + 'currency': earnedCurrency, + 'cost': this.safeFloat (trade, 'serviceChargeAmount'), + }; + let symbol = undefined; + let amount = undefined; + let cost = undefined; + let side = undefined; + if (tradedCurrencyIsQuote) { + symbol = applicantSymbol; + amount = this.sum (this.safeFloat (trade, 'transferedAmount'), fee['cost']); + cost = this.safeFloat (trade, 'amount'); + side = 'buy'; + } else { + symbol = tradedCurrency + '/' + earnedCurrency; + amount = this.safeFloat (trade, 'amount'); + cost = this.sum (this.safeFloat (trade, 'transferedAmount'), fee['cost']); + side = 'sell'; + } + let price = undefined; + if (cost !== undefined) { + if (amount) { + price = cost / amount; + } + } + const id = undefined; + const type = undefined; + const takerOrMaker = undefined; + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'amount': amount, + 'price': price, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + // max 3 months range https://github.com/ccxt/ccxt/issues/6495 + request['endTime'] = this.sum (since, 7776000000); + } + const response = await this.wapiGetDepositHistory (this.extend (request, params)); + // + // { success: true, + // depositList: [ { insertTime: 1517425007000, + // amount: 0.3, + // address: "0x0123456789abcdef", + // addressTag: "", + // txId: "0x0123456789abcdef", + // asset: "ETH", + // status: 1 } ] } + // + return this.parseTransactions (response['depositList'], currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + // max 3 months range https://github.com/ccxt/ccxt/issues/6495 + request['endTime'] = this.sum (since, 7776000000); + } + const response = await this.wapiGetWithdrawHistory (this.extend (request, params)); + // + // { withdrawList: [ { amount: 14, + // address: "0x0123456789abcdef...", + // successTime: 1514489710000, + // transactionFee: 0.01, + // addressTag: "", + // txId: "0x0123456789abcdef...", + // id: "0123456789abcdef...", + // asset: "ETH", + // applyTime: 1514488724000, + // status: 6 }, + // { amount: 7600, + // address: "0x0123456789abcdef...", + // successTime: 1515323226000, + // transactionFee: 0.01, + // addressTag: "", + // txId: "0x0123456789abcdef...", + // id: "0123456789abcdef...", + // asset: "ICN", + // applyTime: 1515322539000, + // status: 6 } ], + // success: true } + // + return this.parseTransactions (response['withdrawList'], currency, since, limit); + } + + parseTransactionStatusByType (status, type = undefined) { + const statusesByType = { + 'deposit': { + '0': 'pending', + '1': 'ok', + }, + 'withdrawal': { + '0': 'pending', // Email Sent + '1': 'canceled', // Cancelled (different from 1 = ok in deposits) + '2': 'pending', // Awaiting Approval + '3': 'failed', // Rejected + '4': 'pending', // Processing + '5': 'failed', // Failure + '6': 'ok', // Completed + }, + }; + const statuses = this.safeValue (statusesByType, type, {}); + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // insertTime: 1517425007000, + // amount: 0.3, + // address: "0x0123456789abcdef", + // addressTag: "", + // txId: "0x0123456789abcdef", + // asset: "ETH", + // status: 1 + // } + // + // fetchWithdrawals + // + // { + // amount: 14, + // address: "0x0123456789abcdef...", + // successTime: 1514489710000, + // transactionFee: 0.01, + // addressTag: "", + // txId: "0x0123456789abcdef...", + // id: "0123456789abcdef...", + // asset: "ETH", + // applyTime: 1514488724000, + // status: 6 + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString (transaction, 'addressTag'); // set but unused + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const txid = this.safeString (transaction, 'txId'); + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + let timestamp = undefined; + const insertTime = this.safeInteger (transaction, 'insertTime'); + const applyTime = this.safeInteger (transaction, 'applyTime'); + let type = this.safeString (transaction, 'type'); + if (type === undefined) { + if ((insertTime !== undefined) && (applyTime === undefined)) { + type = 'deposit'; + timestamp = insertTime; + } else if ((insertTime === undefined) && (applyTime !== undefined)) { + type = 'withdrawal'; + timestamp = applyTime; + } + } + const status = this.parseTransactionStatusByType (this.safeString (transaction, 'status'), type); + const amount = this.safeFloat (transaction, 'amount'); + const feeCost = this.safeFloat (transaction, 'transactionFee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { 'currency': code, 'cost': feeCost }; + } + const updated = this.safeInteger (transaction, 'successTime'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.wapiGetDepositAddress (this.extend (request, params)); + const success = this.safeValue (response, 'success'); + if ((success === undefined) || !success) { + throw new InvalidAddress (this.id + ' fetchDepositAddress returned an empty response – create the deposit address in the user settings first.'); + } + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'addressTag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchFundingFees (codes = undefined, params = {}) { + const response = await this.wapiGetAssetDetail (params); + // + // { + // "success": true, + // "assetDetail": { + // "CTR": { + // "minWithdrawAmount": "70.00000000", //min withdraw amount + // "depositStatus": false,//deposit status + // "withdrawFee": 35, // withdraw fee + // "withdrawStatus": true, //withdraw status + // "depositTip": "Delisted, Deposit Suspended" //reason + // }, + // "SKY": { + // "minWithdrawAmount": "0.02000000", + // "depositStatus": true, + // "withdrawFee": 0.01, + // "withdrawStatus": true + // } + // } + // } + // + const detail = this.safeValue (response, 'assetDetail', {}); + const ids = Object.keys (detail); + const withdrawFees = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + withdrawFees[code] = this.safeFloat (detail[id], 'withdrawFee'); + } + return { + 'withdraw': withdrawFees, + 'deposit': {}, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // name is optional, can be overrided via params + const name = address.slice (0, 20); + const request = { + 'asset': currency['id'], + 'address': address, + 'amount': parseFloat (amount), + 'name': name, // name is optional, can be overrided via params + // https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi + // issue sapiGetCapitalConfigGetall () to get networks for withdrawing USDT ERC20 vs USDT Omni + // 'network': 'ETH', // 'BTC', 'TRX', etc, optional + }; + if (tag !== undefined) { + request['addressTag'] = tag; + } + const response = await this.wapiPostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + parseTradingFee (fee, market = undefined) { + // + // { + // "symbol": "ADABNB", + // "maker": 0.9000, + // "taker": 1.0000 + // } + // + const marketId = this.safeString (fee, 'symbol'); + const symbol = this.safeSymbol (marketId); + return { + 'info': fee, + 'symbol': symbol, + 'maker': this.safeFloat (fee, 'maker'), + 'taker': this.safeFloat (fee, 'taker'), + }; + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.wapiGetTradeFee (this.extend (request, params)); + // + // { + // "tradeFee": [ + // { + // "symbol": "ADABNB", + // "maker": 0.9000, + // "taker": 1.0000 + // } + // ], + // "success": true + // } + // + const tradeFee = this.safeValue (response, 'tradeFee', []); + const first = this.safeValue (tradeFee, 0, {}); + return this.parseTradingFee (first); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.wapiGetTradeFee (params); + // + // { + // "tradeFee": [ + // { + // "symbol": "ADABNB", + // "maker": 0.9000, + // "taker": 1.0000 + // } + // ], + // "success": true + // } + // + const tradeFee = this.safeValue (response, 'tradeFee', []); + const result = {}; + for (let i = 0; i < tradeFee.length; i++) { + const fee = this.parseTradingFee (tradeFee[i]); + const symbol = fee['symbol']; + result[symbol] = fee; + } + return result; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (!(api in this.urls['api'])) { + throw new NotSupported (this.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints'); + } + let url = this.urls['api'][api]; + url += '/' + path; + if (api === 'wapi') { + url += '.html'; + } + const userDataStream = (path === 'userDataStream') || (path === 'listenKey'); + if (path === 'historicalTrades') { + if (this.apiKey) { + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + } else { + throw new AuthenticationError (this.id + ' historicalTrades endpoint requires `apiKey` credential'); + } + } else if (userDataStream) { + if (this.apiKey) { + // v1 special case for userDataStream + body = this.urlencode (params); + headers = { + 'X-MBX-APIKEY': this.apiKey, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } else { + throw new AuthenticationError (this.id + ' userDataStream endpoint requires `apiKey` credential'); + } + } + if ((api === 'private') || (api === 'sapi') || (api === 'wapi' && path !== 'systemStatus') || (api === 'dapiPrivate') || (api === 'fapiPrivate') || (api === 'fapiPrivateV2')) { + this.checkRequiredCredentials (); + let query = undefined; + const recvWindow = this.safeInteger (this.options, 'recvWindow', 5000); + if ((api === 'sapi') && (path === 'asset/dust')) { + query = this.urlencodeWithArrayRepeat (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': recvWindow, + }, params)); + } else if (path === 'batchOrders') { + query = this.rawencode (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': recvWindow, + }, params)); + } else { + query = this.urlencode (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': recvWindow, + }, params)); + } + const signature = this.hmac (this.encode (query), this.encode (this.secret)); + query += '&' + 'signature=' + signature; + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + if ((method === 'GET') || (method === 'DELETE') || (api === 'wapi')) { + url += '?' + query; + } else { + body = query; + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + } else { + // userDataStream endpoints are public, but POST, PUT, DELETE + // therefore they don't accept URL query arguments + // https://github.com/ccxt/ccxt/issues/5224 + if (!userDataStream) { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 418) || (code === 429)) { + throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body); + } + // error response in a form: { "code": -1013, "msg": "Invalid quantity." } + // following block cointains legacy checks against message patterns in "msg" property + // will switch "code" checks eventually, when we know all of them + if (code >= 400) { + if (body.indexOf ('Price * QTY is zero or less') >= 0) { + throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body); + } + if (body.indexOf ('LOT_SIZE') >= 0) { + throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size ' + body); + } + if (body.indexOf ('PRICE_FILTER') >= 0) { + throw new InvalidOrder (this.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use this.priceToPrecision (symbol, amount) ' + body); + } + } + if (response === undefined) { + return; // fallback to default error handler + } + // check success value for wapi endpoints + // response in format {'msg': 'The coin does not exist.', 'success': true/false} + const success = this.safeValue (response, 'success', true); + if (!success) { + const message = this.safeString (response, 'msg'); + let parsedMessage = undefined; + if (message !== undefined) { + try { + parsedMessage = JSON.parse (message); + } catch (e) { + // do nothing + parsedMessage = undefined; + } + if (parsedMessage !== undefined) { + response = parsedMessage; + } + } + } + const message = this.safeString (response, 'msg'); + if (message !== undefined) { + this.throwExactlyMatchedException (this.exceptions, message, this.id + ' ' + message); + } + // checks against error codes + const error = this.safeString (response, 'code'); + if (error !== undefined) { + // https://github.com/ccxt/ccxt/issues/6501 + // https://github.com/ccxt/ccxt/issues/7742 + if ((error === '200') || (error === '0')) { + return; + } + // a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} + // despite that their message is very confusing, it is raised by Binance + // on a temporary ban, the API key is valid, but disabled for a while + if ((error === '-2015') && this.options['hasAlreadyAuthenticatedSuccessfully']) { + throw new DDoSProtection (this.id + ' temporary banned: ' + body); + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, error, feedback); + throw new ExchangeError (feedback); + } + if (!success) { + throw new ExchangeError (this.id + ' ' + body); + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + // a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} + if ((api === 'private') || (api === 'wapi')) { + this.options['hasAlreadyAuthenticatedSuccessfully'] = true; + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/binanceus.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/binanceus.js new file mode 100644 index 000000000..c35f3f3e4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/binanceus.js @@ -0,0 +1,46 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const binance = require ('./binance.js'); + +// --------------------------------------------------------------------------- + +module.exports = class binanceus extends binance { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'binanceus', + 'name': 'Binance US', + 'countries': [ 'US' ], // US + 'certified': false, + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/65177307-217b7c80-da5f-11e9-876e-0b748ba0a358.jpg', + 'api': { + 'web': 'https://www.binance.us', + 'wapi': 'https://api.binance.us/wapi/v3', + 'public': 'https://api.binance.us/api/v1', + 'private': 'https://api.binance.us/api/v3', + 'v3': 'https://api.binance.us/api/v3', + 'v1': 'https://api.binance.us/api/v1', + }, + 'www': 'https://www.binance.us', + 'referral': 'https://www.binance.us/?ref=35005074', + 'doc': 'https://github.com/binance-us/binance-official-api-docs', + 'fees': 'https://www.binance.us/en/fee/schedule', + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1 + 'maker': 0.001, // 0.1% trading fee, zero fees for all trading pairs before November 1 + }, + }, + 'options': { + 'quoteOrderQty': false, + }, + }); + } +}; + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bit2c.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bit2c.js new file mode 100644 index 000000000..f09a98746 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bit2c.js @@ -0,0 +1,458 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, InvalidNonce, AuthenticationError, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bit2c extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bit2c', + 'name': 'Bit2C', + 'countries': [ 'IL' ], // Israel + 'rateLimit': 3000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766119-3593220e-5ece-11e7-8b3a-5a041f6bcc3f.jpg', + 'api': 'https://bit2c.co.il', + 'www': 'https://www.bit2c.co.il', + 'referral': 'https://bit2c.co.il/Aff/63bfed10-e359-420c-ab5a-ad368dab0baf', + 'doc': [ + 'https://www.bit2c.co.il/home/api', + 'https://github.com/OferE/bit2c', + ], + }, + 'api': { + 'public': { + 'get': [ + 'Exchanges/{pair}/Ticker', + 'Exchanges/{pair}/orderbook', + 'Exchanges/{pair}/trades', + 'Exchanges/{pair}/lasttrades', + ], + }, + 'private': { + 'post': [ + 'Merchant/CreateCheckout', + 'Order/AddCoinFundsRequest', + 'Order/AddFund', + 'Order/AddOrder', + 'Order/AddOrderMarketPriceBuy', + 'Order/AddOrderMarketPriceSell', + 'Order/CancelOrder', + 'Order/AddCoinFundsRequest', + 'Order/AddStopOrder', + 'Payment/GetMyId', + 'Payment/Send', + 'Payment/Pay', + ], + 'get': [ + 'Account/Balance', + 'Account/Balance/v2', + 'Order/MyOrders', + 'Order/GetById', + 'Order/AccountHistory', + 'Order/OrderHistory', + ], + }, + }, + 'markets': { + 'BTC/NIS': { 'id': 'BtcNis', 'symbol': 'BTC/NIS', 'base': 'BTC', 'quote': 'NIS', 'baseId': 'Btc', 'quoteId': 'Nis' }, + 'ETH/NIS': { 'id': 'EthNis', 'symbol': 'ETH/NIS', 'base': 'ETH', 'quote': 'NIS', 'baseId': 'Eth', 'quoteId': 'Nis' }, + 'BCH/NIS': { 'id': 'BchabcNis', 'symbol': 'BCH/NIS', 'base': 'BCH', 'quote': 'NIS', 'baseId': 'Bchabc', 'quoteId': 'Nis' }, + 'LTC/NIS': { 'id': 'LtcNis', 'symbol': 'LTC/NIS', 'base': 'LTC', 'quote': 'NIS', 'baseId': 'Ltc', 'quoteId': 'Nis' }, + 'ETC/NIS': { 'id': 'EtcNis', 'symbol': 'ETC/NIS', 'base': 'ETC', 'quote': 'NIS', 'baseId': 'Etc', 'quoteId': 'Nis' }, + 'BTG/NIS': { 'id': 'BtgNis', 'symbol': 'BTG/NIS', 'base': 'BTG', 'quote': 'NIS', 'baseId': 'Btg', 'quoteId': 'Nis' }, + 'BSV/NIS': { 'id': 'BchsvNis', 'symbol': 'BSV/NIS', 'base': 'BSV', 'quote': 'NIS', 'baseId': 'Bchsv', 'quoteId': 'Nis' }, + 'GRIN/NIS': { 'id': 'GrinNis', 'symbol': 'GRIN/NIS', 'base': 'GRIN', 'quote': 'NIS', 'baseId': 'Grin', 'quoteId': 'Nis' }, + }, + 'fees': { + 'trading': { + 'maker': 0.5 / 100, + 'taker': 0.5 / 100, + }, + }, + 'options': { + 'fetchTradesMethod': 'public_get_exchanges_pair_trades', + }, + 'exceptions': { + 'exact': { + 'Please provide valid APIkey': AuthenticationError, // { "error" : "Please provide valid APIkey" } + }, + 'broad': { + // { "error": "Please provide valid nonce in Request Nonce (1598218490) is not bigger than last nonce (1598218490)."} + // { "error": "Please provide valid nonce in Request UInt64.TryParse failed for nonce :" } + 'Please provide valid nonce': InvalidNonce, + 'please approve new terms of use on site': PermissionDenied, // { "error" : "please approve new terms of use on site." } + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balance = await this.privateGetAccountBalanceV2 (params); + // + // { + // "AVAILABLE_NIS": 0.0, + // "NIS": 0.0, + // "LOCKED_NIS": 0.0, + // "AVAILABLE_BTC": 0.0, + // "BTC": 0.0, + // "LOCKED_BTC": 0.0, + // "AVAILABLE_ETH": 0.0, + // "ETH": 0.0, + // "LOCKED_ETH": 0.0, + // "AVAILABLE_BCHSV": 0.0, + // "BCHSV": 0.0, + // "LOCKED_BCHSV": 0.0, + // "AVAILABLE_BCHABC": 0.0, + // "BCHABC": 0.0, + // "LOCKED_BCHABC": 0.0, + // "AVAILABLE_LTC": 0.0, + // "LTC": 0.0, + // "LOCKED_LTC": 0.0, + // "AVAILABLE_ETC": 0.0, + // "ETC": 0.0, + // "LOCKED_ETC": 0.0, + // "AVAILABLE_BTG": 0.0, + // "BTG": 0.0, + // "LOCKED_BTG": 0.0, + // "AVAILABLE_GRIN": 0.0, + // "GRIN": 0.0, + // "LOCKED_GRIN": 0.0, + // "Fees": { + // "BtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "EthNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "BchabcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "LtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "EtcNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "BtgNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "LtcBtc": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "BchsvNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 }, + // "GrinNis": { "FeeMaker": 1.0, "FeeTaker": 1.0 } + // } + // } + // + const result = { 'info': balance }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const account = this.account (); + const currencyId = this.currencyId (code); + const uppercase = currencyId.toUpperCase (); + if (uppercase in balance) { + account['free'] = this.safeFloat (balance, 'AVAILABLE_' + uppercase); + account['total'] = this.safeFloat (balance, uppercase); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const orderbook = await this.publicGetExchangesPairOrderbook (this.extend (request, params)); + return this.parseOrderBook (orderbook); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const ticker = await this.publicGetExchangesPairTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const averagePrice = this.safeFloat (ticker, 'av'); + const baseVolume = this.safeFloat (ticker, 'a'); + let quoteVolume = undefined; + if (baseVolume !== undefined && averagePrice !== undefined) { + quoteVolume = baseVolume * averagePrice; + } + const last = this.safeFloat (ticker, 'll'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'h'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'l'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': averagePrice, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = this.options['fetchTradesMethod']; + const request = { + 'pair': market['id'], + }; + if (since !== undefined) { + request['date'] = parseInt (since); + } + if (limit !== undefined) { + request['limit'] = limit; // max 100000 + } + const response = await this[method] (this.extend (request, params)); + if (typeof response === 'string') { + throw new ExchangeError (response); + } + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let method = 'privatePostOrderAddOrder'; + const request = { + 'Amount': amount, + 'Pair': this.marketId (symbol), + }; + if (type === 'market') { + method += 'MarketPrice' + this.capitalize (side); + } else { + request['Price'] = price; + request['Total'] = amount * price; + request['IsBid'] = (side === 'buy'); + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['NewOrder']['id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privatePostOrderCancelOrder (this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.privateGetOrderMyOrders (this.extend (request, params)); + const orders = this.safeValue (response, market['id'], {}); + const asks = this.safeValue (orders, 'ask', []); + const bids = this.safeValue (orders, 'bid', []); + return this.parseOrders (this.arrayConcat (asks, bids), market, since, limit); + } + + parseOrder (order, market = undefined) { + const timestamp = this.safeInteger (order, 'created'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let side = this.safeValue (order, 'type'); + if (side === 0) { + side = 'buy'; + } else if (side === 1) { + side = 'sell'; + } + const id = this.safeString (order, 'id'); + const status = this.safeString (order, 'status'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': undefined, + 'remaining': undefined, + 'cost': cost, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (limit !== undefined) { + request['take'] = limit; + } + request['take'] = limit; + if (since !== undefined) { + request['toTime'] = this.ymd (this.milliseconds (), '.'); + request['fromTime'] = this.ymd (since, '.'); + } + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privateGetOrderOrderHistory (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + let timestamp = undefined; + let id = undefined; + let price = undefined; + let amount = undefined; + let orderId = undefined; + let feeCost = undefined; + let side = undefined; + const reference = this.safeString (trade, 'reference'); + if (reference !== undefined) { + timestamp = this.safeTimestamp (trade, 'ticks'); + price = this.safeFloat (trade, 'price'); + amount = this.safeFloat (trade, 'firstAmount'); + const reference_parts = reference.split ('|'); // reference contains 'pair|orderId|tradeId' + if (market === undefined) { + const marketId = this.safeString (trade, 'pair'); + if (marketId in this.markets_by_id[marketId]) { + market = this.markets_by_id[marketId]; + } else if (reference_parts[0] in this.markets_by_id) { + market = this.markets_by_id[reference_parts[0]]; + } + } + orderId = reference_parts[1]; + id = reference_parts[2]; + side = this.safeInteger (trade, 'action'); + if (side === 0) { + side = 'buy'; + } else if (side === 1) { + side = 'sell'; + } + feeCost = this.safeFloat (trade, 'feeAmount'); + } else { + timestamp = this.safeTimestamp (trade, 'date'); + id = this.safeString (trade, 'tid'); + price = this.safeFloat (trade, 'price'); + amount = this.safeFloat (trade, 'amount'); + side = this.safeValue (trade, 'isBid'); + if (side !== undefined) { + if (side) { + side = 'buy'; + } else { + side = 'sell'; + } + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': price * amount, + 'fee': { + 'cost': feeCost, + 'currency': 'NIS', + 'rate': undefined, + }, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + if (api === 'public') { + url += '.json'; + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const query = this.extend ({ + 'nonce': nonce, + }, params); + const auth = this.urlencode (query); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + auth; + } + } else { + body = auth; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512', 'base64'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'key': this.apiKey, + 'sign': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { "error" : "please approve new terms of use on site." } + // { "error": "Please provide valid nonce in Request Nonce (1598218490) is not bigger than last nonce (1598218490)."} + // + const error = this.safeString (response, 'error'); + if (error !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitbank.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitbank.js new file mode 100644 index 000000000..722f16be6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitbank.js @@ -0,0 +1,622 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, InvalidNonce, InsufficientFunds, InvalidOrder, OrderNotFound, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitbank extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitbank', + 'name': 'bitbank', + 'countries': [ 'JP' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '4h': '4hour', + '8h': '8hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/37808081-b87f2d9c-2e59-11e8-894d-c1900b7584fe.jpg', + 'api': { + 'public': 'https://public.bitbank.cc', + 'private': 'https://api.bitbank.cc', + }, + 'www': 'https://bitbank.cc/', + 'doc': 'https://docs.bitbank.cc/', + 'fees': 'https://bitbank.cc/docs/fees/', + }, + 'api': { + 'public': { + 'get': [ + '{pair}/ticker', + '{pair}/depth', + '{pair}/transactions', + '{pair}/transactions/{yyyymmdd}', + '{pair}/candlestick/{candletype}/{yyyymmdd}', + ], + }, + 'private': { + 'get': [ + 'user/assets', + 'user/spot/order', + 'user/spot/active_orders', + 'user/spot/trade_history', + 'user/withdrawal_account', + ], + 'post': [ + 'user/spot/order', + 'user/spot/cancel_order', + 'user/spot/cancel_orders', + 'user/spot/orders_info', + 'user/request_withdrawal', + ], + }, + }, + 'markets': { + 'BCH/BTC': { 'id': 'bcc_btc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bcc', 'quoteId': 'btc' }, + 'BCH/JPY': { 'id': 'bcc_jpy', 'symbol': 'BCH/JPY', 'base': 'BCH', 'quote': 'JPY', 'baseId': 'bcc', 'quoteId': 'jpy' }, + 'MONA/BTC': { 'id': 'mona_btc', 'symbol': 'MONA/BTC', 'base': 'MONA', 'quote': 'BTC', 'baseId': 'mona', 'quoteId': 'btc' }, + 'MONA/JPY': { 'id': 'mona_jpy', 'symbol': 'MONA/JPY', 'base': 'MONA', 'quote': 'JPY', 'baseId': 'mona', 'quoteId': 'jpy' }, + 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc' }, + 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' }, + 'XRP/JPY': { 'id': 'xrp_jpy', 'symbol': 'XRP/JPY', 'base': 'XRP', 'quote': 'JPY', 'baseId': 'xrp', 'quoteId': 'jpy' }, + 'XLM/JPY': { 'id': 'xlm_jpy', 'symbol': 'XLM/JPY', 'base': 'XLM', 'quote': 'JPY', 'baseId': 'xlm', 'quoteId': 'jpy' }, + 'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' }, + 'ETH/JPY': { 'id': 'eth_jpy', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy' }, + 'LTC/JPY': { 'id': 'ltc_jpy', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy' }, + 'XRP/BTC': { 'id': 'xrp_btc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc' }, + }, + 'fees': { + 'trading': { + 'maker': -0.02 / 100, + 'taker': 0.12 / 100, + }, + 'funding': { + 'withdraw': { + // 'JPY': (amount > 30000) ? 756 : 540, + 'BTC': 0.001, + 'LTC': 0.001, + 'XRP': 0.15, + 'ETH': 0.0005, + 'MONA': 0.001, + 'BCC': 0.001, + }, + }, + }, + 'precision': { + 'price': 8, + 'amount': 8, + }, + 'exceptions': { + '20001': AuthenticationError, + '20002': AuthenticationError, + '20003': AuthenticationError, + '20005': AuthenticationError, + '20004': InvalidNonce, + '40020': InvalidOrder, + '40021': InvalidOrder, + '40025': ExchangeError, + '40013': OrderNotFound, + '40014': OrderNotFound, + '50008': PermissionDenied, + '50009': OrderNotFound, + '50010': OrderNotFound, + '60001': InsufficientFunds, + '60005': InvalidOrder, + }, + }); + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTicker (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetPairDepth (this.extend (request, params)); + const orderbook = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeInteger (trade, 'executed_at'); + let symbol = undefined; + let feeCurrency = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const id = this.safeString2 (trade, 'transaction_id', 'trade_id'); + const takerOrMaker = this.safeString (trade, 'maker_taker'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee_amount_quote'); + if (feeCost !== undefined) { + fee = { + 'currency': market['quote'], + 'cost': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'order_id'); + const type = this.safeString (trade, 'type'); + const side = this.safeString (trade, 'side'); + return { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTransactions (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'transactions', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "0.02501786", + // "0.02501786", + // "0.02501786", + // "0.02501786", + // "0.0000", + // 1591488000000 + // ] + // + return [ + this.safeInteger (ohlcv, 5), + this.safeFloat (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let date = this.milliseconds (); + date = this.ymd (date); + date = date.split ('-'); + const request = { + 'pair': market['id'], + 'candletype': this.timeframes[timeframe], + 'yyyymmdd': date.join (''), + }; + const response = await this.publicGetPairCandlestickCandletypeYyyymmdd (this.extend (request, params)); + // + // { + // "success":1, + // "data":{ + // "candlestick":[ + // { + // "type":"5min", + // "ohlcv":[ + // ["0.02501786","0.02501786","0.02501786","0.02501786","0.0000",1591488000000], + // ["0.02501747","0.02501953","0.02501747","0.02501953","0.3017",1591488300000], + // ["0.02501762","0.02501762","0.02500392","0.02500392","0.1500",1591488600000], + // ] + // } + // ], + // "timestamp":1591508668190 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const candlestick = this.safeValue (data, 'candlestick', []); + const first = this.safeValue (candlestick, 0, {}); + const ohlcv = this.safeValue (first, 'ohlcv', []); + return this.parseOHLCVs (ohlcv, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUserAssets (params); + const result = { 'info': response }; + const data = this.safeValue (response, 'data', {}); + const assets = this.safeValue (data, 'assets', []); + for (let i = 0; i < assets.length; i++) { + const balance = assets[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'free_amount'), + 'used': this.safeFloat (balance, 'locked_amount'), + 'total': this.safeFloat (balance, 'onhand_amount'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'UNFILLED': 'open', + 'PARTIALLY_FILLED': 'open', + 'FULLY_FILLED': 'closed', + 'CANCELED_UNFILLED': 'canceled', + 'CANCELED_PARTIALLY_FILLED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'order_id'); + const marketId = this.safeString (order, 'pair'); + let symbol = undefined; + if (marketId && !market && (marketId in this.marketsById)) { + market = this.marketsById[marketId]; + } + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (order, 'ordered_at'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'start_amount'); + const filled = this.safeFloat (order, 'executed_amount'); + const remaining = this.safeFloat (order, 'remaining_amount'); + const average = this.safeFloat (order, 'average_price'); + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = filled * average; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': order, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a price argument for both market and limit orders'); + } + const request = { + 'pair': market['id'], + 'amount': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), + 'side': side, + 'type': type, + }; + const response = await this.privatePostUserSpotOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'pair': market['id'], + }; + const response = await this.privatePostUserSpotCancelOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return data; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'pair': market['id'], + }; + const response = await this.privateGetUserSpotOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privateGetUserSpotActiveOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const orders = this.safeValue (data, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = {}; + if (market !== undefined) { + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['count'] = limit; + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privateGetUserSpotTradeHistory (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privateGetUserWithdrawalAccount (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + // Not sure about this if there could be more than one account... + const accounts = this.safeValue (data, 'accounts', []); + const firstAccount = this.safeValue (accounts, 0, {}); + const address = this.safeString (firstAccount, 'address'); + return { + 'currency': currency, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + if (!('uuid' in params)) { + throw new ExchangeError (this.id + ' uuid is required for withdrawal'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + 'amount': amount, + }; + const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const txid = this.safeString (data, 'txid'); + return { + 'info': response, + 'id': txid, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'][api] + '/'; + if (api === 'public') { + url += this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let auth = nonce; + url += this.version + '/' + this.implodeParams (path, params); + if (method === 'POST') { + body = this.json (query); + auth += body; + } else { + auth += '/' + this.version + '/' + path; + if (Object.keys (query).length) { + query = this.urlencode (query); + url += '?' + query; + auth += '?' + query; + } + } + headers = { + 'Content-Type': 'application/json', + 'ACCESS-KEY': this.apiKey, + 'ACCESS-NONCE': nonce, + 'ACCESS-SIGNATURE': this.hmac (this.encode (auth), this.encode (this.secret)), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const success = this.safeInteger (response, 'success'); + const data = this.safeValue (response, 'data'); + if (!success || !data) { + const errorMessages = { + '10000': 'URL does not exist', + '10001': 'A system error occurred. Please contact support', + '10002': 'Invalid JSON format. Please check the contents of transmission', + '10003': 'A system error occurred. Please contact support', + '10005': 'A timeout error occurred. Please wait for a while and try again', + '20001': 'API authentication failed', + '20002': 'Illegal API key', + '20003': 'API key does not exist', + '20004': 'API Nonce does not exist', + '20005': 'API signature does not exist', + '20011': 'Two-step verification failed', + '20014': 'SMS authentication failed', + '30001': 'Please specify the order quantity', + '30006': 'Please specify the order ID', + '30007': 'Please specify the order ID array', + '30009': 'Please specify the stock', + '30012': 'Please specify the order price', + '30013': 'Trade Please specify either', + '30015': 'Please specify the order type', + '30016': 'Please specify asset name', + '30019': 'Please specify uuid', + '30039': 'Please specify the amount to be withdrawn', + '40001': 'The order quantity is invalid', + '40006': 'Count value is invalid', + '40007': 'End time is invalid', + '40008': 'end_id Value is invalid', + '40009': 'The from_id value is invalid', + '40013': 'The order ID is invalid', + '40014': 'The order ID array is invalid', + '40015': 'Too many specified orders', + '40017': 'Incorrect issue name', + '40020': 'The order price is invalid', + '40021': 'The trading classification is invalid', + '40022': 'Start date is invalid', + '40024': 'The order type is invalid', + '40025': 'Incorrect asset name', + '40028': 'uuid is invalid', + '40048': 'The amount of withdrawal is illegal', + '50003': 'Currently, this account is in a state where you can not perform the operation you specified. Please contact support', + '50004': 'Currently, this account is temporarily registered. Please try again after registering your account', + '50005': 'Currently, this account is locked. Please contact support', + '50006': 'Currently, this account is locked. Please contact support', + '50008': 'User identification has not been completed', + '50009': 'Your order does not exist', + '50010': 'Can not cancel specified order', + '50011': 'API not found', + '60001': 'The number of possessions is insufficient', + '60002': 'It exceeds the quantity upper limit of the tender buying order', + '60003': 'The specified quantity exceeds the limit', + '60004': 'The specified quantity is below the threshold', + '60005': 'The specified price is above the limit', + '60006': 'The specified price is below the lower limit', + '70001': 'A system error occurred. Please contact support', + '70002': 'A system error occurred. Please contact support', + '70003': 'A system error occurred. Please contact support', + '70004': 'We are unable to accept orders as the transaction is currently suspended', + '70005': 'Order can not be accepted because purchase order is currently suspended', + '70006': 'We can not accept orders because we are currently unsubscribed ', + '70009': 'We are currently temporarily restricting orders to be carried out. Please use the limit order.', + '70010': 'We are temporarily raising the minimum order quantity as the system load is now rising.', + }; + const errorClasses = this.exceptions; + const code = this.safeString (data, 'code'); + const message = this.safeString (errorMessages, code, 'Error'); + const ErrorClass = this.safeValue (errorClasses, code); + if (ErrorClass !== undefined) { + throw new ErrorClass (message); + } else { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitbay.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitbay.js new file mode 100644 index 000000000..f1cb3a288 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitbay.js @@ -0,0 +1,1272 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InvalidNonce, InsufficientFunds, AuthenticationError, InvalidOrder, ExchangeError, OrderNotFound, AccountSuspended, BadSymbol, OrderImmediatelyFillable, RateLimitExceeded, OnMaintenance, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitbay extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitbay', + 'name': 'BitBay', + 'countries': [ 'MT', 'EU' ], // Malta + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '3d': '259200', + '1w': '604800', + }, + 'urls': { + 'referral': 'https://auth.bitbay.net/ref/jHlbB4mIkdS1', + 'logo': 'https://user-images.githubusercontent.com/1294454/27766132-978a7bd8-5ece-11e7-9540-bc96d1e9bbb8.jpg', + 'www': 'https://bitbay.net', + 'api': { + 'public': 'https://bitbay.net/API/Public', + 'private': 'https://bitbay.net/API/Trading/tradingApi.php', + 'v1_01Public': 'https://api.bitbay.net/rest', + 'v1_01Private': 'https://api.bitbay.net/rest', + }, + 'doc': [ + 'https://bitbay.net/public-api', + 'https://bitbay.net/en/private-api', + 'https://bitbay.net/account/tab-api', + 'https://github.com/BitBayNet/API', + 'https://docs.bitbay.net/v1.0.1-en/reference', + ], + 'support': 'https://support.bitbay.net', + 'fees': 'https://bitbay.net/en/fees', + }, + 'api': { + 'public': { + 'get': [ + '{id}/all', + '{id}/market', + '{id}/orderbook', + '{id}/ticker', + '{id}/trades', + ], + }, + 'private': { + 'post': [ + 'info', + 'trade', + 'cancel', + 'orderbook', + 'orders', + 'transfer', + 'withdraw', + 'history', + 'transactions', + ], + }, + 'v1_01Public': { + 'get': [ + 'trading/ticker', + 'trading/ticker/{symbol}', + 'trading/stats', + 'trading/orderbook/{symbol}', + 'trading/transactions/{symbol}', + 'trading/candle/history/{symbol}/{resolution}', + ], + }, + 'v1_01Private': { + 'get': [ + 'payments/withdrawal/{detailId}', + 'payments/deposit/{detailId}', + 'trading/offer', + 'trading/config/{symbol}', + 'trading/history/transactions', + 'balances/BITBAY/history', + 'balances/BITBAY/balance', + 'fiat_cantor/rate/{baseId}/{quoteId}', + 'fiat_cantor/history', + ], + 'post': [ + 'trading/offer/{symbol}', + 'trading/config/{symbol}', + 'balances/BITBAY/balance', + 'balances/BITBAY/balance/transfer/{source}/{destination}', + 'fiat_cantor/exchange', + ], + 'delete': [ + 'trading/offer/{symbol}/{id}/{side}/{price}', + ], + 'put': [ + 'balances/BITBAY/balance/{id}', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.0, + 'taker': 0.1 / 100, + 'percentage': true, + 'tierBased': false, + }, + 'fiat': { + 'maker': 0.30 / 100, + 'taker': 0.43 / 100, + 'percentage': true, + 'tierBased': true, + 'tiers': { + 'taker': [ + [ 0.0043, 0 ], + [ 0.0042, 1250 ], + [ 0.0041, 3750 ], + [ 0.0040, 7500 ], + [ 0.0039, 10000 ], + [ 0.0038, 15000 ], + [ 0.0037, 20000 ], + [ 0.0036, 25000 ], + [ 0.0035, 37500 ], + [ 0.0034, 50000 ], + [ 0.0033, 75000 ], + [ 0.0032, 100000 ], + [ 0.0031, 150000 ], + [ 0.0030, 200000 ], + [ 0.0029, 250000 ], + [ 0.0028, 375000 ], + [ 0.0027, 500000 ], + [ 0.0026, 625000 ], + [ 0.0025, 875000 ], + ], + 'maker': [ + [ 0.0030, 0 ], + [ 0.0029, 1250 ], + [ 0.0028, 3750 ], + [ 0.0028, 7500 ], + [ 0.0027, 10000 ], + [ 0.0026, 15000 ], + [ 0.0025, 20000 ], + [ 0.0025, 25000 ], + [ 0.0024, 37500 ], + [ 0.0023, 50000 ], + [ 0.0023, 75000 ], + [ 0.0022, 100000 ], + [ 0.0021, 150000 ], + [ 0.0021, 200000 ], + [ 0.0020, 250000 ], + [ 0.0019, 375000 ], + [ 0.0018, 500000 ], + [ 0.0018, 625000 ], + [ 0.0017, 875000 ], + ], + }, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.0009, + 'LTC': 0.005, + 'ETH': 0.00126, + 'LSK': 0.2, + 'BCH': 0.0006, + 'GAME': 0.005, + 'DASH': 0.001, + 'BTG': 0.0008, + 'PLN': 4, + 'EUR': 1.5, + }, + }, + }, + 'options': { + 'fiatCurrencies': [ 'EUR', 'USD', 'GBP', 'PLN' ], + }, + 'exceptions': { + '400': ExchangeError, // At least one parameter wasn't set + '401': InvalidOrder, // Invalid order type + '402': InvalidOrder, // No orders with specified currencies + '403': InvalidOrder, // Invalid payment currency name + '404': InvalidOrder, // Error. Wrong transaction type + '405': InvalidOrder, // Order with this id doesn't exist + '406': InsufficientFunds, // No enough money or crypto + // code 407 not specified are not specified in their docs + '408': InvalidOrder, // Invalid currency name + '501': AuthenticationError, // Invalid public key + '502': AuthenticationError, // Invalid sign + '503': InvalidNonce, // Invalid moment parameter. Request time doesn't match current server time + '504': ExchangeError, // Invalid method + '505': AuthenticationError, // Key has no permission for this action + '506': AccountSuspended, // Account locked. Please contact with customer service + // codes 507 and 508 are not specified in their docs + '509': ExchangeError, // The BIC/SWIFT is required for this currency + '510': BadSymbol, // Invalid market name + 'FUNDS_NOT_SUFFICIENT': InsufficientFunds, + 'OFFER_FUNDS_NOT_EXCEEDING_MINIMUMS': InvalidOrder, + 'OFFER_NOT_FOUND': OrderNotFound, + 'OFFER_WOULD_HAVE_BEEN_PARTIALLY_FILLED': OrderImmediatelyFillable, + 'ACTION_LIMIT_EXCEEDED': RateLimitExceeded, + 'UNDER_MAINTENANCE': OnMaintenance, + 'REQUEST_TIMESTAMP_TOO_OLD': InvalidNonce, + 'PERMISSIONS_NOT_SUFFICIENT': PermissionDenied, + }, + 'commonCurrencies': { + 'GGC': 'Global Game Coin', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.v1_01PublicGetTradingTicker (params); + const fiatCurrencies = this.safeValue (this.options, 'fiatCurrencies', []); + // + // { + // status: 'Ok', + // items: { + // 'BSV-USD': { + // market: { + // code: 'BSV-USD', + // first: { currency: 'BSV', minOffer: '0.00035', scale: 8 }, + // second: { currency: 'USD', minOffer: '5', scale: 2 } + // }, + // time: '1557569762154', + // highestBid: '52.31', + // lowestAsk: '62.99', + // rate: '63', + // previousRate: '51.21', + // }, + // }, + // } + // + const result = []; + const items = this.safeValue (response, 'items'); + const keys = Object.keys (items); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const item = items[key]; + const market = this.safeValue (item, 'market', {}); + const first = this.safeValue (market, 'first', {}); + const second = this.safeValue (market, 'second', {}); + const baseId = this.safeString (first, 'currency'); + const quoteId = this.safeString (second, 'currency'); + const id = baseId + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (first, 'scale'), + 'price': this.safeInteger (second, 'scale'), + }; + let fees = this.safeValue (this.fees, 'trading', {}); + if (this.inArray (base, fiatCurrencies) || this.inArray (quote, fiatCurrencies)) { + fees = this.safeValue (this.fees, 'fiat', {}); + } + const maker = this.safeFloat (fees, 'maker'); + const taker = this.safeFloat (fees, 'taker'); + // todo: check that the limits have ben interpreted correctly + // todo: parse the fees page + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'active': undefined, + 'maker': maker, + 'taker': taker, + 'limits': { + 'amount': { + 'min': this.safeFloat (first, 'minOffer'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (second, 'minOffer'), + 'max': undefined, + }, + }, + 'info': item, + }); + } + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const response = await this.v1_01PrivateGetTradingOffer (this.extend (request, params)); + const items = this.safeValue (response, 'items', []); + return this.parseOrders (items, undefined, since, limit, { 'status': 'open' }); + } + + parseOrder (order, market = undefined) { + // + // { + // market: 'ETH-EUR', + // offerType: 'Sell', + // id: '93d3657b-d616-11e9-9248-0242ac110005', + // currentAmount: '0.04', + // lockedAmount: '0.04', + // rate: '280', + // startAmount: '0.04', + // time: '1568372806924', + // postOnly: false, + // hidden: false, + // mode: 'limit', + // receivedAmount: '0.0', + // firstBalanceId: '5b816c3e-437c-4e43-9bef-47814ae7ebfc', + // secondBalanceId: 'ab43023b-4079-414c-b340-056e3430a3af' + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.safeInteger (order, 'time'); + const amount = this.safeFloat (order, 'startAmount'); + const remaining = this.safeFloat (order, 'currentAmount'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = Math.max (0, amount - remaining); + } + } + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': undefined, + 'symbol': symbol, + 'type': this.safeString (order, 'mode'), + 'timeInForce': undefined, + 'side': this.safeStringLower (order, 'offerType'), + 'price': this.safeFloat (order, 'rate'), + 'stopPrice': undefined, + 'amount': amount, + 'cost': undefined, + 'filled': filled, + 'remaining': remaining, + 'average': undefined, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbol) { + const markets = [ this.marketId (symbol) ]; + request['markets'] = markets; + } + const query = { 'query': this.json (this.extend (request, params)) }; + const response = await this.v1_01PrivateGetTradingHistoryTransactions (query); + // + // { + // status: 'Ok', + // totalRows: '67', + // items: [ + // { + // id: 'b54659a0-51b5-42a0-80eb-2ac5357ccee2', + // market: 'BTC-EUR', + // time: '1541697096247', + // amount: '0.00003', + // rate: '4341.44', + // initializedBy: 'Sell', + // wasTaker: false, + // userAction: 'Buy', + // offerId: 'bd19804a-6f89-4a69-adb8-eb078900d006', + // commissionValue: null + // }, + // ] + // } + // + const items = this.safeValue (response, 'items'); + const result = this.parseTrades (items, undefined, since, limit); + if (symbol === undefined) { + return result; + } + return this.filterBySymbol (result, symbol); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.v1_01PrivateGetBalancesBITBAYBalance (params); + const balances = this.safeValue (response, 'balances'); + if (balances === undefined) { + throw new ExchangeError (this.id + ' empty balance response ' + this.json (response)); + } + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'lockedFunds'); + account['free'] = this.safeFloat (balance, 'availableFunds'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const orderbook = await this.publicGetIdOrderbook (this.extend (request, params)); + return this.parseOrderBook (orderbook); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const ticker = await this.publicGetIdTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const baseVolume = this.safeFloat (ticker, 'volume'); + const vwap = this.safeFloat (ticker, 'vwap'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max'), + 'low': this.safeFloat (ticker, 'min'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'average'), + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + const balanceCurrencies = []; + if (code !== undefined) { + const currency = this.currency (code); + balanceCurrencies.push (currency['id']); + } + let request = { + 'balanceCurrencies': balanceCurrencies, + }; + if (since !== undefined) { + request['fromTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + request = this.extend (request, params); + const response = await this.v1_01PrivateGetBalancesBITBAYHistory ({ 'query': this.json (request) }); + const items = response['items']; + return this.parseLedger (items, undefined, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // FUNDS_MIGRATION + // { + // "historyId": "84ea7a29-7da5-4de5-b0c0-871e83cad765", + // "balance": { + // "id": "821ec166-cb88-4521-916c-f4eb44db98df", + // "currency": "LTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "LTC" + // }, + // "detailId": null, + // "time": 1506128252968, + // "type": "FUNDS_MIGRATION", + // "value": 0.0009957, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 0.0009957, "available": 0.0009957, "locked": 0 }, + // "change": { "total": 0.0009957, "available": 0.0009957, "locked": 0 } + // } + // + // CREATE_BALANCE + // { + // "historyId": "d0fabd8d-9107-4b5e-b9a6-3cab8af70d49", + // "balance": { + // "id": "653ffcf2-3037-4ebe-8e13-d5ea1a01d60d", + // "currency": "BTG", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTG" + // }, + // "detailId": null, + // "time": 1508895244751, + // "type": "CREATE_BALANCE", + // "value": 0, + // "fundsBefore": { "total": null, "available": null, "locked": null }, + // "fundsAfter": { "total": 0, "available": 0, "locked": 0 }, + // "change": { "total": 0, "available": 0, "locked": 0 } + // } + // + // BITCOIN_GOLD_FORK + // { + // "historyId": "2b4d52d3-611c-473d-b92c-8a8d87a24e41", + // "balance": { + // "id": "653ffcf2-3037-4ebe-8e13-d5ea1a01d60d", + // "currency": "BTG", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTG" + // }, + // "detailId": null, + // "time": 1508895244778, + // "type": "BITCOIN_GOLD_FORK", + // "value": 0.00453512, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 0.00453512, "available": 0.00453512, "locked": 0 }, + // "change": { "total": 0.00453512, "available": 0.00453512, "locked": 0 } + // } + // + // ADD_FUNDS + // { + // "historyId": "3158236d-dae5-4a5d-81af-c1fa4af340fb", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "8e83a960-e737-4380-b8bb-259d6e236faa", + // "time": 1520631178816, + // "type": "ADD_FUNDS", + // "value": 0.628405, + // "fundsBefore": { "total": 0.00453512, "available": 0.00453512, "locked": 0 }, + // "fundsAfter": { "total": 0.63294012, "available": 0.63294012, "locked": 0 }, + // "change": { "total": 0.628405, "available": 0.628405, "locked": 0 } + // } + // + // TRANSACTION_PRE_LOCKING + // { + // "historyId": "e7d19e0f-03b3-46a8-bc72-dde72cc24ead", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": null, + // "time": 1520706403868, + // "type": "TRANSACTION_PRE_LOCKING", + // "value": -0.1, + // "fundsBefore": { "total": 0.63294012, "available": 0.63294012, "locked": 0 }, + // "fundsAfter": { "total": 0.63294012, "available": 0.53294012, "locked": 0.1 }, + // "change": { "total": 0, "available": -0.1, "locked": 0.1 } + // } + // + // TRANSACTION_POST_OUTCOME + // { + // "historyId": "c4010825-231d-4a9c-8e46-37cde1f7b63c", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "bf2876bc-b545-4503-96c8-ef4de8233876", + // "time": 1520706404032, + // "type": "TRANSACTION_POST_OUTCOME", + // "value": -0.01771415, + // "fundsBefore": { "total": 0.63294012, "available": 0.53294012, "locked": 0.1 }, + // "fundsAfter": { "total": 0.61522597, "available": 0.53294012, "locked": 0.08228585 }, + // "change": { "total": -0.01771415, "available": 0, "locked": -0.01771415 } + // } + // + // TRANSACTION_POST_INCOME + // { + // "historyId": "7f18b7af-b676-4125-84fd-042e683046f6", + // "balance": { + // "id": "ab43023b-4079-414c-b340-056e3430a3af", + // "currency": "EUR", + // "type": "FIAT", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "EUR" + // }, + // "detailId": "f5fcb274-0cc7-4385-b2d3-bae2756e701f", + // "time": 1520706404035, + // "type": "TRANSACTION_POST_INCOME", + // "value": 628.78, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 628.78, "available": 628.78, "locked": 0 }, + // "change": { "total": 628.78, "available": 628.78, "locked": 0 } + // } + // + // TRANSACTION_COMMISSION_OUTCOME + // { + // "historyId": "843177fa-61bc-4cbf-8be5-b029d856c93b", + // "balance": { + // "id": "ab43023b-4079-414c-b340-056e3430a3af", + // "currency": "EUR", + // "type": "FIAT", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "EUR" + // }, + // "detailId": "f5fcb274-0cc7-4385-b2d3-bae2756e701f", + // "time": 1520706404050, + // "type": "TRANSACTION_COMMISSION_OUTCOME", + // "value": -2.71, + // "fundsBefore": { "total": 766.06, "available": 766.06, "locked": 0 }, + // "fundsAfter": { "total": 763.35,"available": 763.35, "locked": 0 }, + // "change": { "total": -2.71, "available": -2.71, "locked": 0 } + // } + // + // TRANSACTION_OFFER_COMPLETED_RETURN + // { + // "historyId": "cac69b04-c518-4dc5-9d86-e76e91f2e1d2", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": null, + // "time": 1520714886425, + // "type": "TRANSACTION_OFFER_COMPLETED_RETURN", + // "value": 0.00000196, + // "fundsBefore": { "total": 0.00941208, "available": 0.00941012, "locked": 0.00000196 }, + // "fundsAfter": { "total": 0.00941208, "available": 0.00941208, "locked": 0 }, + // "change": { "total": 0, "available": 0.00000196, "locked": -0.00000196 } + // } + // + // WITHDRAWAL_LOCK_FUNDS + // { + // "historyId": "03de2271-66ab-4960-a786-87ab9551fc14", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "6ad3dc72-1d6d-4ec2-8436-ca43f85a38a6", + // "time": 1522245654481, + // "type": "WITHDRAWAL_LOCK_FUNDS", + // "value": -0.8, + // "fundsBefore": { "total": 0.8, "available": 0.8, "locked": 0 }, + // "fundsAfter": { "total": 0.8, "available": 0, "locked": 0.8 }, + // "change": { "total": 0, "available": -0.8, "locked": 0.8 } + // } + // + // WITHDRAWAL_SUBTRACT_FUNDS + // { + // "historyId": "b0308c89-5288-438d-a306-c6448b1a266d", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "6ad3dc72-1d6d-4ec2-8436-ca43f85a38a6", + // "time": 1522246526186, + // "type": "WITHDRAWAL_SUBTRACT_FUNDS", + // "value": -0.8, + // "fundsBefore": { "total": 0.8, "available": 0, "locked": 0.8 }, + // "fundsAfter": { "total": 0, "available": 0, "locked": 0 }, + // "change": { "total": -0.8, "available": 0, "locked": -0.8 } + // } + // + // TRANSACTION_OFFER_ABORTED_RETURN + // { + // "historyId": "b1a3c075-d403-4e05-8f32-40512cdd88c0", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": null, + // "time": 1522512298662, + // "type": "TRANSACTION_OFFER_ABORTED_RETURN", + // "value": 0.0564931, + // "fundsBefore": { "total": 0.44951311, "available": 0.39302001, "locked": 0.0564931 }, + // "fundsAfter": { "total": 0.44951311, "available": 0.44951311, "locked": 0 }, + // "change": { "total": 0, "available": 0.0564931, "locked": -0.0564931 } + // } + // + // WITHDRAWAL_UNLOCK_FUNDS + // { + // "historyId": "0ed569a2-c330-482e-bb89-4cb553fb5b11", + // "balance": { + // "id": "3a7e7a1e-0324-49d5-8f59-298505ebd6c7", + // "currency": "BTC", + // "type": "CRYPTO", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "BTC" + // }, + // "detailId": "0c7be256-c336-4111-bee7-4eb22e339700", + // "time": 1527866360785, + // "type": "WITHDRAWAL_UNLOCK_FUNDS", + // "value": 0.05045, + // "fundsBefore": { "total": 0.86001578, "available": 0.80956578, "locked": 0.05045 }, + // "fundsAfter": { "total": 0.86001578, "available": 0.86001578, "locked": 0 }, + // "change": { "total": 0, "available": 0.05045, "locked": -0.05045 } + // } + // + // TRANSACTION_COMMISSION_RETURN + // { + // "historyId": "07c89c27-46f1-4d7a-8518-b73798bf168a", + // "balance": { + // "id": "ab43023b-4079-414c-b340-056e3430a3af", + // "currency": "EUR", + // "type": "FIAT", + // "userId": "a34d361d-7bad-49c1-888e-62473b75d877", + // "name": "EUR" + // }, + // "detailId": null, + // "time": 1528304043063, + // "type": "TRANSACTION_COMMISSION_RETURN", + // "value": 0.6, + // "fundsBefore": { "total": 0, "available": 0, "locked": 0 }, + // "fundsAfter": { "total": 0.6, "available": 0.6, "locked": 0 }, + // "change": { "total": 0.6, "available": 0.6, "locked": 0 } + // } + // + const timestamp = this.safeInteger (item, 'time'); + const balance = this.safeValue (item, 'balance', {}); + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const change = this.safeValue (item, 'change', {}); + let amount = this.safeFloat (change, 'total'); + let direction = 'in'; + if (amount < 0) { + direction = 'out'; + amount = -amount; + } + const id = this.safeString (item, 'historyId'); + // there are 2 undocumented api calls: (v1_01PrivateGetPaymentsDepositDetailId and v1_01PrivateGetPaymentsWithdrawalDetailId) + // that can be used to enrich the transfers with txid, address etc (you need to use info.detailId as a parameter) + const referenceId = this.safeString (item, 'detailId'); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const fundsBefore = this.safeValue (item, 'fundsBefore', {}); + const before = this.safeFloat (fundsBefore, 'total'); + const fundsAfter = this.safeValue (item, 'fundsAfter', {}); + const after = this.safeFloat (fundsAfter, 'total'); + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': undefined, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': 'ok', + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'ADD_FUNDS': 'transaction', + 'BITCOIN_GOLD_FORK': 'transaction', + 'CREATE_BALANCE': 'transaction', + 'FUNDS_MIGRATION': 'transaction', + 'WITHDRAWAL_LOCK_FUNDS': 'transaction', + 'WITHDRAWAL_SUBTRACT_FUNDS': 'transaction', + 'WITHDRAWAL_UNLOCK_FUNDS': 'transaction', + 'TRANSACTION_COMMISSION_OUTCOME': 'fee', + 'TRANSACTION_COMMISSION_RETURN': 'fee', + 'TRANSACTION_OFFER_ABORTED_RETURN': 'trade', + 'TRANSACTION_OFFER_COMPLETED_RETURN': 'trade', + 'TRANSACTION_POST_INCOME': 'trade', + 'TRANSACTION_POST_OUTCOME': 'trade', + 'TRANSACTION_PRE_LOCKING': 'trade', + }; + return this.safeString (types, type, type); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // '1582399800000', + // { + // o: '0.0001428', + // c: '0.0001428', + // h: '0.0001428', + // l: '0.0001428', + // v: '4', + // co: '1' + // } + // ] + // + const first = this.safeValue (ohlcv, 1, {}); + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (first, 'o'), + this.safeFloat (first, 'h'), + this.safeFloat (first, 'l'), + this.safeFloat (first, 'c'), + this.safeFloat (first, 'v'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + 'resolution': this.timeframes[timeframe], + // 'from': 1574709092000, // unix timestamp in milliseconds, required + // 'to': 1574709092000, // unix timestamp in milliseconds, required + }; + if (limit === undefined) { + limit = 100; + } + const duration = this.parseTimeframe (timeframe); + const timerange = limit * duration * 1000; + if (since === undefined) { + request['to'] = this.milliseconds (); + request['from'] = request['to'] - timerange; + } else { + request['from'] = parseInt (since); + request['to'] = this.sum (request['from'], timerange); + } + const response = await this.v1_01PublicGetTradingCandleHistorySymbolResolution (this.extend (request, params)); + // + // { + // "status":"Ok", + // "items":[ + // ["1591503060000",{"o":"0.02509572","c":"0.02509438","h":"0.02509664","l":"0.02509438","v":"0.02082165","co":"17"}], + // ["1591503120000",{"o":"0.02509606","c":"0.02509515","h":"0.02509606","l":"0.02509487","v":"0.04971703","co":"13"}], + // ["1591503180000",{"o":"0.02509532","c":"0.02509589","h":"0.02509589","l":"0.02509454","v":"0.01332236","co":"7"}], + // ] + // } + // + const items = this.safeValue (response, 'items', []); + return this.parseOHLCVs (items, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // createOrder trades + // + // { + // "rate": "0.02195928", + // "amount": "0.00167952" + // } + // + // fetchMyTrades (private) + // + // { + // amount: "0.29285199", + // commissionValue: "0.00125927", + // id: "11c8203a-a267-11e9-b698-0242ac110007", + // initializedBy: "Buy", + // market: "ETH-EUR", + // offerId: "11c82038-a267-11e9-b698-0242ac110007", + // rate: "277", + // time: "1562689917517", + // userAction: "Buy", + // wasTaker: true, + // } + // + // fetchTrades (public) + // + // { + // id: 'df00b0da-e5e0-11e9-8c19-0242ac11000a', + // t: '1570108958831', + // a: '0.04776653', + // r: '0.02145854', + // ty: 'Sell' + // } + // + const timestamp = this.safeInteger2 (trade, 'time', 't'); + const userAction = this.safeString (trade, 'userAction'); + const side = (userAction === 'Buy') ? 'buy' : 'sell'; + const wasTaker = this.safeValue (trade, 'wasTaker'); + let takerOrMaker = undefined; + if (wasTaker !== undefined) { + takerOrMaker = wasTaker ? 'taker' : 'maker'; + } + const price = this.safeFloat2 (trade, 'rate', 'r'); + const amount = this.safeFloat2 (trade, 'amount', 'a'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const feeCost = this.safeFloat (trade, 'commissionValue'); + const marketId = this.safeString (trade, 'market'); + market = this.safeMarket (marketId, market, '-'); + const symbol = market['symbol']; + let fee = undefined; + if (feeCost !== undefined) { + const feeCcy = (side === 'buy') ? market['base'] : market['quote']; + fee = { + 'currency': feeCcy, + 'cost': feeCost, + }; + } + const order = this.safeString (trade, 'offerId'); + // todo: check this logic + let type = undefined; + if (order !== undefined) { + type = order ? 'limit' : 'market'; + } + return { + 'id': this.safeString (trade, 'id'), + 'order': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + }; + if (since !== undefined) { + request['fromTime'] = since - 1; // result does not include exactly `since` time therefore decrease by 1 + } + if (limit !== undefined) { + request['limit'] = limit; // default - 10, max - 300 + } + const response = await this.v1_01PublicGetTradingTransactionsSymbol (this.extend (request, params)); + const items = this.safeValue (response, 'items'); + return this.parseTrades (items, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + 'offerType': side, + 'amount': amount, + 'mode': type, + }; + if (type === 'limit') { + request['rate'] = price; + price = parseFloat (price); + } + amount = parseFloat (amount); + const response = await this.v1_01PrivatePostTradingOfferSymbol (this.extend (request, params)); + // + // unfilled (open order) + // + // { + // status: 'Ok', + // completed: false, // can deduce status from here + // offerId: 'ce9cc72e-d61c-11e9-9248-0242ac110005', + // transactions: [], // can deduce order info from here + // } + // + // filled (closed order) + // + // { + // "status": "Ok", + // "offerId": "942a4a3e-e922-11e9-8c19-0242ac11000a", + // "completed": true, + // "transactions": [ + // { + // "rate": "0.02195928", + // "amount": "0.00167952" + // }, + // { + // "rate": "0.02195928", + // "amount": "0.00167952" + // }, + // { + // "rate": "0.02196207", + // "amount": "0.27704177" + // } + // ] + // } + // + // partially-filled (open order) + // + // { + // "status": "Ok", + // "offerId": "d0ebefab-f4d7-11e9-8c19-0242ac11000a", + // "completed": false, + // "transactions": [ + // { + // "rate": "0.02106404", + // "amount": "0.0019625" + // }, + // { + // "rate": "0.02106404", + // "amount": "0.0019625" + // }, + // { + // "rate": "0.02105901", + // "amount": "0.00975256" + // } + // ] + // } + // + const timestamp = this.milliseconds (); // the real timestamp is missing in the response + const id = this.safeString (response, 'offerId'); + const completed = this.safeValue (response, 'completed', false); + const status = completed ? 'closed' : 'open'; + let filled = 0; + let cost = undefined; + const transactions = this.safeValue (response, 'transactions'); + let trades = undefined; + if (transactions !== undefined) { + trades = this.parseTrades (transactions, market, undefined, undefined, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'side': side, + 'type': type, + 'orderId': id, + }); + cost = 0; + for (let i = 0; i < trades.length; i++) { + filled = this.sum (filled, trades[i]['amount']); + cost = this.sum (cost, trades[i]['cost']); + } + } + const remaining = amount - filled; + return { + 'id': id, + 'info': response, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'average': undefined, + 'fee': undefined, + 'trades': trades, + 'clientOrderId': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const side = this.safeString (params, 'side'); + if (side === undefined) { + throw new ExchangeError (this.id + ' cancelOrder() requires a `side` parameter ("buy" or "sell")'); + } + const price = this.safeValue (params, 'price'); + if (price === undefined) { + throw new ExchangeError (this.id + ' cancelOrder() requires a `price` parameter (float or string)'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const tradingSymbol = market['baseId'] + '-' + market['quoteId']; + const request = { + 'symbol': tradingSymbol, + 'id': id, + 'side': side, + 'price': price, + }; + // { status: 'Fail', errors: [ 'NOT_RECOGNIZED_OFFER_TYPE' ] } -- if required params are missing + // { status: 'Ok', errors: [] } + return this.v1_01PrivateDeleteTradingOfferSymbolIdSidePrice (this.extend (request, params)); + } + + isFiat (currency) { + const fiatCurrencies = { + 'USD': true, + 'EUR': true, + 'PLN': true, + }; + return this.safeValue (fiatCurrencies, currency, false); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + let method = undefined; + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'quantity': amount, + }; + if (this.isFiat (code)) { + method = 'privatePostWithdraw'; + // request['account'] = params['account']; // they demand an account number + // request['express'] = params['express']; // whatever it means, they don't explain + // request['bic'] = ''; + } else { + method = 'privatePostTransfer'; + if (tag !== undefined) { + address += '?dt=' + tag.toString (); + } + request['address'] = address; + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api === 'public') { + const query = this.omit (params, this.extractParams (path)); + url += '/' + this.implodeParams (path, params) + '.json'; + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'v1_01Public') { + const query = this.omit (params, this.extractParams (path)); + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'v1_01Private') { + this.checkRequiredCredentials (); + const query = this.omit (params, this.extractParams (path)); + url += '/' + this.implodeParams (path, params); + const nonce = this.milliseconds ().toString (); + let payload = undefined; + if (method !== 'POST') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + payload = this.apiKey + nonce; + } else if (body === undefined) { + body = this.json (query); + payload = this.apiKey + nonce + body; + } + headers = { + 'Request-Timestamp': nonce, + 'Operation-Id': this.uuid (), + 'API-Key': this.apiKey, + 'API-Hash': this.hmac (this.encode (payload), this.encode (this.secret), 'sha512'), + 'Content-Type': 'application/json', + }; + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'method': path, + 'moment': this.nonce (), + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'API-Key': this.apiKey, + 'API-Hash': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('code' in response) { + // + // bitbay returns the integer 'success': 1 key from their private API + // or an integer 'code' value from 0 to 510 and an error message + // + // { 'success': 1, ... } + // { 'code': 502, 'message': 'Invalid sign' } + // { 'code': 0, 'message': 'offer funds not exceeding minimums' } + // + // 400 At least one parameter wasn't set + // 401 Invalid order type + // 402 No orders with specified currencies + // 403 Invalid payment currency name + // 404 Error. Wrong transaction type + // 405 Order with this id doesn't exist + // 406 No enough money or crypto + // 408 Invalid currency name + // 501 Invalid public key + // 502 Invalid sign + // 503 Invalid moment parameter. Request time doesn't match current server time + // 504 Invalid method + // 505 Key has no permission for this action + // 506 Account locked. Please contact with customer service + // 509 The BIC/SWIFT is required for this currency + // 510 Invalid market name + // + const code = this.safeString (response, 'code'); // always an integer + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } else if ('status' in response) { + // + // {"status":"Fail","errors":["OFFER_FUNDS_NOT_EXCEEDING_MINIMUMS"]} + // + const status = this.safeString (response, 'status'); + if (status === 'Fail') { + const errors = this.safeValue (response, 'errors'); + const feedback = this.id + ' ' + body; + for (let i = 0; i < errors.length; i++) { + const error = errors[i]; + this.throwExactlyMatchedException (this.exceptions, error, feedback); + } + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitcoincom.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitcoincom.js new file mode 100644 index 000000000..9321784dc --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitcoincom.js @@ -0,0 +1,34 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const hitbtc = require ('./hitbtc.js'); + +// --------------------------------------------------------------------------- + +module.exports = class bitcoincom extends hitbtc { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitcoincom', + 'name': 'bitcoin.com', + 'countries': [ 'KN' ], + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/97296144-514fa300-1861-11eb-952b-3d55d492200b.jpg', + 'api': { + 'public': 'https://api.exchange.bitcoin.com', + 'private': 'https://api.exchange.bitcoin.com', + }, + 'www': 'https://exchange.bitcoin.com', + 'doc': 'https://api.exchange.bitcoin.com/api/2/explore', + 'fees': 'https://exchange.bitcoin.com/fees-and-limits', + 'referral': 'https://exchange.bitcoin.com/referral/da948b21d6c92d69', + }, + 'fees': { + 'trading': { + 'maker': 0.15 / 100, + 'taker': 0.2 / 100, + }, + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitfinex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitfinex.js new file mode 100644 index 000000000..ed9038a81 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitfinex.js @@ -0,0 +1,1263 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { NotSupported, RateLimitExceeded, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, InvalidNonce, BadSymbol } = require ('./base/errors'); +const { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitfinex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitfinex', + 'name': 'Bitfinex', + 'countries': [ 'VG' ], + 'version': 'v1', + 'rateLimit': 1500, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'deposit': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': false, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': true, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '3h': '3h', + '6h': '6h', + '12h': '12h', + '1d': '1D', + '1w': '7D', + '2w': '14D', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg', + 'api': { + 'v2': 'https://api-pub.bitfinex.com', // https://github.com/ccxt/ccxt/issues/5109 + 'public': 'https://api.bitfinex.com', + 'private': 'https://api.bitfinex.com', + }, + 'www': 'https://www.bitfinex.com', + 'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL', + 'doc': [ + 'https://docs.bitfinex.com/v1/docs', + 'https://github.com/bitfinexcom/bitfinex-api-node', + ], + }, + 'api': { + // v2 symbol ids require a 't' prefix + // just the public part of it (use bitfinex2 for everything else) + 'v2': { + 'get': [ + 'platform/status', + 'tickers', + 'ticker/{symbol}', + 'trades/{symbol}/hist', + 'book/{symbol}/{precision}', + 'book/{symbol}/P0', + 'book/{symbol}/P1', + 'book/{symbol}/P2', + 'book/{symbol}/P3', + 'book/{symbol}/R0', + 'stats1/{key}:{size}:{symbol}:{side}/{section}', + 'stats1/{key}:{size}:{symbol}/{section}', + 'stats1/{key}:{size}:{symbol}:long/last', + 'stats1/{key}:{size}:{symbol}:long/hist', + 'stats1/{key}:{size}:{symbol}:short/last', + 'stats1/{key}:{size}:{symbol}:short/hist', + 'candles/trade:{timeframe}:{symbol}/{section}', + 'candles/trade:{timeframe}:{symbol}/last', + 'candles/trade:{timeframe}:{symbol}/hist', + ], + }, + 'public': { + 'get': [ + 'book/{symbol}', + // 'candles/{symbol}', + 'lendbook/{currency}', + 'lends/{currency}', + 'pubticker/{symbol}', + 'stats/{symbol}', + 'symbols', + 'symbols_details', + 'tickers', + 'trades/{symbol}', + ], + }, + 'private': { + 'post': [ + 'account_fees', + 'account_infos', + 'balances', + 'basket_manage', + 'credits', + 'deposit/new', + 'funding/close', + 'history', + 'history/movements', + 'key_info', + 'margin_infos', + 'mytrades', + 'mytrades_funding', + 'offer/cancel', + 'offer/new', + 'offer/status', + 'offers', + 'offers/hist', + 'order/cancel', + 'order/cancel/all', + 'order/cancel/multi', + 'order/cancel/replace', + 'order/new', + 'order/new/multi', + 'order/status', + 'orders', + 'orders/hist', + 'position/claim', + 'position/close', + 'positions', + 'summary', + 'taken_funds', + 'total_taken_funds', + 'transfer', + 'unused_taken_funds', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + 'tiers': { + 'taker': [ + [0, 0.2 / 100], + [500000, 0.2 / 100], + [1000000, 0.2 / 100], + [2500000, 0.2 / 100], + [5000000, 0.2 / 100], + [7500000, 0.2 / 100], + [10000000, 0.18 / 100], + [15000000, 0.16 / 100], + [20000000, 0.14 / 100], + [25000000, 0.12 / 100], + [30000000, 0.1 / 100], + ], + 'maker': [ + [0, 0.1 / 100], + [500000, 0.08 / 100], + [1000000, 0.06 / 100], + [2500000, 0.04 / 100], + [5000000, 0.02 / 100], + [7500000, 0], + [10000000, 0], + [15000000, 0], + [20000000, 0], + [25000000, 0], + [30000000, 0], + ], + }, + }, + 'funding': { + 'tierBased': false, // true for tier-based/progressive + 'percentage': false, // fixed commission + // Actually deposit fees are free for larger deposits (> $1000 USD equivalent) + // these values below are deprecated, we should not hardcode fees and limits anymore + // to be reimplemented with bitfinex funding fees from their API or web endpoints + 'deposit': { + 'BTC': 0.0004, + 'IOTA': 0.5, + 'ETH': 0.0027, + 'BCH': 0.0001, + 'LTC': 0.001, + 'EOS': 0.24279, + 'XMR': 0.04, + 'SAN': 0.99269, + 'DASH': 0.01, + 'ETC': 0.01, + 'XRP': 0.02, + 'YYW': 16.915, + 'NEO': 0, + 'ZEC': 0.001, + 'BTG': 0, + 'OMG': 0.14026, + 'DATA': 20.773, + 'QASH': 1.9858, + 'ETP': 0.01, + 'QTUM': 0.01, + 'EDO': 0.95001, + 'AVT': 1.3045, + 'USDT': 0, + 'TRX': 28.184, + 'ZRX': 1.9947, + 'RCN': 10.793, + 'TNB': 31.915, + 'SNT': 14.976, + 'RLC': 1.414, + 'GNT': 5.8952, + 'SPK': 10.893, + 'REP': 0.041168, + 'BAT': 6.1546, + 'ELF': 1.8753, + 'FUN': 32.336, + 'SNG': 18.622, + 'AID': 8.08, + 'MNA': 16.617, + 'NEC': 1.6504, + 'XTZ': 0.2, + }, + 'withdraw': { + 'BTC': 0.0004, + 'IOTA': 0.5, + 'ETH': 0.0027, + 'BCH': 0.0001, + 'LTC': 0.001, + 'EOS': 0.24279, + 'XMR': 0.04, + 'SAN': 0.99269, + 'DASH': 0.01, + 'ETC': 0.01, + 'XRP': 0.02, + 'YYW': 16.915, + 'NEO': 0, + 'ZEC': 0.001, + 'BTG': 0, + 'OMG': 0.14026, + 'DATA': 20.773, + 'QASH': 1.9858, + 'ETP': 0.01, + 'QTUM': 0.01, + 'EDO': 0.95001, + 'AVT': 1.3045, + 'USDT': 20, + 'TRX': 28.184, + 'ZRX': 1.9947, + 'RCN': 10.793, + 'TNB': 31.915, + 'SNT': 14.976, + 'RLC': 1.414, + 'GNT': 5.8952, + 'SPK': 10.893, + 'REP': 0.041168, + 'BAT': 6.1546, + 'ELF': 1.8753, + 'FUN': 32.336, + 'SNG': 18.622, + 'AID': 8.08, + 'MNA': 16.617, + 'NEC': 1.6504, + 'XTZ': 0.2, + }, + }, + }, + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + 'commonCurrencies': { + 'ABS': 'ABYSS', + 'AIO': 'AION', + 'ALG': 'ALGO', // https://github.com/ccxt/ccxt/issues/6034 + 'AMP': 'AMPL', + 'ATM': 'ATMI', + 'ATO': 'ATOM', // https://github.com/ccxt/ccxt/issues/5118 + 'BAB': 'BCH', + 'CTX': 'CTXC', + 'DAD': 'DADI', + 'DAT': 'DATA', + 'DSH': 'DASH', + 'DRK': 'DRK', + // https://github.com/ccxt/ccxt/issues/7399 + // https://coinmarketcap.com/currencies/pnetwork/ + // https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ + 'EDO': 'PNT', + 'GSD': 'GUSD', + 'HOT': 'Hydro Protocol', + 'IOS': 'IOST', + 'IOT': 'IOTA', + 'IQX': 'IQ', + 'MIT': 'MITH', + 'MNA': 'MANA', + 'NCA': 'NCASH', + 'ORS': 'ORS Group', // conflict with Origin Sport #3230 + 'POY': 'POLY', + 'QSH': 'QASH', + 'QTM': 'QTUM', + 'RBT': 'RBTC', + 'SEE': 'SEER', + 'SNG': 'SNGLS', + 'SPK': 'SPANK', + 'STJ': 'STORJ', + 'TRI': 'TRIO', + 'TSD': 'TUSD', + 'YYW': 'YOYOW', + 'UDC': 'USDC', + 'UST': 'USDT', + 'UTN': 'UTNP', + 'VSY': 'VSYS', + 'WAX': 'WAXP', + 'XCH': 'XCHF', + 'ZBT': 'ZB', + }, + 'exceptions': { + 'exact': { + 'temporarily_unavailable': ExchangeNotAvailable, // Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info. + 'Order could not be cancelled.': OrderNotFound, // non-existent order + 'No such order found.': OrderNotFound, // ? + 'Order price must be positive.': InvalidOrder, // on price <= 0 + 'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError, + 'Key price should be a decimal number, e.g. "123.456"': InvalidOrder, // on isNaN (price) + 'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder, // on isNaN (amount) + 'ERR_RATE_LIMIT': RateLimitExceeded, + 'Ratelimit': RateLimitExceeded, + 'Nonce is too small.': InvalidNonce, + 'No summary found.': ExchangeError, // fetchTradingFees (summary) endpoint can give this vague error message + 'Cannot evaluate your available balance, please try again': ExchangeNotAvailable, + 'Unknown symbol': BadSymbol, + }, + 'broad': { + 'Invalid X-BFX-SIGNATURE': AuthenticationError, + 'This API key does not have permission': PermissionDenied, // authenticated but not authorized + 'not enough exchange balance for ': InsufficientFunds, // when buying cost is greater than the available quote currency + 'minimum size for ': InvalidOrder, // when amount below limits.amount.min + 'Invalid order': InvalidOrder, // ? + 'The available balance is only': InsufficientFunds, // {"status":"error","message":"Cannot withdraw 1.0027 ETH from your exchange wallet. The available balance is only 0.0 ETH. If you have limit orders, open positions, unused or active margin funding, this will decrease your available balance. To increase it, you can cancel limit orders or reduce/close your positions.","withdrawal_id":0,"fees":"0.0027"} + }, + }, + 'precisionMode': SIGNIFICANT_DIGITS, + 'options': { + 'currencyNames': { + 'AGI': 'agi', + 'AID': 'aid', + 'AIO': 'aio', + 'ANT': 'ant', + 'AVT': 'aventus', // #1811 + 'BAT': 'bat', + // https://github.com/ccxt/ccxt/issues/5833 + 'BCH': 'bab', // undocumented + // 'BCH': 'bcash', // undocumented + 'BCI': 'bci', + 'BFT': 'bft', + 'BSV': 'bsv', + 'BTC': 'bitcoin', + 'BTG': 'bgold', + 'CFI': 'cfi', + 'COMP': 'comp', + 'DAI': 'dai', + 'DADI': 'dad', + 'DASH': 'dash', + 'DATA': 'datacoin', + 'DTH': 'dth', + 'EDO': 'eidoo', // #1811 + 'ELF': 'elf', + 'EOS': 'eos', + 'ETC': 'ethereumc', + 'ETH': 'ethereum', + 'ETP': 'metaverse', + 'FUN': 'fun', + 'GNT': 'golem', + 'IOST': 'ios', + 'IOTA': 'iota', + // https://github.com/ccxt/ccxt/issues/5833 + 'LEO': 'let', // ETH chain + // 'LEO': 'les', // EOS chain + 'LINK': 'link', + 'LRC': 'lrc', + 'LTC': 'litecoin', + 'LYM': 'lym', + 'MANA': 'mna', + 'MIT': 'mit', + 'MKR': 'mkr', + 'MTN': 'mtn', + 'NEO': 'neo', + 'ODE': 'ode', + 'OMG': 'omisego', + 'OMNI': 'mastercoin', + 'QASH': 'qash', + 'QTUM': 'qtum', // #1811 + 'RCN': 'rcn', + 'RDN': 'rdn', + 'REP': 'rep', + 'REQ': 'req', + 'RLC': 'rlc', + 'SAN': 'santiment', + 'SNGLS': 'sng', + 'SNT': 'status', + 'SPANK': 'spk', + 'STORJ': 'stj', + 'TNB': 'tnb', + 'TRX': 'trx', + 'TUSD': 'tsd', + 'USD': 'wire', + 'USDC': 'udc', // https://github.com/ccxt/ccxt/issues/5833 + 'UTK': 'utk', + 'USDT': 'tetheruso', // Tether on Omni + // 'USDT': 'tetheruse', // Tether on ERC20 + // 'USDT': 'tetherusl', // Tether on Liquid + // 'USDT': 'tetherusx', // Tether on Tron + // 'USDT': 'tetheruss', // Tether on EOS + 'VEE': 'vee', + 'WAX': 'wax', + 'XLM': 'xlm', + 'XMR': 'monero', + 'XRP': 'ripple', + 'XVG': 'xvg', + 'YOYOW': 'yoyow', + 'ZEC': 'zcash', + 'ZRX': 'zrx', + 'XTZ': 'xtz', + }, + 'orderTypes': { + 'limit': 'exchange limit', + 'market': 'exchange market', + }, + }, + }); + } + + async fetchFundingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostAccountFees (params); + const fees = response['withdraw']; + const withdraw = {}; + const ids = Object.keys (fees); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + withdraw[code] = this.safeFloat (fees, id); + } + return { + 'info': response, + 'withdraw': withdraw, + 'deposit': withdraw, // only for deposits of less than $1000 + }; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostSummary (params); + // + // { + // time: '2019-02-20T15:50:19.152000Z', + // trade_vol_30d: [ + // { + // curr: 'Total (USD)', + // vol: 0, + // vol_maker: 0, + // vol_BFX: 0, + // vol_BFX_maker: 0, + // vol_ETHFX: 0, + // vol_ETHFX_maker: 0 + // } + // ], + // fees_funding_30d: {}, + // fees_funding_total_30d: 0, + // fees_trading_30d: {}, + // fees_trading_total_30d: 0, + // maker_fee: 0.001, + // taker_fee: 0.002 + // } + // + return { + 'info': response, + 'maker': this.safeFloat (response, 'maker_fee'), + 'taker': this.safeFloat (response, 'taker_fee'), + }; + } + + async fetchMarkets (params = {}) { + const ids = await this.publicGetSymbols (); + const details = await this.publicGetSymbolsDetails (); + const result = []; + for (let i = 0; i < details.length; i++) { + const market = details[i]; + let id = this.safeString (market, 'pair'); + if (!this.inArray (id, ids)) { + continue; + } + id = id.toUpperCase (); + let baseId = undefined; + let quoteId = undefined; + if (id.indexOf (':') >= 0) { + const parts = id.split (':'); + baseId = parts[0]; + quoteId = parts[1]; + } else { + baseId = id.slice (0, 3); + quoteId = id.slice (3, 6); + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'price_precision'), + // https://docs.bitfinex.com/docs/introduction#amount-precision + // The amount field allows up to 8 decimals. + // Anything exceeding this will be rounded to the 8th decimal. + 'amount': 8, + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_order_size'), + 'max': this.safeFloat (market, 'maximum_order_size'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + }; + limits['cost'] = { + 'min': limits['amount']['min'] * limits['price']['min'], + 'max': undefined, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + amountToPrecision (symbol, amount) { + // https://docs.bitfinex.com/docs/introduction#amount-precision + // The amount field allows up to 8 decimals. + // Anything exceeding this will be rounded to the 8th decimal. + return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); + } + + priceToPrecision (symbol, price) { + price = this.decimalToPrecision (price, ROUND, this.markets[symbol]['precision']['price'], this.precisionMode); + // https://docs.bitfinex.com/docs/introduction#price-precision + // The precision level of all trading prices is based on significant figures. + // All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals (e.g. 1.2345, 123.45, 1234.5, 0.00012345). + // Prices submit with a precision larger than 5 will be cut by the API. + return this.decimalToPrecision (price, TRUNCATE, 8, DECIMAL_PLACES); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let key = 'quote'; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + const code = market[key]; + const currency = this.safeValue (this.currencies, code); + if (currency !== undefined) { + const precision = this.safeInteger (currency, 'precision'); + if (precision !== undefined) { + cost = parseFloat (this.currencyToPrecision (code, cost)); + } + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balanceType = this.safeString (params, 'type', 'exchange'); + const query = this.omit (params, 'type'); + const response = await this.privatePostBalances (query); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + if (balance['type'] === balanceType) { + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + // bitfinex had BCH previously, now it's BAB, but the old + // BCH symbol is kept for backward-compatibility + // we need a workaround here so that the old BCH balance + // would not override the new BAB balance (BAB is unified to BCH) + // https://github.com/ccxt/ccxt/issues/4989 + if (!(code in result)) { + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'amount'); + result[code] = account; + } + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit_bids'] = limit; + request['limit_asks'] = limit; + } + const response = await this.publicGetBookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const ticker = await this.publicGetPubtickerSymbol (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + let timestamp = this.safeFloat (ticker, 'timestamp'); + if (timestamp !== undefined) { + timestamp *= 1000; + } + timestamp = parseInt (timestamp); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } else if ('pair' in ticker) { + const marketId = this.safeString (ticker, 'pair'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + const baseId = marketId.slice (0, 3); + const quoteId = marketId.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + } + const last = this.safeFloat (ticker, 'last_price'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'mid'), + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market) { + const id = this.safeString (trade, 'tid'); + let timestamp = this.safeFloat (trade, 'timestamp'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp) * 1000; + } + const type = undefined; + const side = this.safeStringLower (trade, 'type'); + const orderId = this.safeString (trade, 'order_id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + if ('fee_amount' in trade) { + const feeCost = -this.safeFloat (trade, 'fee_amount'); + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'type': type, + 'order': orderId, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'limit_trades': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.publicGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit_trades'] = limit; + } + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.privatePostMytrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'side': side, + 'amount': this.amountToPrecision (symbol, amount), + 'type': this.safeString (this.options['orderTypes'], type, type), + 'ocoorder': false, + 'buy_price_oco': 0, + 'sell_price_oco': 0, + }; + if (type === 'market') { + request['price'] = this.nonce ().toString (); + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrderNew (this.extend (request, params)); + return this.parseOrder (response); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const order = { + 'order_id': parseInt (id), + }; + if (price !== undefined) { + order['price'] = this.priceToPrecision (symbol, price); + } + if (amount !== undefined) { + order['amount'] = this.numberToString (amount); + } + if (symbol !== undefined) { + order['symbol'] = this.marketId (symbol); + } + if (side !== undefined) { + order['side'] = side; + } + if (type !== undefined) { + order['type'] = this.safeString (this.options['orderTypes'], type, type); + } + const response = await this.privatePostOrderCancelReplace (this.extend (order, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + return await this.privatePostOrderCancel (this.extend (request, params)); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + return await this.privatePostOrderCancelAll (params); + } + + parseOrder (order, market = undefined) { + const side = this.safeString (order, 'side'); + const open = this.safeValue (order, 'is_live'); + const canceled = this.safeValue (order, 'is_cancelled'); + let status = undefined; + if (open) { + status = 'open'; + } else if (canceled) { + status = 'canceled'; + } else { + status = 'closed'; + } + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeStringUpper (order, 'symbol'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let orderType = order['type']; + const exchange = orderType.indexOf ('exchange ') >= 0; + if (exchange) { + const parts = order['type'].split (' '); + orderType = parts[1]; + } + let timestamp = this.safeFloat (order, 'timestamp'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp) * 1000; + } + const id = this.safeString (order, 'id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'average': this.safeFloat (order, 'avg_execution_price'), + 'amount': this.safeFloat (order, 'original_amount'), + 'remaining': this.safeFloat (order, 'remaining_amount'), + 'filled': this.safeFloat (order, 'executed_amount'), + 'status': status, + 'fee': undefined, + 'cost': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbol !== undefined) { + if (!(symbol in this.markets)) { + throw new ExchangeError (this.id + ' has no symbol ' + symbol); + } + } + const response = await this.privatePostOrders (params); + let orders = this.parseOrders (response, undefined, since, limit); + if (symbol !== undefined) { + orders = this.filterBy (orders, 'symbol', symbol); + } + return orders; + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostOrdersHist (this.extend (request, params)); + let orders = this.parseOrders (response, undefined, since, limit); + if (symbol !== undefined) { + orders = this.filterBy (orders, 'symbol', symbol); + } + orders = this.filterByArray (orders, 'status', [ 'closed', 'canceled' ], false); + return orders; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1457539800000, + // 0.02594, + // 0.02594, + // 0.02594, + // 0.02594, + // 0.1 + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const market = this.market (symbol); + const v2id = 't' + market['id']; + const request = { + 'symbol': v2id, + 'timeframe': this.timeframes[timeframe], + 'sort': 1, + 'limit': limit, + }; + if (since !== undefined) { + request['start'] = since; + } + const response = await this.v2GetCandlesTradeTimeframeSymbolHist (this.extend (request, params)); + // + // [ + // [1457539800000,0.02594,0.02594,0.02594,0.02594,0.1], + // [1457547300000,0.02577,0.02577,0.02577,0.02577,0.01], + // [1457550240000,0.0255,0.0253,0.0255,0.0252,3.2640000000000002], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + getCurrencyName (code) { + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + if (code in this.options['currencyNames']) { + return this.options['currencyNames'][code]; + } + throw new NotSupported (this.id + ' ' + code + ' not supported for withdrawal'); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const request = { + 'renew': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + return response; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'method': name, + 'wallet_name': 'exchange', + 'renew': 0, // a value of 1 will generate a new address + }; + const response = await this.privatePostDepositNew (this.extend (request, params)); + let address = this.safeValue (response, 'address'); + let tag = undefined; + if ('address_pool' in response) { + tag = address; + address = response['address_pool']; + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currencyId = this.safeString (params, 'currency'); + const query = this.omit (params, 'currency'); + let currency = undefined; + if (currencyId === undefined) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument or a `currency` parameter'); + } else { + currency = this.currency (code); + currencyId = currency['id']; + } + } + query['currency'] = currencyId; + if (since !== undefined) { + query['since'] = parseInt (since / 1000); + } + const response = await this.privatePostHistoryMovements (this.extend (query, params)); + // + // [ + // { + // "id":581183, + // "txid": 123456, + // "currency":"BTC", + // "method":"BITCOIN", + // "type":"WITHDRAWAL", + // "amount":".01", + // "description":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ, offchain transfer ", + // "address":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ", + // "status":"COMPLETED", + // "timestamp":"1443833327.0", + // "timestamp_created": "1443833327.1", + // "fee": 0.1, + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // crypto + // + // { + // "id": 12042490, + // "fee": "-0.02", + // "txid": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D", + // "type": "DEPOSIT", + // "amount": "2099.849999", + // "method": "RIPPLE", + // "status": "COMPLETED", + // "address": "2505189261", + // "currency": "XRP", + // "timestamp": "1551730524.0", + // "description": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D", + // "timestamp_created": "1551730523.0" + // } + // + // fiat + // + // { + // "id": 12725095, + // "fee": "-60.0", + // "txid": null, + // "type": "WITHDRAWAL", + // "amount": "9943.0", + // "method": "WIRE", + // "status": "SENDING", + // "address": null, + // "currency": "EUR", + // "timestamp": "1561802484.0", + // "description": "Name: bob, AccountAddress: some address, Account: someaccountno, Bank: bank address, SWIFT: foo, Country: UK, Details of Payment: withdrawal name, Intermediary Bank Name: , Intermediary Bank Address: , Intermediary Bank City: , Intermediary Bank Country: , Intermediary Bank Account: , Intermediary Bank SWIFT: , Fee: -60.0", + // "timestamp_created": "1561716066.0" + // } + // + let timestamp = this.safeFloat (transaction, 'timestamp_created'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp * 1000); + } + let updated = this.safeFloat (transaction, 'timestamp'); + if (updated !== undefined) { + updated = parseInt (updated * 1000); + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeStringLower (transaction, 'type'); // DEPOSIT or WITHDRAWAL + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + let feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'txid'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'address'), // todo: this is actually the tag for XRP transfers (the address is missing) + 'tag': undefined, // refix it properly for the tag from description + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'currency': code, + 'cost': feeCost, + 'rate': undefined, + }, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'SENDING': 'pending', + 'CANCELED': 'canceled', + 'ZEROCONFIRMED': 'failed', // ZEROCONFIRMED happens e.g. in a double spend attempt (I had one in my movements!) + 'COMPLETED': 'ok', + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'withdraw_type': name, + 'walletselected': 'exchange', + 'amount': this.numberToString (amount), + 'address': address, + }; + if (tag !== undefined) { + request['payment_id'] = tag; + } + const responses = await this.privatePostWithdraw (this.extend (request, params)); + const response = responses[0]; + const id = this.safeString (response, 'withdrawal_id'); + const message = this.safeString (response, 'message'); + const errorMessage = this.findBroadlyMatchedKey (this.exceptions['broad'], message); + if (id === 0) { + if (errorMessage !== undefined) { + const ExceptionClass = this.exceptions['broad'][errorMessage]; + throw new ExceptionClass (this.id + ' ' + message); + } + throw new ExchangeError (this.id + ' withdraw returned an id of zero: ' + this.json (response)); + } + return { + 'info': response, + 'id': id, + }; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostPositions (params); + // + // [ + // { + // "id":943715, + // "symbol":"btcusd", + // "status":"ACTIVE", + // "base":"246.94", + // "amount":"1.0", + // "timestamp":"1444141857.0", + // "swap":"0.0", + // "pl":"-2.22042" + // } + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + if (api === 'v2') { + request = '/' + api + request; + } else { + request = '/' + this.version + request; + } + let query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'][api] + request; + if ((api === 'public') || (path.indexOf ('/hist') >= 0)) { + if (Object.keys (query).length) { + const suffix = '?' + this.urlencode (query); + url += suffix; + request += suffix; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + query = this.extend ({ + 'nonce': nonce.toString (), + 'request': request, + }, query); + body = this.json (query); + const payload = this.stringToBase64 (body); + const secret = this.encode (this.secret); + const signature = this.hmac (payload, secret, 'sha384'); + headers = { + 'X-BFX-APIKEY': this.apiKey, + 'X-BFX-PAYLOAD': this.decode (payload), + 'X-BFX-SIGNATURE': signature, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code >= 400) { + if (body[0] === '{') { + const feedback = this.id + ' ' + body; + const message = this.safeString2 (response, 'message', 'error'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitfinex2.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitfinex2.js new file mode 100644 index 000000000..5c44875e0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitfinex2.js @@ -0,0 +1,1537 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const bitfinex = require ('./bitfinex.js'); +const { ExchangeError, InvalidAddress, ArgumentsRequired, InsufficientFunds, AuthenticationError, OrderNotFound, InvalidOrder, BadRequest, InvalidNonce, BadSymbol, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitfinex2 extends bitfinex { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitfinex2', + 'name': 'Bitfinex', + 'countries': [ 'VG' ], + 'version': 'v2', + 'certified': false, + 'pro': false, + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createDepositAddress': true, + 'createLimitOrder': true, + 'createMarketOrder': true, + 'createOrder': true, + 'deposit': false, + 'editOrder': false, + 'fetchBalance': true, + 'fetchClosedOrder': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchFundingFees': false, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrder': true, + 'fetchOpenOrders': true, + 'fetchOrder': false, + 'fetchOrderTrades': true, + 'fetchStatus': true, + 'fetchTickers': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTransactions': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '3h': '3h', + '6h': '6h', + '12h': '12h', + '1d': '1D', + '1w': '7D', + '2w': '14D', + '1M': '1M', + }, + 'rateLimit': 1500, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg', + 'api': { + 'v1': 'https://api.bitfinex.com', + 'public': 'https://api-pub.bitfinex.com', + 'private': 'https://api.bitfinex.com', + }, + 'www': 'https://www.bitfinex.com', + 'doc': [ + 'https://docs.bitfinex.com/v2/docs/', + 'https://github.com/bitfinexcom/bitfinex-api-node', + ], + 'fees': 'https://www.bitfinex.com/fees', + }, + 'api': { + 'v1': { + 'get': [ + 'symbols', + 'symbols_details', + ], + }, + 'public': { + 'get': [ + 'conf/{config}', + 'conf/pub:{action}:{object}', + 'conf/pub:{action}:{object}:{detail}', + 'conf/pub:map:{object}', + 'conf/pub:map:{object}:{detail}', + 'conf/pub:map:currency:{detail}', + 'conf/pub:map:currency:sym', // maps symbols to their API symbols, BAB > BCH + 'conf/pub:map:currency:label', // verbose friendly names, BNT > Bancor + 'conf/pub:map:currency:unit', // maps symbols to unit of measure where applicable + 'conf/pub:map:currency:undl', // maps derivatives symbols to their underlying currency + 'conf/pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on + 'conf/pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs + 'conf/pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745 + 'conf/pub:map:tx:method', + 'conf/pub:list:{object}', + 'conf/pub:list:{object}:{detail}', + 'conf/pub:list:currency', + 'conf/pub:list:pair:exchange', + 'conf/pub:list:pair:margin', + 'conf/pub:list:pair:futures', + 'conf/pub:list:competitions', + 'conf/pub:info:{object}', + 'conf/pub:info:{object}:{detail}', + 'conf/pub:info:pair', + 'conf/pub:info:tx:status', // [ deposit, withdrawal ] statuses 1 = active, 0 = maintenance + 'conf/pub:fees', + 'platform/status', + 'tickers', + 'ticker/{symbol}', + 'trades/{symbol}/hist', + 'book/{symbol}/{precision}', + 'book/{symbol}/P0', + 'book/{symbol}/P1', + 'book/{symbol}/P2', + 'book/{symbol}/P3', + 'book/{symbol}/R0', + 'stats1/{key}:{size}:{symbol}:{side}/{section}', + 'stats1/{key}:{size}:{symbol}:{side}/last', + 'stats1/{key}:{size}:{symbol}:{side}/hist', + 'stats1/{key}:{size}:{symbol}/{section}', + 'stats1/{key}:{size}:{symbol}/last', + 'stats1/{key}:{size}:{symbol}/hist', + 'stats1/{key}:{size}:{symbol}:long/last', + 'stats1/{key}:{size}:{symbol}:long/hist', + 'stats1/{key}:{size}:{symbol}:short/last', + 'stats1/{key}:{size}:{symbol}:short/hist', + 'candles/trade:{timeframe}:{symbol}/{section}', + 'candles/trade:{timeframe}:{symbol}/last', + 'candles/trade:{timeframe}:{symbol}/hist', + 'status/{type}', + 'status/deriv', + 'liquidations/hist', + 'rankings/{key}:{timeframe}:{symbol}/{section}', + 'rankings/{key}:{timeframe}:{symbol}/hist', + ], + 'post': [ + 'calc/trade/avg', + 'calc/fx', + ], + }, + 'private': { + 'post': [ + // 'auth/r/orders/{symbol}/new', // outdated + // 'auth/r/stats/perf:{timeframe}/hist', // outdated + 'auth/r/wallets', + 'auth/r/wallets/hist', + 'auth/r/orders', + 'auth/r/orders/{symbol}', + 'auth/w/order/submit', + 'auth/w/order/update', + 'auth/w/order/cancel', + 'auth/w/order/multi', + 'auth/w/order/cancel/multi', + 'auth/r/orders/{symbol}/hist', + 'auth/r/orders/hist', + 'auth/r/order/{symbol}:{id}/trades', + 'auth/r/trades/{symbol}/hist', + 'auth/r/trades/hist', + 'auth/r/ledgers/{currency}/hist', + 'auth/r/ledgers/hist', + 'auth/r/info/margin/{key}', + 'auth/r/info/margin/base', + 'auth/r/info/margin/sym_all', + 'auth/r/positions', + 'auth/w/position/claim', + 'auth/r/positions/hist', + 'auth/r/positions/audit', + 'auth/w/deriv/collateral/set', + 'auth/r/funding/offers', + 'auth/r/funding/offers/{symbol}', + 'auth/w/funding/offer/submit', + 'auth/w/funding/offer/cancel', + 'auth/w/funding/offer/cancel/all', + 'auth/w/funding/close', + 'auth/w/funding/auto', + 'auth/w/funding/keep', + 'auth/r/funding/offers/{symbol}/hist', + 'auth/r/funding/offers/hist', + 'auth/r/funding/loans', + 'auth/r/funding/loans/hist', + 'auth/r/funding/loans/{symbol}', + 'auth/r/funding/loans/{symbol}/hist', + 'auth/r/funding/credits', + 'auth/r/funding/credits/hist', + 'auth/r/funding/credits/{symbol}', + 'auth/r/funding/credits/{symbol}/hist', + 'auth/r/funding/trades/{symbol}/hist', + 'auth/r/funding/trades/hist', + 'auth/r/info/funding/{key}', + 'auth/r/info/user', + 'auth/r/logins/hist', + 'auth/w/transfer', + 'auth/w/deposit/address', + 'auth/w/deposit/invoice', + 'auth/w/withdraw', + 'auth/r/movements/{currency}/hist', + 'auth/r/movements/hist', + 'auth/r/alerts', + 'auth/w/alert/set', + 'auth/w/alert/price:{symbol}:{price}/del', + 'auth/w/alert/{type}:{symbol}:{price}/del', + 'auth/calc/order/avail', + 'auth/w/settings/set', + 'auth/r/settings', + 'auth/w/settings/del', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.0004, + 'BCH': 0.0001, + 'ETH': 0.00135, + 'EOS': 0.0, + 'LTC': 0.001, + 'OMG': 0.15097, + 'IOT': 0.0, + 'NEO': 0.0, + 'ETC': 0.01, + 'XRP': 0.02, + 'ETP': 0.01, + 'ZEC': 0.001, + 'BTG': 0.0, + 'DASH': 0.01, + 'XMR': 0.0001, + 'QTM': 0.01, + 'EDO': 0.23687, + 'DAT': 9.8858, + 'AVT': 1.1251, + 'SAN': 0.35977, + 'USDT': 5.0, + 'SPK': 16.971, + 'BAT': 1.1209, + 'GNT': 2.8789, + 'SNT': 9.0848, + 'QASH': 1.726, + 'YYW': 7.9464, + }, + }, + }, + 'options': { + 'precision': 'R0', // P0, P1, P2, P3, P4, R0 + // convert 'EXCHANGE MARKET' to lowercase 'market' + // convert 'EXCHANGE LIMIT' to lowercase 'limit' + // everything else remains uppercase + 'exchangeTypes': { + // 'MARKET': undefined, + 'EXCHANGE MARKET': 'market', + // 'LIMIT': undefined, + 'EXCHANGE LIMIT': 'limit', + // 'STOP': undefined, + // 'EXCHANGE STOP': undefined, + // 'TRAILING STOP': undefined, + // 'EXCHANGE TRAILING STOP': undefined, + // 'FOK': undefined, + // 'EXCHANGE FOK': undefined, + // 'STOP LIMIT': undefined, + // 'EXCHANGE STOP LIMIT': undefined, + // 'IOC': undefined, + // 'EXCHANGE IOC': undefined, + }, + // convert 'market' to 'EXCHANGE MARKET' + // convert 'limit' 'EXCHANGE LIMIT' + // everything else remains as is + 'orderTypes': { + 'market': 'EXCHANGE MARKET', + 'limit': 'EXCHANGE LIMIT', + }, + 'fiat': { + 'USD': 'USD', + 'EUR': 'EUR', + 'JPY': 'JPY', + 'GBP': 'GBP', + }, + }, + 'exceptions': { + 'exact': { + '10020': BadRequest, + '10100': AuthenticationError, + '10114': InvalidNonce, + '20060': OnMaintenance, + }, + 'broad': { + 'address': InvalidAddress, + 'available balance is only': InsufficientFunds, + 'not enough exchange balance': InsufficientFunds, + 'Order not found': OrderNotFound, + 'symbol: invalid': BadSymbol, + 'Invalid order': InvalidOrder, + }, + }, + }); + } + + isFiat (code) { + return (code in this.options['fiat']); + } + + getCurrencyId (code) { + return 'f' + code; + } + + async fetchStatus (params = {}) { + // + // [1] // operative + // [0] // maintenance + // + const response = await this.publicGetPlatformStatus (params); + const status = this.safeValue (response, 0); + const formattedStatus = (status === 1) ? 'ok' : 'maintenance'; + this.status = this.extend (this.status, { + 'status': formattedStatus, + 'updated': this.milliseconds (), + }); + return this.status; + } + + async fetchMarkets (params = {}) { + // todo drop v1 in favor of v2 configs + // pub:list:pair:exchange,pub:list:pair:margin,pub:list:pair:futures,pub:info:pair + const v2response = await this.publicGetConfPubListPairFutures (params); + const v1response = await this.v1GetSymbolsDetails (params); + const futuresMarketIds = this.safeValue (v2response, 0, []); + const result = []; + for (let i = 0; i < v1response.length; i++) { + const market = v1response[i]; + let id = this.safeStringUpper (market, 'pair'); + let spot = true; + if (this.inArray (id, futuresMarketIds)) { + spot = false; + } + const futures = !spot; + const type = spot ? 'spot' : 'futures'; + let baseId = undefined; + let quoteId = undefined; + if (id.indexOf (':') >= 0) { + const parts = id.split (':'); + baseId = parts[0]; + quoteId = parts[1]; + } else { + baseId = id.slice (0, 3); + quoteId = id.slice (3, 6); + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + id = 't' + id; + baseId = this.getCurrencyId (baseId); + quoteId = this.getCurrencyId (quoteId); + const precision = { + 'price': this.safeInteger (market, 'price_precision'), + 'amount': 8, // https://github.com/ccxt/ccxt/issues/7310 + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_order_size'), + 'max': this.safeFloat (market, 'maximum_order_size'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + }; + limits['cost'] = { + 'min': limits['amount']['min'] * limits['price']['min'], + 'max': undefined, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + 'type': type, + 'swap': false, + 'spot': spot, + 'futures': futures, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const labels = [ + 'pub:list:currency', + 'pub:map:currency:sym', // maps symbols to their API symbols, BAB > BCH + 'pub:map:currency:label', // verbose friendly names, BNT > Bancor + 'pub:map:currency:unit', // maps symbols to unit of measure where applicable + 'pub:map:currency:undl', // maps derivatives symbols to their underlying currency + 'pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on + 'pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs + 'pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745 + ]; + const config = labels.join (','); + const request = { + 'config': config, + }; + const response = await this.publicGetConfConfig (this.extend (request, params)); + // + // [ + // + // a list of symbols + // ["AAA","ABS","ADA"], + // + // // sym + // // maps symbols to their API symbols, BAB > BCH + // [ + // [ 'BAB', 'BCH' ], + // [ 'CNHT', 'CNHt' ], + // [ 'DSH', 'DASH' ], + // [ 'IOT', 'IOTA' ], + // [ 'LES', 'LEO-EOS' ], + // [ 'LET', 'LEO-ERC20' ], + // [ 'STJ', 'STORJ' ], + // [ 'TSD', 'TUSD' ], + // [ 'UDC', 'USDC' ], + // [ 'USK', 'USDK' ], + // [ 'UST', 'USDt' ], + // [ 'USTF0', 'USDt0' ], + // [ 'XCH', 'XCHF' ], + // [ 'YYW', 'YOYOW' ], + // // ... + // ], + // // label + // // verbose friendly names, BNT > Bancor + // [ + // [ 'BAB', 'Bitcoin Cash' ], + // [ 'BCH', 'Bitcoin Cash' ], + // [ 'LEO', 'Unus Sed LEO' ], + // [ 'LES', 'Unus Sed LEO (EOS)' ], + // [ 'LET', 'Unus Sed LEO (ERC20)' ], + // // ... + // ], + // // unit + // // maps symbols to unit of measure where applicable + // [ + // [ 'IOT', 'Mi|MegaIOTA' ], + // ], + // // undl + // // maps derivatives symbols to their underlying currency + // [ + // [ 'USTF0', 'UST' ], + // [ 'BTCF0', 'BTC' ], + // [ 'ETHF0', 'ETH' ], + // ], + // // pool + // // maps symbols to underlying network/protocol they operate on + // [ + // [ 'SAN', 'ETH' ], [ 'OMG', 'ETH' ], [ 'AVT', 'ETH' ], [ 'EDO', 'ETH' ], + // [ 'ESS', 'ETH' ], [ 'ATD', 'EOS' ], [ 'ADD', 'EOS' ], [ 'MTO', 'EOS' ], + // [ 'PNK', 'ETH' ], [ 'BAB', 'BCH' ], [ 'WLO', 'XLM' ], [ 'VLD', 'ETH' ], + // [ 'BTT', 'TRX' ], [ 'IMP', 'ETH' ], [ 'SCR', 'ETH' ], [ 'GNO', 'ETH' ], + // // ... + // ], + // // explorer + // // maps symbols to their recognised block explorer URLs + // [ + // [ + // 'AIO', + // [ + // "https://mainnet.aion.network", + // "https://mainnet.aion.network/#/account/VAL", + // "https://mainnet.aion.network/#/transaction/VAL" + // ] + // ], + // // ... + // ], + // // fee + // // maps currencies to their withdrawal fees + // [ + // ["AAA",[0,0]], + // ["ABS",[0,131.3]], + // ["ADA",[0,0.3]], + // ], + // ] + // + const indexed = { + 'sym': this.indexBy (this.safeValue (response, 1, []), 0), + 'label': this.indexBy (this.safeValue (response, 2, []), 0), + 'unit': this.indexBy (this.safeValue (response, 3, []), 0), + 'undl': this.indexBy (this.safeValue (response, 4, []), 0), + 'pool': this.indexBy (this.safeValue (response, 5, []), 0), + 'explorer': this.indexBy (this.safeValue (response, 6, []), 0), + 'fees': this.indexBy (this.safeValue (response, 7, []), 0), + }; + const ids = this.safeValue (response, 0, []); + const result = {}; + for (let i = 0; i < ids.length; i++) { + let id = ids[i]; + const code = this.safeCurrencyCode (id); + const label = this.safeValue (indexed['label'], id, []); + const name = this.safeString (label, 1); + const pool = this.safeValue (indexed['pool'], id, []); + const type = this.safeString (pool, 1); + const feeValues = this.safeValue (indexed['fees'], id, []); + const fees = this.safeValue (feeValues, 1, []); + const fee = this.safeFloat (fees, 1); + const precision = 8; // default precision, todo: fix "magic constants" + id = 'f' + id; + result[code] = { + 'id': id, + 'code': code, + 'info': [ id, label, pool, feeValues ], + 'type': type, + 'name': name, + 'active': true, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'price': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': fee, + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + // this api call does not return the 'used' amount - use the v1 version instead (which also returns zero balances) + await this.loadMarkets (); + const response = await this.privatePostAuthRWallets (params); + const balanceType = this.safeString (params, 'type', 'exchange'); + const result = { 'info': response }; + for (let b = 0; b < response.length; b++) { + const balance = response[b]; + const accountType = balance[0]; + let currency = balance[1]; + const total = balance[2]; + const available = balance[4]; + if (accountType === balanceType) { + if (currency[0] === 't') { + currency = currency.slice (1); + } + const code = this.safeCurrencyCode (currency); + const account = this.account (); + // do not fill in zeroes and missing values in the parser + // rewrite and unify the following to use the unified parseBalance + account['total'] = total; + if (!available) { + if (available === 0) { + account['free'] = 0; + account['used'] = total; + } else { + account['free'] = total; + } + } else { + account['free'] = available; + account['used'] = account['total'] - account['free']; + } + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const precision = this.safeValue (this.options, 'precision', 'R0'); + const request = { + 'symbol': this.marketId (symbol), + 'precision': precision, + }; + if (limit !== undefined) { + request['len'] = limit; // 25 or 100 + } + const fullRequest = this.extend (request, params); + const orderbook = await this.publicGetBookSymbolPrecision (fullRequest); + const timestamp = this.milliseconds (); + const result = { + 'bids': [], + 'asks': [], + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + const priceIndex = (fullRequest['precision'] === 'R0') ? 1 : 0; + for (let i = 0; i < orderbook.length; i++) { + const order = orderbook[i]; + const price = order[priceIndex]; + const amount = Math.abs (order[2]); + const side = (order[2] > 0) ? 'bids' : 'asks'; + result[side].push ([ price, amount ]); + } + result['bids'] = this.sortBy (result['bids'], 0, true); + result['asks'] = this.sortBy (result['asks'], 0); + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const length = ticker.length; + const last = ticker[length - 4]; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': ticker[length - 2], + 'low': ticker[length - 1], + 'bid': ticker[length - 10], + 'bidVolume': undefined, + 'ask': ticker[length - 8], + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': ticker[length - 6], + 'percentage': ticker[length - 5] * 100, + 'average': undefined, + 'baseVolume': ticker[length - 3], + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['symbols'] = ids.join (','); + } else { + request['symbols'] = 'ALL'; + } + const tickers = await this.publicGetTickers (this.extend (request, params)); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const id = ticker[0]; + if (id in this.markets_by_id) { + const market = this.markets_by_id[id]; + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const ticker = await this.publicGetTickerSymbol (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseSymbol (marketId) { + if (marketId === undefined) { + return marketId; + } + marketId = marketId.replace ('t', ''); + let baseId = undefined; + let quoteId = undefined; + if (marketId.indexOf (':') >= 0) { + const parts = marketId.split (':'); + baseId = parts[0]; + quoteId = parts[1]; + } else { + baseId = marketId.slice (0, 3); + quoteId = marketId.slice (3, 6); + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + return base + '/' + quote; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // ID, + // MTS, // timestamp + // AMOUNT, + // PRICE + // ] + // + // fetchMyTrades (private) + // + // [ + // ID, + // PAIR, + // MTS_CREATE, + // ORDER_ID, + // EXEC_AMOUNT, + // EXEC_PRICE, + // ORDER_TYPE, + // ORDER_PRICE, + // MAKER, + // FEE, + // FEE_CURRENCY, + // ... + // ] + // + const tradeLength = trade.length; + const isPrivate = (tradeLength > 5); + const id = trade[0].toString (); + const amountIndex = isPrivate ? 4 : 2; + let amount = trade[amountIndex]; + let cost = undefined; + const priceIndex = isPrivate ? 5 : 3; + const price = trade[priceIndex]; + let side = undefined; + let orderId = undefined; + let takerOrMaker = undefined; + let type = undefined; + let fee = undefined; + let symbol = undefined; + const timestampIndex = isPrivate ? 2 : 1; + const timestamp = trade[timestampIndex]; + if (isPrivate) { + const marketId = trade[1]; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + symbol = this.parseSymbol (marketId); + } + orderId = trade[3].toString (); + takerOrMaker = (trade[8] === 1) ? 'maker' : 'taker'; + let feeCost = trade[9]; + const feeCurrency = this.safeCurrencyCode (trade[10]); + if (feeCost !== undefined) { + feeCost = -feeCost; + if (symbol in this.markets) { + feeCost = this.feeToPrecision (symbol, feeCost); + } else { + const currencyId = 'f' + feeCurrency; + if (currencyId in this.currencies_by_id) { + const currency = this.currencies_by_id[currencyId]; + feeCost = this.currencyToPrecision (currency['code'], feeCost); + } + } + fee = { + 'cost': parseFloat (feeCost), + 'currency': feeCurrency, + }; + } + const orderType = trade[6]; + type = this.safeString (this.options['exchangeTypes'], orderType); + } + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } + } + if (amount !== undefined) { + side = (amount < 0) ? 'sell' : 'buy'; + amount = Math.abs (amount); + if (cost === undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'side': side, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let sort = '-1'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + sort = '1'; + } + if (limit !== undefined) { + request['limit'] = limit; // default 120, max 5000 + } + request['sort'] = sort; + const response = await this.publicGetTradesSymbolHist (this.extend (request, params)); + // + // [ + // [ + // ID, + // MTS, // timestamp + // AMOUNT, + // PRICE + // ] + // ] + // + const trades = this.sortBy (response, 1); + return this.parseTrades (trades, market, undefined, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 100, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; // default 100, max 5000 + } + if (since === undefined) { + since = this.milliseconds () - this.parseTimeframe (timeframe) * limit * 1000; + } + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + 'sort': 1, + 'start': since, + 'limit': limit, + }; + const response = await this.publicGetCandlesTradeTimeframeSymbolHist (this.extend (request, params)); + // + // [ + // [1591503840000,0.025069,0.025068,0.025069,0.025068,1.97828998], + // [1591504500000,0.025065,0.025065,0.025065,0.025065,1.0164], + // [1591504620000,0.025062,0.025062,0.025062,0.025062,0.5], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + if (status === undefined) { + return status; + } + const parts = status.split (' '); + const state = this.safeString (parts, 0); + const statuses = { + 'ACTIVE': 'open', + 'PARTIALLY': 'open', + 'EXECUTED': 'closed', + 'CANCELED': 'canceled', + 'INSUFFICIENT': 'canceled', + 'RSN_DUST': 'rejected', + 'RSN_PAUSE': 'rejected', + }; + return this.safeString (statuses, state, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 0); + let symbol = undefined; + const marketId = this.safeString (order, 3); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + symbol = this.parseSymbol (marketId); + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + // https://github.com/ccxt/ccxt/issues/6686 + // const timestamp = this.safeTimestamp (order, 5); + const timestamp = this.safeInteger (order, 5); + const remaining = Math.abs (this.safeFloat (order, 6)); + const amount = Math.abs (this.safeFloat (order, 7)); + const filled = amount - remaining; + const side = (order[7] < 0) ? 'sell' : 'buy'; + const orderType = this.safeString (order, 8); + const type = this.safeString (this.safeValue (this.options, 'exchangeTypes'), orderType); + let status = undefined; + const statusString = this.safeString (order, 13); + if (statusString !== undefined) { + const parts = statusString.split (' @ '); + status = this.parseOrderStatus (this.safeString (parts, 0)); + } + const price = this.safeFloat (order, 16); + const average = this.safeFloat (order, 17); + const cost = price * filled; + const clientOrderId = this.safeString (order, 2); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderTypes = this.safeValue (this.options, 'orderTypes', {}); + const orderType = this.safeString (orderTypes, type, type); + amount = (side === 'sell') ? -amount : amount; + const request = { + 'symbol': market['id'], + 'type': orderType, + 'amount': this.numberToString (amount), + }; + if (type !== 'market') { + request['price'] = this.numberToString (price); + } + const clientOrderId = this.safeValue2 (params, 'cid', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['cid'] = clientOrderId; + params = this.omit (params, [ 'cid', 'clientOrderId' ]); + } + const response = await this.privatePostAuthWOrderSubmit (this.extend (request, params)); + // + // [ + // 1578784364.748, // Millisecond Time Stamp of the update + // "on-req", // Purpose of notification ('on-req', 'oc-req', 'uca', 'fon-req', 'foc-req') + // null, // Unique ID of the message + // null, // Ignore + // [ + // [ + // 37271830598, // Order ID + // null, // Group ID + // 1578784364748, // Client Order ID + // "tBTCUST", // Pair + // 1578784364748, // Millisecond timestamp of creation + // 1578784364748, // Millisecond timestamp of update + // -0.005, // Positive means buy, negative means sell + // -0.005, // Original amount + // "EXCHANGE LIMIT", // Order type (LIMIT, MARKET, STOP, TRAILING STOP, EXCHANGE MARKET, EXCHANGE LIMIT, EXCHANGE STOP, EXCHANGE TRAILING STOP, FOK, EXCHANGE FOK, IOC, EXCHANGE IOC) + // null, // Previous order type + // null, // Millisecond timestamp of Time-In-Force: automatic order cancellation + // null, // Ignore + // 0, // Flags (see https://docs.bitfinex.com/docs/flag-values) + // "ACTIVE", // Order Status + // null, // Ignore + // null, // Ignore + // 20000, // Price + // 0, // Average price + // 0, // The trailing price + // 0, // Auxiliary Limit price (for STOP LIMIT) + // null, // Ignore + // null, // Ignore + // null, // Ignore + // 0, // 1 - hidden order + // null, // If another order caused this order to be placed (OCO) this will be that other order's ID + // null, // Ignore + // null, // Ignore + // null, // Ignore + // "API>BFX", // Origin of action: BFX, ETHFX, API>BFX, API>ETHFX + // null, // Ignore + // null, // Ignore + // null // Meta + // ] + // ], + // null, // Error code + // "SUCCESS", // Status (SUCCESS, ERROR, FAILURE, ...) + // "Submitting 1 orders." // Text of the notification + // ] + // + const status = this.safeString (response, 6); + if (status !== 'SUCCESS') { + const errorCode = response[5]; + const errorText = response[7]; + throw new ExchangeError (this.id + ' ' + response[6] + ': ' + errorText + ' (#' + errorCode + ')'); + } + const orders = this.safeValue (response, 4, []); + const order = this.safeValue (orders, 0); + return this.parseOrder (order, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const request = { + 'all': 1, + }; + const response = await this.privatePostAuthWOrderCancelMulti (this.extend (request, params)); + const orders = this.safeValue (response, 4, []); + return this.parseOrders (orders); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const cid = this.safeValue2 (params, 'cid', 'clientOrderId'); // client order id + let request = undefined; + if (cid !== undefined) { + const cidDate = this.safeValue (params, 'cidDate'); // client order id date + if (cidDate === undefined) { + throw new InvalidOrder (this.id + " canceling an order by clientOrderId ('cid') requires both 'cid' and 'cid_date' ('YYYY-MM-DD')"); + } + request = { + 'cid': cid, + 'cid_date': cidDate, + }; + params = this.omit (params, [ 'cid', 'clientOrderId' ]); + } else { + request = { + 'id': parseInt (id), + }; + } + const response = await this.privatePostAuthWOrderCancel (this.extend (request, params)); + const order = this.safeValue (response, 4); + return this.parseOrder (order); + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': [ parseInt (id) ], + }; + const orders = await this.fetchOpenOrders (symbol, undefined, undefined, this.extend (request, params)); + const order = this.safeValue (orders, 0); + if (order === undefined) { + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + return order; + } + + async fetchClosedOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': [ parseInt (id) ], + }; + const orders = await this.fetchClosedOrders (symbol, undefined, undefined, this.extend (request, params)); + const order = this.safeValue (orders, 0); + if (order === undefined) { + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + return order; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + let response = undefined; + if (symbol === undefined) { + response = await this.privatePostAuthROrders (this.extend (request, params)); + } else { + market = this.market (symbol); + request['symbol'] = market['id']; + response = await this.privatePostAuthROrdersSymbol (this.extend (request, params)); + } + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // returns the most recent closed or canceled orders up to circa two weeks ago + await this.loadMarkets (); + const request = {}; + let market = undefined; + let response = undefined; + if (symbol === undefined) { + response = await this.privatePostAuthROrdersHist (this.extend (request, params)); + } else { + market = this.market (symbol); + request['symbol'] = market['id']; + response = await this.privatePostAuthROrdersSymbolHist (this.extend (request, params)); + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 25, max 2500 + } + return this.parseOrders (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrderTrades() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const orderId = parseInt (id); + const request = { + 'id': orderId, + 'symbol': market['id'], + }; + // valid for trades upto 10 days old + const response = await this.privatePostAuthROrderSymbolIdTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + 'end': this.milliseconds (), + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 25, max 1000 + } + let method = 'privatePostAuthRTradesHist'; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + method = 'privatePostAuthRTradesSymbolHist'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const request = { + 'op_renew': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + return response; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'method': name, + 'wallet': 'exchange', // 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively + 'op_renew': 0, // a value of 1 will generate a new address + }; + const response = await this.privatePostAuthWDepositAddress (this.extend (request, params)); + // + // [ + // 1582269616687, // MTS Millisecond Time Stamp of the update + // 'acc_dep', // TYPE Purpose of notification 'acc_dep' for account deposit + // null, // MESSAGE_ID unique ID of the message + // null, // not documented + // [ + // null, // PLACEHOLDER + // 'BITCOIN', // METHOD Method of deposit + // 'BTC', // CURRENCY_CODE Currency code of new address + // null, // PLACEHOLDER + // '1BC9PZqpUmjyEB54uggn8TFKj49zSDYzqG', // ADDRESS + // null, // POOL_ADDRESS + // ], + // null, // CODE null or integer work in progress + // 'SUCCESS', // STATUS Status of the notification, SUCCESS, ERROR, FAILURE + // 'success', // TEXT Text of the notification + // ] + // + const result = this.safeValue (response, 4, []); + const poolAddress = this.safeString (result, 5); + const address = (poolAddress === undefined) ? this.safeString (result, 4) : poolAddress; + const tag = (poolAddress === undefined) ? undefined : this.safeString (result, 4); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'SUCCESS': 'ok', + 'ERROR': 'failed', + 'FAILURE': 'failed', + 'CANCELED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // [ + // 1582271520931, // MTS Millisecond Time Stamp of the update + // "acc_wd-req", // TYPE Purpose of notification 'acc_wd-req' account withdrawal request + // null, // MESSAGE_ID unique ID of the message + // null, // not documented + // [ + // 0, // WITHDRAWAL_ID Unique Withdrawal ID + // null, // PLACEHOLDER + // "bitcoin", // METHOD Method of withdrawal + // null, // PAYMENT_ID Payment ID if relevant + // "exchange", // WALLET Sending wallet + // 1, // AMOUNT Amount of Withdrawal less fee + // null, // PLACEHOLDER + // null, // PLACEHOLDER + // 0.0004, // WITHDRAWAL_FEE Fee on withdrawal + // ], + // null, // CODE null or integer Work in progress + // "SUCCESS", // STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE + // "Invalid bitcoin address (abcdef)", // TEXT Text of the notification + // ] + // + // fetchTransactions + // + // [ + // 13293039, // ID + // 'ETH', // CURRENCY + // 'ETHEREUM', // CURRENCY_NAME + // null, + // null, + // 1574175052000, // MTS_STARTED + // 1574181326000, // MTS_UPDATED + // null, + // null, + // 'CANCELED', // STATUS + // null, + // null, + // -0.24, // AMOUNT, negative for withdrawals + // -0.00135, // FEES + // null, + // null, + // 'DESTINATION_ADDRESS', + // null, + // null, + // null, + // 'TRANSACTION_ID', + // "Purchase of 100 pizzas", // WITHDRAW_TRANSACTION_NOTE + // ] + // + const transactionLength = transaction.length; + let timestamp = undefined; + let updated = undefined; + let code = undefined; + let amount = undefined; + let id = undefined; + let status = undefined; + let tag = undefined; + let type = undefined; + let feeCost = undefined; + let txid = undefined; + let addressTo = undefined; + if (transactionLength < 9) { + const data = this.safeValue (transaction, 4, []); + timestamp = this.safeInteger (transaction, 0); + if (currency !== undefined) { + code = currency['code']; + } + feeCost = this.safeFloat (data, 8); + if (feeCost !== undefined) { + feeCost = -feeCost; + } + amount = this.safeFloat (data, 5); + id = this.safeValue (data, 0); + status = 'ok'; + if (id === 0) { + id = undefined; + status = 'failed'; + } + tag = this.safeString (data, 3); + type = 'withdrawal'; + } else { + id = this.safeString (transaction, 0); + timestamp = this.safeInteger (transaction, 5); + updated = this.safeInteger (transaction, 6); + status = this.parseTransactionStatus (this.safeString (transaction, 9)); + amount = this.safeFloat (transaction, 12); + if (amount !== undefined) { + if (amount < 0) { + type = 'withdrawal'; + } else { + type = 'deposit'; + } + } + feeCost = this.safeFloat (transaction, 13); + if (feeCost !== undefined) { + feeCost = -feeCost; + } + addressTo = this.safeString (transaction, 16); + txid = this.safeString (transaction, 20); + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': addressTo, // this is actually the tag for XRP transfers (the address is missing) + 'addressTo': addressTo, + 'tagFrom': undefined, + 'tag': tag, // refix it properly for the tag from description + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'currency': code, + 'cost': feeCost, + 'rate': undefined, + }, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + let method = 'privatePostAuthRMovementsHist'; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + method = 'privatePostAuthRMovementsCurrencyHist'; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // max 1000 + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // [ + // 13293039, // ID + // 'ETH', // CURRENCY + // 'ETHEREUM', // CURRENCY_NAME + // null, + // null, + // 1574175052000, // MTS_STARTED + // 1574181326000, // MTS_UPDATED + // null, + // null, + // 'CANCELED', // STATUS + // null, + // null, + // -0.24, // AMOUNT, negative for withdrawals + // -0.00135, // FEES + // null, + // null, + // 'DESTINATION_ADDRESS', + // null, + // null, + // null, + // 'TRANSACTION_ID', + // "Purchase of 100 pizzas", // WITHDRAW_TRANSACTION_NOTE + // ] + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method + const name = this.getCurrencyName (code); + const request = { + 'method': name, + 'wallet': 'exchange', // 'exchange', 'margin', 'funding' and also old labels 'exchange', 'trading', 'deposit', respectively + 'amount': this.numberToString (amount), + 'address': address, + }; + if (tag !== undefined) { + request['payment_id'] = tag; + } + const response = await this.privatePostAuthWWithdraw (this.extend (request, params)); + // + // [ + // 1582271520931, // MTS Millisecond Time Stamp of the update + // "acc_wd-req", // TYPE Purpose of notification 'acc_wd-req' account withdrawal request + // null, // MESSAGE_ID unique ID of the message + // null, // not documented + // [ + // 0, // WITHDRAWAL_ID Unique Withdrawal ID + // null, // PLACEHOLDER + // "bitcoin", // METHOD Method of withdrawal + // null, // PAYMENT_ID Payment ID if relevant + // "exchange", // WALLET Sending wallet + // 1, // AMOUNT Amount of Withdrawal less fee + // null, // PLACEHOLDER + // null, // PLACEHOLDER + // 0.0004, // WITHDRAWAL_FEE Fee on withdrawal + // ], + // null, // CODE null or integer Work in progress + // "SUCCESS", // STATUS Status of the notification, it may vary over time SUCCESS, ERROR, FAILURE + // "Invalid bitcoin address (abcdef)", // TEXT Text of the notification + // ] + // + const text = this.safeString (response, 7); + if (text !== 'success') { + this.throwBroadlyMatchedException (this.exceptions['broad'], text, text); + } + const transaction = this.parseTransaction (response, currency); + return this.extend (transaction, { + 'address': address, + }); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostPositions (params); + // + // [ + // [ + // "tBTCUSD", // SYMBOL + // "ACTIVE", // STATUS + // 0.0195, // AMOUNT + // 8565.0267019, // BASE_PRICE + // 0, // MARGIN_FUNDING + // 0, // MARGIN_FUNDING_TYPE + // -0.33455568705000516, // PL + // -0.0003117550117425625, // PL_PERC + // 7045.876419249083, // PRICE_LIQ + // 3.0673001895895604, // LEVERAGE + // null, // _PLACEHOLDER + // 142355652, // POSITION_ID + // 1574002216000, // MTS_CREATE + // 1574002216000, // MTS_UPDATE + // null, // _PLACEHOLDER + // 0, // TYPE + // null, // _PLACEHOLDER + // 0, // COLLATERAL + // 0, // COLLATERAL_MIN + // // META + // { + // "reason":"TRADE", + // "order_id":34271018124, + // "liq_stage":null, + // "trade_price":"8565.0267019", + // "trade_amount":"0.0195", + // "order_id_oppo":34277498022 + // } + // ] + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'v1') { + request = api + request; + } else { + request = this.version + request; + } + let url = this.urls['api'][api] + '/' + request; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + body = this.json (query); + const auth = '/api/' + request + nonce + body; + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha384'); + headers = { + 'bfx-nonce': nonce, + 'bfx-apikey': this.apiKey, + 'bfx-signature': signature, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (response) { + if ('message' in response) { + if (response['message'].indexOf ('not enough exchange balance') >= 0) { + throw new InsufficientFunds (this.id + ' ' + this.json (response)); + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } else if (response === '') { + throw new ExchangeError (this.id + ' returned empty response'); + } + return response; + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + if (statusCode === 500) { + // See https://docs.bitfinex.com/docs/abbreviations-glossary#section-errorinfo-codes + const errorCode = this.numberToString (response[1]); + const errorText = response[2]; + const feedback = this.id + ' ' + errorText; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorText, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorText, feedback); + throw new ExchangeError (this.id + ' ' + errorText + ' (#' + errorCode + ')'); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitflyer.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitflyer.js new file mode 100644 index 000000000..99c60448b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitflyer.js @@ -0,0 +1,523 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, OrderNotFound } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitflyer extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitflyer', + 'name': 'bitFlyer', + 'countries': [ 'JP' ], + 'version': 'v1', + 'rateLimit': 1000, // their nonce-timestamp is in seconds... + 'hostname': 'bitflyer.com', // or bitflyer.com + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': 'emulated', + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': 'emulated', + 'fetchOrder': 'emulated', + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg', + 'api': 'https://api.{hostname}', + 'www': 'https://bitflyer.com', + 'doc': 'https://lightning.bitflyer.com/docs?lang=en', + }, + 'api': { + 'public': { + 'get': [ + 'getmarkets/usa', // new (wip) + 'getmarkets/eu', // new (wip) + 'getmarkets', // or 'markets' + 'getboard', // ... + 'getticker', + 'getexecutions', + 'gethealth', + 'getboardstate', + 'getchats', + ], + }, + 'private': { + 'get': [ + 'getpermissions', + 'getbalance', + 'getbalancehistory', + 'getcollateral', + 'getcollateralhistory', + 'getcollateralaccounts', + 'getaddresses', + 'getcoinins', + 'getcoinouts', + 'getbankaccounts', + 'getdeposits', + 'getwithdrawals', + 'getchildorders', + 'getparentorders', + 'getparentorder', + 'getexecutions', + 'getpositions', + 'gettradingcommission', + ], + 'post': [ + 'sendcoin', + 'withdraw', + 'sendchildorder', + 'cancelchildorder', + 'sendparentorder', + 'cancelparentorder', + 'cancelallchildorders', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'BTC/JPY': { + 'maker': 0.15 / 100, + 'taker': 0.15 / 100, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const jp_markets = await this.publicGetGetmarkets (params); + const us_markets = await this.publicGetGetmarketsUsa (params); + const eu_markets = await this.publicGetGetmarketsEu (params); + let markets = this.arrayConcat (jp_markets, us_markets); + markets = this.arrayConcat (markets, eu_markets); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'product_code'); + const currencies = id.split ('_'); + let baseId = undefined; + let quoteId = undefined; + let base = undefined; + let quote = undefined; + const numCurrencies = currencies.length; + if (numCurrencies === 1) { + baseId = id.slice (0, 3); + quoteId = id.slice (3, 6); + } else if (numCurrencies === 2) { + baseId = currencies[0]; + quoteId = currencies[1]; + } else { + baseId = currencies[1]; + quoteId = currencies[2]; + } + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + const symbol = (numCurrencies === 2) ? (base + '/' + quote) : id; + const fees = this.safeValue (this.fees, symbol, this.fees['trading']); + let maker = this.safeValue (fees, 'maker', this.fees['trading']['maker']); + let taker = this.safeValue (fees, 'taker', this.fees['trading']['taker']); + let spot = true; + let future = false; + let type = 'spot'; + if (('alias' in market) || (currencies[0] === 'FX')) { + type = 'future'; + future = true; + spot = false; + maker = 0.0; + taker = 0.0; + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'maker': maker, + 'taker': taker, + 'type': type, + 'spot': spot, + 'future': future, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetGetbalance (params); + // + // [ + // { + // "currency_code": "JPY", + // "amount": 1024078, + // "available": 508000 + // }, + // { + // "currency_code": "BTC", + // "amount": 10.24, + // "available": 4.12 + // }, + // { + // "currency_code": "ETH", + // "amount": 20.48, + // "available": 16.38 + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'amount'); + account['free'] = this.safeFloat (balance, 'available'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + }; + const orderbook = await this.publicGetGetboard (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'size'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + }; + const ticker = await this.publicGetGetticker (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const last = this.safeFloat (ticker, 'ltp'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume_by_product'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + let side = this.safeStringLower (trade, 'side'); + if (side !== undefined) { + if (side.length < 1) { + side = undefined; + } + } + let order = undefined; + if (side !== undefined) { + const id = side + '_child_order_acceptance_id'; + if (id in trade) { + order = trade[id]; + } + } + if (order === undefined) { + order = this.safeString (trade, 'child_order_acceptance_id'); + } + const timestamp = this.parse8601 (this.safeString (trade, 'exec_date')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const id = this.safeString (trade, 'id'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': order, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_code': market['id'], + }; + const response = await this.publicGetGetexecutions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + 'child_order_type': type.toUpperCase (), + 'side': side.toUpperCase (), + 'price': price, + 'size': amount, + }; + const result = await this.privatePostSendchildorder (this.extend (request, params)); + // { "status": - 200, "error_message": "Insufficient funds", "data": null } + const id = this.safeString (result, 'child_order_acceptance_id'); + return { + 'info': result, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder() requires a `symbol` argument'); + } + await this.loadMarkets (); + const request = { + 'product_code': this.marketId (symbol), + 'child_order_acceptance_id': id, + }; + return await this.privatePostCancelchildorder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'ACTIVE': 'open', + 'COMPLETED': 'closed', + 'CANCELED': 'canceled', + 'EXPIRED': 'canceled', + 'REJECTED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const timestamp = this.parse8601 (this.safeString (order, 'child_order_date')); + const amount = this.safeFloat (order, 'size'); + const remaining = this.safeFloat (order, 'outstanding_size'); + const filled = this.safeFloat (order, 'executed_size'); + const price = this.safeFloat (order, 'price'); + const cost = price * filled; + const status = this.parseOrderStatus (this.safeString (order, 'child_order_state')); + const type = this.safeStringLower (order, 'child_order_type'); + const side = this.safeStringLower (order, 'side'); + const marketId = this.safeString (order, 'product_code'); + const symbol = this.safeSymbol (marketId, market); + let fee = undefined; + const feeCost = this.safeFloat (order, 'total_commission'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + 'rate': undefined, + }; + } + const id = this.safeString (order, 'child_order_acceptance_id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = 100, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_code': market['id'], + 'count': limit, + }; + const response = await this.privateGetGetchildorders (this.extend (request, params)); + let orders = this.parseOrders (response, market, since, limit); + if (symbol !== undefined) { + orders = this.filterBy (orders, 'symbol', symbol); + } + return orders; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = 100, params = {}) { + const request = { + 'child_order_state': 'ACTIVE', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = 100, params = {}) { + const request = { + 'child_order_state': 'COMPLETED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a `symbol` argument'); + } + const orders = await this.fetchOrders (symbol); + const ordersById = this.indexBy (orders, 'id'); + if (id in ordersById) { + return ordersById[id]; + } + throw new OrderNotFound (this.id + ' No order found with id ' + id); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_code': market['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetexecutions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + if (symbols === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPositions requires a `symbols` argument, exactly one symbol in an array'); + } + await this.loadMarkets (); + const request = { + 'product_code': this.marketIds (symbols), + }; + const response = await this.privateGetpositions (this.extend (request, params)); + // + // [ + // { + // "product_code": "FX_BTC_JPY", + // "side": "BUY", + // "price": 36000, + // "size": 10, + // "commission": 0, + // "swap_point_accumulate": -35, + // "require_collateral": 120000, + // "open_date": "2015-11-03T10:04:45.011", + // "leverage": 3, + // "pnl": 965, + // "sfd": -0.5 + // } + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + if (code !== 'JPY' && code !== 'USD' && code !== 'EUR') { + throw new ExchangeError (this.id + ' allows withdrawing JPY, USD, EUR only, ' + code + ' is not supported'); + } + const currency = this.currency (code); + const request = { + 'currency_code': currency['id'], + 'amount': amount, + // 'bank_account_id': 1234, + }; + const response = await this.privatePostWithdraw (this.extend (request, params)); + const id = this.safeString (response, 'message_id'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.version + '/'; + if (api === 'private') { + request += 'me/'; + } + request += path; + if (method === 'GET') { + if (Object.keys (params).length) { + request += '?' + this.urlencode (params); + } + } + const baseUrl = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }); + const url = baseUrl + request; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let auth = [ nonce, method, request ].join (''); + if (Object.keys (params).length) { + if (method !== 'GET') { + body = this.json (params); + auth += body; + } + } + headers = { + 'ACCESS-KEY': this.apiKey, + 'ACCESS-TIMESTAMP': nonce, + 'ACCESS-SIGN': this.hmac (this.encode (auth), this.encode (this.secret)), + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitforex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitforex.js new file mode 100644 index 000000000..9932a4d34 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitforex.js @@ -0,0 +1,628 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, OrderNotFound, InsufficientFunds, DDoSProtection, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitforex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitforex', + 'name': 'Bitforex', + 'countries': [ 'CN' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + '1M': '1month', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295553-1160ec00-c50e-11ea-8ea0-df79276a9646.jpg', + 'api': 'https://api.bitforex.com', + 'www': 'https://www.bitforex.com', + 'doc': 'https://github.com/githubdev2020/API_Doc_en/wiki', + 'fees': 'https://help.bitforex.com/en_us/?cat=13', + 'referral': 'https://www.bitforex.com/en/invitationRegister?inviterId=1867438', + }, + 'api': { + 'public': { + 'get': [ + 'api/v1/market/symbols', + 'api/v1/market/ticker', + 'api/v1/market/depth', + 'api/v1/market/trades', + 'api/v1/market/kline', + ], + }, + 'private': { + 'post': [ + 'api/v1/fund/mainAccount', + 'api/v1/fund/allAccount', + 'api/v1/trade/placeOrder', + 'api/v1/trade/placeMultiOrder', + 'api/v1/trade/cancelOrder', + 'api/v1/trade/orderInfo', + 'api/v1/trade/orderInfos', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': true, + 'deposit': {}, + 'withdraw': { + 'BTC': 0.0005, + 'ETH': 0.01, + 'BCH': 0.0001, + 'LTC': 0.001, + 'ETC': 0.005, + 'USDT': 5, + 'CMCT': 30, + 'AION': 3, + 'LVT': 0, + 'DATA': 40, + 'RHP': 50, + 'NEO': 0, + 'AIDOC': 10, + 'BQT': 2, + 'R': 2, + 'DPY': 0.8, + 'GTC': 40, + 'AGI': 30, + 'DENT': 100, + 'SAN': 1, + 'SPANK': 8, + 'AID': 5, + 'OMG': 0.1, + 'BFT': 5, + 'SHOW': 150, + 'TRX': 20, + 'ABYSS': 10, + 'THM': 25, + 'ZIL': 20, + 'PPT': 0.2, + 'WTC': 0.4, + 'LRC': 7, + 'BNT': 1, + 'CTXC': 1, + 'MITH': 20, + 'TRUE': 4, + 'LYM': 10, + 'VEE': 100, + 'AUTO': 200, + 'REN': 50, + 'TIO': 2.5, + 'NGC': 1.5, + 'PST': 10, + 'CRE': 200, + 'IPC': 5, + 'PTT': 1000, + 'XMCT': 20, + 'ATMI': 40, + 'TERN': 40, + 'XLM': 0.01, + 'ODE': 15, + 'FTM': 100, + 'RTE': 100, + 'DCC': 100, + 'IMT': 500, + 'GOT': 3, + 'EGT': 500, + 'DACC': 1000, + 'UBEX': 500, + 'ABL': 100, + 'OLT': 100, + 'DAV': 40, + 'THRT': 10, + 'RMESH': 3, + 'UPP': 20, + 'SDT': 0, + 'SHR': 10, + 'MTV': 3, + 'ESS': 100, + 'MET': 3, + 'TTC': 20, + 'LXT': 10, + 'XCLP': 100, + 'LUK': 100, + 'UBC': 100, + 'DTX': 10, + 'BEAT': 20, + 'DEED': 2, + 'BGX': 3000, + 'PRL': 20, + 'ELY': 50, + 'CARD': 300, + 'SQR': 15, + 'VRA': 400, + 'BWX': 3500, + 'MAS': 75, + 'FLP': 0.6, + 'UNC': 300, + 'CRNC': 15, + 'MFG': 70, + 'ZXC': 70, + 'TRT': 30, + 'ZIX': 35, + 'XRA': 10, + 'AMO': 1600, + 'IPG': 3, + 'uDoo': 50, + 'URB': 30, + 'ARCONA': 3, + 'CRAD': 5, + 'NOBS': 1000, + 'ADF': 2, + 'ELF': 5, + 'LX': 20, + 'PATH': 15, + 'SILK': 120, + 'SKYFT': 50, + 'EDN': 50, + 'ADE': 50, + 'EDR': 10, + 'TIME': 0.25, + 'SPRK': 20, + 'QTUM': 0.01, + 'BF': 5, + 'ZPR': 100, + 'HYB': 10, + 'CAN': 30, + 'CEL': 10, + 'ATS': 50, + 'KCASH': 1, + 'ACT': 0.01, + 'MT': 300, + 'DXT': 30, + 'WAB': 4000, + 'HYDRO': 400, + 'LQD': 5, + 'OPTC': 200, + 'EQUAD': 80, + 'LATX': 50, + 'LEDU': 100, + 'RIT': 70, + 'ACDC': 500, + 'FSN': 2, + }, + }, + }, + 'commonCurrencies': { + 'CREDIT': 'TerraCredit', + 'IQ': 'IQ.Cash', + 'UOS': 'UOS Network', + }, + 'exceptions': { + '4004': OrderNotFound, + '1013': AuthenticationError, + '1016': AuthenticationError, + '1017': PermissionDenied, // {"code":"1017","success":false,"time":1602670594367,"message":"IP not allow"} + '3002': InsufficientFunds, + '10204': DDoSProtection, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetApiV1MarketSymbols (params); + const data = response['data']; + const result = []; + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const id = this.safeString (market, 'symbol'); + const symbolParts = id.split ('-'); + const baseId = symbolParts[2]; + const quoteId = symbolParts[1]; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = true; + const precision = { + 'amount': this.safeInteger (market, 'amountPrecision'), + 'price': this.safeInteger (market, 'pricePrecision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minOrderAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (trade, 'time'); + const id = this.safeString (trade, 'tid'); + const orderId = undefined; + const amount = this.safeFloat (trade, 'amount'); + const price = this.safeFloat (trade, 'price'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const sideId = this.safeInteger (trade, 'direction'); + const side = this.parseSide (sideId); + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'order': orderId, + 'fee': undefined, + 'takerOrMaker': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['size'] = limit; + } + const market = this.market (symbol); + const response = await this.publicGetApiV1MarketTrades (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostApiV1FundAllAccount (params); + const data = response['data']; + const result = { 'info': response }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'frozen'); + account['free'] = this.safeFloat (balance, 'active'); + account['total'] = this.safeFloat (balance, 'fix'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.markets[symbol]; + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetApiV1MarketTicker (this.extend (request, params)); + const data = response['data']; + const timestamp = this.safeInteger (data, 'date'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (data, 'high'), + 'low': this.safeFloat (data, 'low'), + 'bid': this.safeFloat (data, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (data, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': this.safeFloat (data, 'last'), + 'last': this.safeFloat (data, 'last'), + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (data, 'vol'), + 'quoteVolume': undefined, + 'info': response, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "close":0.02505143, + // "currencyVol":0, + // "high":0.02506422, + // "low":0.02505143, + // "open":0.02506095, + // "time":1591508940000, + // "vol":51.1869 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'vol'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'ktype': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['size'] = limit; // default 1, max 600 + } + const response = await this.publicGetApiV1MarketKline (this.extend (request, params)); + // + // { + // "data":[ + // {"close":0.02505143,"currencyVol":0,"high":0.02506422,"low":0.02505143,"open":0.02506095,"time":1591508940000,"vol":51.1869}, + // {"close":0.02503914,"currencyVol":0,"high":0.02506687,"low":0.02503914,"open":0.02505358,"time":1591509000000,"vol":9.1082}, + // {"close":0.02505172,"currencyVol":0,"high":0.02507466,"low":0.02503895,"open":0.02506371,"time":1591509060000,"vol":63.7431}, + // ], + // "success":true, + // "time":1591509427131 + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.publicGetApiV1MarketDepth (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const timestamp = this.safeInteger (response, 'time'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', + '2': 'closed', + '3': 'canceled', + '4': 'canceled', + }; + return (status in statuses) ? statuses[status] : status; + } + + parseSide (sideId) { + if (sideId === 1) { + return 'buy'; + } else if (sideId === 2) { + return 'sell'; + } else { + return undefined; + } + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'orderId'); + const timestamp = this.safeFloat (order, 'createTime'); + const lastTradeTimestamp = this.safeFloat (order, 'lastTime'); + const symbol = market['symbol']; + const sideId = this.safeInteger (order, 'tradeType'); + const side = this.parseSide (sideId); + const type = undefined; + const price = this.safeFloat (order, 'orderPrice'); + const average = this.safeFloat (order, 'avgPrice'); + const amount = this.safeFloat (order, 'orderAmount'); + const filled = this.safeFloat (order, 'dealAmount'); + const remaining = amount - filled; + const status = this.parseOrderStatus (this.safeString (order, 'orderState')); + const cost = filled * price; + const feeSide = (side === 'buy') ? 'base' : 'quote'; + const feeCurrency = market[feeSide]; + const fee = { + 'cost': this.safeFloat (order, 'tradeFee'), + 'currency': feeCurrency, + }; + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': this.marketId (symbol), + 'orderId': id, + }; + const response = await this.privatePostApiV1TradeOrderInfo (this.extend (request, params)); + const order = this.parseOrder (response['data'], market); + return order; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': this.marketId (symbol), + 'state': 0, + }; + const response = await this.privatePostApiV1TradeOrderInfos (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': this.marketId (symbol), + 'state': 1, + }; + const response = await this.privatePostApiV1TradeOrderInfos (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let sideId = undefined; + if (side === 'buy') { + sideId = 1; + } else if (side === 'sell') { + sideId = 2; + } + const request = { + 'symbol': this.marketId (symbol), + 'price': price, + 'amount': amount, + 'tradeType': sideId, + }; + const response = await this.privatePostApiV1TradePlaceOrder (this.extend (request, params)); + const data = response['data']; + return { + 'info': response, + 'id': this.safeString (data, 'orderId'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + if (symbol !== undefined) { + request['symbol'] = this.marketId (symbol); + } + const results = await this.privatePostApiV1TradeCancelOrder (this.extend (request, params)); + const success = results['success']; + const returnVal = { 'info': results, 'success': success }; + return returnVal; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + let payload = this.urlencode ({ 'accessKey': this.apiKey }); + query['nonce'] = this.milliseconds (); + if (Object.keys (query).length) { + payload += '&' + this.urlencode (this.keysort (query)); + } + // let message = '/' + 'api/' + this.version + '/' + path + '?' + payload; + const message = '/' + path + '?' + payload; + const signature = this.hmac (this.encode (message), this.encode (this.secret)); + body = payload + '&signData=' + signature; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (typeof body !== 'string') { + return; // fallback to default error handler + } + if ((body[0] === '{') || (body[0] === '[')) { + const feedback = this.id + ' ' + body; + const success = this.safeValue (response, 'success'); + if (success !== undefined) { + if (!success) { + const code = this.safeString (response, 'code'); + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitget.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitget.js new file mode 100644 index 000000000..78e7fe8aa --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitget.js @@ -0,0 +1,2904 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, OnMaintenance, ArgumentsRequired, BadRequest, AccountSuspended, InvalidAddress, PermissionDenied, DDoSProtection, InsufficientFunds, InvalidNonce, CancelPending, InvalidOrder, OrderNotFound, AuthenticationError, RequestTimeout, NotSupported, BadSymbol, RateLimitExceeded } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitget extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitget', + 'name': 'Bitget', + 'countries': [ 'SG' ], + 'version': 'v3', + 'rateLimit': 1000, // up to 3000 requests per 5 minutes ≈ 600 requests per minute ≈ 10 requests per second ≈ 100 ms + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + }, + 'hostname': 'bitget.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/88317935-a8a21c80-cd22-11ea-8e2b-4b9fac5975eb.jpg', + 'api': { + 'data': 'https://api.{hostname}', + 'api': 'https://api.{hostname}', + 'capi': 'https://capi.{hostname}', + 'swap': 'https://capi.{hostname}', + }, + 'www': 'https://www.bitget.com', + 'doc': [ + 'https://bitgetlimited.github.io/apidoc/en/swap', + 'https://bitgetlimited.github.io/apidoc/en/spot', + ], + 'fees': 'https://www.bitget.cc/zh-CN/rate?tab=1', + 'test': { + 'rest': 'https://testnet.bitget.com', + }, + 'referral': 'https://www.bitget.com/expressly?languageType=0&channelCode=ccxt&vipCode=tg9j', + }, + 'api': { + 'data': { + 'get': [ + 'market/history/kline', // Kline data + 'market/detail/merged', // Get aggregated ticker + 'market/tickers', // Get all trading tickers + 'market/allticker', // Get all trading market method 2 + 'market/depth', // Get Market Depth Data + 'market/trade', // Get Trade Detail Data + 'market/history/trade', // Get record of trading + 'market/detail', // Get Market Detail 24h Volume + 'common/symbols', // Query all trading pairs and accuracy supported in the station + 'common/currencys', // Query all currencies supported in the station + 'common/timestamp', // Query system current time + ], + }, + 'api': { + 'get': [ + 'account/accounts', // Get all accounts of current user(即account_id)。 + 'accounts/{account_id}/balance', // Get the balance of the specified account + 'order/orders', // Query order, deprecated + 'order/orders/openOrders', + 'order/orders/history', + 'order/deposit_withdraw', // Query assets history + ], + 'post': [ + 'order/orders/place', // Place order + 'order/orders/{order_id}/submitcancel', // Request to cancel an order request + 'order/orders/batchcancel', // Bulk order cancellation + 'order/orders/{order_id}', // Query an order details + 'order/orders/{order_id}/matchresults', // Query the transaction details of an order + 'order/matchresults', // Query current order, order history + ], + }, + 'capi': { + 'get': [ + 'market/time', + 'market/contracts', + 'market/depth', + 'market/tickers', + 'market/ticker', + 'market/trades', + 'market/candles', + 'market/index', + 'market/open_count', + 'market/open_interest', + 'market/price_limit', + 'market/funding_time', + 'market/mark_price', + 'market/open_count', + 'market/historyFundRate', + ], + }, + 'swap': { + 'get': [ + 'account/accounts', + 'account/account', + 'account/settings', + 'position/allPosition', + 'position/singlePosition', + 'position/holds', + 'order/detail', + 'order/orders', + 'order/fills', + 'order/current', + 'order/currentPlan', // conditional + 'order/history', + 'order/historyPlan', // conditional + 'trace/closeTrack', + 'trace/currentTrack', + 'trace/historyTrack', + ], + 'post': [ + 'account/leverage', + 'account/adjustMargin', + 'account/modifyAutoAppendMargin', + 'order/placeOrder', + 'order/batchOrders', + 'order/cancel_order', + 'order/cancel_batch_orders', + 'order/plan_order', + 'order/cancel_plan', + 'position/changeHoldModel', + ], + }, + }, + 'fees': { + 'spot': { + 'taker': 0.002, + 'maker': 0.002, + }, + 'swap': { + 'taker': 0.0006, + 'maker': 0.0004, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'exceptions': { + // http error codes + // 400 Bad Request — Invalid request format + // 401 Unauthorized — Invalid API Key + // 403 Forbidden — You do not have access to the requested resource + // 404 Not Found + // 500 Internal Server Error — We had a problem with our server + 'exact': { + '1': ExchangeError, // { "code": 1, "message": "System error" } + // undocumented + 'failure to get a peer from the ring-balancer': ExchangeNotAvailable, // { "message": "failure to get a peer from the ring-balancer" } + '4010': PermissionDenied, // { "code": 4010, "message": "For the security of your funds, withdrawals are not permitted within 24 hours after changing fund password / mobile number / Google Authenticator settings " } + // common + // '0': ExchangeError, // 200 successful,when the order placement / cancellation / operation is successful + '4001': ExchangeError, // no data received in 30s + '4002': ExchangeError, // Buffer full. cannot write data + // -------------------------------------------------------- + '30001': AuthenticationError, // { "code": 30001, "message": 'request header "OK_ACCESS_KEY" cannot be blank'} + '30002': AuthenticationError, // { "code": 30002, "message": 'request header "OK_ACCESS_SIGN" cannot be blank'} + '30003': AuthenticationError, // { "code": 30003, "message": 'request header "OK_ACCESS_TIMESTAMP" cannot be blank'} + '30004': AuthenticationError, // { "code": 30004, "message": 'request header "OK_ACCESS_PASSPHRASE" cannot be blank'} + '30005': InvalidNonce, // { "code": 30005, "message": "invalid OK_ACCESS_TIMESTAMP" } + '30006': AuthenticationError, // { "code": 30006, "message": "invalid OK_ACCESS_KEY" } + '30007': BadRequest, // { "code": 30007, "message": 'invalid Content_Type, please use "application/json" format'} + '30008': RequestTimeout, // { "code": 30008, "message": "timestamp request expired" } + '30009': ExchangeError, // { "code": 30009, "message": "system error" } + '30010': AuthenticationError, // { "code": 30010, "message": "API validation failed" } + '30011': PermissionDenied, // { "code": 30011, "message": "invalid IP" } + '30012': AuthenticationError, // { "code": 30012, "message": "invalid authorization" } + '30013': AuthenticationError, // { "code": 30013, "message": "invalid sign" } + '30014': DDoSProtection, // { "code": 30014, "message": "request too frequent" } + '30015': AuthenticationError, // { "code": 30015, "message": 'request header "OK_ACCESS_PASSPHRASE" incorrect'} + '30016': ExchangeError, // { "code": 30015, "message": "you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey" } + '30017': ExchangeError, // { "code": 30017, "message": "apikey's broker id does not match" } + '30018': ExchangeError, // { "code": 30018, "message": "apikey's domain does not match" } + '30019': ExchangeNotAvailable, // { "code": 30019, "message": "Api is offline or unavailable" } + '30020': BadRequest, // { "code": 30020, "message": "body cannot be blank" } + '30021': BadRequest, // { "code": 30021, "message": "Json data format error" }, { "code": 30021, "message": "json data format error" } + '30022': PermissionDenied, // { "code": 30022, "message": "Api has been frozen" } + '30023': BadRequest, // { "code": 30023, "message": "{0} parameter cannot be blank" } + '30024': BadSymbol, // {"code":30024,"message":"\"instrument_id\" is an invalid parameter"} + '30025': BadRequest, // { "code": 30025, "message": "{0} parameter category error" } + '30026': DDoSProtection, // { "code": 30026, "message": "requested too frequent" } + '30027': AuthenticationError, // { "code": 30027, "message": "login failure" } + '30028': PermissionDenied, // { "code": 30028, "message": "unauthorized execution" } + '30029': AccountSuspended, // { "code": 30029, "message": "account suspended" } + '30030': ExchangeError, // { "code": 30030, "message": "endpoint request failed. Please try again" } + '30031': BadRequest, // { "code": 30031, "message": "token does not exist" } + '30032': BadSymbol, // { "code": 30032, "message": "pair does not exist" } + '30033': BadRequest, // { "code": 30033, "message": "exchange domain does not exist" } + '30034': ExchangeError, // { "code": 30034, "message": "exchange ID does not exist" } + '30035': ExchangeError, // { "code": 30035, "message": "trading is not supported in this website" } + '30036': ExchangeError, // { "code": 30036, "message": "no relevant data" } + '30037': ExchangeNotAvailable, // { "code": 30037, "message": "endpoint is offline or unavailable" } + // '30038': AuthenticationError, // { "code": 30038, "message": "user does not exist" } + '30038': OnMaintenance, // {"client_oid":"","code":"30038","error_code":"30038","error_message":"Matching engine is being upgraded. Please try in about 1 minute.","message":"Matching engine is being upgraded. Please try in about 1 minute.","order_id":"-1","result":false} + // futures + '32001': AccountSuspended, // { "code": 32001, "message": "futures account suspended" } + '32002': PermissionDenied, // { "code": 32002, "message": "futures account does not exist" } + '32003': CancelPending, // { "code": 32003, "message": "canceling, please wait" } + '32004': ExchangeError, // { "code": 32004, "message": "you have no unfilled orders" } + '32005': InvalidOrder, // { "code": 32005, "message": "max order quantity" } + '32006': InvalidOrder, // { "code": 32006, "message": "the order price or trigger price exceeds USD 1 million" } + '32007': InvalidOrder, // { "code": 32007, "message": "leverage level must be the same for orders on the same side of the contract" } + '32008': InvalidOrder, // { "code": 32008, "message": "Max. positions to open (cross margin)" } + '32009': InvalidOrder, // { "code": 32009, "message": "Max. positions to open (fixed margin)" } + '32010': ExchangeError, // { "code": 32010, "message": "leverage cannot be changed with open positions" } + '32011': ExchangeError, // { "code": 32011, "message": "futures status error" } + '32012': ExchangeError, // { "code": 32012, "message": "futures order update error" } + '32013': ExchangeError, // { "code": 32013, "message": "token type is blank" } + '32014': ExchangeError, // { "code": 32014, "message": "your number of contracts closing is larger than the number of contracts available" } + '32015': ExchangeError, // { "code": 32015, "message": "margin ratio is lower than 100% before opening positions" } + '32016': ExchangeError, // { "code": 32016, "message": "margin ratio is lower than 100% after opening position" } + '32017': ExchangeError, // { "code": 32017, "message": "no BBO" } + '32018': ExchangeError, // { "code": 32018, "message": "the order quantity is less than 1, please try again" } + '32019': ExchangeError, // { "code": 32019, "message": "the order price deviates from the price of the previous minute by more than 3%" } + '32020': ExchangeError, // { "code": 32020, "message": "the price is not in the range of the price limit" } + '32021': ExchangeError, // { "code": 32021, "message": "leverage error" } + '32022': ExchangeError, // { "code": 32022, "message": "this function is not supported in your country or region according to the regulations" } + '32023': ExchangeError, // { "code": 32023, "message": "this account has outstanding loan" } + '32024': ExchangeError, // { "code": 32024, "message": "order cannot be placed during delivery" } + '32025': ExchangeError, // { "code": 32025, "message": "order cannot be placed during settlement" } + '32026': ExchangeError, // { "code": 32026, "message": "your account is restricted from opening positions" } + '32027': ExchangeError, // { "code": 32027, "message": "cancelled over 20 orders" } + '32028': AccountSuspended, // { "code": 32028, "message": "account is suspended and liquidated" } + '32029': ExchangeError, // { "code": 32029, "message": "order info does not exist" } + '32030': InvalidOrder, // The order cannot be cancelled + '32031': ArgumentsRequired, // client_oid or order_id is required. + '32038': AuthenticationError, // User does not exist + '32040': ExchangeError, // User have open contract orders or position + '32044': ExchangeError, // { "code": 32044, "message": "The margin ratio after submitting this order is lower than the minimum requirement ({0}) for your tier." } + '32045': ExchangeError, // String of commission over 1 million + '32046': ExchangeError, // Each user can hold up to 10 trade plans at the same time + '32047': ExchangeError, // system error + '32048': InvalidOrder, // Order strategy track range error + '32049': ExchangeError, // Each user can hold up to 10 track plans at the same time + '32050': InvalidOrder, // Order strategy rang error + '32051': InvalidOrder, // Order strategy ice depth error + '32052': ExchangeError, // String of commission over 100 thousand + '32053': ExchangeError, // Each user can hold up to 6 ice plans at the same time + '32057': ExchangeError, // The order price is zero. Market-close-all function cannot be executed + '32054': ExchangeError, // Trade not allow + '32055': InvalidOrder, // cancel order error + '32056': ExchangeError, // iceberg per order average should between {0}-{1} contracts + '32058': ExchangeError, // Each user can hold up to 6 initiative plans at the same time + '32059': InvalidOrder, // Total amount should exceed per order amount + '32060': InvalidOrder, // Order strategy type error + '32061': InvalidOrder, // Order strategy initiative limit error + '32062': InvalidOrder, // Order strategy initiative range error + '32063': InvalidOrder, // Order strategy initiative rate error + '32064': ExchangeError, // Time Stringerval of orders should set between 5-120s + '32065': ExchangeError, // Close amount exceeds the limit of Market-close-all (999 for BTC, and 9999 for the rest tokens) + '32066': ExchangeError, // You have open orders. Please cancel all open orders before changing your leverage level. + '32067': ExchangeError, // Account equity < required margin in this setting. Please adjust your leverage level again. + '32068': ExchangeError, // The margin for this position will fall short of the required margin in this setting. Please adjust your leverage level or increase your margin to proceed. + '32069': ExchangeError, // Target leverage level too low. Your account balance is insufficient to cover the margin required. Please adjust the leverage level again. + '32070': ExchangeError, // Please check open position or unfilled order + '32071': ExchangeError, // Your current liquidation mode does not support this action. + '32072': ExchangeError, // The highest available margin for your order’s tier is {0}. Please edit your margin and place a new order. + '32073': ExchangeError, // The action does not apply to the token + '32074': ExchangeError, // The number of contracts of your position, open orders, and the current order has exceeded the maximum order limit of this asset. + '32075': ExchangeError, // Account risk rate breach + '32076': ExchangeError, // Liquidation of the holding position(s) at market price will require cancellation of all pending close orders of the contracts. + '32077': ExchangeError, // Your margin for this asset in futures account is insufficient and the position has been taken over for liquidation. (You will not be able to place orders, close positions, transfer funds, or add margin during this period of time. Your account will be restored after the liquidation is complete.) + '32078': ExchangeError, // Please cancel all open orders before switching the liquidation mode(Please cancel all open orders before switching the liquidation mode) + '32079': ExchangeError, // Your open positions are at high risk.(Please add margin or reduce positions before switching the mode) + '32080': ExchangeError, // Funds cannot be transferred out within 30 minutes after futures settlement + '32083': ExchangeError, // The number of contracts should be a positive multiple of %%. Please place your order again + // token and margin trading + '33001': PermissionDenied, // { "code": 33001, "message": "margin account for this pair is not enabled yet" } + '33002': AccountSuspended, // { "code": 33002, "message": "margin account for this pair is suspended" } + '33003': InsufficientFunds, // { "code": 33003, "message": "no loan balance" } + '33004': ExchangeError, // { "code": 33004, "message": "loan amount cannot be smaller than the minimum limit" } + '33005': ExchangeError, // { "code": 33005, "message": "repayment amount must exceed 0" } + '33006': ExchangeError, // { "code": 33006, "message": "loan order not found" } + '33007': ExchangeError, // { "code": 33007, "message": "status not found" } + '33008': InsufficientFunds, // { "code": 33008, "message": "loan amount cannot exceed the maximum limit" } + '33009': ExchangeError, // { "code": 33009, "message": "user ID is blank" } + '33010': ExchangeError, // { "code": 33010, "message": "you cannot cancel an order during session 2 of call auction" } + '33011': ExchangeError, // { "code": 33011, "message": "no new market data" } + '33012': ExchangeError, // { "code": 33012, "message": "order cancellation failed" } + '33013': InvalidOrder, // { "code": 33013, "message": "order placement failed" } + '33014': OrderNotFound, // { "code": 33014, "message": "order does not exist" } + '33015': InvalidOrder, // { "code": 33015, "message": "exceeded maximum limit" } + '33016': ExchangeError, // { "code": 33016, "message": "margin trading is not open for this token" } + '33017': InsufficientFunds, // { "code": 33017, "message": "insufficient balance" } + '33018': ExchangeError, // { "code": 33018, "message": "this parameter must be smaller than 1" } + '33020': ExchangeError, // { "code": 33020, "message": "request not supported" } + '33021': BadRequest, // { "code": 33021, "message": "token and the pair do not match" } + '33022': InvalidOrder, // { "code": 33022, "message": "pair and the order do not match" } + '33023': ExchangeError, // { "code": 33023, "message": "you can only place market orders during call auction" } + '33024': InvalidOrder, // { "code": 33024, "message": "trading amount too small" } + '33025': InvalidOrder, // { "code": 33025, "message": "base token amount is blank" } + '33026': ExchangeError, // { "code": 33026, "message": "transaction completed" } + '33027': InvalidOrder, // { "code": 33027, "message": "cancelled order or order cancelling" } + '33028': InvalidOrder, // { "code": 33028, "message": "the decimal places of the trading price exceeded the limit" } + '33029': InvalidOrder, // { "code": 33029, "message": "the decimal places of the trading size exceeded the limit" } + '33034': ExchangeError, // { "code": 33034, "message": "You can only place limit order after Call Auction has started" } + '33035': ExchangeError, // This type of order cannot be canceled(This type of order cannot be canceled) + '33036': ExchangeError, // Exceeding the limit of entrust order + '33037': ExchangeError, // The buy order price should be lower than 130% of the trigger price + '33038': ExchangeError, // The sell order price should be higher than 70% of the trigger price + '33039': ExchangeError, // The limit of callback rate is 0 < x <= 5% + '33040': ExchangeError, // The trigger price of a buy order should be lower than the latest transaction price + '33041': ExchangeError, // The trigger price of a sell order should be higher than the latest transaction price + '33042': ExchangeError, // The limit of price variance is 0 < x <= 1% + '33043': ExchangeError, // The total amount must be larger than 0 + '33044': ExchangeError, // The average amount should be 1/1000 * total amount <= x <= total amount + '33045': ExchangeError, // The price should not be 0, including trigger price, order price, and price limit + '33046': ExchangeError, // Price variance should be 0 < x <= 1% + '33047': ExchangeError, // Sweep ratio should be 0 < x <= 100% + '33048': ExchangeError, // Per order limit: Total amount/1000 < x <= Total amount + '33049': ExchangeError, // Total amount should be X > 0 + '33050': ExchangeError, // Time interval should be 5 <= x <= 120s + '33051': ExchangeError, // cancel order number not higher limit: plan and track entrust no more than 10, ice and time entrust no more than 6 + '33059': BadRequest, // { "code": 33059, "message": "client_oid or order_id is required" } + '33060': BadRequest, // { "code": 33060, "message": "Only fill in either parameter client_oid or order_id" } + '33061': ExchangeError, // Value of a single market price order cannot exceed 100,000 USD + '33062': ExchangeError, // The leverage ratio is too high. The borrowed position has exceeded the maximum position of this leverage ratio. Please readjust the leverage ratio + '33063': ExchangeError, // Leverage multiple is too low, there is insufficient margin in the account, please readjust the leverage ratio + '33064': ExchangeError, // The setting of the leverage ratio cannot be less than 2, please readjust the leverage ratio + '33065': ExchangeError, // Leverage ratio exceeds maximum leverage ratio, please readjust leverage ratio + // account + '21009': ExchangeError, // Funds cannot be transferred out within 30 minutes after swap settlement(Funds cannot be transferred out within 30 minutes after swap settlement) + '34001': PermissionDenied, // { "code": 34001, "message": "withdrawal suspended" } + '34002': InvalidAddress, // { "code": 34002, "message": "please add a withdrawal address" } + '34003': ExchangeError, // { "code": 34003, "message": "sorry, this token cannot be withdrawn to xx at the moment" } + '34004': ExchangeError, // { "code": 34004, "message": "withdrawal fee is smaller than minimum limit" } + '34005': ExchangeError, // { "code": 34005, "message": "withdrawal fee exceeds the maximum limit" } + '34006': ExchangeError, // { "code": 34006, "message": "withdrawal amount is lower than the minimum limit" } + '34007': ExchangeError, // { "code": 34007, "message": "withdrawal amount exceeds the maximum limit" } + '34008': InsufficientFunds, // { "code": 34008, "message": "insufficient balance" } + '34009': ExchangeError, // { "code": 34009, "message": "your withdrawal amount exceeds the daily limit" } + '34010': ExchangeError, // { "code": 34010, "message": "transfer amount must be larger than 0" } + '34011': ExchangeError, // { "code": 34011, "message": "conditions not met" } + '34012': ExchangeError, // { "code": 34012, "message": "the minimum withdrawal amount for NEO is 1, and the amount must be an integer" } + '34013': ExchangeError, // { "code": 34013, "message": "please transfer" } + '34014': ExchangeError, // { "code": 34014, "message": "transfer limited" } + '34015': ExchangeError, // { "code": 34015, "message": "subaccount does not exist" } + '34016': PermissionDenied, // { "code": 34016, "message": "transfer suspended" } + '34017': AccountSuspended, // { "code": 34017, "message": "account suspended" } + '34018': AuthenticationError, // { "code": 34018, "message": "incorrect trades password" } + '34019': PermissionDenied, // { "code": 34019, "message": "please bind your email before withdrawal" } + '34020': PermissionDenied, // { "code": 34020, "message": "please bind your funds password before withdrawal" } + '34021': InvalidAddress, // { "code": 34021, "message": "Not verified address" } + '34022': ExchangeError, // { "code": 34022, "message": "Withdrawals are not available for sub accounts" } + '34023': PermissionDenied, // { "code": 34023, "message": "Please enable futures trading before transferring your funds" } + '34026': ExchangeError, // transfer too frequently(transfer too frequently) + '34036': ExchangeError, // Parameter is incorrect, please refer to API documentation + '34037': ExchangeError, // Get the sub-account balance interface, account type is not supported + '34038': ExchangeError, // Since your C2C transaction is unusual, you are restricted from fund transfer. Please contact our customer support to cancel the restriction + '34039': ExchangeError, // You are now restricted from transferring out your funds due to abnormal trades on C2C Market. Please transfer your fund on our website or app instead to verify your identity + // swap + '35001': ExchangeError, // { "code": 35001, "message": "Contract does not exist" } + '35002': ExchangeError, // { "code": 35002, "message": "Contract settling" } + '35003': ExchangeError, // { "code": 35003, "message": "Contract paused" } + '35004': ExchangeError, // { "code": 35004, "message": "Contract pending settlement" } + '35005': AuthenticationError, // { "code": 35005, "message": "User does not exist" } + '35008': InvalidOrder, // { "code": 35008, "message": "Risk ratio too high" } + '35010': InvalidOrder, // { "code": 35010, "message": "Position closing too large" } + '35012': InvalidOrder, // { "code": 35012, "message": "Incorrect order size" } + '35014': InvalidOrder, // { "code": 35014, "message": "Order price is not within limit" } + '35015': InvalidOrder, // { "code": 35015, "message": "Invalid leverage level" } + '35017': ExchangeError, // { "code": 35017, "message": "Open orders exist" } + '35019': InvalidOrder, // { "code": 35019, "message": "Order size too large" } + '35020': InvalidOrder, // { "code": 35020, "message": "Order price too high" } + '35021': InvalidOrder, // { "code": 35021, "message": "Order size exceeded current tier limit" } + '35022': ExchangeError, // { "code": 35022, "message": "Contract status error" } + '35024': ExchangeError, // { "code": 35024, "message": "Contract not initialized" } + '35025': InsufficientFunds, // { "code": 35025, "message": "No account balance" } + '35026': ExchangeError, // { "code": 35026, "message": "Contract settings not initialized" } + '35029': OrderNotFound, // { "code": 35029, "message": "Order does not exist" } + '35030': InvalidOrder, // { "code": 35030, "message": "Order size too large" } + '35031': InvalidOrder, // { "code": 35031, "message": "Cancel order size too large" } + '35032': ExchangeError, // { "code": 35032, "message": "Invalid user status" } + '35037': ExchangeError, // No last traded price in cache + '35039': ExchangeError, // { "code": 35039, "message": "Open order quantity exceeds limit" } + '35040': InvalidOrder, // {"error_message":"Invalid order type","result":"true","error_code":"35040","order_id":"-1"} + '35044': ExchangeError, // { "code": 35044, "message": "Invalid order status" } + '35046': InsufficientFunds, // { "code": 35046, "message": "Negative account balance" } + '35047': InsufficientFunds, // { "code": 35047, "message": "Insufficient account balance" } + '35048': ExchangeError, // { "code": 35048, "message": "User contract is frozen and liquidating" } + '35049': InvalidOrder, // { "code": 35049, "message": "Invalid order type" } + '35050': InvalidOrder, // { "code": 35050, "message": "Position settings are blank" } + '35052': InsufficientFunds, // { "code": 35052, "message": "Insufficient cross margin" } + '35053': ExchangeError, // { "code": 35053, "message": "Account risk too high" } + '35055': InsufficientFunds, // { "code": 35055, "message": "Insufficient account balance" } + '35057': ExchangeError, // { "code": 35057, "message": "No last traded price" } + '35058': ExchangeError, // { "code": 35058, "message": "No limit" } + '35059': BadRequest, // { "code": 35059, "message": "client_oid or order_id is required" } + '35060': BadRequest, // { "code": 35060, "message": "Only fill in either parameter client_oid or order_id" } + '35061': BadRequest, // { "code": 35061, "message": "Invalid instrument_id" } + '35062': InvalidOrder, // { "code": 35062, "message": "Invalid match_price" } + '35063': InvalidOrder, // { "code": 35063, "message": "Invalid order_size" } + '35064': InvalidOrder, // { "code": 35064, "message": "Invalid client_oid" } + '35066': InvalidOrder, // Order interval error + '35067': InvalidOrder, // Time-weighted order ratio error + '35068': InvalidOrder, // Time-weighted order range error + '35069': InvalidOrder, // Time-weighted single transaction limit error + '35070': InvalidOrder, // Algo order type error + '35071': InvalidOrder, // Order total must be larger than single order limit + '35072': InvalidOrder, // Maximum 6 unfulfilled time-weighted orders can be held at the same time + '35073': InvalidOrder, // Order price is 0. Market-close-all not available + '35074': InvalidOrder, // Iceberg order single transaction average error + '35075': InvalidOrder, // Failed to cancel order + '35076': InvalidOrder, // LTC 20x leverage. Not allowed to open position + '35077': InvalidOrder, // Maximum 6 unfulfilled iceberg orders can be held at the same time + '35078': InvalidOrder, // Order amount exceeded 100,000 + '35079': InvalidOrder, // Iceberg order price variance error + '35080': InvalidOrder, // Callback rate error + '35081': InvalidOrder, // Maximum 10 unfulfilled trail orders can be held at the same time + '35082': InvalidOrder, // Trail order callback rate error + '35083': InvalidOrder, // Each user can only hold a maximum of 10 unfulfilled stop-limit orders at the same time + '35084': InvalidOrder, // Order amount exceeded 1 million + '35085': InvalidOrder, // Order amount is not in the correct range + '35086': InvalidOrder, // Price exceeds 100 thousand + '35087': InvalidOrder, // Price exceeds 100 thousand + '35088': InvalidOrder, // Average amount error + '35089': InvalidOrder, // Price exceeds 100 thousand + '35090': ExchangeError, // No stop-limit orders available for cancelation + '35091': ExchangeError, // No trail orders available for cancellation + '35092': ExchangeError, // No iceberg orders available for cancellation + '35093': ExchangeError, // No trail orders available for cancellation + '35094': ExchangeError, // Stop-limit order last traded price error + '35095': BadRequest, // Instrument_id error + '35096': ExchangeError, // Algo order status error + '35097': ExchangeError, // Order status and order ID cannot exist at the same time + '35098': ExchangeError, // An order status or order ID must exist + '35099': ExchangeError, // Algo order ID error + // option + '36001': BadRequest, // Invalid underlying index. + '36002': BadRequest, // Instrument does not exist. + '36005': ExchangeError, // Instrument status is invalid. + '36101': AuthenticationError, // Account does not exist. + '36102': PermissionDenied, // Account status is invalid. + '36103': AccountSuspended, // Account is suspended due to ongoing liquidation. + '36104': PermissionDenied, // Account is not enabled for options trading. + '36105': PermissionDenied, // Please enable the account for option contract. + '36106': AccountSuspended, // Funds cannot be transferred in or out, as account is suspended. + '36107': PermissionDenied, // Funds cannot be transferred out within 30 minutes after option exercising or settlement. + '36108': InsufficientFunds, // Funds cannot be transferred in or out, as equity of the account is less than zero. + '36109': PermissionDenied, // Funds cannot be transferred in or out during option exercising or settlement. + '36201': PermissionDenied, // New order function is blocked. + '36202': PermissionDenied, // Account does not have permission to short option. + '36203': InvalidOrder, // Invalid format for client_oid. + '36204': ExchangeError, // Invalid format for request_id. + '36205': BadRequest, // Instrument id does not match underlying index. + '36206': BadRequest, // Order_id and client_oid can not be used at the same time. + '36207': InvalidOrder, // Either order price or fartouch price must be present. + '36208': InvalidOrder, // Either order price or size must be present. + '36209': InvalidOrder, // Either order_id or client_oid must be present. + '36210': InvalidOrder, // Either order_ids or client_oids must be present. + '36211': InvalidOrder, // Exceeding max batch size for order submission. + '36212': InvalidOrder, // Exceeding max batch size for oder cancellation. + '36213': InvalidOrder, // Exceeding max batch size for order amendment. + '36214': ExchangeError, // Instrument does not have valid bid/ask quote. + '36216': OrderNotFound, // Order does not exist. + '36217': InvalidOrder, // Order submission failed. + '36218': InvalidOrder, // Order cancellation failed. + '36219': InvalidOrder, // Order amendment failed. + '36220': InvalidOrder, // Order is pending cancel. + '36221': InvalidOrder, // Order qty is not valid multiple of lot size. + '36222': InvalidOrder, // Order price is breaching highest buy limit. + '36223': InvalidOrder, // Order price is breaching lowest sell limit. + '36224': InvalidOrder, // Exceeding max order size. + '36225': InvalidOrder, // Exceeding max open order count for instrument. + '36226': InvalidOrder, // Exceeding max open order count for underlying. + '36227': InvalidOrder, // Exceeding max open size across all orders for underlying + '36228': InvalidOrder, // Exceeding max available qty for instrument. + '36229': InvalidOrder, // Exceeding max available qty for underlying. + '36230': InvalidOrder, // Exceeding max position limit for underlying. + // -------------------------------------------------------- + // swap + '400': BadRequest, // Bad Request + '401': AuthenticationError, // Unauthorized access + '403': PermissionDenied, // Access prohibited + '404': BadRequest, // Request address does not exist + '405': BadRequest, // The HTTP Method is not supported + '415': BadRequest, // The current media type is not supported + '429': DDoSProtection, // Too many requests + '500': ExchangeNotAvailable, // System busy + '1001': RateLimitExceeded, // The request is too frequent and has been throttled + '1002': ExchangeError, // {0} verifications within 24 hours + '1003': ExchangeError, // You failed more than {0} times today, the current operation is locked, please try again in 24 hours + // '00000': ExchangeError, // success + '40001': AuthenticationError, // ACCESS_KEY cannot be empty + '40002': AuthenticationError, // SECRET_KEY cannot be empty + '40003': AuthenticationError, // Signature cannot be empty + '40004': InvalidNonce, // Request timestamp expired + '40005': InvalidNonce, // Invalid ACCESS_TIMESTAMP + '40006': AuthenticationError, // Invalid ACCESS_KEY + '40007': BadRequest, // Invalid Content_Type + '40008': InvalidNonce, // Request timestamp expired + '40009': AuthenticationError, // sign signature error + '40010': AuthenticationError, // sign signature error + '40011': AuthenticationError, // ACCESS_PASSPHRASE cannot be empty + '40012': AuthenticationError, // apikey/password is incorrect + '40013': ExchangeError, // User status is abnormal + '40014': PermissionDenied, // Incorrect permissions + '40015': ExchangeError, // System is abnormal, please try again later + '40016': PermissionDenied, // The user must bind the phone or Google + '40017': ExchangeError, // Parameter verification failed + '40018': PermissionDenied, // Invalid IP + '40102': BadRequest, // Contract configuration does not exist, please check the parameters + '40103': BadRequest, // Request method cannot be empty + '40104': ExchangeError, // Lever adjustment failure + '40105': ExchangeError, // Abnormal access to current price limit data + '40106': ExchangeError, // Abnormal get next settlement time + '40107': ExchangeError, // Abnormal access to index price data + '40108': InvalidOrder, // Wrong order quantity + '40109': OrderNotFound, // The data of the order cannot be found, please confirm the order number + '40200': OnMaintenance, // Server upgrade, please try again later + '40201': InvalidOrder, // Order number cannot be empty + '40202': ExchangeError, // User information cannot be empty + '40203': BadRequest, // The amount of adjustment margin cannot be empty or negative + '40204': BadRequest, // Adjustment margin type cannot be empty + '40205': BadRequest, // Adjusted margin type data is wrong + '40206': BadRequest, // The direction of the adjustment margin cannot be empty + '40207': BadRequest, // The adjustment margin data is wrong + '40208': BadRequest, // The accuracy of the adjustment margin amount is incorrect + '40209': BadRequest, // The current page number is wrong, please confirm + '40300': ExchangeError, // User does not exist + '40301': PermissionDenied, // Permission has not been obtained yet. If you need to use it, please contact customer service + '40302': BadRequest, // Parameter abnormality + '40303': BadRequest, // Can only query up to 20,000 data + '40304': BadRequest, // Parameter type is abnormal + '40305': BadRequest, // Client_oid length is not greater than 50, and cannot be Martian characters + '40306': ExchangeError, // Batch processing orders can only process up to 20 + '40308': OnMaintenance, // The contract is being temporarily maintained + '40309': BadSymbol, // The contract has been removed + '40400': ExchangeError, // Status check abnormal + '40401': ExchangeError, // The operation cannot be performed + '40402': BadRequest, // The opening direction cannot be empty + '40403': BadRequest, // Wrong opening direction format + '40404': BadRequest, // Whether to enable automatic margin call parameters cannot be empty + '40405': BadRequest, // Whether to enable the automatic margin call parameter type is wrong + '40406': BadRequest, // Whether to enable automatic margin call parameters is of unknown type + '40407': ExchangeError, // The query direction is not the direction entrusted by the plan + '40408': ExchangeError, // Wrong time range + '40409': ExchangeError, // Time format error + '40500': InvalidOrder, // Client_oid check error + '40501': ExchangeError, // Channel name error + '40502': ExchangeError, // If it is a copy user, you must pass the copy to whom + '40503': ExchangeError, // With the single type + '40504': ExchangeError, // Platform code must pass + '40505': ExchangeError, // Not the same as single type + '40506': AuthenticationError, // Platform signature error + '40507': AuthenticationError, // Api signature error + '40508': ExchangeError, // KOL is not authorized + '40509': ExchangeError, // Abnormal copy end + '40600': ExchangeError, // Copy function suspended + '40601': ExchangeError, // Followers cannot be KOL + '40602': ExchangeError, // The number of copies has reached the limit and cannot process the request + '40603': ExchangeError, // Abnormal copy end + '40604': ExchangeNotAvailable, // Server is busy, please try again later + '40605': ExchangeError, // Copy type, the copy number must be passed + '40606': ExchangeError, // The type of document number is wrong + '40607': ExchangeError, // Document number must be passed + '40608': ExchangeError, // No documented products currently supported + '40609': ExchangeError, // The contract product does not support copying + '40700': BadRequest, // Cursor parameters are incorrect + '40701': ExchangeError, // KOL is not authorized + '40702': ExchangeError, // Unauthorized copying user + '40703': ExchangeError, // Bill inquiry start and end time cannot be empty + '40704': ExchangeError, // Can only check the data of the last three months + '40705': BadRequest, // The start and end time cannot exceed 90 days + '40706': InvalidOrder, // Wrong order price + '40707': BadRequest, // Start time is greater than end time + '40708': BadRequest, // Parameter verification is abnormal + '40709': ExchangeError, // There is no position in this position, and no automatic margin call can be set + '40710': ExchangeError, // Abnormal account status + '40711': InsufficientFunds, // Insufficient contract account balance + '40712': InsufficientFunds, // Insufficient margin + '40713': ExchangeError, // Cannot exceed the maximum transferable margin amount + '40714': ExchangeError, // No direct margin call is allowed + // spot + 'invalid sign': AuthenticationError, + 'invalid currency': BadSymbol, // invalid trading pair + 'invalid symbol': BadSymbol, + 'invalid period': BadRequest, // invalid Kline type + 'invalid user': ExchangeError, + 'invalid amount': InvalidOrder, + 'invalid type': InvalidOrder, // {"status":"error","ts":1595700344504,"err_code":"invalid-parameter","err_msg":"invalid type"} + 'invalid orderId': InvalidOrder, + 'invalid record': ExchangeError, + 'invalid accountId': BadRequest, + 'invalid address': BadRequest, + 'accesskey not null': AuthenticationError, // {"status":"error","ts":1595704360508,"err_code":"invalid-parameter","err_msg":"accesskey not null"} + 'illegal accesskey': AuthenticationError, + 'sign not null': AuthenticationError, + 'req_time is too much difference from server time': InvalidNonce, + 'permissions not right': PermissionDenied, // {"status":"error","ts":1595704490084,"err_code":"invalid-parameter","err_msg":"permissions not right"} + 'illegal sign invalid': AuthenticationError, // {"status":"error","ts":1595684716042,"err_code":"invalid-parameter","err_msg":"illegal sign invalid"} + 'user locked': AccountSuspended, + 'Request Frequency Is Too High': RateLimitExceeded, + 'more than a daily rate of cash': BadRequest, + 'more than the maximum daily withdrawal amount': BadRequest, + 'need to bind email or mobile': ExchangeError, + 'user forbid': PermissionDenied, + 'User Prohibited Cash Withdrawal': PermissionDenied, + 'Cash Withdrawal Is Less Than The Minimum Value': BadRequest, + 'Cash Withdrawal Is More Than The Maximum Value': BadRequest, + 'the account with in 24 hours ban coin': PermissionDenied, + 'order cancel fail': BadRequest, // {"status":"error","ts":1595703343035,"err_code":"bad-request","err_msg":"order cancel fail"} + 'base symbol error': BadSymbol, + 'base date error': ExchangeError, + 'api signature not valid': AuthenticationError, + 'gateway internal error': ExchangeError, + 'audit failed': ExchangeError, + 'order queryorder invalid': BadRequest, + 'market no need price': InvalidOrder, + 'limit need price': InvalidOrder, + 'userid not equal to account_id': ExchangeError, + 'your balance is low': InsufficientFunds, // {"status":"error","ts":1595594160149,"err_code":"invalid-parameter","err_msg":"invalid size, valid range: [1,2000]"} + 'address invalid cointype': ExchangeError, + 'system exception': ExchangeError, // {"status":"error","ts":1595711862763,"err_code":"system exception","err_msg":"system exception"} + '50003': ExchangeError, // No record + '50004': BadSymbol, // The transaction pair is currently not supported or has been suspended + '50006': PermissionDenied, // The account is forbidden to withdraw. If you have any questions, please contact customer service. + '50007': PermissionDenied, // The account is forbidden to withdraw within 24 hours. If you have any questions, please contact customer service. + '50008': RequestTimeout, // network timeout + '50009': RateLimitExceeded, // The operation is too frequent, please try again later + '50010': ExchangeError, // The account is abnormally frozen. If you have any questions, please contact customer service. + '50014': InvalidOrder, // The transaction amount under minimum limits + '50015': InvalidOrder, // The transaction amount exceed maximum limits + '50016': InvalidOrder, // The price can't be higher than the current price + '50017': InvalidOrder, // Price under minimum limits + '50018': InvalidOrder, // The price exceed maximum limits + '50019': InvalidOrder, // The amount under minimum limits + '50020': InsufficientFunds, // Insufficient balance + '50021': InvalidOrder, // Price is under minimum limits + '50026': InvalidOrder, // Market price parameter error + 'invalid order query time': ExchangeError, // start time is greater than end time; or the time interval between start time and end time is greater than 48 hours + 'invalid start time': BadRequest, // start time is a date 30 days ago; or start time is a date in the future + 'invalid end time': BadRequest, // end time is a date 30 days ago; or end time is a date in the future + '20003': ExchangeError, // operation failed, {"status":"error","ts":1595730308979,"err_code":"bad-request","err_msg":"20003"} + '01001': ExchangeError, // order failed, {"status":"fail","err_code":"01001","err_msg":"系统异常,请稍后重试"} + }, + 'broad': { + 'invalid size, valid range': ExchangeError, + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarkets': [ + 'spot', + 'swap', + ], + 'parseOHLCV': { + 'volume': { + 'spot': 'amount', + 'swap': 5, + }, + }, + 'defaultType': 'spot', // 'spot', 'swap' + 'accountId': undefined, // '1012838157', + 'timeframes': { + 'spot': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '60min', + '2h': '120min', + '4h': '240min', + '6h': '360min', + '12h': '720min', + '1d': '1day', + '1w': '1week', + }, + 'swap': { + '1m': '60', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '604800', + }, + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.dataGetCommonTimestamp (params); + // + // { + // "status":"ok", + // "data":"1595525139400" + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchMarkets (params = {}) { + let types = this.safeValue (this.options, 'fetchMarkets'); + if (!types.length) { + types = [ + this.options['defaultType'], + ]; + } + let result = []; + for (let i = 0; i < types.length; i++) { + const markets = await this.fetchMarketsByType (types[i], params); + result = this.arrayConcat (result, markets); + } + return result; + } + + parseMarkets (markets) { + const result = []; + for (let i = 0; i < markets.length; i++) { + result.push (this.parseMarket (markets[i])); + } + return result; + } + + parseMarket (market) { + // + // spot + // + // { + // "base_currency":"btc", + // "quote_currency":"usdt", + // "symbol":"btc_usdt", + // "tick_size":"2", + // "size_increment":"4", + // "status":"1", + // "base_asset_precision":"8" + // } + // + // + // swap + // + // { + // "symbol":"btcusd", + // "underlying_index":"BTC", + // "quote_currency":"USD", + // "coin":"BTC", + // "contract_val":"1", + // "listing":null, + // "delivery":["07:00:00","15:00:00","23:00:00"], + // "size_increment":"0", + // "tick_size":"1", + // "forwardContractFlag":false, + // "priceEndStep":5 + // } + // + const id = this.safeString (market, 'symbol'); + let marketType = 'spot'; + let spot = true; + let swap = false; + const baseId = this.safeString2 (market, 'base_currency', 'coin'); + const quoteId = this.safeString (market, 'quote_currency'); + const contractVal = this.safeFloat (market, 'contract_val'); + if (contractVal !== undefined) { + marketType = 'swap'; + spot = false; + swap = true; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = id.toUpperCase (); + if (spot) { + symbol = base + '/' + quote; + } + const tickSize = this.safeString (market, 'tick_size'); + const sizeIncrement = this.safeString (market, 'size_increment'); + const precision = { + 'amount': parseFloat ('1e-' + sizeIncrement), + 'price': parseFloat ('1e-' + tickSize), + }; + const minAmount = this.safeFloat2 (market, 'min_size', 'base_min_size'); + const status = this.safeString (market, 'status'); + let active = undefined; + if (status !== undefined) { + active = (status === '1'); + } + const fees = this.safeValue2 (this.fees, marketType, 'trading', {}); + return this.extend (fees, { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': marketType, + 'spot': spot, + 'swap': swap, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': precision['price'], + 'max': undefined, + }, + 'cost': { + 'min': precision['price'], + 'max': undefined, + }, + }, + }); + } + + async fetchMarketsByType (type, params = {}) { + if (type === 'spot') { + const response = await this.dataGetCommonSymbols (params); + // + // { + // "status":"ok", + // "ts":1595526622408, + // "data":[ + // { + // "base_currency":"btc", + // "quote_currency":"usdt", + // "symbol":"btc_usdt", + // "tick_size":"2", + // "size_increment":"4", + // "status":"1", + // "base_asset_precision":"8" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseMarkets (data); + } else if (type === 'swap') { + const response = await this.capiGetMarketContracts (params); + // + // { + // "data":{ + // "contractApis":[ + // { + // "instrument_id":"btcusd", + // "underlying_index":"BTC", + // "quote_currency":"USD", + // "coin":"BTC", + // "contract_val":"1", + // "delivery":["07:00:00","15:00:00","23:00:00"], + // "size_increment":"0", + // "tick_size":"1", + // "forwardContractFlag":false, + // "priceEndStep":"5" + // }, + // ] + // }, + // "status":"ok", + // "err_code":"00000" + // } + // + return this.parseMarkets (response); + } else { + throw new NotSupported (this.id + ' fetchMarketsByType does not support market type ' + type); + } + } + + async fetchCurrencies (params = {}) { + const response = await this.dataGetCommonCurrencys (params); + // + // { + // "status":"ok", + // "ts":1595537740466, + // "data":[ + // "btc", + // "bft", + // "usdt", + // "usdt-omni", + // "usdt-erc20" + // ] + // } + // + const result = {}; + const data = this.safeValue (response, 'data', []); + for (let i = 0; i < data.length; i++) { + const id = data[i]; + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'info': id, + 'type': undefined, + 'name': undefined, + 'active': undefined, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (market['spot']) { + method = 'dataGetMarketDepth'; + request['type'] = 'step0'; // step0, step1, step2, step3, step4, step5, do not merge depth if step0 + } else if (market['swap']) { + method = 'capiGetMarketDepth'; + request['limit'] = (limit === undefined) ? 100 : limit; // max 100 + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.btc_usdt.depth.step0", + // "ts":1595607628197, + // "data":{ + // "id":"1595607628197", + // "ts":"1595607628197", + // "bids":[ + // ["9534.99","15.36160000000000000000"], + // ["9534.85","0.14580000000000000000"], + // ["9534.73","0.02100000000000000000"], + // ], + // "asks":[ + // ["9535.02","7.37160000000000000000"], + // ["9535.03","0.09040000000000000000"], + // ["9535.05","0.02180000000000000000"], + // ] + // } + // } + // + // swap + // + // { + // "asks":[ + // ["9579.0","119865",1], + // ["9579.5","90069",1], + // ["9580.0","256673",1], + // ], + // "bids":[ + // ["9578.5","2417",1], + // ["9577.5","3024",1], + // ["9577.0","21548",1], + // ], + // "timestamp":"1595664767349" + // } + // + const data = this.safeValue (response, 'data', response); + const timestamp = this.safeInteger2 (data, 'timestamp', 'ts'); + const nonce = this.safeInteger (data, 'id'); + const orderbook = this.parseOrderBook (data, timestamp); + orderbook['nonce'] = nonce; + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // spot + // + // fetchTicker + // + // { + // "id":"1595538241113", + // "bid":["0.028474000000","1.139400000000"], + // "ask":["0.028482000000","0.353100000000"], + // "amount":"2850.6649", + // "count":"818", + // "open":"0.02821", + // "close":"0.028474", + // "low":"0.02821", + // "high":"0.029091", + // "vol":"79.4548693404" + // } + // + // fetchTickers + // + // { + // "amount":"30086.8095", + // "count":"22450", + // "open":"9525.11", + // "close":"9591.81", + // "low":"9510.68", + // "high":"9659.7", + // "vol":"286239092.250461", + // "symbol":"btc_usdt" + // } + // + // swap + // + // { + // "instrument_id":"btcusd", + // "last":"9574.5", + // "best_ask":"9575.0", + // "best_bid":"9574.0", + // "high_24h":"9672", + // "low_24h":"9512", + // "volume_24h":"567697050", + // "timestamp":"1595538450096" + // } + // + const timestamp = this.safeInteger2 (ticker, 'timestamp', 'id'); + let symbol = undefined; + const marketId = this.safeString2 (ticker, 'instrument_id', 'symbol'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else if (marketId !== undefined) { + const parts = marketId.split ('_'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const last = this.safeFloat2 (ticker, 'last', 'close'); + const open = this.safeFloat (ticker, 'open'); + let bidVolume = undefined; + let askVolume = undefined; + let bid = this.safeValue (ticker, 'bid'); + if (bid === undefined) { + bid = this.safeFloat (ticker, 'best_bid'); + } else { + bidVolume = this.safeFloat (bid, 1); + bid = this.safeFloat (bid, 0); + } + let ask = this.safeValue (ticker, 'ask'); + if (ask === undefined) { + ask = this.safeFloat (ticker, 'best_ask'); + } else { + askVolume = this.safeFloat (ask, 1); + ask = this.safeFloat (ask, 0); + } + const baseVolume = this.safeFloat2 (ticker, 'amount', 'volume_24h'); + const quoteVolume = this.safeFloat (ticker, 'vol'); + const vwap = this.vwap (baseVolume, quoteVolume); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + percentage = change / open * 100; + average = this.sum (open, last) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (ticker, 'high', 'high_24h'), + 'low': this.safeFloat2 (ticker, 'low', 'low_24h'), + 'bid': bid, + 'bidVolume': bidVolume, + 'ask': ask, + 'askVolume': askVolume, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + if (market['spot']) { + method = 'dataGetMarketDetailMerged'; + } else if (market['swap']) { + method = 'capiGetMarketTicker'; + } + const request = { + 'symbol': market['id'], + }; + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.eth_btc.detail.merged", + // "ts":1595538241474, + // "data":{ + // "id":"1595538241113", + // "bid":["0.028474000000","1.139400000000"], + // "ask":["0.028482000000","0.353100000000"], + // "amount":"2850.6649", + // "count":"818", + // "open":"0.02821", + // "close":"0.028474", + // "low":"0.02821", + // "high":"0.029091", + // "vol":"79.4548693404" + // } + // } + // + // swap + // + // { + // "symbol":"btcusd", + // "last":"9575.5", + // "best_ask":"9576.0", + // "best_bid":"9575.0", + // "high_24h":"9646", + // "low_24h":"9516", + // "volume_24h":"516656839", + // "timestamp":"1595664217405" + // } + // + const data = this.safeValue (response, 'data', response); + return this.parseTicker (data, market); + } + + async fetchTickersByType (type, symbols = undefined, params = {}) { + await this.loadMarkets (); + let method = undefined; + if (type === 'spot') { + method = 'dataGetMarketTickers'; + } else if (type === 'swap') { + method = 'capiGetMarketTickers'; + } + const response = await this[method] (params); + // + // spot + // + // { + // "status":"ok", + // "ts":1595542893250, + // "data":[ + // { + // "amount":"30086.8095", + // "count":"22450", + // "open":"9525.11", + // "close":"9591.81", + // "low":"9510.68", + // "high":"9659.7", + // "vol":"286239092.250461", + // "symbol":"btc_usdt" + // } + // ] + // } + // + // swap + // + // [ + // { + // "symbol":"btcusd", + // "last":"9572", + // "best_ask":"9571.5", + // "best_bid":"9570.5", + // "high_24h":"9646", + // "low_24h":"9516", + // "volume_24h":"515401635", + // "timestamp":"1595664479952" + // } + // ] + // + const data = this.safeValue (response, 'data', response); + let timestamp = undefined; + if (!Array.isArray (response)) { + timestamp = this.safeInteger (response, 'ts'); + } + const result = {}; + for (let i = 0; i < data.length; i++) { + const ticker = this.parseTicker (this.extend ({ + 'timestamp': timestamp, + }, data[i])); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + return await this.fetchTickersByType (type, symbols, this.omit (params, 'type')); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // spot + // + // { + // "id":"1", + // "price":"9533.81", + // "amount":"0.7326", + // "direction":"sell", + // "ts":"1595604964000" + // } + // + // swap + // + // { + // "trade_id":"670581881367954915", + // "price":"9553.00", + // "size":"20", + // "side":"sell", + // "timestamp":"1595605100004", + // "symbol":"btcusd" + // } + // + // spot fetchMyTrades (private) + // + // { + // "id": 29555, + // "order_id": 59378, + // "match_id": 59335, + // "symbol": "eth_usdt", + // "type": "buy-limit", + // "source": "api", + // "price": "100.1000000000", + // "filled_amount": "0.9845000000", + // "filled_fees": "0.0019690000", + // "created_at": 1494901400487 + // } + // + // fetchOrderTrades (private) + // + // spot + // + // { + // "id":"614164775", + // "created_at":"1596298860602", + // "filled_amount":"0.0417000000000000", + // "filled_fees":"0.0000834000000000", + // "match_id":"673491702661292033", + // "order_id":"673491720340279296", + // "price":"359.240000000000", + // "source":"接口", + // "symbol":"eth_usdt", + // "type":"buy-market" + // } + // + // swap + // + // { + // "trade_id":"6667390", + // "symbol":"cmt_btcusdt", + // "order_id":"525946425993854915", + // "price":"9839.00", + // "order_qty":"3466", + // "fee":"-0.0000528407360000", + // "timestamp":"1561121514442", + // "exec_type":"M", + // "side":"3" + // } + // + let symbol = undefined; + const marketId = this.safeString (trade, 'symbol'); + let base = undefined; + let quote = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } else if (marketId !== undefined) { + const parts = marketId.split ('_'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId.toUpperCase (); + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let timestamp = this.safeInteger (trade, 'created_at'); + timestamp = this.safeInteger2 (trade, 'timestamp', 'ts', timestamp); + const price = this.safeFloat (trade, 'price'); + let amount = this.safeFloat2 (trade, 'filled_amount', 'order_qty'); + amount = this.safeFloat2 (trade, 'size', 'amount', amount); + let takerOrMaker = this.safeString2 (trade, 'exec_type', 'liquidity'); + if (takerOrMaker === 'M') { + takerOrMaker = 'maker'; + } else if (takerOrMaker === 'T') { + takerOrMaker = 'taker'; + } + const orderType = this.safeString (trade, 'type'); + let side = undefined; + let type = undefined; + if (orderType !== undefined) { + side = this.safeString (trade, 'type'); + type = this.parseOrderType (side); + side = this.parseOrderSide (side); + } else { + side = this.safeString2 (trade, 'side', 'direction'); + type = this.parseOrderType (side); + side = this.parseOrderSide (side); + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let feeCost = this.safeFloat (trade, 'fee'); + if (feeCost === undefined) { + feeCost = this.safeFloat (trade, 'filled_fees'); + } else { + feeCost = -feeCost; + } + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = (side === 'buy') ? base : quote; + fee = { + // fee is either a positive number (invitation rebate) + // or a negative number (transaction fee deduction) + // therefore we need to invert the fee + // more about it https://github.com/ccxt/ccxt/issues/5909 + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'order_id'); + const id = this.safeString2 (trade, 'trade_id', 'id'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, limit = undefined, since = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (market['spot']) { + method = 'dataGetMarketHistoryTrade'; + } else if (market['swap']) { + method = 'capiGetMarketTrades'; + } + if (market['spot']) { + if (limit !== undefined) { + request['size'] = limit; // default 1, max 2000 + } + } else if (market['swap']) { + if (limit === undefined) { + limit = 100; // default 20, max 100 + } + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.btc_usdt.trade.detail", + // "ts":1595604968430, + // "data":{ + // "ts":"1595604964000", + // "data":[ + // {"id":"1","price":"9533.81","amount":"0.7326","direction":"sell","ts":"1595604964000"}, + // {"id":"2","price":"9533.67","amount":"1.1591","direction":"buy","ts":"1595604961000"}, + // {"id":"3","price":"9533.67","amount":"1.5022","direction":"sell","ts":"1595604959000"}, + // ] + // } + // } + // + // swap + // + // [ + // {"trade_id":"670833198971748613","price":"9578.50","size":"5412","side":"sell","timestamp":"1595665018790","symbol":"btcusd"}, + // {"trade_id":"670833194240574915","price":"9579.00","size":"3972","side":"buy","timestamp":"1595665017662","symbol":"btcusd"}, + // {"trade_id":"670833194240573915","price":"9579.00","size":"1227","side":"buy","timestamp":"1595665017662","symbol":"btcusd"}, + // ] + // + let trades = undefined; + if (Array.isArray (response)) { + trades = response; + } else { + const data = this.safeValue (response, 'data', {}); + trades = this.safeValue2 (data, 'data', []); + } + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined, timeframe = '1m') { + // + // spot + // + // { + // "id":"1594694700000", + // "amount":"283.6811", + // "count":"234", + // "open":"9230.00", + // "close":"9227.15", + // "low":"9206.66", + // "high":"9232.33", + // "vol":"2618015.032504000000" + // } + // + // swap + // + // [ + // "1594693800000", + // "9240", + // "9241", + // "9222", + // "9228.5", + // "3913370", + // "424.003616350563" + // ] + // + const options = this.safeValue (this.options, 'parseOHLCV', {}); + const volume = this.safeValue (options, 'volume', {}); + if (Array.isArray (ohlcv)) { + const volumeIndex = this.safeString (volume, market['type'], 'amount'); + return [ + this.safeInteger (ohlcv, 0), // timestamp + this.safeFloat (ohlcv, 1), // Open + this.safeFloat (ohlcv, 2), // High + this.safeFloat (ohlcv, 3), // Low + this.safeFloat (ohlcv, 4), // Close + // this.safeFloat (ohlcv, 5), // Quote Volume + // this.safeFloat (ohlcv, 6), // Base Volume + this.safeFloat (ohlcv, volumeIndex), // Volume, bitget will return base volume in the 7th element for future markets + ]; + } else { + const volumeIndex = this.safeValue (volume, market['type'], 6); + return [ + this.safeInteger (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), // Open + this.safeFloat (ohlcv, 'high'), // High + this.safeFloat (ohlcv, 'low'), // Low + this.safeFloat (ohlcv, 'close'), // Close + this.safeFloat (ohlcv, volumeIndex), // Base Volume + ]; + } + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + const type = market['type']; + const options = this.safeValue (this.options, 'timeframes', {}); + const intervals = this.safeValue (options, type, {}); + const interval = this.safeValue (intervals, this.timeframes[timeframe]); + if (market['spot']) { + method = 'dataGetMarketHistoryKline'; + request['period'] = interval; + if (limit !== undefined) { + request['size'] = limit; // default 150, max 1000 + } + } else if (market['swap']) { + const duration = this.parseTimeframe (timeframe); + method = 'capiGetMarketCandles'; + request['granularity'] = interval; + const now = this.milliseconds (); + if (since === undefined) { + if (limit === undefined) { + limit = 1000; + } + request['start'] = this.iso8601 (now - limit * duration * 1000); + request['end'] = this.iso8601 (now); + } else { + request['start'] = this.iso8601 (since); + if (limit === undefined) { + request['end'] = this.iso8601 (now); + } else { + request['end'] = this.iso8601 (this.sum (since, limit * duration * 1000)); + } + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ch":"market.btc_usdt.kline.15min", + // "ts":1595594183874, + // "data":[ + // {"id":"1594694700000","amount":"283.6811","count":"234","open":"9230.00","close":"9227.15","low":"9206.66","high":"9232.33","vol":"2618015.032504000000"}, + // {"id":"1594695600000","amount":"457.2904","count":"238","open":"9227.15","close":"9229.46","low":"9223.80","high":"9235.14","vol":"4220734.684570000000"}, + // {"id":"1594696500000","amount":"501.2353","count":"255","open":"9229.46","close":"9227.78","low":"9222.69","high":"9230.74","vol":"4625779.185006000000"}, + // ] + // } + // + // swap + // + // [ + // ["1594764900000","9255.5","9261","9251","9255.5","3958946","427.742307964305"], + // ["1594765800000","9255.5","9264","9252","9258","3609496","389.832756058107"], + // ["1594766700000","9258","9260","9244.5","9250.5","3738600","403.97870345085"], + // ] + // + let candles = response; + if (!Array.isArray (response)) { + candles = this.safeValue (response, 'data', []); + } + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + parseSpotBalance (response) { + // + // { + // "status":"ok", + // "ts":1595681450932, + // "data":{ + // "list":[ + // {"balance":"0.0000000000000000","currency":"BTC","type":"trade"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"frozen"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"lock"}, + // ], + // "id":"7420922606", + // "type":"spot", + // "state":"working" + // } + // } + // + const result = { 'info': response }; + const data = this.safeValue (response, 'data'); + const balances = this.safeValue (data, 'list'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + const account = this.account (); + result[code] = account; + } + const type = this.safeValue (balance, 'type'); + if (type === 'trade') { + result[code]['free'] = this.safeFloat (balance, 'balance'); + } else if ((type === 'frozen') || (type === 'lock')) { + const used = this.safeFloat (result[code], 'used'); + result[code]['used'] = this.sum (used, this.safeFloat (balance, 'balance')); + } + } + return this.parseBalance (result); + } + + parseSwapBalance (response) { + // + // swap + // + // [ + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"bchusd","margin_frozen":"0","timestamp":"1595673431547","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"ethusd","margin_frozen":"0","timestamp":"1595673431573","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"cmt_btcsusdt","margin_frozen":"0","timestamp":"1595673431577","margin_mode":"fixed","forwardContractFlag":true}, + // ] + // + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const marketId = this.safeString (balance, 'symbol'); + let symbol = marketId; + if (marketId in this.markets_by_id) { + symbol = this.markets_by_id[marketId]['symbol']; + } + const account = this.account (); + // it may be incorrect to use total, free and used for swap accounts + account['total'] = this.safeFloat (balance, 'equity'); + account['free'] = this.safeFloat (balance, 'total_avail_balance'); + result[symbol] = account; + } + return this.parseBalance (result); + } + + async fetchAccounts (params = {}) { + const request = { + 'method': 'accounts', + }; + const response = await this.apiGetAccountAccounts (this.extend (request, params)); + // + // { + // "status":"ok", + // "ts":1595679591824, + // "data":[ + // {"id":"7420922606","type":"spot","state":"working"} + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const accountId = this.safeString (account, 'id'); + const type = this.safeStringLower (account, 'type'); + result.push ({ + 'id': accountId, + 'type': type, + 'currency': undefined, + 'info': account, + }); + } + return result; + } + + async findAccountByType (type) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountsByType = this.groupBy (this.accounts, 'type'); + const accounts = this.safeValue (accountsByType, type); + if (accounts === undefined) { + throw new ExchangeError (this.id + " findAccountByType() could not find an accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + const numAccounts = accounts.length; + if (numAccounts > 1) { + throw new ExchangeError (this.id + " findAccountByType() found more than one accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + return accounts[0]; + } + + async getAccountId (params) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountId = this.safeString (this.options, 'accountId'); + const accountId = this.safeString (params, 'accountId', defaultAccountId); + if (accountId !== undefined) { + return accountId; + } + const defaultType = this.safeString (this.options, 'defaultType', 'margin'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " requires an 'accountId' parameter"); + } + const account = await this.findAccountByType (type); + return account['id']; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchBalance requires a 'type' parameter, one of 'spot', 'swap'"); + } + let method = undefined; + const query = this.omit (params, 'type'); + if (type === 'spot') { + const accountId = await this.getAccountId (params); + method = 'apiGetAccountsAccountIdBalance'; + query['account_id'] = accountId; + query['method'] = 'balance'; + } else if (type === 'swap') { + method = 'swapGetAccountAccounts'; + } + const response = await this[method] (query); + // + // spot + // + // { + // "status":"ok", + // "ts":1595681450932, + // "data":{ + // "list":[ + // {"balance":"0.0000000000000000","currency":"BTC","type":"trade"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"frozen"}, + // {"balance":"0.0000000000000000","currency":"BTC","type":"lock"}, + // ], + // "id":"7420922606", + // "type":"spot", + // "state":"working" + // } + // } + // + // swap + // + // [ + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"bchusd","margin_frozen":"0","timestamp":"1595673431547","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"ethusd","margin_frozen":"0","timestamp":"1595673431573","margin_mode":"fixed","forwardContractFlag":false}, + // {"equity":"0","fixed_balance":"0","total_avail_balance":"0","margin":"0","realized_pnl":"0","unrealized_pnl":"0","symbol":"cmt_btcsusdt","margin_frozen":"0","timestamp":"1595673431577","margin_mode":"fixed","forwardContractFlag":true}, + // ] + // + return this.parseBalanceByType (type, response); + } + + parseBalanceByType (type, response) { + if (type === 'spot') { + return this.parseSpotBalance (response); + } else if (type === 'swap') { + return this.parseSwapBalance (response); + } + throw new NotSupported (this.id + " fetchBalance does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + + parseOrderStatus (status) { + const statuses = { + 'submitted': 'open', + 'partial-filled': 'open', + 'partial-canceled': 'canceled', + 'filled': 'closed', + 'canceled': 'canceled', + '-2': 'failed', + '-1': 'canceled', + '0': 'open', + '1': 'open', + '2': 'closed', + '3': 'open', + '4': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrderSide (side) { + const sides = { + 'buy-market': 'buy', + 'sell-market': 'sell', + 'buy-limit': 'buy', + 'sell-limit': 'sell', + '1': 'long', // open long + '2': 'short', // open short + '3': 'long', // close long + '4': 'short', // close short + }; + return this.safeString (sides, side, side); + } + + parseOrderType (type) { + const types = { + 'buy-market': 'market', + 'sell-market': 'market', + 'buy-limit': 'limit', + 'sell-limit': 'limit', + '1': 'open', // open long + '2': 'open', // open short + '3': 'close', // close long + '4': 'close', // close short + }; + return this.safeString (types, type, type); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // spot + // + // { + // "status":"ok", + // "ts":1595792596056, + // "data":671368296142774272 + // } + // + // swap + // + // { + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "order_id":"671757564085534713" + // } + // + // cancelOrder + // + // spot + // + // { + // "status": "ok", + // "ts": 1595818631279, + // "data": 671368296142774272 + // } + // + // swap + // + // { + // "order_id":"671757564085534713", + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "symbol":"cmt_ethusdt", + // "result":true, + // "err_code":null, + // "err_msg":null + // } + // + // fetchOpenOrders, fetchClosedOrders, fetchOrder + // + // spot + // + // { + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595872129618", + // "created_at":"1595872089525", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595872129618", + // "id":"671701716584665088", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // + // swap + // + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595885546770", + // "client_oid":"f3aa81d6-9a4c-4eab-bebe-ebc19da21cf2", + // "createTime":"1595885521200", + // "filled_qty":"0", + // "fee":"0.00000000", + // "order_id":"671758053112020913", + // "price":"150.00", + // "price_avg":"0.00", + // "status":"0", + // "type":"1", + // "order_type":"0", + // "totalProfits":null + // } + // + let id = this.safeString (order, 'order_id'); + id = this.safeString2 (order, 'id', 'data', id); + const timestamp = this.safeInteger2 (order, 'created_at', 'createTime'); + let type = this.safeString (order, 'type'); + const side = this.parseOrderSide (type); + type = this.parseOrderType (type); + // if ((side !== 'buy') && (side !== 'sell')) { + // side = this.parseOrderSide (type); + // } + // if ((type !== 'limit') && (type !== 'market')) { + // if ('pnl' in order) { + // type = 'futures'; + // } else { + // type = 'swap'; + // } + // } + let symbol = undefined; + const marketId = this.safeString (order, 'symbol'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + symbol = marketId.toUpperCase (); + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let amount = this.safeFloat2 (order, 'amount', 'size'); + const filled = this.safeFloat2 (order, 'filled_amount', 'filled_qty'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + amount = Math.max (amount, filled); + remaining = Math.max (0, amount - filled); + } + } + if (type === 'market') { + remaining = 0; + } + const cost = this.safeFloat (order, 'filled_cash_amount'); + const price = this.safeFloat (order, 'price'); + let average = this.safeFloat (order, 'price_avg'); + if ((average === undefined) && (filled !== undefined) && (cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + const status = this.parseOrderStatus (this.safeString2 (order, 'state', 'status')); + const feeCost = this.safeFloat2 (order, 'filled_fees', 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const clientOrderId = this.safeString (order, 'client_oid'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + // + // spot + // + // account_id true string Account ID, obtained using the accounts method. Currency transactions use the accountid of the'spot' account; for loan asset transactions, please use the accountid of the'margin' account + // amount true string A limit order indicates the quantity of the order, when a market price buy order indicates how much money to buy, and when a market price sell order indicates how much currency to sell + // price false string Order price, market order does not pass this parameter + // source false string Order source api + // symbol true string Trading pair btc_usdt, eth_btc ... + // type true string Order Type buy-market: buy at market price, sell-market: sell at market price, buy-limit: buy at limit price, sell-limit: sell at limit price + // + // swap + // + // symbol String Yes Contract ID + // client_oid String Yes customize order IDs to identify your orders. (Less than 50 characters without special characters, + // size String Yes Quantity to buy or sell (value not equal to 0 or negative) + // type String Yes 1 Open long 2Open short 3 Close long 4 Close short + // order_type String Yes 0: Normal order (Unfilled and 0 imply normal limit order) 1: Post only 2: Fill or Kill 3: Immediate Or Cancel + // match_price String Yes 0 Limit price 1 market price + // price String No Price of each contract + // + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId', this.uuid ()); + params = this.omit (params, [ 'client_oid', 'clientOrderId' ]); + let method = undefined; + if (market['spot']) { + const accountId = await this.getAccountId ({ + 'type': market['type'], + }); + method = 'apiPostOrderOrdersPlace'; + request['account_id'] = accountId; + request['method'] = 'place'; + request['type'] = side + '-' + type; + if (type === 'limit') { + request['amount'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let cost = this.safeFloat (params, 'amount'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (cost === undefined) { + cost = amount * price; + } + } else if (cost === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'amount' extra parameter (the exchange-specific behaviour)"); + } + } else { + cost = (cost === undefined) ? amount : cost; + } + request['amount'] = this.costToPrecision (symbol, cost); + } else if (side === 'sell') { + request['amount'] = this.amountToPrecision (symbol, amount); + } + } + // ... + } else if (market['swap']) { + request['order_type'] = '0'; // '0' = Normal order, undefined and 0 imply a normal limit order, '1' = Post only, '2' = Fill or Kill, '3' = Immediate Or Cancel + request['client_oid'] = clientOrderId; + const orderType = this.safeString (params, 'type'); + if (orderType === undefined) { + throw new ArgumentsRequired (this.id + " createOrder requires a type parameter, '1' = open long, '2' = open short, '3' = close long, '4' = close short for " + market['type'] + ' orders'); + } + request['size'] = this.amountToPrecision (symbol, amount); + request['type'] = orderType; + // if match_price is set to '1', the price parameter will be ignored for market orders + if (type === 'limit') { + request['match_price'] = '0'; + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + request['match_price'] = '1'; + } + method = 'swapPostOrderPlaceOrder'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status":"ok", + // "ts":1595792596056, + // "data":"671368296142774272" + // } + // + // swap + // + // { + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "order_id":"671757564085534713" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let type = undefined; + if (symbol === undefined) { + const defaultType = this.safeString2 (this.options, 'cancelOrder', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if (type === 'spot') { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument for spot orders'); + } + } + } else { + market = this.market (symbol); + type = market['type']; + } + const query = this.omit (params, 'type'); + let method = undefined; + const request = {}; + if (type === 'spot') { + method = 'apiPostOrderOrdersOrderIdSubmitcancel'; + request['order_id'] = id; + request['method'] = 'submitcancel'; + } else if (type === 'swap') { + method = 'swapPostOrderCancelOrder'; + request['orderId'] = id; + request['symbol'] = market['id']; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { "status": "ok", "ts": 1595818631279, "data": 671368296142774272 } + // + // swap + // + // { + // "order_id":"671757564085534713", + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "symbol":"cmt_ethusdt", + // "result":true, + // "err_code":null, + // "err_msg":null + // } + // + return this.parseOrder (response, market); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " cancelOrders requires a type parameter (one of 'spot', 'swap')."); + } + const request = {}; + let method = undefined; + if (type === 'spot') { + method = 'apiPostOrderOrdersBatchcancel'; + request['method'] = 'batchcancel'; + const jsonIds = this.json (ids); + const parts = jsonIds.split ('"'); + request['order_ids'] = parts.join (''); + } else if (type === 'swap') { + method = 'swapPostOrderCancelBatchOrders'; + request['symbol'] = market['id']; + request['ids'] = ids; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "status": "ok", + // "data": { + // "success": [ + // "673451224205135872", + // ], + // "failed": [ + // { + // "err-msg": "invalid record", + // "order-id": "673451224205135873", + // "err-code": "base record invalid" + // } + // ] + // } + // } + // + // swap + // + // { + // "result":true, + // "symbol":"cmt_btcusdt", + // "order_ids":[ + // "258414711", + // "478585558" + // ], + // "fail_infos":[ + // { + // "order_id":"258414711", + // "err_code":"401", + // "err_msg":"" + // } + // ] + // } + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'swap')."); + } + let method = undefined; + const request = {}; + if (type === 'spot') { + const clientOid = this.safeString (params, 'client_oid'); + if (clientOid !== undefined) { + method = 'apiPostOrderOrdersClientOid'; + request['client_oid'] = clientOid; + } else { + method = 'apiPostOrderOrdersOrderId'; + request['order_id'] = id; + } + request['method'] = 'getOrder'; + } else if (type === 'swap') { + method = 'swapGetOrderDetail'; + request['symbol'] = market['id']; + request['orderId'] = id; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // "status":"ok", + // "ts":1595897886717, + // "data":{ + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595818631541", + // "created_at":"1595792595897", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595818631541", + // "id":"671368296142774272", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // } + // + // + // swap + // + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595896459890", + // "client_oid":"58775e54-0592-491c-97e8-e2369025f2d1", + // "createTime":"1595885404607", + // "filled_qty":"0", + // "fee":"0", + // "order_id":"671757564085534713", + // "price":"150", + // "price_avg":"0", + // "status":"-1", + // "type":"1", + // "order_type":"0", + // "totalProfits":"0" + // } + // + const data = this.safeValue (response, 'data', response); + return this.parseOrder (data, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (type === 'spot') { + method = 'apiGetOrderOrdersOpenOrders'; + // request['from'] = this.safeString (params, 'from'); // order id + // request['direct'] = 'next'; // or 'prev' + request['method'] = 'openOrders'; + if (limit === undefined) { + request['size'] = limit; // default 100, max 1000 + } + } else if (type === 'swap') { + method = 'swapGetOrderOrders'; + request['status'] = '3'; // 0 Failed, 1 Partially Filled, 2 Fully Filled 3 = Open + Partially Filled, 4 Canceling + request['from'] = '1'; + request['to'] = '1'; + if (limit === undefined) { + request['limit'] = 100; // default 100, max 100 + } + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // + // { + // "status":"ok", + // "ts":1595875165865, + // "data":[ + // { + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595872129618", + // "created_at":"1595872089525", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595872129618", + // "id":"671701716584665088", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // ] + // } + // + // swap + // + // [ + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595885546770", + // "client_oid":"f3aa81d6-9a4c-4eab-bebe-ebc19da21cf2", + // "createTime":"1595885521200", + // "filled_qty":"0", + // "fee":"0.00000000", + // "order_id":"671758053112020913", + // "price":"150.00", + // "price_avg":"0.00", + // "status":"0", + // "type":"1", + // "order_type":"0", + // "totalProfits":null + // } + // ] + // + let data = response; + if (!Array.isArray (response)) { + data = this.safeValue (response, 'data', []); + } + return this.parseOrders (data, market, undefined, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (type === 'spot') { + method = 'apiGetOrderOrdersHistory'; + // Value range [((end_time) – 48h), (end_time)] + // the query window is 48 hours at most + // the window shift range is the last 30 days + if (since !== undefined) { + request['start_time'] = since; + } + // request['end_time'] = this.safeInteger (params, 'end_time'); + // request['from'] = this.safeString (params, 'from'); // order id + // request['direct'] = 'next'; // or 'prev' + request['method'] = 'openOrders'; + if (limit === undefined) { + request['size'] = limit; // default 100, max 1000 + } + } else if (type === 'swap') { + method = 'swapGetOrderOrders'; + request['status'] = '2'; // 0 Failed, 1 Partially Filled, 2 Fully Filled 3 = Open + Partially Filled, 4 Canceling + request['from'] = '1'; + request['to'] = '1'; + if (limit === undefined) { + request['limit'] = 100; // default 100, max 100 + } + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // + // { + // "status":"ok", + // "ts":1595875165865, + // "data":[ + // { + // "account_id":"7420922606", + // "amount":"0.1000000000000000", + // "canceled_at":"1595872129618", + // "created_at":"1595872089525", + // "filled_amount":"0.000000000000", + // "filled_cash_amount":"0.000000000000", + // "filled_fees":"0.000000000000", + // "finished_at":"1595872129618", + // "id":"671701716584665088", + // "price":"150.000000000000", + // "source":"接口", + // "state":"canceled", + // "symbol":"eth_usdt", + // "type":"buy-limit" + // } + // ] + // } + // + // swap + // + // [ + // { + // "symbol":"cmt_ethusdt", + // "size":"1", + // "timestamp":"1595885546770", + // "client_oid":"f3aa81d6-9a4c-4eab-bebe-ebc19da21cf2", + // "createTime":"1595885521200", + // "filled_qty":"0", + // "fee":"0.00000000", + // "order_id":"671758053112020913", + // "price":"150.00", + // "price_avg":"0.00", + // "status":"0", + // "type":"1", + // "order_type":"0", + // "totalProfits":null + // } + // ] + // + let data = response; + if (!Array.isArray (response)) { + data = this.safeValue (response, 'data', []); + } + return this.parseOrders (data, market, undefined, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'method': 'deposit_withdraw', + 'type': 'deposit', + 'size': 12, + }; + const response = await this.apiGetOrderDepositWithdraw (this.extend (request, params)); + // + // { + // "status": "ok", + // "data": [ + // { + // "id": 1171, + // "type": "deposit", + // "currency": "usdt", + // "tx_hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "address_tag": "100040", + // "fee": 0, + // "state": "safe", + // "created_at": 1510912472199, + // "updated_at": 1511145876575 + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'method': 'deposit_withdraw', + 'type': 'withdraw', + 'size': 12, + }; + const response = await this.apiGetOrderDepositWithdraw (this.extend (request, params)); + // + // { + // "status": "ok", + // "data": [ + // { + // "id": 1171, + // "type": "withdraw", + // "currency": "usdt", + // "tx_hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "address_tag": "100040", + // "fee": 0, + // "state": "safe", + // "created_at": 1510912472199, + // "updated_at": 1511145876575 + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + // withdrawals + 'WaitForOperation': 'pending', // 等待提现 + 'OperationLock': 'pending', // 初审锁定成功 + 'OperationSuccess': 'ok', // 提现成功 + 'Cancel': 'canceled', // 用户撤销 + 'Sure': 'ok', // 复审锁定成功 + 'Fail': 'failed', // 出币异常 + 'WaitForChainSure': 'ok', // 等待链上确认 + // deposits + 'WAIT_0': 'pending', // 待确认 + 'WAIT_1': 'pending', // 待确认 + 'DATA_CHANGE': 'pending', // 待确认中 + 'SUCCESS': 'ok', // 充值成功 + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchWithdrawals + // + // { + // "id": 1171, + // "type": "withdraw", + // "currency": "usdt", + // "tx_hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "address_tag": "100040", + // "fee": 0, + // "state": "safe", + // "created_at": 1510912472199, + // "updated_at": 1511145876575 + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'address_tag'); + const tagFrom = undefined; + const tagTo = tag; + const addressFrom = undefined; + const addressTo = address; + let type = this.safeString (transaction, 'type'); + if (type === 'withdraw') { + type = 'withdrawal'; + } else if (type === 'deposit') { + type = 'deposit'; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const txid = this.safeString (transaction, 'tx_hash'); + const timestamp = this.safeInteger (transaction, 'created_at'); + const updated = this.safeInteger (transaction, 'updated_at'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'currency': code, + 'cost': feeCost, + }; + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': tagFrom, + 'tagTo': tagTo, + 'tag': tag, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const query = this.omit (params, 'type'); + if (type === 'swap') { + throw new ArgumentsRequired (this.id + ' fetchMyTrades is not supported for ' + type + ' type'); + } + // + // spot + // + // POST /api/v1/order/matchresults Query current order, order history + // symbol true string trading pair btc_usdt, eth_btc ... + // types false string Query order type combination buy-market, sell-market, buy-limit, sell-limit + // start_date false string Query start date, date format yyyy-mm-dd -61 days [-61day, end-date] + // end_date false string Query end date, date format yyyy-mm-dd Now [start-date, now] + // from false string Query start ID order record id + // direct false string Query direction ‘next’ is default , the transaction record ID is sorted from large to small prev,next + // size false string Query record size 100 <=100 + // + const request = { + 'symbol': market['id'], + 'method': 'matchresults', + // 'types': 'buy-market,sell-market,buy-limit,sell-limit', + // 'start_date': this.ymd (since), + // 'end_date': this.ymd (this.milliseconds ()), + // 'size': 100, + // 'direct': 'next', + }; + if (since !== undefined) { + request['start_date'] = this.ymd (since); + const end = this.sum (since, 2 * 24 * 60 * 60 * 1000); + request['end_date'] = this.ymd (end); + } + if (limit !== undefined) { + request['size'] = limit; // default 100, max 100 + } + const response = await this.apiPostOrderMatchresults (this.extend (request, query)); + // + // { + // "status": "ok", + // "data": [ + // { + // "id": 29555, + // "order_id": 59378, + // "match_id": 59335, + // "symbol": "eth_usdt", + // "type": "buy-limit", + // "source": "api", + // "price": "100.1000000000", + // "filled_amount": "0.9845000000", + // "filled_fees": "0.0019690000", + // "created_at": 1494901400487 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrderTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const type = this.safeString (params, 'type', market['type']); + const query = this.omit (params, 'type'); + let method = undefined; + const request = {}; + if (type === 'spot') { + request['order_id'] = id; + request['method'] = 'matchresults'; + method = 'apiPostOrderOrdersOrderIdMatchresults'; + } else if (type === 'swap') { + request['orderId'] = id; + request['symbol'] = market['id']; + method = 'swapGetOrderFills'; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // "status":"ok", + // "ts":1596298917277, + // "data":[ + // { + // "id":"614164775", + // "created_at":"1596298860602", + // "filled_amount":"0.0417000000000000", + // "filled_fees":"0.0000834000000000", + // "match_id":"673491702661292033", + // "order_id":"673491720340279296", + // "price":"359.240000000000", + // "source":"接口", + // "symbol":"eth_usdt", + // "type":"buy-market" + // } + // ] + // } + // + // swap + // + // + // [ + // { + // "trade_id":"6667390", + // "symbol":"cmt_btcusdt", + // "order_id":"525946425993854915", + // "price":"9839.00", + // "order_qty":"3466", + // "fee":"-0.0000528407360000", + // "timestamp":"1561121514442", + // "exec_type":"M", + // "side":"3" + // } + // ] + // + let data = response; + if (!Array.isArray (data)) { + data = this.safeValue (response, 'data', []); + } + return await this.parseTrades (data, market, since, limit); + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.swapGetPositionSinglePosition (this.extend (request, params)); + // + // { + // "margin_mode":"fixed", // Margin mode: crossed / fixed + // "holding":[ + // { + // "symbol":"cmt_btcusdt", // Contract name + // "liquidation_price":"0.00", // Estimated liquidation price + // "position":"0", // Position Margin, the margin for holding current positions + // "avail_position":"0", // Available position + // "avg_cost":"0.00", // Transaction average price + // "leverage":"2", // Leverage + // "realized_pnl":"0.00000000", // Realized Profit and loss + // "keepMarginRate":"0.005", // Maintenance margin rate + // "side":"1", // Position Direction Long or short, Mark obsolete + // "holdSide":"1", // Position Direction Long or short + // "timestamp":"1557571623963", // System timestamp + // "margin":"0.0000000000000000", // Used margin + // "unrealized_pnl":"0.00000000", // Unrealized profit and loss + // } + // ] + // } + return response; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.swapGetPositionAllPosition (params); + // + // [ + // { + // "margin_mode":"fixed", + // "holding":[ + // { + // "liquidation_price":"0.00", + // "position":"0", + // "avail_position":"0", + // "avg_cost":"0.00", + // "symbol":"btcusd", + // "leverage":"20", + // "keepMarginRate":"0.005", + // "realized_pnl":"0.00000000", + // "unrealized_pnl":"0", + // "side":"long", + // "holdSide":"1", + // "timestamp":"1595698564915", + // "margin":"0.0000000000000000" + // }, + // ] + // }, + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + if ((api === 'capi') || (api === 'swap')) { + request = '/api/swap/' + this.version + request; + } else { + request = '/' + api + '/v1' + request; + } + let query = this.omit (params, this.extractParams (path)); + let url = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }) + request; + if ((api === 'data') || (api === 'capi')) { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'swap') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + let auth = timestamp + method + request; + if (method === 'POST') { + body = this.json (params); + auth += body; + } else { + if (Object.keys (params).length) { + const query = this.urlencode (this.keysort (params)); + url += '?' + query; + auth += '?' + query; + } + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); + headers = { + 'ACCESS-KEY': this.apiKey, + 'ACCESS-SIGN': signature, + 'ACCESS-TIMESTAMP': timestamp, + 'ACCESS-PASSPHRASE': this.password, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + } + } else if (api === 'api') { + const timestamp = this.milliseconds ().toString (); + let auth = ''; + query = this.keysort (query); + auth = this.rawencode (query); + const hash = this.hash (this.encode (this.secret), 'sha1'); + let signed = auth; + const signature = this.hmac (this.encode (auth), this.encode (hash), 'md5'); + if (auth.length > 0) { + signed += '&'; + } + signed += 'sign=' + signature + '&req_time=' + timestamp + '&accesskey=' + this.apiKey; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + signed; + } + } else if (method === 'POST') { + url += '?sign=' + signature + '&req_time=' + timestamp + '&accesskey=' + this.apiKey; + body = auth; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + // + // spot + // + // {"status":"fail","err_code":"01001","err_msg":"系统异常,请稍后重试"} + // {"status":"error","ts":1595594160149,"err_code":"invalid-parameter","err_msg":"invalid size, valid range: [1,2000]"} + // {"status":"error","ts":1595684716042,"err_code":"invalid-parameter","err_msg":"illegal sign invalid"} + // {"status":"error","ts":1595700216275,"err_code":"bad-request","err_msg":"your balance is low!"} + // {"status":"error","ts":1595700344504,"err_code":"invalid-parameter","err_msg":"invalid type"} + // {"status":"error","ts":1595703343035,"err_code":"bad-request","err_msg":"order cancel fail"} + // {"status":"error","ts":1595704360508,"err_code":"invalid-parameter","err_msg":"accesskey not null"} + // {"status":"error","ts":1595704490084,"err_code":"invalid-parameter","err_msg":"permissions not right"} + // {"status":"error","ts":1595711862763,"err_code":"system exception","err_msg":"system exception"} + // {"status":"error","ts":1595730308979,"err_code":"bad-request","err_msg":"20003"} + // + // swap + // + // {"code":"40015","msg":"","requestTime":1595698564931,"data":null} + // {"code":"40017","msg":"Order id must not be blank","requestTime":1595702477835,"data":null} + // {"code":"40017","msg":"Order Type must not be blank","requestTime":1595698516162,"data":null} + // {"code":"40301","msg":"","requestTime":1595667662503,"data":null} + // {"code":"40017","msg":"Contract code must not be blank","requestTime":1595703151651,"data":null} + // {"code":"40108","msg":"","requestTime":1595885064600,"data":null} + // {"order_id":"513468410013679613","client_oid":null,"symbol":"ethusd","result":false,"err_code":"order_no_exist_error","err_msg":"订单不存在!"} + // + const message = this.safeString (response, 'err_msg'); + const errorCode = this.safeString2 (response, 'code', 'err_code'); + const feedback = this.id + ' ' + body; + const nonEmptyMessage = ((message !== undefined) && (message !== '')); + if (nonEmptyMessage) { + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + const nonZeroErrorCode = (errorCode !== undefined) && (errorCode !== '00000'); + if (nonZeroErrorCode) { + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + if (nonZeroErrorCode || nonEmptyMessage) { + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bithumb.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bithumb.js new file mode 100644 index 000000000..904b8715d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bithumb.js @@ -0,0 +1,886 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, AuthenticationError, BadRequest, PermissionDenied, InvalidAddress, ArgumentsRequired, InvalidOrder } = require ('./base/errors'); +const { DECIMAL_PLACES, SIGNIFICANT_DIGITS, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bithumb extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bithumb', + 'name': 'Bithumb', + 'countries': [ 'KR' ], // South Korea + 'rateLimit': 500, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg', + 'api': { + 'public': 'https://api.bithumb.com/public', + 'private': 'https://api.bithumb.com', + }, + 'www': 'https://www.bithumb.com', + 'doc': 'https://apidocs.bithumb.com', + 'fees': 'https://en.bithumb.com/customer_support/info_fee', + }, + 'api': { + 'public': { + 'get': [ + 'ticker/{currency}', + 'ticker/all', + 'orderbook/{currency}', + 'orderbook/all', + 'transaction_history/{currency}', + 'transaction_history/all', + ], + }, + 'private': { + 'post': [ + 'info/account', + 'info/balance', + 'info/wallet_address', + 'info/ticker', + 'info/orders', + 'info/user_transactions', + 'info/order_detail', + 'trade/place', + 'trade/cancel', + 'trade/btc_withdrawal', + 'trade/krw_deposit', + 'trade/krw_withdrawal', + 'trade/market_buy', + 'trade/market_sell', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.25 / 100, + 'taker': 0.25 / 100, + }, + }, + 'precisionMode': SIGNIFICANT_DIGITS, + 'exceptions': { + 'Bad Request(SSL)': BadRequest, + 'Bad Request(Bad Method)': BadRequest, + 'Bad Request.(Auth Data)': AuthenticationError, // { "status": "5100", "message": "Bad Request.(Auth Data)" } + 'Not Member': AuthenticationError, + 'Invalid Apikey': AuthenticationError, // {"status":"5300","message":"Invalid Apikey"} + 'Method Not Allowed.(Access IP)': PermissionDenied, + 'Method Not Allowed.(BTC Adress)': InvalidAddress, + 'Method Not Allowed.(Access)': PermissionDenied, + 'Database Fail': ExchangeNotAvailable, + 'Invalid Parameter': BadRequest, + '5600': ExchangeError, + 'Unknown Error': ExchangeError, + 'After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions': ExchangeError, // {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"} + }, + }); + } + + amountToPrecision (symbol, amount) { + return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTickerAll (params); + const data = this.safeValue (response, 'data'); + const currencyIds = Object.keys (data); + const result = []; + const quote = this.safeCurrencyCode ('KRW'); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + if (currencyId === 'date') { + continue; + } + const market = data[currencyId]; + const base = this.safeCurrencyCode (currencyId); + const symbol = currencyId + '/' + quote; + let active = true; + if (Array.isArray (market)) { + const numElements = market.length; + if (numElements === 0) { + active = false; + } + } + result.push ({ + 'id': currencyId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'info': market, + 'active': active, + 'precision': { + 'amount': 4, + 'price': 4, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': 500, + 'max': 5000000000, + }, + }, + 'baseId': undefined, + 'quoteId': undefined, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'currency': 'ALL', + }; + const response = await this.privatePostInfoBalance (this.extend (request, params)); + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const account = this.account (); + const currency = this.currency (code); + const lowerCurrencyId = this.safeStringLower (currency, 'id'); + account['total'] = this.safeFloat (balances, 'total_' + lowerCurrencyId); + account['used'] = this.safeFloat (balances, 'in_use_' + lowerCurrencyId); + account['free'] = this.safeFloat (balances, 'available_' + lowerCurrencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['base'], + }; + if (limit !== undefined) { + request['count'] = limit; // default 30, max 30 + } + const response = await this.publicGetOrderbookCurrency (this.extend (request, params)); + // + // { + // "status":"0000", + // "data":{ + // "timestamp":"1587621553942", + // "payment_currency":"KRW", + // "order_currency":"BTC", + // "bids":[ + // {"price":"8652000","quantity":"0.0043"}, + // {"price":"8651000","quantity":"0.0049"}, + // {"price":"8650000","quantity":"8.4791"}, + // ], + // "asks":[ + // {"price":"8654000","quantity":"0.119"}, + // {"price":"8655000","quantity":"0.254"}, + // {"price":"8658000","quantity":"0.119"}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'timestamp'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks', 'price', 'quantity'); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "opening_price":"227100", + // "closing_price":"228400", + // "min_price":"222300", + // "max_price":"230000", + // "units_traded":"82618.56075337", + // "acc_trade_value":"18767376138.6031", + // "prev_closing_price":"227100", + // "units_traded_24H":"151871.13484676", + // "acc_trade_value_24H":"34247610416.8974", + // "fluctate_24H":"8700", + // "fluctate_rate_24H":"3.96", + // "date":"1587710327264", // fetchTickers inject this + // } + // + const timestamp = this.safeInteger (ticker, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const open = this.safeFloat (ticker, 'opening_price'); + const close = this.safeFloat (ticker, 'closing_price'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((close !== undefined) && (open !== undefined)) { + change = close - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, close) / 2; + } + const baseVolume = this.safeFloat (ticker, 'units_traded_24H'); + const quoteVolume = this.safeFloat (ticker, 'acc_trade_value_24H'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max_price'), + 'low': this.safeFloat (ticker, 'min_price'), + 'bid': this.safeFloat (ticker, 'buy_price'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell_price'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickerAll (params); + // + // { + // "status":"0000", + // "data":{ + // "BTC":{ + // "opening_price":"9045000", + // "closing_price":"9132000", + // "min_price":"8938000", + // "max_price":"9168000", + // "units_traded":"4619.79967497", + // "acc_trade_value":"42021363832.5187", + // "prev_closing_price":"9041000", + // "units_traded_24H":"8793.5045804", + // "acc_trade_value_24H":"78933458515.4962", + // "fluctate_24H":"530000", + // "fluctate_rate_24H":"6.16" + // }, + // "date":"1587710878669" + // } + // } + // + const result = {}; + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'date'); + const tickers = this.omit (data, 'date'); + const ids = Object.keys (tickers); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } + const ticker = tickers[id]; + const isArray = Array.isArray (ticker); + if (!isArray) { + ticker['date'] = timestamp; + result[symbol] = this.parseTicker (ticker, market); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['base'], + }; + const response = await this.publicGetTickerCurrency (this.extend (request, params)); + // + // { + // "status":"0000", + // "data":{ + // "opening_price":"227100", + // "closing_price":"228400", + // "min_price":"222300", + // "max_price":"230000", + // "units_traded":"82618.56075337", + // "acc_trade_value":"18767376138.6031", + // "prev_closing_price":"227100", + // "units_traded_24H":"151871.13484676", + // "acc_trade_value_24H":"34247610416.8974", + // "fluctate_24H":"8700", + // "fluctate_rate_24H":"3.96", + // "date":"1587710327264" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "transaction_date":"2020-04-23 22:21:46", + // "type":"ask", + // "units_traded":"0.0125", + // "price":"8667000", + // "total":"108337" + // } + // + // fetchOrder (private) + // + // { + // "transaction_date": "1572497603902030", + // "price": "8601000", + // "units": "0.005", + // "fee_currency": "KRW", + // "fee": "107.51", + // "total": "43005" + // } + // + // a workaround for their bug in date format, hours are not 0-padded + let timestamp = undefined; + const transactionDatetime = this.safeString (trade, 'transaction_date'); + if (transactionDatetime !== undefined) { + const parts = transactionDatetime.split (' '); + const numParts = parts.length; + if (numParts > 1) { + const transactionDate = parts[0]; + let transactionTime = parts[1]; + if (transactionTime.length < 8) { + transactionTime = '0' + transactionTime; + } + timestamp = this.parse8601 (transactionDate + ' ' + transactionTime); + } else { + timestamp = this.safeIntegerProduct (trade, 'transaction_date', 0.001); + } + } + if (timestamp !== undefined) { + timestamp -= 9 * 3600000; // they report UTC + 9 hours, server in Korean timezone + } + const type = undefined; + let side = this.safeString (trade, 'type'); + side = (side === 'ask') ? 'sell' : 'buy'; + const id = this.safeString (trade, 'cont_no'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'units_traded', 'units'); + let cost = this.safeFloat (trade, 'total'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.commonCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['base'], + }; + if (limit === undefined) { + request['count'] = limit; // default 20, max 100 + } + const response = await this.publicGetTransactionHistoryCurrency (this.extend (request, params)); + // + // { + // "status":"0000", + // "data":[ + // { + // "transaction_date":"2020-04-23 22:21:46", + // "type":"ask", + // "units_traded":"0.0125", + // "price":"8667000", + // "total":"108337" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_currency': market['id'], + 'Payment_currency': market['quote'], + 'units': amount, + }; + let method = 'privatePostTradePlace'; + if (type === 'limit') { + request['price'] = price; + request['type'] = (side === 'buy') ? 'bid' : 'ask'; + } else { + method = 'privatePostTradeMarket' + this.capitalize (side); + } + const response = await this[method] (this.extend (request, params)); + const id = this.safeString (response, 'order_id'); + if (id === undefined) { + throw new InvalidOrder (this.id + ' createOrder did not return an order id'); + } + return { + 'info': response, + 'symbol': symbol, + 'type': type, + 'side': side, + 'id': id, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'count': 1, + 'order_currency': market['base'], + 'payment_currency': market['quote'], + }; + const response = await this.privatePostInfoOrderDetail (this.extend (request, params)); + // + // { + // "status": "0000", + // "data": { + // order_date: '1603161798539254', + // type: 'ask', + // order_status: 'Cancel', + // order_currency: 'BTC', + // payment_currency: 'KRW', + // watch_price: '0', + // order_price: '13344000', + // order_qty: '0.0125', + // cancel_date: '1603161803809993', + // cancel_type: '사용자취소', + // contract: [ + // { + // transaction_date: '1603161799976383', + // price: '13344000', + // units: '0.0015', + // fee_currency: 'KRW', + // fee: '0', + // total: '20016' + // } + // ], + // } + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (this.extend (data, { 'order_id': id }), market); + } + + parseOrderStatus (status) { + const statuses = { + 'Pending': 'open', + 'Completed': 'closed', + 'Cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // + // fetchOrder + // + // { + // "transaction_date": "1572497603668315", + // "type": "bid", + // "order_status": "Completed", + // "order_currency": "BTC", + // "payment_currency": "KRW", + // "order_price": "8601000", + // "order_qty": "0.007", + // "cancel_date": "", + // "cancel_type": "", + // "contract": [ + // { + // "transaction_date": "1572497603902030", + // "price": "8601000", + // "units": "0.005", + // "fee_currency": "KRW", + // "fee": "107.51", + // "total": "43005" + // }, + // ] + // } + // + // { + // order_date: '1603161798539254', + // type: 'ask', + // order_status: 'Cancel', + // order_currency: 'BTC', + // payment_currency: 'KRW', + // watch_price: '0', + // order_price: '13344000', + // order_qty: '0.0125', + // cancel_date: '1603161803809993', + // cancel_type: '사용자취소', + // contract: [ + // { + // transaction_date: '1603161799976383', + // price: '13344000', + // units: '0.0015', + // fee_currency: 'KRW', + // fee: '0', + // total: '20016' + // } + // ], + // } + // + // fetchOpenOrders + // + // { + // "order_currency": "BTC", + // "payment_currency": "KRW", + // "order_id": "C0101000007408440032", + // "order_date": "1571728739360570", + // "type": "bid", + // "units": "5.0", + // "units_remaining": "5.0", + // "price": "501000", + // } + // + const timestamp = this.safeIntegerProduct (order, 'order_date', 0.001); + const sideProperty = this.safeValue2 (order, 'type', 'side'); + const side = (sideProperty === 'bid') ? 'buy' : 'sell'; + const status = this.parseOrderStatus (this.safeString (order, 'order_status')); + let price = this.safeFloat2 (order, 'order_price', 'price'); + let type = 'limit'; + if (price === 0) { + price = undefined; + type = 'market'; + } + const amount = this.safeFloat2 (order, 'order_qty', 'units'); + let remaining = this.safeFloat (order, 'units_remaining'); + if (remaining === undefined) { + if (status === 'closed') { + remaining = 0; + } else if (status !== 'canceled') { + remaining = amount; + } + } + let symbol = undefined; + const baseId = this.safeString (order, 'order_currency'); + const quoteId = this.safeString (order, 'payment_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + if ((base !== undefined) && (quote !== undefined)) { + symbol = base + '/' + quote; + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let filled = undefined; + let cost = undefined; + let average = undefined; + const id = this.safeString (order, 'order_id'); + const rawTrades = this.safeValue (order, 'contract'); + let trades = undefined; + let fee = undefined; + let fees = undefined; + let feesByCurrency = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'side': side, + 'symbol': symbol, + 'order': id, + }); + filled = 0; + feesByCurrency = {}; + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + filled = this.sum (filled, trade['amount']); + cost = this.sum (cost, trade['cost']); + const tradeFee = trade['fee']; + const feeCurrency = tradeFee['currency']; + if (feeCurrency in feesByCurrency) { + feesByCurrency[feeCurrency] = { + 'currency': feeCurrency, + 'cost': this.sum (feesByCurrency[feeCurrency]['cost'], tradeFee['cost']), + }; + } else { + feesByCurrency[feeCurrency] = { + 'currency': feeCurrency, + 'cost': tradeFee['cost'], + }; + } + } + const feeCurrencies = Object.keys (feesByCurrency); + const feeCurrenciesLength = feeCurrencies.length; + if (feeCurrenciesLength > 1) { + fees = []; + for (let i = 0; i < feeCurrencies.length; i++) { + const feeCurrency = feeCurrencies[i]; + fees.push (feesByCurrency[feeCurrency]); + } + } else { + fee = this.safeValue (feesByCurrency, feeCurrencies[0]); + } + if (filled !== 0) { + average = cost / filled; + } + } + if (amount !== undefined) { + if ((filled === undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + if ((remaining === undefined) && (filled !== undefined)) { + remaining = Math.max (0, amount - filled); + } + } + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': trades, + }; + if (fee !== undefined) { + result['fee'] = fee; + } else if (fees !== undefined) { + result['fees'] = fees; + } + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; + } + const request = { + 'count': limit, + 'order_currency': market['base'], + 'payment_currency': market['quote'], + }; + if (since !== undefined) { + request['after'] = since; + } + const response = await this.privatePostInfoOrders (this.extend (request, params)); + // + // { + // "status": "0000", + // "data": [ + // { + // "order_currency": "BTC", + // "payment_currency": "KRW", + // "order_id": "C0101000007408440032", + // "order_date": "1571728739360570", + // "type": "bid", + // "units": "5.0", + // "units_remaining": "5.0", + // "price": "501000", + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const side_in_params = ('side' in params); + if (!side_in_params) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument and a `side` parameter (sell or buy)'); + } + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a `symbol` argument and a `side` parameter (sell or buy)'); + } + const market = this.market (symbol); + const side = (params['side'] === 'buy') ? 'bid' : 'ask'; + params = this.omit (params, [ 'side', 'currency' ]); + // https://github.com/ccxt/ccxt/issues/6771 + const request = { + 'order_id': id, + 'type': side, + 'order_currency': market['base'], + 'payment_currency': market['quote'], + }; + return await this.privatePostTradeCancel (this.extend (request, params)); + } + + cancelUnifiedOrder (order, params = {}) { + const request = { + 'side': order['side'], + }; + return this.cancelOrder (order['id'], order['symbol'], this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'units': amount, + 'address': address, + 'currency': currency['id'], + }; + if (currency === 'XRP' || currency === 'XMR') { + const destination = this.safeString (params, 'destination'); + if ((tag === undefined) && (destination === undefined)) { + throw new ArgumentsRequired (this.id + ' ' + code + ' withdraw() requires a tag argument or an extra destination param'); + } else if (tag !== undefined) { + request['destination'] = tag; + } + } + const response = await this.privatePostTradeBtcWithdrawal (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const endpoint = '/' + this.implodeParams (path, params); + let url = this.urls['api'][api] + endpoint; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'endpoint': endpoint, + }, query)); + const nonce = this.nonce ().toString (); + const auth = endpoint + "\0" + body + "\0" + nonce; // eslint-disable-line quotes + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'); + const signature64 = this.decode (this.stringToBase64 (signature)); + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Api-Key': this.apiKey, + 'Api-Sign': signature64, + 'Api-Nonce': nonce, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('status' in response) { + // + // {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"} + // + const status = this.safeString (response, 'status'); + const message = this.safeString (response, 'message'); + if (status !== undefined) { + if (status === '0000') { + return; // no error + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, status, feedback); + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('status' in response) { + if (response['status'] === '0000') { + return response; + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitkk.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitkk.js new file mode 100644 index 000000000..055657b41 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitkk.js @@ -0,0 +1,26 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const zb = require ('./zb.js'); + +// --------------------------------------------------------------------------- + +module.exports = class bitkk extends zb { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitkk', + 'name': 'bitkk', + 'comment': 'a Chinese ZB clone', + 'urls': { + 'api': { + 'public': 'http://api.bitkk.com/data', // no https for public API + 'private': 'https://trade.bitkk.com/api', + }, + 'www': 'https://www.bitkk.com', + 'doc': 'https://www.bitkk.com/i/developer', + 'fees': 'https://www.bitkk.com/i/rate', + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmart.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmart.js new file mode 100644 index 000000000..d99c5d095 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmart.js @@ -0,0 +1,2348 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeNotAvailable, AccountSuspended, PermissionDenied, RateLimitExceeded, InvalidNonce, InvalidAddress, ArgumentsRequired, ExchangeError, InvalidOrder, InsufficientFunds, BadRequest, OrderNotFound, BadSymbol, NotSupported } = require ('./base/errors'); +const { ROUND, TICK_SIZE, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitmart extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitmart', + 'name': 'BitMart', + 'countries': [ 'US', 'CN', 'HK', 'KR' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'cancelOrders': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCanceledOrders': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchStatus': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'hostname': 'bitmart.com', // bitmart.info for Hong Kong users + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/61835713-a2662f80-ae85-11e9-9d00-6442919701fd.jpg', + 'api': 'https://api-cloud.{hostname}', // bitmart.info for Hong Kong users + 'www': 'https://www.bitmart.com/', + 'doc': 'https://github.com/bitmartexchange/bitmart-official-api-docs', + 'referral': 'http://www.bitmart.com/?r=rQCFLh', + 'fees': 'https://www.bitmart.com/fee/en', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'system': { + 'get': [ + 'time', // https://api-cloud.bitmart.com/system/time + 'service', // https://api-cloud.bitmart.com/system/service + ], + }, + 'account': { + 'get': [ + 'currencies', // https://api-cloud.bitmart.com/account/v1/currencies + ], + }, + 'spot': { + 'get': [ + 'currencies', + 'symbols', + 'symbols/details', + 'ticker', // ?symbol=BTC_USDT + 'steps', // ?symbol=BMX_ETH + 'symbols/kline', // ?symbol=BMX_ETH&step=15&from=1525760116&to=1525769116 + 'symbols/book', // ?symbol=BMX_ETH&precision=6 + 'symbols/trades', // ?symbol=BMX_ETH + ], + }, + 'contract': { + 'get': [ + 'contracts', // https://api-cloud.bitmart.com/contract/v1/ifcontract/contracts + 'pnls', + 'indexes', + 'tickers', + 'quote', + 'indexquote', + 'trades', + 'depth', + 'fundingrate', + ], + }, + }, + 'private': { + 'account': { + 'get': [ + 'wallet', // ?account_type=1 + 'deposit/address', // ?currency=USDT-TRC20 + 'withdraw/charge', // ?currency=BTC + 'deposit-withdraw/history', // ?limit=10&offset=1&operationType=withdraw + 'deposit-withdraw/detail', // ?id=1679952 + ], + 'post': [ + 'withdraw/apply', + ], + }, + 'spot': { + 'get': [ + 'wallet', + 'order_detail', + 'orders', + 'trades', + ], + 'post': [ + 'submit_order', // https://api-cloud.bitmart.com/spot/v1/submit_order + 'cancel_order', // https://api-cloud.bitmart.com/spot/v2/cancel_order + 'cancel_orders', + ], + }, + 'contract': { + 'get': [ + 'userOrders', + 'userOrderInfo', + 'userTrades', + 'orderTrades', + 'accounts', + 'userPositions', + 'userLiqRecords', + 'positionFee', + ], + 'post': [ + 'batchOrders', + 'submitOrder', + 'cancelOrders', + 'marginOper', + ], + }, + }, + }, + 'timeframes': { + '1m': 1, + '3m': 3, + '5m': 5, + '15m': 15, + '30m': 30, + '45m': 45, + '1h': 60, + '2h': 120, + '3h': 180, + '4h': 240, + '1d': 1440, + '1w': 10080, + '1M': 43200, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.001, + 'tiers': { + 'taker': [ + [0, 0.20 / 100], + [10, 0.18 / 100], + [50, 0.16 / 100], + [250, 0.14 / 100], + [1000, 0.12 / 100], + [5000, 0.10 / 100], + [25000, 0.08 / 100], + [50000, 0.06 / 100], + ], + 'maker': [ + [0, 0.1 / 100], + [10, 0.09 / 100], + [50, 0.08 / 100], + [250, 0.07 / 100], + [1000, 0.06 / 100], + [5000, 0.05 / 100], + [25000, 0.04 / 100], + [50000, 0.03 / 100], + ], + }, + }, + }, + 'precisionMode': TICK_SIZE, + 'exceptions': { + 'exact': { + // general errors + '30000': ExchangeError, // 404, Not found + '30001': AuthenticationError, // 401, Header X-BM-KEY is empty + '30002': AuthenticationError, // 401, Header X-BM-KEY not found + '30003': AccountSuspended, // 401, Header X-BM-KEY has frozen + '30004': AuthenticationError, // 401, Header X-BM-SIGN is empty + '30005': AuthenticationError, // 401, Header X-BM-SIGN is wrong + '30006': AuthenticationError, // 401, Header X-BM-TIMESTAMP is empty + '30007': AuthenticationError, // 401, Header X-BM-TIMESTAMP range. Within a minute + '30008': AuthenticationError, // 401, Header X-BM-TIMESTAMP invalid format + '30010': PermissionDenied, // 403, IP is forbidden. We recommend enabling IP whitelist for API trading. After that reauth your account + '30011': AuthenticationError, // 403, Header X-BM-KEY over expire time + '30012': AuthenticationError, // 403, Header X-BM-KEY is forbidden to request it + '30013': RateLimitExceeded, // 429, Request too many requests + '30014': ExchangeNotAvailable, // 503, Service unavailable + // funding account errors + '60000': BadRequest, // 400, Invalid request (maybe the body is empty, or the int parameter passes string data) + '60001': BadRequest, // 400, Asset account type does not exist + '60002': BadRequest, // 400, currency does not exist + '60003': ExchangeError, // 400, Currency has been closed recharge channel, if there is any problem, please consult customer service + '60004': ExchangeError, // 400, Currency has been closed withdraw channel, if there is any problem, please consult customer service + '60005': ExchangeError, // 400, Minimum amount is %s + '60006': ExchangeError, // 400, Maximum withdraw precision is %d + '60007': InvalidAddress, // 400, Only withdrawals from added addresses are allowed + '60008': InsufficientFunds, // 400, Balance not enough + '60009': ExchangeError, // 400, Beyond the limit + '60010': ExchangeError, // 400, Withdraw id or deposit id not found + '60011': InvalidAddress, // 400, Address is not valid + '60012': ExchangeError, // 400, This action is not supported in this currency(If IOTA, HLX recharge and withdraw calls are prohibited) + '60020': PermissionDenied, // 403, Your account is not allowed to recharge + '60021': PermissionDenied, // 403, Your account is not allowed to withdraw + '60022': PermissionDenied, // 403, No withdrawals for 24 hours + '60030': BadRequest, // 405, Method Not Allowed + '60031': BadRequest, // 415, Unsupported Media Type + '60050': ExchangeError, // 500, User account not found + '60051': ExchangeError, // 500, Internal Server Error + // spot errors + '50000': BadRequest, // 400, Bad Request + '50001': BadSymbol, // 400, Symbol not found + '50002': BadRequest, // 400, From Or To format error + '50003': BadRequest, // 400, Step format error + '50004': BadRequest, // 400, Kline size over 500 + '50005': OrderNotFound, // 400, Order Id not found + '50006': InvalidOrder, // 400, Minimum size is %s + '50007': InvalidOrder, // 400, Maximum size is %s + '50008': InvalidOrder, // 400, Minimum price is %s + '50009': InvalidOrder, // 400, Minimum count*price is %s + '50010': InvalidOrder, // 400, RequestParam size is required + '50011': InvalidOrder, // 400, RequestParam price is required + '50012': InvalidOrder, // 400, RequestParam notional is required + '50013': InvalidOrder, // 400, Maximum limit*offset is %d + '50014': BadRequest, // 400, RequestParam limit is required + '50015': BadRequest, // 400, Minimum limit is 1 + '50016': BadRequest, // 400, Maximum limit is %d + '50017': BadRequest, // 400, RequestParam offset is required + '50018': BadRequest, // 400, Minimum offset is 1 + '50019': BadRequest, // 400, Maximum price is %s + // '50019': ExchangeError, // 400, Invalid status. validate status is [1=Failed, 2=Success, 3=Frozen Failed, 4=Frozen Success, 5=Partially Filled, 6=Fully Fulled, 7=Canceling, 8=Canceled + '50020': InsufficientFunds, // 400, Balance not enough + '50021': BadRequest, // 400, Invalid %s + '50022': ExchangeNotAvailable, // 400, Service unavailable + '50023': BadSymbol, // 400, This Symbol can't place order by api + '53000': AccountSuspended, // 403, Your account is frozen due to security policies. Please contact customer service + '57001': BadRequest, // 405, Method Not Allowed + '58001': BadRequest, // 415, Unsupported Media Type + '59001': ExchangeError, // 500, User account not found + '59002': ExchangeError, // 500, Internal Server Error + // contract errors + '40001': ExchangeError, // 400, Cloud account not found + '40002': ExchangeError, // 400, out_trade_no not found + '40003': ExchangeError, // 400, out_trade_no already existed + '40004': ExchangeError, // 400, Cloud account count limit + '40005': ExchangeError, // 400, Transfer vol precision error + '40006': PermissionDenied, // 400, Invalid ip error + '40007': BadRequest, // 400, Parse parameter error + '40008': InvalidNonce, // 400, Check nonce error + '40009': BadRequest, // 400, Check ver error + '40010': BadRequest, // 400, Not found func error + '40011': BadRequest, // 400, Invalid request + '40012': ExchangeError, // 500, System error + '40013': ExchangeError, // 400, Access too often" CLIENT_TIME_INVALID, "Please check your system time. + '40014': BadSymbol, // 400, This contract is offline + '40015': BadSymbol, // 400, This contract's exchange has been paused + '40016': InvalidOrder, // 400, This order would trigger user position liquidate + '40017': InvalidOrder, // 400, It is not possible to open and close simultaneously in the same position + '40018': InvalidOrder, // 400, Your position is closed + '40019': ExchangeError, // 400, Your position is in liquidation delegating + '40020': InvalidOrder, // 400, Your position volume is not enough + '40021': ExchangeError, // 400, The position is not exsit + '40022': ExchangeError, // 400, The position is not isolated + '40023': ExchangeError, // 400, The position would liquidate when sub margin + '40024': ExchangeError, // 400, The position would be warnning of liquidation when sub margin + '40025': ExchangeError, // 400, The position’s margin shouldn’t be lower than the base limit + '40026': ExchangeError, // 400, You cross margin position is in liquidation delegating + '40027': InsufficientFunds, // 400, You contract account available balance not enough + '40028': PermissionDenied, // 400, Your plan order's count is more than system maximum limit. + '40029': InvalidOrder, // 400, The order's leverage is too large. + '40030': InvalidOrder, // 400, The order's leverage is too small. + '40031': InvalidOrder, // 400, The deviation between current price and trigger price is too large. + '40032': InvalidOrder, // 400, The plan order's life cycle is too long. + '40033': InvalidOrder, // 400, The plan order's life cycle is too short. + '40034': BadSymbol, // 400, This contract is not found + }, + 'broad': {}, + }, + 'commonCurrencies': { + 'ONE': 'Menlo One', + 'PLA': 'Plair', + }, + 'options': { + 'defaultType': 'spot', // 'spot', 'swap' + 'fetchBalance': { + 'type': 'spot', // 'spot', 'swap', 'contract', 'account' + }, + 'createMarketBuyOrderRequiresPrice': true, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicSystemGetTime (params); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"c4e5e5b7-fe9f-4191-89f7-53f6c5bf9030", + // "data":{ + // "server_time":1599843709578 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.safeInteger (data, 'server_time'); + } + + async fetchStatus (params = {}) { + const options = this.safeValue (this.options, 'fetchBalance', {}); + const defaultType = this.safeString (this.options, 'defaultType'); + let type = this.safeString (options, 'type', defaultType); + type = this.safeString (params, 'type', type); + params = this.omit (params, 'type'); + const response = await this.publicSystemGetService (params); + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "serivce":[ + // { + // "title": "Spot API Stop", + // "service_type": "spot", + // "status": "2", + // "start_time": 1527777538000, + // "end_time": 1527777538000 + // }, + // { + // "title": "Contract API Stop", + // "service_type": "contract", + // "status": "2", + // "start_time": 1527777538000, + // "end_time": 1527777538000 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const services = this.safeValue (data, 'service', []); + const servicesByType = this.indexBy (services, 'service_type'); + if ((type === 'swap') || (type === 'future')) { + type = 'contract'; + } + const service = this.safeValue (servicesByType, type); + let status = undefined; + let eta = undefined; + if (service !== undefined) { + const statusCode = this.safeInteger (service, 'status'); + if (statusCode === 2) { + status = 'ok'; + } else { + status = 'maintenance'; + eta = this.safeInteger (service, 'end_time'); + } + } + this.status = this.extend (this.status, { + 'status': status, + 'updated': this.milliseconds (), + 'eta': eta, + }); + return this.status; + } + + async fetchSpotMarkets (params = {}) { + const response = await this.publicSpotGetSymbolsDetails (params); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a67c9146-086d-4d3f-9897-5636a9bb26e1", + // "data":{ + // "symbols":[ + // { + // "symbol":"PRQ_BTC", + // "symbol_id":1232, + // "base_currency":"PRQ", + // "quote_currency":"BTC", + // "quote_increment":"1.0000000000", + // "base_min_size":"1.0000000000", + // "base_max_size":"10000000.0000000000", + // "price_min_precision":8, + // "price_max_precision":10, + // "expiration":"NA", + // "min_buy_amount":"0.0001000000", + // "min_sell_amount":"0.0001000000" + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const symbols = this.safeValue (data, 'symbols', []); + const result = []; + for (let i = 0; i < symbols.length; i++) { + const market = symbols[i]; + const id = this.safeString (market, 'symbol'); + const numericId = this.safeInteger (market, 'symbol_id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + // + // https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details + // from the above API doc: + // quote_increment Minimum order price as well as the price increment + // price_min_precision Minimum price precision (digit) used to query price and kline + // price_max_precision Maximum price precision (digit) used to query price and kline + // + // the docs are wrong: https://github.com/ccxt/ccxt/issues/5612 + // + const pricePrecision = this.safeInteger (market, 'price_max_precision'); + const precision = { + 'amount': this.safeFloat (market, 'base_min_size'), + 'price': parseFloat (this.decimalToPrecision (Math.pow (10, -pricePrecision), ROUND, 10)), + }; + const minBuyCost = this.safeFloat (market, 'min_buy_amount'); + const minSellCost = this.safeFloat (market, 'min_sell_amount'); + const minCost = Math.max (minBuyCost, minSellCost); + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'base_min_size'), + 'max': this.safeFloat (market, 'base_max_size'), + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': 'spot', + 'spot': true, + 'future': false, + 'swap': false, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': undefined, + }); + } + return result; + } + + async fetchContractMarkets (params = {}) { + const response = await this.publicContractGetContracts (params); + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"7fcedfb5-a660-4780-8a7a-b36a9e2159f7", + // "data":{ + // "contracts":[ + // { + // "contract":{ + // "contract_id":1, + // "index_id":1, + // "name":"BTCUSDT", + // "display_name":"BTCUSDT永续合约", + // "display_name_en":"BTCUSDT_SWAP", + // "contract_type":1, + // "base_coin":"BTC", + // "quote_coin":"USDT", + // "price_coin":"BTC", + // "exchange":"*", + // "contract_size":"0.0001", + // "begin_at":"2018-08-17T04:00:00Z", + // "delive_at":"2020-08-15T12:00:00Z", + // "delivery_cycle":28800, + // "min_leverage":"1", + // "max_leverage":"100", + // "price_unit":"0.1", + // "vol_unit":"1", + // "value_unit":"0.0001", + // "min_vol":"1", + // "max_vol":"300000", + // "liquidation_warn_ratio":"0.85", + // "fast_liquidation_ratio":"0.8", + // "settgle_type":1, + // "open_type":3, + // "compensate_type":1, + // "status":3, + // "block":1, + // "rank":1, + // "created_at":"2018-07-12T19:16:57Z", + // "depth_bord":"1.001", + // "base_coin_zh":"比特币", + // "base_coin_en":"Bitcoin", + // "max_rate":"0.00375", + // "min_rate":"-0.00375" + // }, + // "risk_limit":{"contract_id":1,"base_limit":"1000000","step":"500000","maintenance_margin":"0.005","initial_margin":"0.01"}, + // "fee_config":{"contract_id":1,"maker_fee":"-0.0003","taker_fee":"0.001","settlement_fee":"0","created_at":"2018-07-12T20:47:22Z"}, + // "plan_order_config":{"contract_id":0,"min_scope":"0.001","max_scope":"2","max_count":10,"min_life_cycle":24,"max_life_cycle":168} + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const contracts = this.safeValue (data, 'contracts', []); + const result = []; + for (let i = 0; i < contracts.length; i++) { + const market = contracts[i]; + const contract = this.safeValue (market, 'contract', {}); + const id = this.safeString (contract, 'contract_id'); + const numericId = this.safeInteger (contract, 'contract_id'); + const baseId = this.safeString (contract, 'base_coin'); + const quoteId = this.safeString (contract, 'quote_coin'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = this.safeString (contract, 'name'); + // + // https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details + // from the above API doc: + // quote_increment Minimum order price as well as the price increment + // price_min_precision Minimum price precision (digit) used to query price and kline + // price_max_precision Maximum price precision (digit) used to query price and kline + // + // the docs are wrong: https://github.com/ccxt/ccxt/issues/5612 + // + const amountPrecision = this.safeFloat (contract, 'vol_unit'); + const pricePrecision = this.safeFloat (contract, 'price_unit'); + const precision = { + 'amount': amountPrecision, + 'price': pricePrecision, + }; + const limits = { + 'amount': { + 'min': this.safeFloat (contract, 'min_vol'), + 'max': this.safeFloat (contract, 'max_vol'), + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + const contractType = this.safeValue (contract, 'contract_type'); + let future = false; + let swap = false; + let type = 'contract'; + if (contractType === 1) { + type = 'swap'; + swap = true; + } else if (contractType === 2) { + type = 'future'; + future = true; + } + const feeConfig = this.safeValue (market, 'fee_config', {}); + const maker = this.safeFloat (feeConfig, 'maker_fee'); + const taker = this.safeFloat (feeConfig, 'taker_fee'); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'maker': maker, + 'taker': taker, + 'type': type, + 'spot': false, + 'future': future, + 'swap': swap, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': undefined, + }); + } + return result; + } + + async fetchMarkets (params = {}) { + const spotMarkets = await this.fetchSpotMarkets (); + const contractMarkets = await this.fetchContractMarkets (); + const allMarkets = this.arrayConcat (spotMarkets, contractMarkets); + return allMarkets; + } + + parseTicker (ticker, market = undefined) { + // + // spot + // + // { + // "symbol":"ETH_BTC", + // "last_price":"0.036037", + // "quote_volume_24h":"4380.6660000000", + // "base_volume_24h":"159.3582006712", + // "high_24h":"0.036972", + // "low_24h":"0.035524", + // "open_24h":"0.036561", + // "close_24h":"0.036037", + // "best_ask":"0.036077", + // "best_ask_size":"9.9500", + // "best_bid":"0.035983", + // "best_bid_size":"4.2792", + // "fluctuation":"-0.0143", + // "url":"https://www.bitmart.com/trade?symbol=ETH_BTC" + // } + // + // contract + // + // { + // "last_price":"422.2", + // "open":"430.5", + // "close":"422.2", + // "low":"421.9", + // "high":"436.9", + // "avg_price":"430.8569900089815372072", + // "volume":"2720", + // "total_volume":"18912248", + // "timestamp":1597631495, + // "rise_fall_rate":"-0.0192799070847851336", + // "rise_fall_value":"-8.3", + // "contract_id":2, + // "position_size":"3067404", + // "volume_day":"9557384", + // "amount24":"80995537.0919999999999974153", + // "base_coin_volume":"189122.48", + // "quote_coin_volume":"81484742.475833810590837937856", + // "pps":"1274350547", + // "index_price":"422.135", + // "fair_price":"422.147253318507", + // "depth_price":{"bid_price":"421.9","ask_price":"422","mid_price":"421.95"}, + // "fair_basis":"0.000029027013", + // "fair_value":"0.012253318507", + // "rate":{"quote_rate":"0.0006","base_rate":"0.0003","interest_rate":"0.000099999999"}, + // "premium_index":"0.000045851604", + // "funding_rate":"0.000158", + // "next_funding_rate":"0.000099999999", + // "next_funding_at":"2020-08-17T04:00:00Z" + // } + // + const timestamp = this.safeTimestamp (ticker, 'timestamp', this.milliseconds ()); + const marketId = this.safeString2 (ticker, 'symbol', 'contract_id'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat2 (ticker, 'close_24h', 'last_price'); + let percentage = this.safeFloat (ticker, 'fluctuation', 'rise_fall_rate'); + if (percentage !== undefined) { + percentage *= 100; + } + const baseVolume = this.safeFloat2 (ticker, 'base_volume_24h', 'base_coin_volume'); + const quoteVolume = this.safeFloat2 (ticker, 'quote_volume_24h', 'quote_coin_volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + const open = this.safeFloat2 (ticker, 'open_24h', 'open'); + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + average = this.sum (last, open) / 2; + } + average = this.safeFloat (ticker, 'avg_price', average); + const price = this.safeValue (ticker, 'depth_price', ticker); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (ticker, 'high', 'high_24h'), + 'low': this.safeFloat2 (ticker, 'low', 'low_24h'), + 'bid': this.safeFloat (price, 'best_bid', 'bid_price'), + 'bidVolume': this.safeFloat (ticker, 'best_bid_size'), + 'ask': this.safeFloat (price, 'best_ask', 'ask_price'), + 'askVolume': this.safeFloat (ticker, 'best_ask_size'), + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open_24h'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['swap'] || market['future']) { + method = 'publicContractGetTickers'; + request['contractID'] = market['id']; + } else if (market['spot']) { + method = 'publicSpotGetTicker'; + request['symbol'] = market['id']; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"6aa5b923-2f57-46e3-876d-feca190e0b82", + // "data":{ + // "tickers":[ + // { + // "symbol":"ETH_BTC", + // "last_price":"0.036037", + // "quote_volume_24h":"4380.6660000000", + // "base_volume_24h":"159.3582006712", + // "high_24h":"0.036972", + // "low_24h":"0.035524", + // "open_24h":"0.036561", + // "close_24h":"0.036037", + // "best_ask":"0.036077", + // "best_ask_size":"9.9500", + // "best_bid":"0.035983", + // "best_bid_size":"4.2792", + // "fluctuation":"-0.0143", + // "url":"https://www.bitmart.com/trade?symbol=ETH_BTC" + // } + // ] + // } + // } + // + // contract + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"d09b57c4-d99b-4a13-91a8-2df98f889909", + // "data":{ + // "tickers":[ + // { + // "last_price":"422.2", + // "open":"430.5", + // "close":"422.2", + // "low":"421.9", + // "high":"436.9", + // "avg_price":"430.8569900089815372072", + // "volume":"2720", + // "total_volume":"18912248", + // "timestamp":1597631495, + // "rise_fall_rate":"-0.0192799070847851336", + // "rise_fall_value":"-8.3", + // "contract_id":2, + // "position_size":"3067404", + // "volume_day":"9557384", + // "amount24":"80995537.0919999999999974153", + // "base_coin_volume":"189122.48", + // "quote_coin_volume":"81484742.475833810590837937856", + // "pps":"1274350547", + // "index_price":"422.135", + // "fair_price":"422.147253318507", + // "depth_price":{"bid_price":"421.9","ask_price":"422","mid_price":"421.95"}, + // "fair_basis":"0.000029027013", + // "fair_value":"0.012253318507", + // "rate":{"quote_rate":"0.0006","base_rate":"0.0003","interest_rate":"0.000099999999"}, + // "premium_index":"0.000045851604", + // "funding_rate":"0.000158", + // "next_funding_rate":"0.000099999999", + // "next_funding_at":"2020-08-17T04:00:00Z" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'tickers', []); + const tickersById = this.indexBy (tickers, 'symbol'); + const ticker = this.safeValue (tickersById, market['id']); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + let method = undefined; + if ((type === 'swap') || (type === 'future')) { + method = 'publicContractGetTickers'; + } else if (type === 'spot') { + method = 'publicSpotGetTicker'; + } + const response = await this[method] (params); + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'tickers', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicAccountGetCurrencies (params); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"8c768b3c-025f-413f-bec5-6d6411d46883", + // "data":{ + // "currencies":[ + // {"currency":"MATIC","name":"Matic Network","withdraw_enabled":true,"deposit_enabled":true}, + // {"currency":"KTN","name":"Kasoutuuka News","withdraw_enabled":true,"deposit_enabled":false}, + // {"currency":"BRT","name":"Berith","withdraw_enabled":true,"deposit_enabled":true}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const currencies = this.safeValue (data, 'currencies', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + const withdrawEnabled = this.safeValue (currency, 'withdraw_enabled'); + const depositEnabled = this.safeValue (currency, 'deposit_enabled'); + const active = withdrawEnabled && depositEnabled; + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'info': currency, // the original payload + 'active': active, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + method = 'publicSpotGetSymbolsBook'; + request['symbol'] = market['id']; + // request['precision'] = 4; // optional price precision / depth level whose range is defined in symbol details + } else if (market['swap'] || market['future']) { + method = 'publicContractGetDepth'; + request['contractID'] = market['id']; + if (limit !== undefined) { + request['count'] = limit; // returns all records if size is omitted + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"8254f8fc-431d-404f-ad9a-e716339f66c7", + // "data":{ + // "buys":[ + // {"amount":"4.7091","total":"4.71","price":"0.034047","count":"1"}, + // {"amount":"5.7439","total":"10.45","price":"0.034039","count":"1"}, + // {"amount":"2.5249","total":"12.98","price":"0.032937","count":"1"}, + // ], + // "sells":[ + // {"amount":"41.4365","total":"41.44","price":"0.034174","count":"1"}, + // {"amount":"4.2317","total":"45.67","price":"0.034183","count":"1"}, + // {"amount":"0.3000","total":"45.97","price":"0.034240","count":"1"}, + // ] + // } + // } + // + // contract + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"c330dfca-ca5b-4f15-b350-9fef3f049b4f", + // "data":{ + // "sells":[ + // {"price":"347.6","vol":"6678"}, + // {"price":"347.7","vol":"3452"}, + // {"price":"347.8","vol":"6331"}, + // ], + // "buys":[ + // {"price":"347.5","vol":"6222"}, + // {"price":"347.4","vol":"20979"}, + // {"price":"347.3","vol":"15179"}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + if (market['spot']) { + return this.parseOrderBook (data, undefined, 'buys', 'sells', 'price', 'amount'); + } else if (market['swap'] || market['future']) { + return this.parseOrderBook (data, undefined, 'buys', 'sells', 'price', 'vol'); + } + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades spot + // + // { + // "amount":"0.005703", + // "order_time":1599652045394, + // "price":"0.034029", + // "count":"0.1676", + // "type":"sell" + // } + // + // public fetchTrades contract, private fetchMyTrades contract + // + // { + // "order_id":109159616160, + // "trade_id":109159616197, + // "contract_id":2, + // "deal_price":"347.6", + // "deal_vol":"5623", + // "make_fee":"-5.8636644", + // "take_fee":"9.772774", + // "created_at":"2020-09-09T11:49:50.749170536Z", + // "way":1, + // "fluctuation":"0" + // } + // + // private fetchMyTrades spot + // + // { + // "detail_id":256348632, + // "order_id":2147484350, + // "symbol":"BTC_USDT", + // "create_time":1590462303000, + // "side":"buy", + // "fees":"0.00001350", + // "fee_coin_name":"BTC", + // "notional":"88.00000000", + // "price_avg":"8800.00", + // "size":"0.01000", + // "exec_type":"M" + // } + // + const id = this.safeString2 (trade, 'trade_id', 'detail_id'); + let timestamp = this.safeInteger2 (trade, 'order_time', 'create_time'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + const type = undefined; + const way = this.safeInteger (trade, 'way'); + let side = this.safeStringLower2 (trade, 'type', 'side'); + if ((side === undefined) && (way !== undefined)) { + if (way < 5) { + side = 'buy'; + } else { + side = 'sell'; + } + } + let takerOrMaker = undefined; + const execType = this.safeString (trade, 'exec_type'); + if (execType !== undefined) { + takerOrMaker = (execType === 'M') ? 'maker' : 'taker'; + } + let price = this.safeFloat2 (trade, 'price', 'deal_price'); + price = this.safeFloat (trade, 'price_avg', price); + let amount = this.safeFloat2 (trade, 'amount', 'deal_vol'); + amount = this.safeFloat (trade, 'size', amount); + let cost = this.safeFloat2 (trade, 'count', 'notional'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = amount * price; + } + const orderId = this.safeInteger (trade, 'order_id'); + const marketId = this.safeString2 (trade, 'contract_id', 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const feeCost = this.safeFloat (trade, 'fees'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_coin_name'); + let feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + if ((feeCurrencyCode === undefined) && (market !== undefined)) { + feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + let method = undefined; + if (market['spot']) { + request['symbol'] = market['id']; + method = 'publicSpotGetSymbolsTrades'; + } else if (market['swap'] || market['future']) { + method = 'publicContractGetTrades'; + request['contractID'] = market['id']; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"222d74c0-8f6d-49d9-8e1b-98118c50eeba", + // "data":{ + // "trades":[ + // { + // "amount":"0.005703", + // "order_time":1599652045394, + // "price":"0.034029", + // "count":"0.1676", + // "type":"sell" + // }, + // ] + // } + // } + // + // contract + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"782bc746-b86e-43bf-8d1a-c68b479c9bdd", + // "data":{ + // "trades":[ + // { + // "order_id":109159616160, + // "trade_id":109159616197, + // "contract_id":2, + // "deal_price":"347.6", + // "deal_vol":"5623", + // "make_fee":"-5.8636644", + // "take_fee":"9.772774", + // "created_at":"2020-09-09T11:49:50.749170536Z", + // "way":1, + // "fluctuation":"0" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // spot + // + // { + // "last_price":"0.034987", + // "timestamp":1598787420, + // "volume":"1.0198", + // "open":"0.035007", + // "close":"0.034987", + // "high":"0.035007", + // "low":"0.034986" + // } + // + // contract + // + // { + // "low":"404.4", + // "high":"404.4", + // "open":"404.4", + // "close":"404.4", + // "last_price":"404.4", + // "avg_price":"404.4", + // "volume":"7670", + // "timestamp":1598758441, + // "rise_fall_rate":"0", + // "rise_fall_value":"0", + // "base_coin_volume":"76.7", + // "quote_coin_volume":"31017.48" + // } + // + return [ + this.safeTimestamp (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const type = market['type']; + let method = undefined; + const request = {}; + const duration = this.parseTimeframe (timeframe); + if (type === 'spot') { + method = 'publicSpotGetSymbolsKline'; + request['symbol'] = market['id']; + request['step'] = this.timeframes[timeframe]; + // the exchange will return an empty array if more than 500 candles is requested + const maxLimit = 500; + if (limit === undefined) { + limit = maxLimit; + } + limit = Math.min (maxLimit, limit); + if (since === undefined) { + const end = parseInt (this.milliseconds () / 1000); + const start = end - limit * duration; + request['from'] = start; + request['to'] = end; + } else { + const start = parseInt (since / 1000); + const end = this.sum (start, limit * duration); + request['from'] = start; + request['to'] = end; + } + } else if ((type === 'swap') || (type === 'future')) { + method = 'publicContractGetQuote'; + request['contractID'] = market['id']; + const defaultLimit = 500; + if (limit === undefined) { + limit = defaultLimit; + } + if (since === undefined) { + const end = parseInt (this.milliseconds () / 1000); + const start = end - limit * duration; + request['startTime'] = start; + request['endTime'] = end; + } else { + const start = parseInt (since / 1000); + const end = this.sum (start, limit * duration); + request['startTime'] = start; + request['endTime'] = end; + } + request['unit'] = this.timeframes[timeframe]; + request['resolution'] = 'M'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"80d86378-ab4e-4c70-819e-b42146cf87ad", + // "data":{ + // "klines":[ + // {"last_price":"0.034987","timestamp":1598787420,"volume":"1.0198","open":"0.035007","close":"0.034987","high":"0.035007","low":"0.034986"}, + // {"last_price":"0.034986","timestamp":1598787480,"volume":"0.3959","open":"0.034982","close":"0.034986","high":"0.034986","low":"0.034980"}, + // {"last_price":"0.034978","timestamp":1598787540,"volume":"0.3259","open":"0.034987","close":"0.034978","high":"0.034987","low":"0.034977"}, + // ] + // } + // } + // + // swap + // + // { + // "errno":"OK", + // "message":"OK", + // "code":1000, + // "trace":"32965074-5804-4655-b693-e953e36026a0", + // "data":[ + // {"low":"404.4","high":"404.4","open":"404.4","close":"404.4","last_price":"404.4","avg_price":"404.4","volume":"7670","timestamp":1598758441,"rise_fall_rate":"0","rise_fall_value":"0","base_coin_volume":"76.7","quote_coin_volume":"31017.48"}, + // {"low":"404.1","high":"404.4","open":"404.4","close":"404.1","last_price":"404.1","avg_price":"404.15881086","volume":"12076","timestamp":1598758501,"rise_fall_rate":"-0.000741839762611276","rise_fall_value":"-0.3","base_coin_volume":"120.76","quote_coin_volume":"48806.2179994536"}, + // {"low":"404","high":"404.3","open":"404.1","close":"404","last_price":"404","avg_price":"404.08918918","volume":"740","timestamp":1598758561,"rise_fall_rate":"-0.000247463499133878","rise_fall_value":"-0.1","base_coin_volume":"7.4","quote_coin_volume":"2990.259999932"}, + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + if (Array.isArray (data)) { + return this.parseOHLCVs (data, market, timeframe, since, limit); + } else { + const klines = this.safeValue (data, 'klines', []); + return this.parseOHLCVs (klines, market, timeframe, since, limit); + } + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + const request = {}; + if (market['spot']) { + request['symbol'] = market['id']; + request['offset'] = 1; + if (limit === undefined) { + limit = 100; // max 100 + } + request['limit'] = limit; + method = 'privateSpotGetTrades'; + } else if (market['swap'] || market['future']) { + request['contractID'] = market['id']; + // request['offset'] = 1; + if (limit !== undefined) { + request['size'] = limit; // max 60 + } + method = 'privateContractGetUserTrades'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a06a5c53-8e6f-42d6-8082-2ff4718d221c", + // "data":{ + // "current_page":1, + // "trades":[ + // { + // "detail_id":256348632, + // "order_id":2147484350, + // "symbol":"BTC_USDT", + // "create_time":1590462303000, + // "side":"buy", + // "fees":"0.00001350", + // "fee_coin_name":"BTC", + // "notional":"88.00000000", + // "price_avg":"8800.00", + // "size":"0.01000", + // "exec_type":"M" + // }, + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "trades": [ + // { + // "order_id": 10116361, + // "trade_id": 10116363, + // "contract_id": 1, + // "deal_price": "16", + // "deal_vol": "10", + // "make_fee": "0.04", + // "take_fee": "0.12", + // "created_at": null, + // "way": 5, + // "fluctuation": "0" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrderTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + const request = {}; + if (market['spot']) { + request['symbol'] = market['id']; + request['order_id'] = id; + method = 'privateSpotGetTrades'; + } else if (market['swap'] || market['future']) { + request['contractID'] = market['id']; + request['orderID'] = id; + method = 'privateContractGetOrderTrades'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a06a5c53-8e6f-42d6-8082-2ff4718d221c", + // "data":{ + // "current_page":1, + // "trades":[ + // { + // "detail_id":256348632, + // "order_id":2147484350, + // "symbol":"BTC_USDT", + // "create_time":1590462303000, + // "side":"buy", + // "fees":"0.00001350", + // "fee_coin_name":"BTC", + // "notional":"88.00000000", + // "price_avg":"8800.00", + // "size":"0.01000", + // "exec_type":"M" + // }, + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "trades": [ + // { + // "order_id": 10116361, + // "trade_id": 10116363, + // "contract_id": 1, + // "deal_price": "16", + // "deal_vol": "10", + // "make_fee": "0.04", + // "take_fee": "0.12", + // "created_at": null, + // "way": 5, + // "fluctuation": "0" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + let method = undefined; + const options = this.safeValue (this.options, 'fetchBalance', {}); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + let type = this.safeString (options, 'type', defaultType); + type = this.safeString (params, 'type', type); + params = this.omit (params, 'type'); + if (type === 'spot') { + method = 'privateSpotGetWallet'; + } else if (type === 'account') { + method = 'privateAccountGetWallet'; + } else if ((type === 'swap') || (type === 'future') || (type === 'contract')) { + method = 'privateContractGetAccounts'; + } + const response = await this[method] (params); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"39069916-72f9-44c7-acde-2ad5afd21cad", + // "data":{ + // "wallet":[ + // {"id":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"}, + // {"id":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"}, + // {"id":"BMX","name":"BitMart Token","available":"0.00000000","frozen":"0.00000000"} + // ] + // } + // } + // + // account + // + // { + // "message":"OK", + // "code":1000, + // "trace":"5c3b7fc7-93b2-49ef-bb59-7fdc56915b59", + // "data":{ + // "wallet":[ + // {"currency":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"}, + // {"currency":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"} + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "accounts": [ + // { + // "account_id": 10, + // "coin_code": "USDT", + // "freeze_vol": "1201.8", + // "available_vol": "8397.65", + // "cash_vol": "0", + // "realised_vol": "-0.5", + // "unrealised_vol": "-0.5", + // "earnings_vol": "-0.5", + // "created_at": "2018-07-13T16:48:49+08:00", + // "updated_at": "2018-07-13T18:34:45.900387+08:00" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const wallet = this.safeValue2 (data, 'wallet', 'accounts', []); + const result = { 'info': response }; + for (let i = 0; i < wallet.length; i++) { + const balance = wallet[i]; + let currencyId = this.safeString2 (balance, 'id', 'currency'); + currencyId = this.safeString (balance, 'coind_code', currencyId); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat2 (balance, 'available', 'available_vol'); + account['used'] = this.safeFloat2 (balance, 'frozen', 'freeze_vol'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "order_id": 2707217580 + // } + // + // cancelOrder + // + // '2707217580' // order id + // + // spot fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders + // + // { + // "order_id":1736871726781, + // "symbol":"BTC_USDT", + // "create_time":1591096004000, + // "side":"sell", + // "type":"market", + // "price":"0.00", + // "price_avg":"0.00", + // "size":"0.02000", + // "notional":"0.00000000", + // "filled_notional":"0.00000000", + // "filled_size":"0.00000", + // "status":"8" + // } + // + // contract fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders, fetchOrders + // + // { + // "order_id": 10539098, + // "contract_id": 1, + // "position_id": 10539088, + // "account_id": 10, + // "price": "16", + // "vol": "1", + // "done_avg_price": "16", + // "done_vol": "1", + // "way": 3, + // "category": 1, + // "open_type": 2, + // "make_fee": "0.00025", + // "take_fee": "0.012", + // "origin": "", + // "created_at": "2018-07-23T11:55:56.715305Z", + // "finished_at": "2018-07-23T11:55:56.763941Z", + // "status": 4, + // "errno": 0 + // } + // + let id = undefined; + if (typeof order === 'string') { + id = order; + order = {}; + } + id = this.safeString (order, 'order_id', id); + let timestamp = this.parse8601 (this.safeString (order, 'created_at')); + timestamp = this.safeInteger (order, 'create_time', timestamp); + const marketId = this.safeString2 (order, 'symbol', 'contract_id'); + const symbol = this.safeSymbol (marketId, market, '_'); + let status = undefined; + if (market !== undefined) { + status = this.parseOrderStatusByType (market['type'], this.safeString (order, 'status')); + } + let price = this.safeFloat (order, 'price'); + let average = this.safeFloat2 (order, 'price_avg', 'done_avg_price'); + const amount = this.safeFloat2 (order, 'size', 'vol'); + let cost = undefined; + let filled = this.safeFloat2 (order, 'filled_size', 'done_vol'); + let remaining = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + if (filled === undefined) { + filled = Math.max (0, amount - remaining); + } + } + if (filled !== undefined) { + if (remaining === undefined) { + remaining = Math.max (0, amount - filled); + } + if (cost === undefined) { + if (average !== undefined) { + cost = average * filled; + } + } + } + } + let side = this.safeString (order, 'side'); + // 1 = Open long + // 2 = Close short + // 3 = Close long + // 4 = Open short + side = this.safeString (order, 'way', side); + const category = this.safeInteger (order, 'category'); + let type = this.safeString (order, 'type'); + if (category === 1) { + type = 'limit'; + } else if (category === 2) { + type = 'market'; + } + if (type === 'market') { + if (price === 0.0) { + price = undefined; + } + if (average === 0.0) { + average = undefined; + } + } + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrderStatusByType (type, status) { + const statusesByType = { + 'spot': { + '1': 'failed', // Order failure + '2': 'open', // Placing order + '3': 'failed', // Order failure, Freeze failure + '4': 'open', // Order success, Pending for fulfilment + '5': 'open', // Partially filled + '6': 'closed', // Fully filled + '7': 'canceling', // Canceling + '8': 'canceled', // Canceled + }, + 'swap': { + '1': 'open', // Submitting + '2': 'open', // Commissioned + '4': 'closed', // Completed + }, + }; + const statuses = this.safeValue (statusesByType, type, {}); + return this.safeString (statuses, status, status); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + request['symbol'] = market['id']; + request['side'] = side; + request['type'] = type; + method = 'privateSpotPostSubmitOrder'; + if (type === 'limit') { + request['size'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let notional = this.safeFloat (params, 'notional'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (notional === undefined) { + notional = amount * price; + } + } else if (notional === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'notional' extra parameter (the exchange-specific behaviour)"); + } + } else { + notional = (notional === undefined) ? amount : notional; + } + const precision = market['precision']['price']; + request['notional'] = this.decimalToPrecision (notional, TRUNCATE, precision, this.precisionMode); + } else if (side === 'sell') { + request['size'] = this.amountToPrecision (symbol, amount); + } + } + } else if (market['swap'] || market['future']) { + method = 'privateContractPostSubmitOrder'; + request['contractID'] = market['id']; + if (type === 'limit') { + request['category'] = 1; + } else if (type === 'market') { + request['category'] = 2; + } + request['way'] = side; // 1 = open long, 2 = close short, 3 = close long, 4 = open short + request['custom_id'] = this.nonce (); + request['open_type'] = 1; // 1 = cross margin, 2 = fixed margin + request['leverage'] = 1; // must meet the effective range of leverage configured in the contract + request['price'] = this.priceToPrecision (symbol, price); + request['vol'] = this.amountToPrecision (symbol, amount); + } + const response = await this[method] (this.extend (request, params)); + // + // spot and contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "order_id": 2707217580 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + method = 'privateSpotPostCancelOrder'; + request['order_id'] = parseInt (id); + request['symbol'] = market['id']; + } else if (market['swap'] || market['future']) { + method = 'privateContractPostCancelOrders'; + request['contractID'] = market['id']; + request['orders'] = [ parseInt (id) ]; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "result": true + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "succeed": [ + // 2707219612 + // ], + // "failed": [] + // } + // } + // + const data = this.safeValue (response, 'data'); + const succeeded = this.safeValue (data, 'succeed'); + if (succeeded !== undefined) { + id = this.safeString (succeeded, 0); + if (id === undefined) { + throw new InvalidOrder (this.id + ' cancelOrder failed to cancel ' + symbol + ' order id ' + id); + } + } else { + const result = this.safeValue (data, 'result'); + if (!result) { + throw new InvalidOrder (this.id + ' cancelOrder ' + symbol + ' order id ' + id + ' is filled or canceled'); + } + } + const order = this.parseOrder (id, market); + return this.extend (order, { 'id': id }); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + const side = this.safeString (params, 'side'); + if (side === undefined) { + throw new ArgumentsRequired (this.id + " cancelAllOrders requires a `side` parameter ('buy' or 'sell')"); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (!market['spot']) { + throw new NotSupported (this.id + ' cancelAllOrders does not support ' + market['type'] + ' orders, only spot orders are accepted'); + } + const request = { + 'symbol': market['id'], + 'side': side, // 'buy' or 'sell' + }; + const response = await this.privateSpotPostCancelOrders (this.extend (request, params)); + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": {} + // } + // + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' canelOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (!market['spot']) { + throw new NotSupported (this.id + ' cancelOrders does not support ' + market['type'] + ' orders, only contract orders are accepted'); + } + const orders = []; + for (let i = 0; i < ids.length; i++) { + orders.push (parseInt (ids[i])); + } + const request = { + 'orders': orders, + }; + const response = await this.privateContractPostCancelOrders (this.extend (request, params)); + // + // spot + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "result": true + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "succeed": [ + // 2707219612 + // ], + // "failed": [] + // } + // } + // + return response; + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrdersByStatus requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + if (market['spot']) { + method = 'privateSpotGetOrders'; + request['symbol'] = market['id']; + request['offset'] = 1; // max offset * limit < 500 + request['limit'] = 100; // max limit is 100 + // 1 = Order failure + // 2 = Placing order + // 3 = Order failure, Freeze failure + // 4 = Order success, Pending for fulfilment + // 5 = Partially filled + // 6 = Fully filled + // 7 = Canceling + // 8 = Canceled + // 9 = Outstanding (4 and 5) + // 10 = 6 and 8 + if (status === 'open') { + request['status'] = 9; + } else if (status === 'closed') { + request['status'] = 6; + } else { + request['status'] = status; + } + } else if (market['swap'] || market['future']) { + method = 'privateContractGetUserOrders'; + request['contractID'] = market['id']; + // request['offset'] = 1; + if (limit !== undefined) { + request['size'] = limit; // max 60 + } + // 0 = All + // 1 = Submitting + // 2 = Commissioned + // 3 = 1 and 2 + // 4 = Completed + if (status === 'open') { + request['status'] = 3; + } else if (status === 'closed') { + request['status'] = 4; + } else { + request['status'] = status; + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"70e7d427-7436-4fb8-8cdd-97e1f5eadbe9", + // "data":{ + // "current_page":1, + // "orders":[ + // { + // "order_id":2147601241, + // "symbol":"BTC_USDT", + // "create_time":1591099963000, + // "side":"sell", + // "type":"limit", + // "price":"9000.00", + // "price_avg":"0.00", + // "size":"1.00000", + // "notional":"9000.00000000", + // "filled_notional":"0.00000000", + // "filled_size":"0.00000", + // "status":"4" + // } + // ] + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "orders": [ + // { + // "order_id": 10284160, + // "contract_id": 1, + // "price": "8", + // "vol": "4", + // "done_avg_price": "0", + // "done_vol": "0", + // "way": 1, + // "category": 1, + // "open_type": 2, + // "make_fee": "0", + // "take_fee": "0", + // "origin": "", + // "created_at": "2018-07-17T07:24:13.410507Z", + // "finished_at": null, + // "status": 2, + // "errno": 0 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orders = this.safeValue (data, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('open', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('closed', symbol, since, limit, params); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (!(market['swap'] || market['future'])) { + throw new NotSupported (this.id + ' fetchOrders does not support ' + market['type'] + ' markets, only contracts are supported'); + } + return await this.fetchOrdersByStatus (0, symbol, since, limit, params); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const request = {}; + const market = this.market (symbol); + let method = undefined; + if (typeof id !== 'string') { + id = id.toString (); + } + if (market['spot']) { + request['symbol'] = market['id']; + request['order_id'] = id; + method = 'privateSpotGetOrderDetail'; + } else if (market['swap'] || market['future']) { + request['contractID'] = market['id']; + request['orderID'] = id; + method = 'privateContractGetUserOrderInfo'; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "message":"OK", + // "code":1000, + // "trace":"a27c2cb5-ead4-471d-8455-1cfeda054ea6", + // "data": { + // "order_id":1736871726781, + // "symbol":"BTC_USDT", + // "create_time":1591096004000, + // "side":"sell", + // "type":"market", + // "price":"0.00", + // "price_avg":"0.00", + // "size":"0.02000", + // "notional":"0.00000000", + // "filled_notional":"0.00000000", + // "filled_size":"0.00000", + // "status":"8" + // } + // } + // + // contract + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "orders": [ + // { + // "order_id": 10539098, + // "contract_id": 1, + // "position_id": 10539088, + // "account_id": 10, + // "price": "16", + // "vol": "1", + // "done_avg_price": "16", + // "done_vol": "1", + // "way": 3, + // "category": 1, + // "make_fee": "0.00025", + // "take_fee": "0.012", + // "origin": "", + // "created_at": "2018-07-23T11:55:56.715305Z", + // "finished_at": "2018-07-23T11:55:56.763941Z", + // "status": 4, + // "errno": 0 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data'); + if ('orders' in data) { + const orders = this.safeValue (data, 'orders', []); + const firstOrder = this.safeValue (orders, 0); + if (firstOrder === undefined) { + throw new OrderNotFound (this.id + ' fetchOrder could not find ' + symbol + ' order id ' + id); + } + return this.parseOrder (firstOrder, market); + } else { + return this.parseOrder (data, market); + } + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateAccountGetDepositAddress (this.extend (request, params)); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"0e6edd79-f77f-4251-abe5-83ba75d06c1a", + // "data":{ + // "currency":"USDT-TRC20", + // "chain":"USDT-TRC20", + // "address":"TGR3ghy2b5VLbyAYrmiE15jasR6aPHTvC5", + // "address_memo":"" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const address = this.safeString (data, 'address'); + const tag = this.safeString (data, 'address_memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'destination': 'To Digital Address', // To Digital Address, To Binance, To OKEX + 'address': address, + }; + if (tag !== undefined) { + request['address_memo'] = tag; + } + const response = await this.privateAccountPostWithdrawApply (this.extend (request, params)); + // + // { + // "code": 1000, + // "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", + // "message": "OK", + // "data": { + // "withdraw_id": "121212" + // } + // } + // + const data = this.safeValue (response, 'data'); + const transaction = this.parseTransaction (data, currency); + return this.extend (transaction, { + 'code': code, + 'address': address, + 'tag': tag, + }); + } + + async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 50; // max 50 + } + const request = { + 'operation_type': type, // deposit or withdraw + 'offset': 1, + 'limit': limit, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currenc (code); + request['currency'] = currency['id']; + } + const response = await this.privateAccountGetDepositWithdrawHistory (this.extend (request, params)); + // + // { + // "message":"OK", + // "code":1000, + // "trace":"142bf92a-fc50-4689-92b6-590886f90b97", + // "data":{ + // "records":[ + // { + // "withdraw_id":"1679952", + // "deposit_id":"", + // "operation_type":"withdraw", + // "currency":"BMX", + // "apply_time":1588867374000, + // "arrival_amount":"59.000000000000", + // "fee":"1.000000000000", + // "status":0, + // "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5", + // "address_memo":"", + // "tx_id":"" + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const records = this.safeValue (data, 'records', []); + return this.parseTransactions (records, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposit', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdraw', code, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + '0': 'pending', // Create + '1': 'pending', // Submitted, waiting for withdrawal + '2': 'pending', // Processing + '3': 'ok', // Success + '4': 'canceled', // Cancel + '5': 'failed', // Fail + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "withdraw_id": "121212" + // } + // + // fetchDeposits, fetchWithdrawals + // + // { + // "withdraw_id":"1679952", + // "deposit_id":"", + // "operation_type":"withdraw", + // "currency":"BMX", + // "apply_time":1588867374000, + // "arrival_amount":"59.000000000000", + // "fee":"1.000000000000", + // "status":0, + // "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5", + // "address_memo":"", + // "tx_id":"" + // } + // + let id = undefined; + const withdrawId = this.safeString (transaction, 'withdraw_id'); + const depositId = this.safeString (transaction, 'deposit_id'); + let type = undefined; + if ((withdrawId !== undefined) && (withdrawId !== '')) { + type = 'withdraw'; + id = withdrawId; + } else if ((depositId !== undefined) && (depositId !== '')) { + type = 'deposit'; + id = depositId; + } + const amount = this.safeFloat (transaction, 'arrival_amount'); + const timestamp = this.safeInteger (transaction, 'tapply_timeime'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + let txid = this.safeString (transaction, 'tx_id'); + if (txid === '') { + txid = undefined; + } + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'address_memo'); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressFrom': undefined, + 'addressTo': undefined, + 'tag': tag, + 'tagFrom': undefined, + 'tagTo': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const baseUrl = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }); + const access = this.safeString (api, 0); + const type = this.safeString (api, 1); + let url = baseUrl + '/' + type; + if (type !== 'system') { + url += '/' + this.version; + } + if (type === 'contract') { + url += '/' + 'ifcontract'; + } + url += '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (type === 'system') { + if (Object.keys (query).length) { + // console.log (query); + url += '?' + this.urlencode (query); + } + } else if (access === 'public') { + if (Object.keys (query).length) { + // console.log (query); + url += '?' + this.urlencode (query); + } + } else if (access === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + let queryString = ''; + headers = { + 'X-BM-KEY': this.apiKey, + 'X-BM-TIMESTAMP': timestamp, + }; + if ((method === 'POST') || (method === 'PUT')) { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + queryString = body; + } else { + if (Object.keys (query).length) { + queryString = this.urlencode (query); + url += '?' + queryString; + } + } + const auth = timestamp + '#' + this.uid + '#' + queryString; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers['X-BM-SIGN'] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // spot + // + // {"message":"Bad Request [to is empty]","code":50000,"trace":"f9d46e1b-4edb-4d07-a06e-4895fb2fc8fc","data":{}} + // {"message":"Bad Request [from is empty]","code":50000,"trace":"579986f7-c93a-4559-926b-06ba9fa79d76","data":{}} + // {"message":"Kline size over 500","code":50004,"trace":"d625caa8-e8ca-4bd2-b77c-958776965819","data":{}} + // {"message":"Balance not enough","code":50020,"trace":"7c709d6a-3292-462c-98c5-32362540aeef","data":{}} + // + // contract + // + // {"errno":"OK","message":"INVALID_PARAMETER","code":49998,"trace":"eb5ebb54-23cd-4de2-9064-e090b6c3b2e3","data":null} + // + const message = this.safeString (response, 'message'); + const errorCode = this.safeString (response, 'code'); + if (((errorCode !== undefined) && (errorCode !== '1000')) || ((message !== undefined) && (message !== 'OK'))) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmax.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmax.js new file mode 100644 index 000000000..0fa940d42 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmax.js @@ -0,0 +1,1725 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, AuthenticationError, ExchangeError, InsufficientFunds, InvalidOrder, BadSymbol, PermissionDenied, BadRequest } = require ('./base/errors'); +const { ROUND, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitmax extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitmax', + 'name': 'BitMax', + 'countries': [ 'CN' ], // China + 'rateLimit': 500, + // new metainfo interface + 'has': { + 'CORS': false, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOHLCV': true, + 'fetchTrades': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchDepositAddress': true, + 'fetchTransactions': true, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '6h': '360', + '12h': '720', + '1d': '1d', + '1w': '1w', + '1M': '1m', + }, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/66820319-19710880-ef49-11e9-8fbe-16be62a11992.jpg', + 'api': 'https://bitmax.io', + 'test': 'https://bitmax-test.io', + 'www': 'https://bitmax.io', + 'doc': [ + 'https://bitmax-exchange.github.io/bitmax-pro-api/#bitmax-pro-api-documentation', + ], + 'fees': 'https://bitmax.io/#/feeRate/tradeRate', + 'referral': 'https://bitmax.io/#/register?inviteCode=EL6BXBQM', + }, + 'api': { + 'public': { + 'get': [ + 'assets', + 'products', + 'ticker', + 'barhist/info', + 'barhist', + 'depth', + 'trades', + 'cash/assets', // not documented + 'cash/products', // not documented + 'margin/assets', // not documented + 'margin/products', // not documented + 'futures/collateral', + 'futures/contracts', + 'futures/ref-px', + 'futures/market-data', + 'futures/funding-rates', + ], + }, + 'accountCategory': { + 'get': [ + 'balance', + 'order/open', + 'order/status', + 'order/hist/current', + 'risk', + ], + 'post': [ + 'order', + 'order/batch', + ], + 'delete': [ + 'order', + 'order/all', + 'order/batch', + ], + }, + 'accountGroup': { + 'get': [ + 'cash/balance', + 'margin/balance', + 'margin/risk', + 'transfer', + 'futures/collateral-balance', + 'futures/position', + 'futures/risk', + 'futures/funding-payments', + 'order/hist', + ], + 'post': [ + 'futures/transfer/deposit', + 'futures/transfer/withdraw', + ], + }, + 'private': { + 'get': [ + 'info', + 'wallet/transactions', + 'wallet/deposit/address', // not documented + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'account-category': 'cash', // 'cash'/'margin'/'futures' + 'account-group': undefined, + 'fetchClosedOrders': { + 'method': 'accountGroupGetOrderHist', // 'accountGroupGetAccountCategoryOrderHistCurrent' + }, + }, + 'exceptions': { + 'exact': { + // not documented + '1900': BadRequest, // {"code":1900,"message":"Invalid Http Request Input"} + '2100': AuthenticationError, // {"code":2100,"message":"ApiKeyFailure"} + '5002': BadSymbol, // {"code":5002,"message":"Invalid Symbol"} + '6001': BadSymbol, // {"code":6001,"message":"Trading is disabled on symbol."} + '6010': InsufficientFunds, // {'code': 6010, 'message': 'Not enough balance.'} + '60060': InvalidOrder, // { 'code': 60060, 'message': 'The order is already filled or canceled.' } + '600503': InvalidOrder, // {"code":600503,"message":"Notional is too small."} + // documented + '100001': BadRequest, // INVALID_HTTP_INPUT Http request is invalid + '100002': BadRequest, // DATA_NOT_AVAILABLE Some required data is missing + '100003': BadRequest, // KEY_CONFLICT The same key exists already + '100004': BadRequest, // INVALID_REQUEST_DATA The HTTP request contains invalid field or argument + '100005': BadRequest, // INVALID_WS_REQUEST_DATA Websocket request contains invalid field or argument + '100006': BadRequest, // INVALID_ARGUMENT The arugment is invalid + '100007': BadRequest, // ENCRYPTION_ERROR Something wrong with data encryption + '100008': BadSymbol, // SYMBOL_ERROR Symbol does not exist or not valid for the request + '100009': AuthenticationError, // AUTHORIZATION_NEEDED Authorization is require for the API access or request + '100010': BadRequest, // INVALID_OPERATION The action is invalid or not allowed for the account + '100011': BadRequest, // INVALID_TIMESTAMP Not a valid timestamp + '100012': BadRequest, // INVALID_STR_FORMAT String format does not + '100013': BadRequest, // INVALID_NUM_FORMAT Invalid number input + '100101': ExchangeError, // UNKNOWN_ERROR Some unknown error + '150001': BadRequest, // INVALID_JSON_FORMAT Require a valid json object + '200001': AuthenticationError, // AUTHENTICATION_FAILED Authorization failed + '200002': ExchangeError, // TOO_MANY_ATTEMPTS Tried and failed too many times + '200003': ExchangeError, // ACCOUNT_NOT_FOUND Account not exist + '200004': ExchangeError, // ACCOUNT_NOT_SETUP Account not setup properly + '200005': ExchangeError, // ACCOUNT_ALREADY_EXIST Account already exist + '200006': ExchangeError, // ACCOUNT_ERROR Some error related with error + '200007': ExchangeError, // CODE_NOT_FOUND + '200008': ExchangeError, // CODE_EXPIRED Code expired + '200009': ExchangeError, // CODE_MISMATCH Code does not match + '200010': AuthenticationError, // PASSWORD_ERROR Wrong assword + '200011': ExchangeError, // CODE_GEN_FAILED Do not generate required code promptly + '200012': ExchangeError, // FAKE_COKE_VERIFY + '200013': ExchangeError, // SECURITY_ALERT Provide security alert message + '200014': PermissionDenied, // RESTRICTED_ACCOUNT Account is restricted for certain activity, such as trading, or withdraw. + '200015': PermissionDenied, // PERMISSION_DENIED No enough permission for the operation + '300001': InvalidOrder, // INVALID_PRICE Order price is invalid + '300002': InvalidOrder, // INVALID_QTY Order size is invalid + '300003': InvalidOrder, // INVALID_SIDE Order side is invalid + '300004': InvalidOrder, // INVALID_NOTIONAL Notional is too small or too large + '300005': InvalidOrder, // INVALID_TYPE Order typs is invalid + '300006': InvalidOrder, // INVALID_ORDER_ID Order id is invalid + '300007': InvalidOrder, // INVALID_TIME_IN_FORCE Time In Force in order request is invalid + '300008': InvalidOrder, // INVALID_ORDER_PARAMETER Some order parameter is invalid + '300009': InvalidOrder, // TRADING_VIOLATION Trading violation on account or asset + '300011': InsufficientFunds, // INVALID_BALANCE No enough account or asset balance for the trading + '300012': BadSymbol, // INVALID_PRODUCT Not a valid product supported by exchange + '300013': InvalidOrder, // INVALID_BATCH_ORDER Some or all orders are invalid in batch order request + '300020': InvalidOrder, // TRADING_RESTRICTED There is some trading restriction on account or asset + '300021': InvalidOrder, // TRADING_DISABLED Trading is disabled on account or asset + '300031': InvalidOrder, // NO_MARKET_PRICE No market price for market type order trading + '310001': InsufficientFunds, // INVALID_MARGIN_BALANCE No enough margin balance + '310002': InvalidOrder, // INVALID_MARGIN_ACCOUNT Not a valid account for margin trading + '310003': InvalidOrder, // MARGIN_TOO_RISKY Leverage is too high + '310004': BadSymbol, // INVALID_MARGIN_ASSET This asset does not support margin trading + '310005': InvalidOrder, // INVALID_REFERENCE_PRICE There is no valid reference price + '510001': ExchangeError, // SERVER_ERROR Something wrong with server. + '900001': ExchangeError, // HUMAN_CHALLENGE Human change do not pass + }, + 'broad': {}, + }, + 'commonCurrencies': { + 'BTCBEAR': 'BEAR', + 'BTCBULL': 'BULL', + }, + }); + } + + getAccount (params = {}) { + // get current or provided bitmax sub-account + const account = this.safeValue (params, 'account', this.options['account']); + return account.toLowerCase ().capitalize (); + } + + async fetchCurrencies (params = {}) { + const assets = await this.publicGetAssets (params); + // + // { + // "code":0, + // "data":[ + // { + // "assetCode" : "LTCBULL", + // "assetName" : "3X Long LTC Token", + // "precisionScale" : 9, + // "nativeScale" : 4, + // "withdrawalFee" : "0.2", + // "minWithdrawalAmt" : "1.0", + // "status" : "Normal" + // }, + // ] + // } + // + const margin = await this.publicGetMarginAssets (params); + // + // { + // "code":0, + // "data":[ + // { + // "assetCode":"BTT", + // "borrowAssetCode":"BTT-B", + // "interestAssetCode":"BTT-I", + // "nativeScale":0, + // "numConfirmations":1, + // "withdrawFee":"100.0", + // "minWithdrawalAmt":"1000.0", + // "statusCode":"Normal", + // "statusMessage":"", + // "interestRate":"0.001" + // } + // ] + // } + // + const cash = await this.publicGetCashAssets (params); + // + // { + // "code":0, + // "data":[ + // { + // "assetCode":"LTCBULL", + // "nativeScale":4, + // "numConfirmations":20, + // "withdrawFee":"0.2", + // "minWithdrawalAmt":"1.0", + // "statusCode":"Normal", + // "statusMessage":"" + // } + // ] + // } + // + const assetsData = this.safeValue (assets, 'data', []); + const marginData = this.safeValue (margin, 'data', []); + const cashData = this.safeValue (cash, 'data', []); + const assetsById = this.indexBy (assetsData, 'assetCode'); + const marginById = this.indexBy (marginData, 'assetCode'); + const cashById = this.indexBy (cashData, 'assetCode'); + const dataById = this.deepExtend (assetsById, marginById, cashById); + const ids = Object.keys (dataById); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = dataById[id]; + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger2 (currency, 'precisionScale', 'nativeScale'); + // why would the exchange API have different names for the same field + const fee = this.safeFloat2 (currency, 'withdrawFee', 'withdrawalFee'); + const status = this.safeString2 (currency, 'status', 'statusCode'); + const active = (status === 'Normal'); + const margin = ('borrowAssetCode' in currency); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'margin': margin, + 'name': this.safeString (currency, 'assetName'), + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawalAmt'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const products = await this.publicGetProducts (params); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"LBA/BTC", + // "baseAsset":"LBA", + // "quoteAsset":"BTC", + // "status":"Normal", + // "minNotional":"0.000625", + // "maxNotional":"6.25", + // "marginTradable":false, + // "commissionType":"Quote", + // "commissionReserveRate":"0.001", + // "tickSize":"0.000000001", + // "lotSize":"1" + // }, + // ] + // } + // + const cash = await this.publicGetCashProducts (params); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"QTUM/BTC", + // "domain":"BTC", + // "tradingStartTime":1569506400000, + // "collapseDecimals":"0.0001,0.000001,0.00000001", + // "minQty":"0.000000001", + // "maxQty":"1000000000", + // "minNotional":"0.000625", + // "maxNotional":"12.5", + // "statusCode":"Normal", + // "statusMessage":"", + // "tickSize":"0.00000001", + // "useTick":false, + // "lotSize":"0.1", + // "useLot":false, + // "commissionType":"Quote", + // "commissionReserveRate":"0.001", + // "qtyScale":1, + // "priceScale":8, + // "notionalScale":4 + // } + // ] + // } + // + const futures = await this.publicGetFuturesContracts (params); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"BTC-PERP", + // "tradingStartTime":1579701600000, + // "collapseDecimals":"1,0.1,0.01", + // "minQty":"0.000000001", + // "maxQty":"1000000000", + // "minNotional":"5", + // "maxNotional":"1000000", + // "statusCode":"Normal", + // "statusMessage":"", + // "tickSize":"0.25", + // "lotSize":"0.0001", + // "priceScale":2, + // "qtyScale":4, + // "notionalScale":2 + // } + // ] + // } + // + const productsData = this.safeValue (products, 'data', []); + const productsById = this.indexBy (productsData, 'symbol'); + const cashData = this.safeValue (cash, 'data', []); + const futuresData = this.safeValue (futures, 'data', []); + const cashAndFuturesData = this.arrayConcat (cashData, futuresData); + const cashAndFuturesById = this.indexBy (cashAndFuturesData, 'symbol'); + const dataById = this.deepExtend (productsById, cashAndFuturesById); + const ids = Object.keys (dataById); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = dataById[id]; + const baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const precision = { + 'amount': this.safeFloat (market, 'lotSize'), + 'price': this.safeFloat (market, 'tickSize'), + }; + const status = this.safeString (market, 'status'); + const active = (status === 'Normal'); + const type = ('useLot' in market) ? 'spot' : 'future'; + const spot = (type === 'spot'); + const future = (type === 'future'); + let symbol = id; + if (!future) { + symbol = base + '/' + quote; + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'future': future, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minQty'), + 'max': this.safeFloat (market, 'maxQty'), + }, + 'price': { + 'min': this.safeFloat (market, 'tickSize'), + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minNotional'), + 'max': this.safeFloat (market, 'maxNotional'), + }, + }, + }); + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + // TODO: fee calculation here is incorrect, we need to support tiered fee calculation. + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchAccounts (params = {}) { + let accountGroup = this.safeString (this.options, 'account-group'); + let response = undefined; + if (accountGroup === undefined) { + response = await this.privateGetInfo (params); + // + // { + // "code":0, + // "data":{ + // "email":"igor.kroitor@gmail.com", + // "accountGroup":8, + // "viewPermission":true, + // "tradePermission":true, + // "transferPermission":true, + // "cashAccount":["cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda"], + // "marginAccount":["martXoh1v1N3EMQC5FDtSj5VHso8aI2Z"], + // "futuresAccount":["futc9r7UmFJAyBY2rE3beA2JFxav2XFF"], + // "userUID":"U6491137460" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + accountGroup = this.safeString (data, 'accountGroup'); + this.options['account-group'] = accountGroup; + } + return [ + { + 'id': accountGroup, + 'type': undefined, + 'currency': undefined, + 'info': response, + }, + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'fetchBalance', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeString (account, 'id'); + const request = { + 'account-group': accountGroup, + }; + let method = 'accountCategoryGetBalance'; + if (accountCategory === 'futures') { + method = 'accountGroupGetFuturesCollateralBalance'; + } else { + request['account-category'] = accountCategory; + } + const response = await this[method] (this.extend (request, params)); + // + // cash + // + // { + // 'code': 0, + // 'data': [ + // { + // 'asset': 'BCHSV', + // 'totalBalance': '64.298000048', + // 'availableBalance': '64.298000048', + // }, + // ] + // } + // + // margin + // + // { + // 'code': 0, + // 'data': [ + // { + // 'asset': 'BCHSV', + // 'totalBalance': '64.298000048', + // 'availableBalance': '64.298000048', + // 'borrowed': '0', + // 'interest': '0', + // }, + // ] + // } + // + // futures + // + // { + // "code":0, + // "data":[ + // {"asset":"BTC","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"9456.59"}, + // {"asset":"ETH","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"235.95"}, + // {"asset":"USDT","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1"}, + // {"asset":"USDC","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1.00035"}, + // {"asset":"PAX","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1.00045"}, + // {"asset":"USDTR","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1"} + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const code = this.safeCurrencyCode (this.safeString (balance, 'asset')); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableBalance'); + account['total'] = this.safeFloat (balance, 'totalBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetDepth (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "m":"depth-snapshot", + // "symbol":"BTC-PERP", + // "data":{ + // "ts":1590223998202, + // "seqnum":115444921, + // "asks":[ + // ["9207.5","18.2383"], + // ["9207.75","18.8235"], + // ["9208","10.7873"], + // ], + // "bids":[ + // ["9207.25","0.4009"], + // ["9207","0.003"], + // ["9206.5","0.003"], + // ] + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orderbook = this.safeValue (data, 'data', {}); + const timestamp = this.safeInteger (orderbook, 'ts'); + const result = this.parseOrderBook (orderbook, timestamp); + result['nonce'] = this.safeInteger (orderbook, 'seqnum'); + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { + // "symbol":"QTUM/BTC", + // "open":"0.00016537", + // "close":"0.00019077", + // "high":"0.000192", + // "low":"0.00016537", + // "volume":"846.6", + // "ask":["0.00018698","26.2"], + // "bid":["0.00018408","503.7"], + // "type":"spot" + // } + // + const timestamp = undefined; + const marketId = this.safeString (ticker, 'symbol'); + let symbol = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else if (marketId !== undefined) { + const type = this.safeString (ticker, 'type'); + if (type === 'spot') { + const [ baseId, quoteId ] = marketId.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const close = this.safeFloat (ticker, 'close'); + const bid = this.safeValue (ticker, 'bid', []); + const ask = this.safeValue (ticker, 'ask', []); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((open !== undefined) && (close !== undefined)) { + change = close - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, close) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (bid, 0), + 'bidVolume': this.safeFloat (bid, 1), + 'ask': this.safeFloat (ask, 0), + 'askVolume': this.safeFloat (ask, 1), + 'vwap': undefined, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "symbol":"BTC-PERP", // or "BTC/USDT" + // "open":"9073", + // "close":"9185.75", + // "high":"9185.75", + // "low":"9185.75", + // "volume":"576.8334", + // "ask":["9185.75","15.5863"], + // "bid":["9185.5","0.003"], + // "type":"derivatives", // or "spot" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const marketIds = this.marketIds (symbols); + request['symbol'] = marketIds.join (','); + } + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // { + // "symbol":"QTUM/BTC", + // "open":"0.00016537", + // "close":"0.00019077", + // "high":"0.000192", + // "low":"0.00016537", + // "volume":"846.6", + // "ask":["0.00018698","26.2"], + // "bid":["0.00018408","503.7"], + // "type":"spot" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTickers (data, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "m":"bar", + // "s":"BTC/USDT", + // "data":{ + // "i":"1", + // "ts":1590228000000, + // "o":"9139.59", + // "c":"9131.94", + // "h":"9139.99", + // "l":"9121.71", + // "v":"25.20648" + // } + // } + // + const data = this.safeValue (ohlcv, 'data', {}); + return [ + this.safeInteger (data, 'ts'), + this.safeFloat (data, 'o'), + this.safeFloat (data, 'h'), + this.safeFloat (data, 'l'), + this.safeFloat (data, 'c'), + this.safeFloat (data, 'v'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + // if since and limit are not specified + // the exchange will return just 1 last candle by default + const duration = this.parseTimeframe (timeframe); + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const defaultLimit = this.safeInteger (options, 'limit', 500); + if (since !== undefined) { + request['from'] = since; + if (limit === undefined) { + limit = defaultLimit; + } else { + limit = Math.min (limit, defaultLimit); + } + request['to'] = this.sum (since, limit * duration * 1000, 1); + } else if (limit !== undefined) { + request['n'] = limit; // max 500 + } + const response = await this.publicGetBarhist (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // { + // "m":"bar", + // "s":"BTC/USDT", + // "data":{ + // "i":"1", + // "ts":1590228000000, + // "o":"9139.59", + // "c":"9131.94", + // "h":"9139.99", + // "l":"9121.71", + // "v":"25.20648" + // } + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "p":"9128.5", // price + // "q":"0.0030", // quantity + // "ts":1590229002385, // timestamp + // "bm":false, // if true, the buyer is the market maker, we only use this field to "define the side" of a public trade + // "seqnum":180143985289898554 + // } + // + const timestamp = this.safeInteger (trade, 'ts'); + const price = this.safeFloat2 (trade, 'price', 'p'); + const amount = this.safeFloat (trade, 'q'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const buyerIsMaker = this.safeValue (trade, 'bm', false); + const makerOrTaker = buyerIsMaker ? 'maker' : 'taker'; + const side = buyerIsMaker ? 'buy' : 'sell'; + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': undefined, + 'order': undefined, + 'type': undefined, + 'takerOrMaker': makerOrTaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['n'] = limit; // max 100 + } + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "m":"trades", + // "symbol":"BTC-PERP", + // "data":[ + // {"p":"9128.5","q":"0.0030","ts":1590229002385,"bm":false,"seqnum":180143985289898554}, + // {"p":"9129","q":"0.0030","ts":1590229002642,"bm":false,"seqnum":180143985289898587}, + // {"p":"9129.5","q":"0.0030","ts":1590229021306,"bm":false,"seqnum":180143985289899043} + // ] + // } + // } + // + const records = this.safeValue (response, 'data', []); + const trades = this.safeValue (records, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'PendingNew': 'open', + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'Canceled': 'canceled', + 'Rejected': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": "16e607e2b83a8bXHbAwwoqDo55c166fa", + // "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830", + // "orderType": "Market", + // "symbol": "BTC/USDT", + // "timestamp": 1573576916201 + // } + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "symbol": "BTC/USDT", + // "price": "8131.22", + // "orderQty": "0.00082", + // "orderType": "Market", + // "avgPx": "7392.02", + // "cumFee": "0.005152238", + // "cumFilledQty": "0.00082", + // "errorCode": "", + // "feeAsset": "USDT", + // "lastExecTime": 1575953151764, + // "orderId": "a16eee20b6750866943712zWEDdAjt3", + // "seqNum": 2623469, + // "side": "Buy", + // "status": "Filled", + // "stopPrice": "", + // "execInst": "NULL_VAL" + // } + // + // { + // "ac": "FUTURES", + // "accountId": "testabcdefg", + // "avgPx": "0", + // "cumFee": "0", + // "cumQty": "0", + // "errorCode": "NULL_VAL", + // "execInst": "NULL_VAL", + // "feeAsset": "USDT", + // "lastExecTime": 1584072844085, + // "orderId": "r170d21956dd5450276356bbtcpKa74", + // "orderQty": "1.1499", + // "orderType": "Limit", + // "price": "4000", + // "sendingTime": 1584072841033, + // "seqNum": 24105338, + // "side": "Buy", + // "status": "Canceled", + // "stopPrice": "", + // "symbol": "BTC-PERP" + // }, + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '/'); + let timestamp = this.safeInteger2 (order, 'timestamp', 'sendingTime'); + let lastTradeTimestamp = this.safeInteger (order, 'lastExecTime'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'orderQty'); + const average = this.safeFloat (order, 'avgPx'); + const filled = this.safeFloat2 (order, 'cumFilledQty', 'cumQty'); + let remaining = undefined; + if (filled !== undefined) { + if (filled === 0) { + timestamp = lastTradeTimestamp; + lastTradeTimestamp = undefined; + } + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + let cost = undefined; + if ((average !== undefined) && (filled !== undefined)) { + cost = average * filled; + } + const id = this.safeString (order, 'orderId'); + let clientOrderId = this.safeString (order, 'id'); + if (clientOrderId !== undefined) { + if (clientOrderId.length < 1) { + clientOrderId = undefined; + } + } + const type = this.safeStringLower (order, 'orderType'); + const side = this.safeStringLower (order, 'side'); + const feeCost = this.safeFloat (order, 'cumFee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (order, 'feeAsset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'createOrder', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'symbol': market['id'], + 'time': this.milliseconds (), + 'orderQty': this.amountToPrecision (symbol, amount), + 'orderType': type, // "limit", "market", "stop_market", "stop_limit" + 'side': side, // "buy" or "sell" + // 'orderPrice': this.priceToPrecision (symbol, price), + // 'stopPrice': this.priceToPrecision (symbol, stopPrice), // required for stop orders + // 'postOnly': 'false', // 'false', 'true' + // 'timeInForce': 'GTC', // GTC, IOC, FOK + // 'respInst': 'ACK', // ACK, 'ACCEPT, DONE + }; + if (clientOrderId !== undefined) { + request['id'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'id' ]); + } + if ((type === 'limit') || (type === 'stop_limit')) { + request['orderPrice'] = this.priceToPrecision (symbol, price); + } + if ((type === 'stop_limit') || (type === 'stop_market')) { + const stopPrice = this.safeFloat (params, 'stopPrice'); + if (stopPrice === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a stopPrice parameter for ' + type + ' orders'); + } else { + request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); + params = this.omit (params, 'stopPrice'); + } + } + const response = await this.accountCategoryPostOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "ac": "MARGIN", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "action": "place-order", + // "info": { + // "id": "16e607e2b83a8bXHbAwwoqDo55c166fa", + // "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830", + // "orderType": "Market", + // "symbol": "BTC/USDT", + // "timestamp": 1573576916201 + // }, + // "status": "Ack" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const info = this.safeValue (data, 'info', {}); + return this.parseOrder (info, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'fetchOrder', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'orderId': id, + }; + const response = await this.accountCategoryGetOrderStatus (this.extend (request, params)); + // + // { + // "code": 0, + // "accountCategory": "CASH", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "data": [ + // { + // "symbol": "BTC/USDT", + // "price": "8131.22", + // "orderQty": "0.00082", + // "orderType": "Market", + // "avgPx": "7392.02", + // "cumFee": "0.005152238", + // "cumFilledQty": "0.00082", + // "errorCode": "", + // "feeAsset": "USDT", + // "lastExecTime": 1575953151764, + // "orderId": "a16eee20b6750866943712zWEDdAjt3", + // "seqNum": 2623469, + // "side": "Buy", + // "status": "Filled", + // "stopPrice": "", + // "execInst": "NULL_VAL" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + }; + const response = await this.accountCategoryGetOrderOpen (this.extend (request, params)); + // + // { + // "ac": "CASH", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "code": 0, + // "data": [ + // { + // "avgPx": "0", // Average filled price of the order + // "cumFee": "0", // cumulative fee paid for this order + // "cumFilledQty": "0", // cumulative filled quantity + // "errorCode": "", // error code; could be empty + // "feeAsset": "USDT", // fee asset + // "lastExecTime": 1576019723550, // The last execution time of the order + // "orderId": "s16ef21882ea0866943712034f36d83", // server provided orderId + // "orderQty": "0.0083", // order quantity + // "orderType": "Limit", // order type + // "price": "7105", // order price + // "seqNum": 8193258, // sequence number + // "side": "Buy", // order side + // "status": "New", // order status on matching engine + // "stopPrice": "", // only available for stop market and stop limit orders; otherwise empty + // "symbol": "BTC/USDT", + // "execInst": "NULL_VAL" // execution instruction + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + if (accountCategory === 'futures') { + return this.parseOrders (data, market, since, limit); + } + // a workaround for https://github.com/ccxt/ccxt/issues/7187 + const orders = []; + for (let i = 0; i < data.length; i++) { + const order = this.parseOrder (data[i], market); + orders.push (order); + } + return this.filterBySymbolSinceLimit (orders, symbol, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const defaultAccountCategory = this.safeString (this.options, 'account-category'); + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + // 'category': accountCategory, + // 'symbol': market['id'], + // 'orderType': 'market', // optional, string + // 'side': 'buy', // or 'sell', optional, case insensitive. + // 'status': 'Filled', // "Filled", "Canceled", or "Rejected" + // 'startTime': exchange.milliseconds (), + // 'endTime': exchange.milliseconds (), + // 'page': 1, + // 'pageSize': 100, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const method = this.safeValue (options, 'method', 'accountGroupGetOrderHist'); + if (method === 'accountGroupGetOrderHist') { + if (accountCategory !== undefined) { + request['category'] = accountCategory; + } + } else { + request['account-category'] = accountCategory; + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // accountCategoryGetOrderHistCurrent + // + // { + // "code":0, + // "accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda", + // "ac":"CASH", + // "data":[ + // { + // "seqNum":15561826728, + // "orderId":"a17294d305c0U6491137460bethu7kw9", + // "symbol":"ETH/USDT", + // "orderType":"Limit", + // "lastExecTime":1591635618200, + // "price":"200", + // "orderQty":"0.1", + // "side":"Buy", + // "status":"Canceled", + // "avgPx":"0", + // "cumFilledQty":"0", + // "stopPrice":"", + // "errorCode":"", + // "cumFee":"0", + // "feeAsset":"USDT", + // "execInst":"NULL_VAL" + // } + // ] + // } + // + // accountGroupGetOrderHist + // + // { + // "code": 0, + // "data": { + // "data": [ + // { + // "ac": "FUTURES", + // "accountId": "testabcdefg", + // "avgPx": "0", + // "cumFee": "0", + // "cumQty": "0", + // "errorCode": "NULL_VAL", + // "execInst": "NULL_VAL", + // "feeAsset": "USDT", + // "lastExecTime": 1584072844085, + // "orderId": "r170d21956dd5450276356bbtcpKa74", + // "orderQty": "1.1499", + // "orderType": "Limit", + // "price": "4000", + // "sendingTime": 1584072841033, + // "seqNum": 24105338, + // "side": "Buy", + // "status": "Canceled", + // "stopPrice": "", + // "symbol": "BTC-PERP" + // }, + // ], + // "hasNext": False, + // "limit": 500, + // "page": 1, + // "pageSize": 20 + // } + // } + // + let data = this.safeValue (response, 'data'); + const isArray = Array.isArray (data); + if (!isArray) { + data = this.safeValue (data, 'data', []); + } + return this.parseOrders (data, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'cancelOrder', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'symbol': market['id'], + 'time': this.milliseconds (), + 'id': 'foobar', + }; + if (clientOrderId === undefined) { + request['orderId'] = id; + } else { + request['id'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'id' ]); + } + const response = await this.accountCategoryDeleteOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "ac": "CASH", + // "action": "cancel-order", + // "status": "Ack", + // "info": { + // "id": "wv8QGquoeamhssvQBeHOHGQCGlcBjj23", + // "orderId": "16e6198afb4s8bXHbAwwoqDo2ebc19dc", + // "orderType": "", // could be empty + // "symbol": "ETH/USDT", + // "timestamp": 1573594877822 + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const info = this.safeValue (data, 'info', {}); + return this.parseOrder (info, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountCategory = this.safeString (this.options, 'account-category', 'cash'); + const options = this.safeValue (this.options, 'cancelAllOrders', {}); + let accountCategory = this.safeString (options, 'account-category', defaultAccountCategory); + accountCategory = this.safeString (params, 'account-category', accountCategory); + params = this.omit (params, 'account-category'); + const account = this.safeValue (this.accounts, 0, {}); + const accountGroup = this.safeValue (account, 'id'); + const request = { + 'account-group': accountGroup, + 'account-category': accountCategory, + 'time': this.milliseconds (), + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.accountCategoryDeleteOrderAll (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "ac": "CASH", + // "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo", + // "action": "cancel-all", + // "info": { + // "id": "2bmYvi7lyTrneMzpcJcf2D7Pe9V1P9wy", + // "orderId": "", + // "orderType": "NULL_VAL", + // "symbol": "", + // "timestamp": 1574118495462 + // }, + // "status": "Ack" + // } + // } + // + return response; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722", + // destTag: "", + // tagType: "", + // tagId: "", + // chainName: "ERC20", + // numConfirmations: 20, + // withdrawalFee: 1, + // nativeScale: 4, + // tips: [] + // } + // + const address = this.safeString (depositAddress, 'address'); + const tagId = this.safeString (depositAddress, 'tagId'); + const tag = this.safeString (depositAddress, tagId); + this.checkAddress (address); + const code = (currency === undefined) ? undefined : currency['code']; + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const chainName = this.safeString (params, 'chainName'); + params = this.omit (params, 'chainName'); + const request = { + 'asset': currency['id'], + }; + const response = await this.privateGetWalletDepositAddress (this.extend (request, params)); + // + // { + // "code":0, + // "data":{ + // "asset":"USDT", + // "assetName":"Tether", + // "address":[ + // { + // "address":"1N22odLHXnLPCjC8kwBJPTayarr9RtPod6", + // "destTag":"", + // "tagType":"", + // "tagId":"", + // "chainName":"Omni", + // "numConfirmations":3, + // "withdrawalFee":4.7, + // "nativeScale":4, + // "tips":[] + // }, + // { + // "address":"0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722", + // "destTag":"", + // "tagType":"", + // "tagId":"", + // "chainName":"ERC20", + // "numConfirmations":20, + // "withdrawalFee":1.0, + // "nativeScale":4, + // "tips":[] + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const addresses = this.safeValue (data, 'address', []); + const numAddresses = addresses.length; + let address = undefined; + if (numAddresses > 1) { + const addressesByChainName = this.indexBy (addresses, 'chainName'); + if (chainName === undefined) { + const chainNames = Object.keys (addressesByChainName); + const chains = chainNames.join (', '); + throw new ArgumentsRequired (this.id + ' fetchDepositAddress returned more than one address, a chainName parameter is required, one of ' + chains); + } + address = this.safeValue (addressesByChainName, chainName, {}); + } else { + // first address + address = this.safeValue (addresses, 0, {}); + } + const result = this.parseDepositAddress (address, currency); + return this.extend (result, { + 'info': response, + }); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'txType': 'deposit', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'txType': 'withdrawal', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'asset': currency['id'], + // 'page': 1, + // 'pageSize': 20, + // 'startTs': this.milliseconds (), + // 'endTs': this.milliseconds (), + // 'txType': undefned, // deposit, withdrawal + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTs'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this.privateGetWalletTransactions (this.extend (request, params)); + // + // { + // code: 0, + // data: { + // data: [ + // { + // requestId: "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB", + // time: 1591606166000, + // asset: "USDT", + // transactionType: "deposit", + // amount: "25", + // commission: "0", + // networkTransactionId: "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce", + // status: "pending", + // numConfirmed: 8, + // numConfirmations: 20, + // destAddress: { address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722" } + // } + // ], + // page: 1, + // pageSize: 20, + // hasNext: false + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const transactions = this.safeValue (data, 'data', []); + return this.parseTransactions (transactions, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'reviewing': 'pending', + 'pending': 'pending', + 'confirmed': 'ok', + 'rejected': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // requestId: "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB", + // time: 1591606166000, + // asset: "USDT", + // transactionType: "deposit", + // amount: "25", + // commission: "0", + // networkTransactionId: "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce", + // status: "pending", + // numConfirmed: 8, + // numConfirmations: 20, + // destAddress: { + // address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722", + // destTag: "..." // for currencies that have it + // } + // } + // + const id = this.safeString (transaction, 'requestId'); + const amount = this.safeFloat (transaction, 'amount'); + const destAddress = this.safeValue (transaction, 'destAddress', {}); + const address = this.safeString (destAddress, 'address'); + const tag = this.safeString (destAddress, 'destTag'); + const txid = this.safeString (transaction, 'networkTransactionId'); + const type = this.safeString (transaction, 'transactionType'); + const timestamp = this.safeInteger (transaction, 'time'); + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const feeCost = this.safeFloat (transaction, 'commission'); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = ''; + let query = params; + const accountCategory = (api === 'accountCategory'); + if (accountCategory || (api === 'accountGroup')) { + url += this.implodeParams ('/{account-group}', params); + query = this.omit (params, 'account-group'); + } + const request = this.implodeParams (path, query); + url += '/api/pro/' + this.version; + if (accountCategory) { + url += this.implodeParams ('/{account-category}', query); + query = this.omit (query, 'account-category'); + } + url += '/' + request; + query = this.omit (query, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + const payload = timestamp + '+' + request; + const hmac = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64'); + headers = { + 'x-auth-key': this.apiKey, + 'x-auth-timestamp': timestamp, + 'x-auth-signature': hmac, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + } + } + url = this.urls['api'] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {'code': 6010, 'message': 'Not enough balance.'} + // {'code': 60060, 'message': 'The order is already filled or canceled.'} + // {"code":2100,"message":"ApiKeyFailure"} + // {"code":300001,"message":"Price is too low from market price.","reason":"INVALID_PRICE","accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda","ac":"CASH","action":"place-order","status":"Err","info":{"symbol":"BTC/USDT"}} + // + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + const error = (code !== undefined) && (code !== '0'); + if (error || (message !== undefined)) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmex.js new file mode 100644 index 000000000..536120bf6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitmex.js @@ -0,0 +1,1640 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { AuthenticationError, BadRequest, DDoSProtection, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitmex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitmex', + 'name': 'BitMEX', + 'countries': [ 'SC' ], // Seychelles + 'version': 'v1', + 'userAgent': undefined, + 'rateLimit': 2000, + 'pro': true, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': 'emulated', + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '1h': '1h', + '1d': '1d', + }, + 'urls': { + 'test': { + 'public': 'https://testnet.bitmex.com', + 'private': 'https://testnet.bitmex.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/27766319-f653c6e6-5ed4-11e7-933d-f0bc3699ae8f.jpg', + 'api': { + 'public': 'https://www.bitmex.com', + 'private': 'https://www.bitmex.com', + }, + 'www': 'https://www.bitmex.com', + 'doc': [ + 'https://www.bitmex.com/app/apiOverview', + 'https://github.com/BitMEX/api-connectors/tree/master/official-http', + ], + 'fees': 'https://www.bitmex.com/app/fees', + 'referral': 'https://www.bitmex.com/register/upZpOX', + }, + 'api': { + 'public': { + 'get': [ + 'announcement', + 'announcement/urgent', + 'funding', + 'instrument', + 'instrument/active', + 'instrument/activeAndIndices', + 'instrument/activeIntervals', + 'instrument/compositeIndex', + 'instrument/indices', + 'insurance', + 'leaderboard', + 'liquidation', + 'orderBook', + 'orderBook/L2', + 'quote', + 'quote/bucketed', + 'schema', + 'schema/websocketHelp', + 'settlement', + 'stats', + 'stats/history', + 'trade', + 'trade/bucketed', + ], + }, + 'private': { + 'get': [ + 'apiKey', + 'chat', + 'chat/channels', + 'chat/connected', + 'execution', + 'execution/tradeHistory', + 'notification', + 'order', + 'position', + 'user', + 'user/affiliateStatus', + 'user/checkReferralCode', + 'user/commission', + 'user/depositAddress', + 'user/executionHistory', + 'user/margin', + 'user/minWithdrawalFee', + 'user/wallet', + 'user/walletHistory', + 'user/walletSummary', + ], + 'post': [ + 'apiKey', + 'apiKey/disable', + 'apiKey/enable', + 'chat', + 'order', + 'order/bulk', + 'order/cancelAllAfter', + 'order/closePosition', + 'position/isolate', + 'position/leverage', + 'position/riskLimit', + 'position/transferMargin', + 'user/cancelWithdrawal', + 'user/confirmEmail', + 'user/confirmEnableTFA', + 'user/confirmWithdrawal', + 'user/disableTFA', + 'user/logout', + 'user/logoutAll', + 'user/preferences', + 'user/requestEnableTFA', + 'user/requestWithdrawal', + ], + 'put': [ + 'order', + 'order/bulk', + 'user', + ], + 'delete': [ + 'apiKey', + 'order', + 'order/all', + ], + }, + }, + 'exceptions': { + 'exact': { + 'Invalid API Key.': AuthenticationError, + 'This key is disabled.': PermissionDenied, + 'Access Denied': PermissionDenied, + 'Duplicate clOrdID': InvalidOrder, + 'orderQty is invalid': InvalidOrder, + 'Invalid price': InvalidOrder, + 'Invalid stopPx for ordType': InvalidOrder, + }, + 'broad': { + 'Signature not valid': AuthenticationError, + 'overloaded': ExchangeNotAvailable, + 'Account has insufficient Available Balance': InsufficientFunds, + 'Service unavailable': ExchangeNotAvailable, // {"error":{"message":"Service unavailable","name":"HTTPError"}} + 'Server Error': ExchangeError, // {"error":{"message":"Server Error","name":"HTTPError"}} + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + // https://blog.bitmex.com/api_announcement/deprecation-of-api-nonce-header/ + // https://github.com/ccxt/ccxt/issues/4789 + 'api-expires': 5, // in seconds + 'fetchOHLCVOpenTimestamp': true, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInstrumentActiveAndIndices (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const active = (market['state'] !== 'Unlisted'); + const id = market['symbol']; + const baseId = market['underlying']; + const quoteId = market['quoteCurrency']; + const basequote = baseId + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const swap = (id === basequote); + // 'positionCurrency' may be empty ("", as Bitmex currently returns for ETHUSD) + // so let's take the quote currency first and then adjust if needed + const positionId = this.safeString2 (market, 'positionCurrency', 'quoteCurrency'); + let type = undefined; + let future = false; + let prediction = false; + const position = this.safeCurrencyCode (positionId); + let symbol = id; + if (swap) { + type = 'swap'; + symbol = base + '/' + quote; + } else if (id.indexOf ('B_') >= 0) { + prediction = true; + type = 'prediction'; + } else { + future = true; + type = 'future'; + } + const precision = { + 'amount': undefined, + 'price': undefined, + }; + const lotSize = this.safeFloat (market, 'lotSize'); + const tickSize = this.safeFloat (market, 'tickSize'); + if (lotSize !== undefined) { + precision['amount'] = lotSize; + } + if (tickSize !== undefined) { + precision['price'] = tickSize; + } + const limits = { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': tickSize, + 'max': this.safeFloat (market, 'maxPrice'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + const limitField = (position === quote) ? 'cost' : 'amount'; + limits[limitField] = { + 'min': lotSize, + 'max': this.safeFloat (market, 'maxOrderQty'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'taker': this.safeFloat (market, 'takerFee'), + 'maker': this.safeFloat (market, 'makerFee'), + 'type': type, + 'spot': false, + 'swap': swap, + 'future': future, + 'prediction': prediction, + 'info': market, + }); + } + return result; + } + + parseBalanceResponse (response) { + // + // [ + // { + // "account":1455728, + // "currency":"XBt", + // "riskLimit":1000000000000, + // "prevState":"", + // "state":"", + // "action":"", + // "amount":263542, + // "pendingCredit":0, + // "pendingDebit":0, + // "confirmedDebit":0, + // "prevRealisedPnl":0, + // "prevUnrealisedPnl":0, + // "grossComm":0, + // "grossOpenCost":0, + // "grossOpenPremium":0, + // "grossExecCost":0, + // "grossMarkValue":0, + // "riskValue":0, + // "taxableMargin":0, + // "initMargin":0, + // "maintMargin":0, + // "sessionMargin":0, + // "targetExcessMargin":0, + // "varMargin":0, + // "realisedPnl":0, + // "unrealisedPnl":0, + // "indicativeTax":0, + // "unrealisedProfit":0, + // "syntheticMargin":null, + // "walletBalance":263542, + // "marginBalance":263542, + // "marginBalancePcnt":1, + // "marginLeverage":0, + // "marginUsedPcnt":0, + // "excessMargin":263542, + // "excessMarginPcnt":1, + // "availableMargin":263542, + // "withdrawableMargin":263542, + // "timestamp":"2020-08-03T12:01:01.246Z", + // "grossLastValue":0, + // "commission":null + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + let free = this.safeFloat (balance, 'availableMargin'); + let total = this.safeFloat (balance, 'marginBalance'); + if (code === 'BTC') { + if (free !== undefined) { + free /= 100000000; + } + if (total !== undefined) { + total /= 100000000; + } + } + account['free'] = free; + account['total'] = total; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'currency': 'all', + }; + const response = await this.privateGetUserMargin (this.extend (request, params)); + // + // [ + // { + // "account":1455728, + // "currency":"XBt", + // "riskLimit":1000000000000, + // "prevState":"", + // "state":"", + // "action":"", + // "amount":263542, + // "pendingCredit":0, + // "pendingDebit":0, + // "confirmedDebit":0, + // "prevRealisedPnl":0, + // "prevUnrealisedPnl":0, + // "grossComm":0, + // "grossOpenCost":0, + // "grossOpenPremium":0, + // "grossExecCost":0, + // "grossMarkValue":0, + // "riskValue":0, + // "taxableMargin":0, + // "initMargin":0, + // "maintMargin":0, + // "sessionMargin":0, + // "targetExcessMargin":0, + // "varMargin":0, + // "realisedPnl":0, + // "unrealisedPnl":0, + // "indicativeTax":0, + // "unrealisedProfit":0, + // "syntheticMargin":null, + // "walletBalance":263542, + // "marginBalance":263542, + // "marginBalancePcnt":1, + // "marginLeverage":0, + // "marginUsedPcnt":0, + // "excessMargin":263542, + // "excessMarginPcnt":1, + // "availableMargin":263542, + // "withdrawableMargin":263542, + // "timestamp":"2020-08-03T12:01:01.246Z", + // "grossLastValue":0, + // "commission":null + // } + // ] + // + return this.parseBalanceResponse (response); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetOrderBookL2 (this.extend (request, params)); + const result = { + 'bids': [], + 'asks': [], + 'timestamp': undefined, + 'datetime': undefined, + 'nonce': undefined, + }; + for (let i = 0; i < response.length; i++) { + const order = response[i]; + const side = (order['side'] === 'Sell') ? 'asks' : 'bids'; + const amount = this.safeFloat (order, 'size'); + const price = this.safeFloat (order, 'price'); + // https://github.com/ccxt/ccxt/issues/4926 + // https://github.com/ccxt/ccxt/issues/4927 + // the exchange sometimes returns null price in the orderbook + if (price !== undefined) { + result[side].push ([ price, amount ]); + } + } + result['bids'] = this.sortBy (result['bids'], 0, true); + result['asks'] = this.sortBy (result['asks'], 0); + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + const filter = { + 'filter': { + 'orderID': id, + }, + }; + const response = await this.fetchOrders (symbol, undefined, undefined, this.deepExtend (filter, params)); + const numResults = response.length; + if (numResults === 1) { + return response[0]; + } + throw new OrderNotFound (this.id + ': The order ' + id + ' not found.'); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['startTime'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['count'] = limit; + } + request = this.deepExtend (request, params); + // why the hassle? urlencode in python is kinda broken for nested dicts. + // E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}" + // Bitmex doesn't like that. Hence resorting to this hack. + if ('filter' in request) { + request['filter'] = this.json (request['filter']); + } + const response = await this.privateGetOrder (request); + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'filter': { + 'open': true, + }, + }; + return await this.fetchOrders (symbol, since, limit, this.deepExtend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // Bitmex barfs if you set 'open': false in the filter... + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['startTime'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['count'] = limit; + } + request = this.deepExtend (request, params); + // why the hassle? urlencode in python is kinda broken for nested dicts. + // E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}" + // Bitmex doesn't like that. Hence resorting to this hack. + if ('filter' in request) { + request['filter'] = this.json (request['filter']); + } + const response = await this.privateGetExecutionTradeHistory (request); + // + // [ + // { + // "execID": "string", + // "orderID": "string", + // "clOrdID": "string", + // "clOrdLinkID": "string", + // "account": 0, + // "symbol": "string", + // "side": "string", + // "lastQty": 0, + // "lastPx": 0, + // "underlyingLastPx": 0, + // "lastMkt": "string", + // "lastLiquidityInd": "string", + // "simpleOrderQty": 0, + // "orderQty": 0, + // "price": 0, + // "displayQty": 0, + // "stopPx": 0, + // "pegOffsetValue": 0, + // "pegPriceType": "string", + // "currency": "string", + // "settlCurrency": "string", + // "execType": "string", + // "ordType": "string", + // "timeInForce": "string", + // "execInst": "string", + // "contingencyType": "string", + // "exDestination": "string", + // "ordStatus": "string", + // "triggered": "string", + // "workingIndicator": true, + // "ordRejReason": "string", + // "simpleLeavesQty": 0, + // "leavesQty": 0, + // "simpleCumQty": 0, + // "cumQty": 0, + // "avgPx": 0, + // "commission": 0, + // "tradePublishIndicator": "string", + // "multiLegReportingType": "string", + // "text": "string", + // "trdMatchID": "string", + // "execCost": 0, + // "execComm": 0, + // "homeNotional": 0, + // "foreignNotional": 0, + // "transactTime": "2019-03-05T12:47:02.762Z", + // "timestamp": "2019-03-05T12:47:02.762Z" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseLedgerEntryType (type) { + const types = { + 'Withdrawal': 'transaction', + 'RealisedPNL': 'margin', + 'UnrealisedPNL': 'margin', + 'Deposit': 'transaction', + 'Transfer': 'transfer', + 'AffiliatePayout': 'referral', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // transactID: "69573da3-7744-5467-3207-89fd6efe7a47", + // account: 24321, + // currency: "XBt", + // transactType: "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ... + // amount: -1000000, + // fee: 300000, + // transactStatus: "Completed", // "Canceled", ... + // address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9", + // tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA", + // text: "", + // transactTime: "2017-03-21T20:05:14.388Z", + // walletBalance: 0, // balance after + // marginBalance: null, + // timestamp: "2017-03-22T13:09:23.514Z" + // } + // + // ButMEX returns the unrealized pnl from the wallet history endpoint. + // The unrealized pnl transaction has an empty timestamp. + // It is not related to historical pnl it has status set to "Pending". + // Therefore it's not a part of the history at all. + // https://github.com/ccxt/ccxt/issues/6047 + // + // { + // "transactID":"00000000-0000-0000-0000-000000000000", + // "account":121210, + // "currency":"XBt", + // "transactType":"UnrealisedPNL", + // "amount":-5508, + // "fee":0, + // "transactStatus":"Pending", + // "address":"XBTUSD", + // "tx":"", + // "text":"", + // "transactTime":null, # ←---------------------------- null + // "walletBalance":139198767, + // "marginBalance":139193259, + // "timestamp":null # ←---------------------------- null + // } + // + const id = this.safeString (item, 'transactID'); + const account = this.safeString (item, 'account'); + const referenceId = this.safeString (item, 'tx'); + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'transactType')); + const currencyId = this.safeString (item, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + let amount = this.safeFloat (item, 'amount'); + if (amount !== undefined) { + amount = amount / 100000000; + } + let timestamp = this.parse8601 (this.safeString (item, 'transactTime')); + if (timestamp === undefined) { + // https://github.com/ccxt/ccxt/issues/6047 + // set the timestamp to zero, 1970 Jan 1 00:00:00 + // for unrealized pnl and other transactions without a timestamp + timestamp = 0; // see comments above + } + let feeCost = this.safeFloat (item, 'fee', 0); + if (feeCost !== undefined) { + feeCost = feeCost / 100000000; + } + const fee = { + 'cost': feeCost, + 'currency': code, + }; + let after = this.safeFloat (item, 'walletBalance'); + if (after !== undefined) { + after = after / 100000000; + } + const before = this.sum (after, -amount); + let direction = undefined; + if (amount < 0) { + direction = 'out'; + amount = Math.abs (amount); + } else { + direction = 'in'; + } + const status = this.parseTransactionStatus (this.safeString (item, 'transactStatus')); + return { + 'id': id, + 'info': item, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = { + // 'start': 123, + }; + // + // if (since !== undefined) { + // // date-based pagination not supported + // } + // + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetUserWalletHistory (this.extend (request, params)); + // + // [ + // { + // transactID: "69573da3-7744-5467-3207-89fd6efe7a47", + // account: 24321, + // currency: "XBt", + // transactType: "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ... + // amount: -1000000, + // fee: 300000, + // transactStatus: "Completed", // "Canceled", ... + // address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9", + // tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA", + // text: "", + // transactTime: "2017-03-21T20:05:14.388Z", + // walletBalance: 0, // balance after + // marginBalance: null, + // timestamp: "2017-03-22T13:09:23.514Z" + // } + // ] + // + return this.parseLedger (response, currency, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'start': 123, + }; + // + // if (since !== undefined) { + // // date-based pagination not supported + // } + // + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetUserWalletHistory (this.extend (request, params)); + const transactions = this.filterByArray (response, 'transactType', [ 'Withdrawal', 'Deposit' ], false); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (transactions, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'Canceled': 'canceled', + 'Completed': 'ok', + 'Pending': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // 'transactID': 'ffe699c2-95ee-4c13-91f9-0faf41daec25', + // 'account': 123456, + // 'currency': 'XBt', + // 'transactType': 'Withdrawal', + // 'amount': -100100000, + // 'fee': 100000, + // 'transactStatus': 'Completed', + // 'address': '385cR5DM96n1HvBDMzLHPYcw89fZAXULJP', + // 'tx': '3BMEXabcdefghijklmnopqrstuvwxyz123', + // 'text': '', + // 'transactTime': '2019-01-02T01:00:00.000Z', + // 'walletBalance': 99900000, + // 'marginBalance': None, + // 'timestamp': '2019-01-02T13:00:00.000Z' + // } + // + const id = this.safeString (transaction, 'transactID'); + // For deposits, transactTime == timestamp + // For withdrawals, transactTime is submission, timestamp is processed + const transactTime = this.parse8601 (this.safeString (transaction, 'transactTime')); + const timestamp = this.parse8601 (this.safeString (transaction, 'timestamp')); + const type = this.safeStringLower (transaction, 'transactType'); + // Deposits have no from address or to address, withdrawals have both + let address = undefined; + let addressFrom = undefined; + let addressTo = undefined; + if (type === 'withdrawal') { + address = this.safeString (transaction, 'address'); + addressFrom = this.safeString (transaction, 'tx'); + addressTo = address; + } + let amount = this.safeInteger (transaction, 'amount'); + if (amount !== undefined) { + amount = Math.abs (amount) / 10000000; + } + let feeCost = this.safeInteger (transaction, 'fee'); + if (feeCost !== undefined) { + feeCost = feeCost / 10000000; + } + const fee = { + 'cost': feeCost, + 'currency': 'BTC', + }; + let status = this.safeString (transaction, 'transactStatus'); + if (status !== undefined) { + status = this.parseTransactionStatus (status); + } + return { + 'info': transaction, + 'id': id, + 'txid': undefined, + 'timestamp': transactTime, + 'datetime': this.iso8601 (transactTime), + 'addressFrom': addressFrom, + 'address': address, + 'addressTo': addressTo, + 'tagFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'type': type, + 'amount': amount, + // BTC is the only currency on Bitmex + 'currency': 'BTC', + 'status': status, + 'updated': timestamp, + 'comment': undefined, + 'fee': fee, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (!market['active']) { + throw new ExchangeError (this.id + ': symbol ' + symbol + ' is delisted'); + } + const tickers = await this.fetchTickers ([ symbol ], params); + const ticker = this.safeValue (tickers, symbol); + if (ticker === undefined) { + throw new ExchangeError (this.id + ' ticker symbol ' + symbol + ' not found'); + } + return ticker; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetInstrumentActiveAndIndices (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = this.safeString (ticker, 'symbol'); + if (symbol !== undefined) { + result[symbol] = ticker; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // + // { symbol: "ETHH19", + // rootSymbol: "ETH", + // state: "Open", + // typ: "FFCCSX", + // listing: "2018-12-17T04:00:00.000Z", + // front: "2019-02-22T12:00:00.000Z", + // expiry: "2019-03-29T12:00:00.000Z", + // settle: "2019-03-29T12:00:00.000Z", + // relistInterval: null, + // inverseLeg: "", + // sellLeg: "", + // buyLeg: "", + // optionStrikePcnt: null, + // optionStrikeRound: null, + // optionStrikePrice: null, + // optionMultiplier: null, + // positionCurrency: "ETH", + // underlying: "ETH", + // quoteCurrency: "XBT", + // underlyingSymbol: "ETHXBT=", + // reference: "BMEX", + // referenceSymbol: ".BETHXBT30M", + // calcInterval: null, + // publishInterval: null, + // publishTime: null, + // maxOrderQty: 100000000, + // maxPrice: 10, + // lotSize: 1, + // tickSize: 0.00001, + // multiplier: 100000000, + // settlCurrency: "XBt", + // underlyingToPositionMultiplier: 1, + // underlyingToSettleMultiplier: null, + // quoteToSettleMultiplier: 100000000, + // isQuanto: false, + // isInverse: false, + // initMargin: 0.02, + // maintMargin: 0.01, + // riskLimit: 5000000000, + // riskStep: 5000000000, + // limit: null, + // capped: false, + // taxed: true, + // deleverage: true, + // makerFee: -0.0005, + // takerFee: 0.0025, + // settlementFee: 0, + // insuranceFee: 0, + // fundingBaseSymbol: "", + // fundingQuoteSymbol: "", + // fundingPremiumSymbol: "", + // fundingTimestamp: null, + // fundingInterval: null, + // fundingRate: null, + // indicativeFundingRate: null, + // rebalanceTimestamp: null, + // rebalanceInterval: null, + // openingTimestamp: "2019-02-13T08:00:00.000Z", + // closingTimestamp: "2019-02-13T09:00:00.000Z", + // sessionInterval: "2000-01-01T01:00:00.000Z", + // prevClosePrice: 0.03347, + // limitDownPrice: null, + // limitUpPrice: null, + // bankruptLimitDownPrice: null, + // bankruptLimitUpPrice: null, + // prevTotalVolume: 1386531, + // totalVolume: 1387062, + // volume: 531, + // volume24h: 17118, + // prevTotalTurnover: 4741294246000, + // totalTurnover: 4743103466000, + // turnover: 1809220000, + // turnover24h: 57919845000, + // homeNotional24h: 17118, + // foreignNotional24h: 579.19845, + // prevPrice24h: 0.03349, + // vwap: 0.03383564, + // highPrice: 0.03458, + // lowPrice: 0.03329, + // lastPrice: 0.03406, + // lastPriceProtected: 0.03406, + // lastTickDirection: "ZeroMinusTick", + // lastChangePcnt: 0.017, + // bidPrice: 0.03406, + // midPrice: 0.034065, + // askPrice: 0.03407, + // impactBidPrice: 0.03406, + // impactMidPrice: 0.034065, + // impactAskPrice: 0.03407, + // hasLiquidity: true, + // openInterest: 83679, + // openValue: 285010674000, + // fairMethod: "ImpactMidPrice", + // fairBasisRate: 0, + // fairBasis: 0, + // fairPrice: 0.03406, + // markMethod: "FairPrice", + // markPrice: 0.03406, + // indicativeTaxRate: 0, + // indicativeSettlePrice: 0.03406, + // optionUnderlyingPrice: null, + // settledPrice: null, + // timestamp: "2019-02-13T08:40:30.000Z", + // } + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'symbol'); + market = this.safeValue (this.markets_by_id, marketId, market); + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const open = this.safeFloat (ticker, 'prevPrice24h'); + const last = this.safeFloat (ticker, 'lastPrice'); + let change = undefined; + let percentage = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + if (open > 0) { + percentage = change / open * 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': undefined, + 'vwap': this.safeFloat (ticker, 'vwap'), + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': this.sum (open, last) / 2, + 'baseVolume': this.safeFloat (ticker, 'homeNotional24h'), + 'quoteVolume': this.safeFloat (ticker, 'foreignNotional24h'), + 'info': ticker, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "timestamp":"2015-09-25T13:38:00.000Z", + // "symbol":"XBTUSD", + // "open":237.45, + // "high":237.45, + // "low":237.45, + // "close":237.45, + // "trades":0, + // "volume":0, + // "vwap":null, + // "lastSize":null, + // "turnover":0, + // "homeNotional":0, + // "foreignNotional":0 + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // send JSON key/value pairs, such as {"key": "value"} + // filter by individual fields and do advanced queries on timestamps + // let filter = { 'key': 'value' }; + // send a bare series (e.g. XBU) to nearest expiring contract in that series + // you can also send a timeframe, e.g. XBU:monthly + // timeframes: daily, weekly, monthly, quarterly, and biquarterly + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'binSize': this.timeframes[timeframe], + 'partial': true, // true == include yet-incomplete current bins + // 'filter': filter, // filter by individual fields and do advanced queries + // 'columns': [], // will return all columns if omitted + // 'start': 0, // starting point for results (wtf?) + // 'reverse': false, // true == newest first + // 'endTime': '', // ending date filter for results + }; + if (limit !== undefined) { + request['count'] = limit; // default 100, max 500 + } + const duration = this.parseTimeframe (timeframe) * 1000; + const fetchOHLCVOpenTimestamp = this.safeValue (this.options, 'fetchOHLCVOpenTimestamp', true); + // if since is not set, they will return candles starting from 2017-01-01 + if (since !== undefined) { + let timestamp = since; + if (fetchOHLCVOpenTimestamp) { + timestamp = this.sum (timestamp, duration); + } + const ymdhms = this.ymdhms (timestamp); + request['startTime'] = ymdhms; // starting date filter for results + } else { + request['reverse'] = true; + } + const response = await this.publicGetTradeBucketed (this.extend (request, params)); + // + // [ + // {"timestamp":"2015-09-25T13:38:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}, + // {"timestamp":"2015-09-25T13:39:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}, + // {"timestamp":"2015-09-25T13:40:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0} + // ] + // + const result = this.parseOHLCVs (response, market, timeframe, since, limit); + if (fetchOHLCVOpenTimestamp) { + // bitmex returns the candle's close timestamp - https://github.com/ccxt/ccxt/issues/4446 + // we can emulate the open timestamp by shifting all the timestamps one place + // so the previous close becomes the current open, and we drop the first candle + for (let i = 0; i < result.length; i++) { + result[i][0] = result[i][0] - duration; + } + } + return result; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // timestamp: '2018-08-28T00:00:02.735Z', + // symbol: 'XBTUSD', + // side: 'Buy', + // size: 2000, + // price: 6906.5, + // tickDirection: 'PlusTick', + // trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8', + // grossValue: 28958000, + // homeNotional: 0.28958, + // foreignNotional: 2000 + // } + // + // fetchMyTrades (private) + // + // { + // "execID": "string", + // "orderID": "string", + // "clOrdID": "string", + // "clOrdLinkID": "string", + // "account": 0, + // "symbol": "string", + // "side": "string", + // "lastQty": 0, + // "lastPx": 0, + // "underlyingLastPx": 0, + // "lastMkt": "string", + // "lastLiquidityInd": "string", + // "simpleOrderQty": 0, + // "orderQty": 0, + // "price": 0, + // "displayQty": 0, + // "stopPx": 0, + // "pegOffsetValue": 0, + // "pegPriceType": "string", + // "currency": "string", + // "settlCurrency": "string", + // "execType": "string", + // "ordType": "string", + // "timeInForce": "string", + // "execInst": "string", + // "contingencyType": "string", + // "exDestination": "string", + // "ordStatus": "string", + // "triggered": "string", + // "workingIndicator": true, + // "ordRejReason": "string", + // "simpleLeavesQty": 0, + // "leavesQty": 0, + // "simpleCumQty": 0, + // "cumQty": 0, + // "avgPx": 0, + // "commission": 0, + // "tradePublishIndicator": "string", + // "multiLegReportingType": "string", + // "text": "string", + // "trdMatchID": "string", + // "execCost": 0, + // "execComm": 0, + // "homeNotional": 0, + // "foreignNotional": 0, + // "transactTime": "2019-03-05T12:47:02.762Z", + // "timestamp": "2019-03-05T12:47:02.762Z" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const price = this.safeFloat2 (trade, 'avgPx', 'price'); + const amount = this.safeFloat2 (trade, 'size', 'lastQty'); + const id = this.safeString (trade, 'trdMatchID'); + const order = this.safeString (trade, 'orderID'); + const side = this.safeStringLower (trade, 'side'); + // price * amount doesn't work for all symbols (e.g. XBT, ETH) + let cost = this.safeFloat (trade, 'execCost'); + if (cost !== undefined) { + cost = Math.abs (cost) / 100000000; + } + let fee = undefined; + if ('execComm' in trade) { + let feeCost = this.safeFloat (trade, 'execComm'); + feeCost = feeCost / 100000000; + const currencyId = this.safeString (trade, 'settlCurrency'); + const feeCurrency = this.safeCurrencyCode (currencyId); + const feeRate = this.safeFloat (trade, 'commission'); + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }; + } + let takerOrMaker = undefined; + if (fee !== undefined) { + takerOrMaker = (fee['cost'] < 0) ? 'maker' : 'taker'; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const type = this.safeStringLower (trade, 'ordType'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': order, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'cost': cost, + 'amount': amount, + 'fee': fee, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'DoneForDay': 'open', + 'Canceled': 'canceled', + 'PendingCancel': 'open', + 'PendingNew': 'open', + 'Rejected': 'rejected', + 'Expired': 'expired', + 'Stopped': 'open', + 'Untriggered': 'open', + 'Triggered': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'Day': 'Day', + 'GoodTillCancel': 'GTC', + 'ImmediateOrCancel': 'IOC', + 'FillOrKill': 'FOK', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'transactTime')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'orderQty'); + const filled = this.safeFloat (order, 'cumQty', 0.0); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = Math.max (amount - filled, 0.0); + } + } + const average = this.safeFloat (order, 'avgPx'); + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = average * filled; + } else if (price !== undefined) { + cost = price * filled; + } + } + const id = this.safeString (order, 'orderID'); + const type = this.safeStringLower (order, 'ordType'); + const side = this.safeStringLower (order, 'side'); + const clientOrderId = this.safeString (order, 'clOrdID'); + const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); + const stopPrice = this.safeFloat (order, 'stopPx'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startTime'] = this.iso8601 (since); + } else { + // by default reverse=false, i.e. trades are fetched since the time of market inception (year 2015 for XBTUSD) + request['reverse'] = true; + } + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.publicGetTrade (this.extend (request, params)); + // + // [ + // { + // timestamp: '2018-08-28T00:00:02.735Z', + // symbol: 'XBTUSD', + // side: 'Buy', + // size: 2000, + // price: 6906.5, + // tickDirection: 'PlusTick', + // trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8', + // grossValue: 28958000, + // homeNotional: 0.28958, + // foreignNotional: 2000 + // }, + // { + // timestamp: '2018-08-28T00:00:03.778Z', + // symbol: 'XBTUSD', + // side: 'Sell', + // size: 1000, + // price: 6906, + // tickDirection: 'MinusTick', + // trdMatchID: '0d4f1682-5270-a800-569b-4a0eb92db97c', + // grossValue: 14480000, + // homeNotional: 0.1448, + // foreignNotional: 1000 + // }, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderType = this.capitalize (type); + const request = { + 'symbol': market['id'], + 'side': this.capitalize (side), + 'orderQty': amount, + 'ordType': orderType, + }; + if (price !== undefined) { + if (orderType === 'Stop') { + const stopPrice = this.safeFloat2 (params, 'stopPx', 'stopPrice'); + request['stopPx'] = stopPrice; + params = this.omit (params, [ 'stopPx', 'stopPrice' ]); + } else { + request['price'] = price; + } + } + const clientOrderId = this.safeString2 (params, 'clOrdID', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + params = this.omit (params, [ 'clOrdID', 'clientOrderId' ]); + } + const response = await this.privatePostOrder (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const origClOrdID = this.safeString2 (params, 'origClOrdID', 'clientOrderId'); + if (origClOrdID !== undefined) { + request['origClOrdID'] = origClOrdID; + const clientOrderId = this.safeString (params, 'clOrdID', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + } + params = this.omit (params, [ 'origClOrdID', 'clOrdID', 'clientOrderId' ]); + } else { + request['orderID'] = id; + } + if (amount !== undefined) { + request['orderQty'] = amount; + } + if (price !== undefined) { + request['price'] = price; + } + const response = await this.privatePutOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // https://github.com/ccxt/ccxt/issues/6507 + const clientOrderId = this.safeString2 (params, 'clOrdID', 'clientOrderId'); + const request = {}; + if (clientOrderId === undefined) { + request['orderID'] = id; + } else { + request['clOrdID'] = clientOrderId; + params = this.omit (params, [ 'clOrdID', 'clientOrderId' ]); + } + const response = await this.privateDeleteOrder (this.extend (request, params)); + const order = this.safeValue (response, 0, {}); + const error = this.safeString (order, 'error'); + if (error !== undefined) { + if (error.indexOf ('Unable to cancel order due to existing state') >= 0) { + throw new OrderNotFound (this.id + ' cancelOrder() failed: ' + error); + } + } + return this.parseOrder (order); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateDeleteOrderAll (this.extend (request, params)); + // + // [ + // { + // "orderID": "string", + // "clOrdID": "string", + // "clOrdLinkID": "string", + // "account": 0, + // "symbol": "string", + // "side": "string", + // "simpleOrderQty": 0, + // "orderQty": 0, + // "price": 0, + // "displayQty": 0, + // "stopPx": 0, + // "pegOffsetValue": 0, + // "pegPriceType": "string", + // "currency": "string", + // "settlCurrency": "string", + // "ordType": "string", + // "timeInForce": "string", + // "execInst": "string", + // "contingencyType": "string", + // "exDestination": "string", + // "ordStatus": "string", + // "triggered": "string", + // "workingIndicator": true, + // "ordRejReason": "string", + // "simpleLeavesQty": 0, + // "leavesQty": 0, + // "simpleCumQty": 0, + // "cumQty": 0, + // "avgPx": 0, + // "multiLegReportingType": "string", + // "text": "string", + // "transactTime": "2020-06-01T09:36:35.290Z", + // "timestamp": "2020-06-01T09:36:35.290Z" + // } + // ] + // + return this.parseOrders (response, market); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetPosition (params); + // [ + // { + // "account": 0, + // "symbol": "string", + // "currency": "string", + // "underlying": "string", + // "quoteCurrency": "string", + // "commission": 0, + // "initMarginReq": 0, + // "maintMarginReq": 0, + // "riskLimit": 0, + // "leverage": 0, + // "crossMargin": true, + // "deleveragePercentile": 0, + // "rebalancedPnl": 0, + // "prevRealisedPnl": 0, + // "prevUnrealisedPnl": 0, + // "prevClosePrice": 0, + // "openingTimestamp": "2020-11-09T06:53:59.892Z", + // "openingQty": 0, + // "openingCost": 0, + // "openingComm": 0, + // "openOrderBuyQty": 0, + // "openOrderBuyCost": 0, + // "openOrderBuyPremium": 0, + // "openOrderSellQty": 0, + // "openOrderSellCost": 0, + // "openOrderSellPremium": 0, + // "execBuyQty": 0, + // "execBuyCost": 0, + // "execSellQty": 0, + // "execSellCost": 0, + // "execQty": 0, + // "execCost": 0, + // "execComm": 0, + // "currentTimestamp": "2020-11-09T06:53:59.893Z", + // "currentQty": 0, + // "currentCost": 0, + // "currentComm": 0, + // "realisedCost": 0, + // "unrealisedCost": 0, + // "grossOpenCost": 0, + // "grossOpenPremium": 0, + // "grossExecCost": 0, + // "isOpen": true, + // "markPrice": 0, + // "markValue": 0, + // "riskValue": 0, + // "homeNotional": 0, + // "foreignNotional": 0, + // "posState": "string", + // "posCost": 0, + // "posCost2": 0, + // "posCross": 0, + // "posInit": 0, + // "posComm": 0, + // "posLoss": 0, + // "posMargin": 0, + // "posMaint": 0, + // "posAllowance": 0, + // "taxableMargin": 0, + // "initMargin": 0, + // "maintMargin": 0, + // "sessionMargin": 0, + // "targetExcessMargin": 0, + // "varMargin": 0, + // "realisedGrossPnl": 0, + // "realisedTax": 0, + // "realisedPnl": 0, + // "unrealisedGrossPnl": 0, + // "longBankrupt": 0, + // "shortBankrupt": 0, + // "taxBase": 0, + // "indicativeTaxRate": 0, + // "indicativeTax": 0, + // "unrealisedTax": 0, + // "unrealisedPnl": 0, + // "unrealisedPnlPcnt": 0, + // "unrealisedRoePcnt": 0, + // "simpleQty": 0, + // "simpleCost": 0, + // "simpleValue": 0, + // "simplePnl": 0, + // "simplePnlPcnt": 0, + // "avgCostPrice": 0, + // "avgEntryPrice": 0, + // "breakEvenPrice": 0, + // "marginCallPrice": 0, + // "liquidationPrice": 0, + // "bankruptPrice": 0, + // "timestamp": "2020-11-09T06:53:59.894Z", + // "lastPrice": 0, + // "lastValue": 0 + // } + // ] + // + // todo unify parsePosition/parsePositions + return response; + } + + isFiat (currency) { + if (currency === 'EUR') { + return true; + } + if (currency === 'PLN') { + return true; + } + return false; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + // let currency = this.currency (code); + if (code !== 'BTC') { + throw new ExchangeError (this.id + ' supoprts BTC withdrawals only, other currencies coming soon...'); + } + const request = { + 'currency': 'XBt', // temporarily + 'amount': amount, + 'address': address, + // 'otpToken': '123456', // requires if two-factor auth (OTP) is enabled + // 'fee': 0.001, // bitcoin network fee + }; + const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'transactID'), + }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code === 429) { + throw new DDoSProtection (this.id + ' ' + body); + } + if (code >= 400) { + const error = this.safeValue (response, 'error', {}); + const message = this.safeString (error, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + if (code === 400) { + throw new BadRequest (feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let query = '/api/' + this.version + '/' + path; + if (method === 'GET') { + if (Object.keys (params).length) { + query += '?' + this.urlencode (params); + } + } else { + const format = this.safeString (params, '_format'); + if (format !== undefined) { + query += '?' + this.urlencode ({ '_format': format }); + params = this.omit (params, '_format'); + } + } + const url = this.urls['api'][api] + query; + if (this.apiKey && this.secret) { + let auth = method + query; + let expires = this.safeInteger (this.options, 'api-expires'); + headers = { + 'Content-Type': 'application/json', + 'api-key': this.apiKey, + }; + expires = this.sum (this.seconds (), expires); + expires = expires.toString (); + auth += expires; + headers['api-expires'] = expires; + if (method === 'POST' || method === 'PUT' || method === 'DELETE') { + if (Object.keys (params).length) { + body = this.json (params); + auth += body; + } + } + headers['api-signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitpanda.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitpanda.js new file mode 100644 index 000000000..41f855168 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitpanda.js @@ -0,0 +1,1811 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, PermissionDenied, BadRequest, ArgumentsRequired, OrderNotFound, InsufficientFunds, ExchangeNotAvailable, DDoSProtection, InvalidAddress, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitpanda extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitpanda', + 'name': 'Bitpanda Pro', + 'countries': [ 'AT' ], // Austria + 'rateLimit': 300, + 'version': 'v1', + // new metainfo interface + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'cancelOrders': true, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDeposits': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingFees': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1/MINUTES', + '5m': '5/MINUTES', + '15m': '15/MINUTES', + '30m': '30/MINUTES', + '1h': '1/HOURS', + '4h': '4/HOURS', + '1d': '1/DAYS', + '1w': '1/WEEKS', + '1M': '1/MONTHS', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87591171-9a377d80-c6f0-11ea-94ac-97a126eac3bc.jpg', + 'api': { + 'public': 'https://api.exchange.bitpanda.com/public', + 'private': 'https://api.exchange.bitpanda.com/public', + }, + 'www': 'https://www.bitpanda.com/en/pro', + 'doc': [ + 'https://developers.bitpanda.com/exchange/', + ], + 'fees': 'https://www.bitpanda.com/en/pro/fees', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'candlesticks/{instrument_code}', + 'fees', + 'instruments', + 'order-book/{instrument_code}', + 'market-ticker', + 'market-ticker/{instrument_code}', + 'price-ticks/{instrument_code}', + 'time', + ], + }, + 'private': { + 'get': [ + 'account/balances', + 'account/deposit/crypto/{currency_code}', + 'account/deposit/fiat/EUR', + 'account/deposits', + 'account/deposits/bitpanda', + 'account/withdrawals', + 'account/withdrawals/bitpanda', + 'account/fees', + 'account/orders', + 'account/orders/{order_id}', + 'account/orders/{order_id}/trades', + 'account/trades', + 'account/trades/{trade_id}', + 'account/trading-volume', + ], + 'post': [ + 'account/deposit/crypto', + 'account/withdraw/crypto', + 'account/withdraw/fiat', + 'account/fees', + 'account/orders', + ], + 'delete': [ + 'account/orders', + 'account/orders/{order_id}', + 'account/orders/client/{client_id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.15 / 100, + 'maker': 0.10 / 100, + 'tiers': [ + // volume in BTC + { + 'taker': [ + [0, 0.15 / 100], + [100, 0.13 / 100], + [250, 0.13 / 100], + [1000, 0.1 / 100], + [5000, 0.09 / 100], + [10000, 0.075 / 100], + [20000, 0.065 / 100], + ], + 'maker': [ + [0, 0.1 / 100], + [100, 0.1 / 100], + [250, 0.09 / 100], + [1000, 0.075 / 100], + [5000, 0.06 / 100], + [10000, 0.05 / 100], + [20000, 0.05 / 100], + ], + }, + ], + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + }, + 'exceptions': { + 'exact': { + 'INVALID_CLIENT_UUID': InvalidOrder, + 'ORDER_NOT_FOUND': OrderNotFound, + 'ONLY_ONE_ERC20_ADDRESS_ALLOWED': InvalidAddress, + 'DEPOSIT_ADDRESS_NOT_USED': InvalidAddress, + 'INVALID_CREDENTIALS': AuthenticationError, + 'MISSING_CREDENTIALS': AuthenticationError, + 'INVALID_APIKEY': AuthenticationError, + 'INVALID_SCOPES': AuthenticationError, + 'INVALID_SUBJECT': AuthenticationError, + 'INVALID_ISSUER': AuthenticationError, + 'INVALID_AUDIENCE': AuthenticationError, + 'INVALID_DEVICE_ID': AuthenticationError, + 'INVALID_IP_RESTRICTION': AuthenticationError, + 'APIKEY_REVOKED': AuthenticationError, + 'APIKEY_EXPIRED': AuthenticationError, + 'SYNCHRONIZER_TOKEN_MISMATCH': AuthenticationError, + 'SESSION_EXPIRED': AuthenticationError, + 'INTERNAL_ERROR': AuthenticationError, + 'CLIENT_IP_BLOCKED': PermissionDenied, + 'MISSING_PERMISSION': PermissionDenied, + 'ILLEGAL_CHARS': BadRequest, + 'UNSUPPORTED_MEDIA_TYPE': BadRequest, + 'ACCOUNT_HISTORY_TIME_RANGE_TOO_BIG': BadRequest, + 'CANDLESTICKS_TIME_RANGE_TOO_BIG': BadRequest, + 'INVALID_INSTRUMENT_CODE': BadRequest, + 'INVALID_ORDER_TYPE': BadRequest, + 'INVALID_UNIT': BadRequest, + 'INVALID_PERIOD': BadRequest, + 'INVALID_TIME': BadRequest, + 'INVALID_DATE': BadRequest, + 'INVALID_CURRENCY': BadRequest, + 'INVALID_AMOUNT': BadRequest, + 'INVALID_PRICE': BadRequest, + 'INVALID_LIMIT': BadRequest, + 'INVALID_QUERY': BadRequest, + 'INVALID_CURSOR': BadRequest, + 'INVALID_ACCOUNT_ID': BadRequest, + 'INVALID_SIDE': InvalidOrder, + 'INVALID_ACCOUNT_HISTORY_FROM_TIME': BadRequest, + 'INVALID_ACCOUNT_HISTORY_MAX_PAGE_SIZE': BadRequest, + 'INVALID_ACCOUNT_HISTORY_TIME_PERIOD': BadRequest, + 'INVALID_ACCOUNT_HISTORY_TO_TIME': BadRequest, + 'INVALID_CANDLESTICKS_GRANULARITY': BadRequest, + 'INVALID_CANDLESTICKS_UNIT': BadRequest, + 'INVALID_ORDER_BOOK_DEPTH': BadRequest, + 'INVALID_ORDER_BOOK_LEVEL': BadRequest, + 'INVALID_PAGE_CURSOR': BadRequest, + 'INVALID_TIME_RANGE': BadRequest, + 'INVALID_TRADE_ID': BadRequest, + 'INVALID_UI_ACCOUNT_SETTINGS': BadRequest, + 'NEGATIVE_AMOUNT': InvalidOrder, + 'NEGATIVE_PRICE': InvalidOrder, + 'MIN_SIZE_NOT_SATISFIED': InvalidOrder, + 'BAD_AMOUNT_PRECISION': InvalidOrder, + 'BAD_PRICE_PRECISION': InvalidOrder, + 'BAD_TRIGGER_PRICE_PRECISION': InvalidOrder, + 'MAX_OPEN_ORDERS_EXCEEDED': BadRequest, + 'MISSING_PRICE': InvalidOrder, + 'MISSING_ORDER_TYPE': InvalidOrder, + 'MISSING_SIDE': InvalidOrder, + 'MISSING_CANDLESTICKS_PERIOD_PARAM': ArgumentsRequired, + 'MISSING_CANDLESTICKS_UNIT_PARAM': ArgumentsRequired, + 'MISSING_FROM_PARAM': ArgumentsRequired, + 'MISSING_INSTRUMENT_CODE': ArgumentsRequired, + 'MISSING_ORDER_ID': InvalidOrder, + 'MISSING_TO_PARAM': ArgumentsRequired, + 'MISSING_TRADE_ID': ArgumentsRequired, + 'INVALID_ORDER_ID': OrderNotFound, + 'NOT_FOUND': OrderNotFound, + 'INSUFFICIENT_LIQUIDITY': InsufficientFunds, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'NO_TRADING': ExchangeNotAvailable, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'GATEWAY_TIMEOUT': ExchangeNotAvailable, + 'RATELIMIT': DDoSProtection, + 'CF_RATELIMIT': DDoSProtection, + 'INTERNAL_SERVER_ERROR': ExchangeError, + }, + 'broad': { + }, + }, + 'commonCurrencies': { + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + }, + // exchange-specific options + 'options': { + 'fetchTradingFees': { + 'method': 'fetchPrivateTradingFees', // or 'fetchPublicTradingFees' + }, + 'fiat': [ 'EUR', 'CHF' ], + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // iso: '2020-07-10T05:17:26.716Z', + // epoch_millis: 1594358246716, + // } + // + return this.safeInteger (response, 'epoch_millis'); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // "code":"BEST", + // "precision":8 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'name': undefined, + 'info': currency, // the original payload + 'active': undefined, + 'fee': undefined, + 'precision': this.safeInteger (currency, 'precision'), + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInstruments (params); + // + // [ + // { + // state: 'ACTIVE', + // base: { code: 'ETH', precision: 8 }, + // quote: { code: 'CHF', precision: 2 }, + // amount_precision: 4, + // market_precision: 2, + // min_size: '10.0' + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const baseAsset = this.safeValue (market, 'base', {}); + const quoteAsset = this.safeValue (market, 'quote', {}); + const baseId = this.safeString (baseAsset, 'code'); + const quoteId = this.safeString (quoteAsset, 'code'); + const id = baseId + '_' + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amount_precision'), + 'price': this.safeInteger (market, 'market_precision'), + }; + const limits = { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'min_size'), + 'max': undefined, + }, + }; + const state = this.safeString (market, 'state'); + const active = (state === 'ACTIVE'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + async fetchTradingFees (params = {}) { + let method = this.safeString (params, 'method'); + params = this.omit (params, 'method'); + if (method === undefined) { + const options = this.safeValue (this.options, 'fetchTradingFees', {}); + method = this.safeString (options, 'method', 'fetchPrivateTradingFees'); + } + return await this[method] (params); + } + + async fetchPublicTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.publicGetFees (params); + // + // [ + // { + // "fee_group_id":"default", + // "display_text":"The standard fee plan.", + // "fee_tiers":[ + // {"volume":"0.0","fee_group_id":"default","maker_fee":"0.1","taker_fee":"0.15"}, + // {"volume":"100.0","fee_group_id":"default","maker_fee":"0.1","taker_fee":"0.13"}, + // {"volume":"250.0","fee_group_id":"default","maker_fee":"0.09","taker_fee":"0.13"}, + // {"volume":"1000.0","fee_group_id":"default","maker_fee":"0.075","taker_fee":"0.1"}, + // {"volume":"5000.0","fee_group_id":"default","maker_fee":"0.06","taker_fee":"0.09"}, + // {"volume":"10000.0","fee_group_id":"default","maker_fee":"0.05","taker_fee":"0.075"}, + // {"volume":"20000.0","fee_group_id":"default","maker_fee":"0.05","taker_fee":"0.065"} + // ], + // "fee_discount_rate":"25.0", + // "minimum_price_value":"0.12" + // } + // ] + // + const feeGroupsById = this.indexBy (response, 'fee_group_id'); + const feeGroupId = this.safeValue (this.options, 'fee_group_id', 'default'); + const feeGroup = this.safeValue (feeGroupsById, feeGroupId, {}); + const feeTiers = this.safeValue (feeGroup, 'fee_tiers'); + const result = {}; + for (let i = 0; i < this.symbols.length; i++) { + const symbol = this.symbols[i]; + const fee = { + 'info': feeGroup, + 'symbol': symbol, + 'maker': undefined, + 'taker': undefined, + 'percentage': true, + 'tierBased': true, + }; + const takerFees = []; + const makerFees = []; + for (let i = 0; i < feeTiers.length; i++) { + const tier = feeTiers[i]; + const volume = this.safeFloat (tier, 'volume'); + let taker = this.safeFloat (tier, 'taker_fee'); + let maker = this.safeFloat (tier, 'maker_fee'); + taker /= 100; + maker /= 100; + takerFees.push ([ volume, taker ]); + makerFees.push ([ volume, maker ]); + if (i === 0) { + fee['taker'] = taker; + fee['maker'] = maker; + } + } + const tiers = { + 'taker': takerFees, + 'maker': makerFees, + }; + fee['tiers'] = tiers; + result[symbol] = fee; + } + return result; + } + + async fetchPrivateTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountFees (params); + // + // { + // "account_id": "ed524d00-820a-11e9-8f1e-69602df16d85", + // "running_trading_volume": "0.0", + // "fee_group_id": "default", + // "collect_fees_in_best": false, + // "fee_discount_rate": "25.0", + // "minimum_price_value": "0.12", + // "fee_tiers": [ + // { "volume": "0.0", "fee_group_id": "default", "maker_fee": "0.1", "taker_fee": "0.1" }, + // { "volume": "100.0", "fee_group_id": "default", "maker_fee": "0.09", "taker_fee": "0.1" }, + // { "volume": "250.0", "fee_group_id": "default", "maker_fee": "0.08", "taker_fee": "0.1" }, + // { "volume": "1000.0", "fee_group_id": "default", "maker_fee": "0.07", "taker_fee": "0.09" }, + // { "volume": "5000.0", "fee_group_id": "default", "maker_fee": "0.06", "taker_fee": "0.08" }, + // { "volume": "10000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.07" }, + // { "volume": "20000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.06" }, + // { "volume": "50000.0", "fee_group_id": "default", "maker_fee": "0.05", "taker_fee": "0.05" } + // ], + // "active_fee_tier": { "volume": "0.0", "fee_group_id": "default", "maker_fee": "0.1", "taker_fee": "0.1" } + // } + // + const activeFeeTier = this.safeValue (response, 'active_fee_tier', {}); + const result = { + 'info': response, + 'maker': this.safeFloat (activeFeeTier, 'maker_fee'), + 'taker': this.safeFloat (activeFeeTier, 'taker_fee'), + 'percentage': true, + 'tierBased': true, + }; + const feeTiers = this.safeValue (response, 'fee_tiers'); + const takerFees = []; + const makerFees = []; + for (let i = 0; i < feeTiers.length; i++) { + const tier = feeTiers[i]; + const volume = this.safeFloat (tier, 'volume'); + let taker = this.safeFloat (tier, 'taker_fee'); + let maker = this.safeFloat (tier, 'maker_fee'); + taker /= 100; + maker /= 100; + takerFees.push ([ volume, taker ]); + makerFees.push ([ volume, maker ]); + } + const tiers = { + 'taker': takerFees, + 'maker': makerFees, + }; + result['tiers'] = tiers; + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "instrument_code":"BTC_EUR", + // "sequence":602562, + // "time":"2020-07-10T06:27:34.951Z", + // "state":"ACTIVE", + // "is_frozen":0, + // "quote_volume":"1695555.1783768", + // "base_volume":"205.67436", + // "last_price":"8143.91", + // "best_bid":"8143.71", + // "best_ask":"8156.9", + // "price_change":"-147.47", + // "price_change_percentage":"-1.78", + // "high":"8337.45", + // "low":"8110.0" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'time')); + const marketId = this.safeString (ticker, 'instrument_code'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat (ticker, 'last_price'); + const percentage = this.safeFloat (ticker, 'price_change_percentage'); + const change = this.safeFloat (ticker, 'price_change'); + let open = undefined; + let average = undefined; + if ((last !== undefined) && (change !== undefined)) { + open = last - change; + average = this.sum (last, open) / 2; + } + const baseVolume = this.safeFloat (ticker, 'base_volume'); + const quoteVolume = this.safeFloat (ticker, 'quote_volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_code': market['id'], + }; + const response = await this.publicGetMarketTickerInstrumentCode (this.extend (request, params)); + // + // { + // "instrument_code":"BTC_EUR", + // "sequence":602562, + // "time":"2020-07-10T06:27:34.951Z", + // "state":"ACTIVE", + // "is_frozen":0, + // "quote_volume":"1695555.1783768", + // "base_volume":"205.67436", + // "last_price":"8143.91", + // "best_bid":"8143.71", + // "best_ask":"8156.9", + // "price_change":"-147.47", + // "price_change_percentage":"-1.78", + // "high":"8337.45", + // "low":"8110.0" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketTicker (params); + // + // [ + // { + // "instrument_code":"BTC_EUR", + // "sequence":602562, + // "time":"2020-07-10T06:27:34.951Z", + // "state":"ACTIVE", + // "is_frozen":0, + // "quote_volume":"1695555.1783768", + // "base_volume":"205.67436", + // "last_price":"8143.91", + // "best_bid":"8143.71", + // "best_ask":"8156.9", + // "price_change":"-147.47", + // "price_change_percentage":"-1.78", + // "high":"8337.45", + // "low":"8110.0" + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'instrument_code': this.marketId (symbol), + // level 1 means only the best bid and ask + // level 2 is a compiled order book up to market precision + // level 3 is a full orderbook + // if you wish to get regular updates about orderbooks please use the Websocket channel + // heavy usage of this endpoint may result in limited access according to rate limits rules + // 'level': 3, // default + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetOrderBookInstrumentCode (this.extend (request, params)); + // + // level 1 + // + // { + // "instrument_code":"BTC_EUR", + // "time":"2020-07-10T07:39:06.343Z", + // "asks":{ + // "value":{ + // "price":"8145.29", + // "amount":"0.96538", + // "number_of_orders":1 + // } + // }, + // "bids":{ + // "value":{ + // "price":"8134.0", + // "amount":"1.5978", + // "number_of_orders":5 + // } + // } + // } + // + // level 2 + // + // { + // "instrument_code":"BTC_EUR","time":"2020-07-10T07:36:43.538Z", + // "asks":[ + // {"price":"8146.59","amount":"0.89691","number_of_orders":1}, + // {"price":"8146.89","amount":"1.92062","number_of_orders":1}, + // {"price":"8169.5","amount":"0.0663","number_of_orders":1}, + // ], + // "bids":[ + // {"price":"8143.49","amount":"0.01329","number_of_orders":1}, + // {"price":"8137.01","amount":"5.34748","number_of_orders":1}, + // {"price":"8137.0","amount":"2.0","number_of_orders":1}, + // ] + // } + // + // level 3 + // + // { + // "instrument_code":"BTC_EUR", + // "time":"2020-07-10T07:32:31.525Z", + // "bids":[ + // {"price":"8146.79","amount":"0.01537","order_id":"5d717da1-a8f4-422d-afcc-03cb6ab66825"}, + // {"price":"8139.32","amount":"3.66009","order_id":"d0715c68-f28d-4cf1-a450-d56cf650e11c"}, + // {"price":"8137.51","amount":"2.61049","order_id":"085fd6f4-e835-4ca5-9449-a8f165772e60"}, + // ], + // "asks":[ + // {"price":"8153.49","amount":"0.93384","order_id":"755d3aa3-42b5-46fa-903d-98f42e9ae6c4"}, + // {"price":"8153.79","amount":"1.80456","order_id":"62034cf3-b70d-45ff-b285-ba6307941e7c"}, + // {"price":"8167.9","amount":"0.0018","order_id":"036354e0-71cd-492f-94f2-01f7d4b66422"}, + // ] + // } + // + const timestamp = this.parse8601 (this.safeString (response, 'time')); + return this.parseOrderBook (response, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "instrument_code":"BTC_EUR", + // "granularity":{"unit":"HOURS","period":1}, + // "high":"9252.65", + // "low":"9115.27", + // "open":"9250.0", + // "close":"9132.35", + // "total_amount":"33.85924", + // "volume":"311958.9635744", + // "time":"2020-05-08T22:59:59.999Z", + // "last_sequence":461123 + // } + // + const granularity = this.safeValue (ohlcv, 'granularity'); + const unit = this.safeString (granularity, 'unit'); + const period = this.safeString (granularity, 'period'); + const units = { + 'MINUTES': 'm', + 'HOURS': 'h', + 'DAYS': 'd', + 'WEEKS': 'w', + 'MONTHS': 'M', + }; + const lowercaseUnit = this.safeString (units, unit); + const timeframe = period + lowercaseUnit; + const durationInSeconds = this.parseTimeframe (timeframe); + const duration = durationInSeconds * 1000; + const timestamp = this.parse8601 (this.safeString (ohlcv, 'time')); + const alignedTimestamp = duration * parseInt (timestamp / duration); + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const volumeField = this.safeString (options, 'volume', 'total_amount'); + return [ + alignedTimestamp, + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, volumeField), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const periodUnit = this.safeString (this.timeframes, timeframe); + const [ period, unit ] = periodUnit.split ('/'); + const durationInSeconds = this.parseTimeframe (timeframe); + const duration = durationInSeconds * 1000; + if (limit === undefined) { + limit = 1500; + } + const request = { + 'instrument_code': market['id'], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + 'period': period, + 'unit': unit, + }; + if (since === undefined) { + const now = this.milliseconds (); + request['to'] = this.iso8601 (now); + request['from'] = this.iso8601 (now - limit * duration); + } else { + request['from'] = this.iso8601 (since); + request['to'] = this.iso8601 (this.sum (since, limit * duration)); + } + const response = await this.publicGetCandlesticksInstrumentCode (this.extend (request, params)); + // + // [ + // {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9252.65","low":"9115.27","open":"9250.0","close":"9132.35","total_amount":"33.85924","volume":"311958.9635744","time":"2020-05-08T22:59:59.999Z","last_sequence":461123}, + // {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9162.49","low":"9040.0","open":"9132.53","close":"9083.69","total_amount":"26.19685","volume":"238553.7812365","time":"2020-05-08T23:59:59.999Z","last_sequence":461376}, + // {"instrument_code":"BTC_EUR","granularity":{"unit":"HOURS","period":1},"high":"9135.7","low":"9002.59","open":"9055.45","close":"9133.98","total_amount":"26.21919","volume":"238278.8724959","time":"2020-05-09T00:59:59.999Z","last_sequence":461521}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "instrument_code":"BTC_EUR", + // "price":"8137.28", + // "amount":"0.22269", + // "taker_side":"BUY", + // "volume":"1812.0908832", + // "time":"2020-07-10T14:44:32.299Z", + // "trade_timestamp":1594392272299, + // "sequence":603047 + // } + // + // fetchMyTrades, fetchOrder, fetchOpenOrders, fetchClosedOrders trades (private) + // + // { + // "fee": { + // "fee_amount": "0.0014", + // "fee_currency": "BTC", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664", + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "amount": "1.4", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "7341.4", + // "time": "2019-09-27T15:05:32.564Z", + // "sequence": 48670 + // } + // } + // + const feeInfo = this.safeValue (trade, 'fee', {}); + trade = this.safeValue (trade, 'trade', trade); + const timestamp = this.parse8601 (this.safeString (trade, 'time')); + const side = this.safeStringLower2 (trade, 'side', 'taker_side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = this.safeFloat (trade, 'volume'); + if ((cost === undefined) && (amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const marketId = this.safeString (trade, 'instrument_code'); + const symbol = this.safeSymbol (marketId, market, '_'); + const feeCost = this.safeFloat (feeInfo, 'fee_amount'); + let takerOrMaker = undefined; + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (feeInfo, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + const feeRate = this.safeFloat (feeInfo, 'fee_percentage'); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + takerOrMaker = this.safeStringLower (feeInfo, 'fee_type'); + } + return { + 'id': this.safeString2 (trade, 'trade_id', 'sequence'), + 'order': this.safeString (trade, 'order_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_code': market['id'], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + }; + if (since !== undefined) { + // returns price ticks for a specific market with an interval of maximum of 4 hours + // sorted by latest first + request['from'] = this.iso8601 (since); + request['to'] = this.iso8601 (this.sum (since, 14400000)); + } + const response = await this.publicGetPriceTicksInstrumentCode (this.extend (request, params)); + // + // [ + // { + // "instrument_code":"BTC_EUR", + // "price":"8137.28", + // "amount":"0.22269", + // "taker_side":"BUY", + // "volume":"1812.0908832", + // "time":"2020-07-10T14:44:32.299Z", + // "trade_timestamp":1594392272299, + // "sequence":603047 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountBalances (params); + // + // { + // "account_id":"4b95934f-55f1-460c-a525-bd5afc0cf071", + // "balances":[ + // { + // "account_id":"4b95934f-55f1-460c-a525-bd5afc0cf071", + // "currency_code":"BTC", + // "change":"10.0", + // "available":"10.0", + // "locked":"0.0", + // "sequence":142135994, + // "time":"2020-07-01T10:57:32.959Z" + // } + // ] + // } + // + const balances = this.safeValue (response, 'balances', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseDepositAddress (depositAddress, currency = undefined) { + let code = undefined; + if (currency !== undefined) { + code = currency['code']; + } + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'destination_tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostAccountDepositCrypto (this.extend (request, params)); + // + // { + // "address":"rBnNhk95FrdNisZtXcStzriFS8vEzz53DM", + // "destination_tag":"865690307", + // "enabled":true, + // "is_smart_contract":false + // } + // + return this.parseDepositAddress (response, currency); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_code': currency['id'], + }; + const response = await this.privateGetAccountDepositCryptoCurrencyCode (this.extend (request, params)); + // + // { + // "address":"rBnNhk95FrdNisZtXcStzriFS8vEzz53DM", + // "destination_tag":"865690307", + // "enabled":true, + // "is_smart_contract":false, + // "can_create_more":false + // } + // + return this.parseDepositAddress (response, currency); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency_code'] = currency['id']; + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a "to" iso8601 string param with the since argument is specified'); + } + request['from'] = this.iso8601 (since); + } + const response = await this.privateGetAccountDeposits (this.extend (request, params)); + // + // { + // "deposit_history": [ + // { + // "transaction_id": "e5342efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "100", + // "type": "CRYPTO", + // "funds_source": "INTERNAL", + // "time": "2020-04-22T09:57:47Z", + // "currency": "BTC", + // "fee_amount": "0.0", + // "fee_currency": "BTC" + // }, + // { + // "transaction_id": "79793d00-2899-4a4d-95b7-73ae6b31384f", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "time": "2020-05-05T11:22:07.925Z", + // "currency": "EUR", + // "funds_source": "EXTERNAL", + // "type": "FIAT", + // "amount": "50.0", + // "fee_amount": "0.01", + // "fee_currency": "EUR" + // } + // ], + // "max_page_size": 2, + // "cursor": "eyJhY2NvdW50X2lkIjp7InMiOiJlMzY5YWM4MC00NTc3LTExZTktYWUwOC05YmVkYzQ3OTBiODQiLCJzcyI6W10sIm5zIjpbXSwiYnMiOltdLCJtIjp7fSwibCI6W119LCJpdGVtX2tleSI6eyJzIjoiV0lUSERSQVdBTDo6MmFlMjYwY2ItOTk3MC00YmNiLTgxNmEtZGY4MDVmY2VhZTY1Iiwic3MiOltdLCJucyI6W10sImJzIjpbXSwibSI6e30sImwiOltdfSwiZ2xvYmFsX3dpdGhkcmF3YWxfaW5kZXhfaGFzaF9rZXkiOnsicyI6ImUzNjlhYzgwLTQ1NzctMTFlOS1hZTA4LTliZWRjNDc5MGI4NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX0sInRpbWVzdGFtcCI6eyJuIjoiMTU4ODA1ODc2Nzk0OCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX19" + // } + // + const depositHistory = this.safeValue (response, 'deposit_history', []); + return this.parseTransactions (depositHistory, currency, since, limit, { 'type': 'deposit' }); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency_code'] = currency['id']; + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a "to" iso8601 string param with the since argument is specified'); + } + request['from'] = this.iso8601 (since); + } + const response = await this.privateGetAccountWithdrawals (this.extend (request, params)); + // + // { + // "withdrawal_history": [ + // { + // "account_id": "e369ac80-4577-11e9-ae08-9bedc4790b84", + // "amount": "0.1", + // "currency": "BTC", + // "fee_amount": "0.00002", + // "fee_currency": "BTC", + // "funds_source": "EXTERNAL", + // "related_transaction_id": "e298341a-3855-405e-bce3-92db368a3157", + // "time": "2020-05-05T11:11:32.110Z", + // "transaction_id": "6693ff40-bb10-4dcf-ada7-3b287727c882", + // "type": "CRYPTO" + // }, + // { + // "account_id": "e369ac80-4577-11e9-ae08-9bedc4790b84", + // "amount": "0.1", + // "currency": "BTC", + // "fee_amount": "0.0", + // "fee_currency": "BTC", + // "funds_source": "INTERNAL", + // "time": "2020-05-05T10:29:53.464Z", + // "transaction_id": "ec9703b1-954b-4f76-adea-faac66eabc0b", + // "type": "CRYPTO" + // } + // ], + // "cursor": "eyJhY2NvdW50X2lkIjp7InMiOiJlMzY5YWM4MC00NTc3LTExZTktYWUwOC05YmVkYzQ3OTBiODQiLCJzcyI6W10sIm5zIjpbXSwiYnMiOltdLCJtIjp7fSwibCI6W119LCJpdGVtX2tleSI6eyJzIjoiV0lUSERSQVdBTDo6ZWM5NzAzYjEtOTU0Yi00Zjc2LWFkZWEtZmFhYzY2ZWFiYzBiIiwic3MiOltdLCJucyI6W10sImJzIjpbXSwibSI6e30sImwiOltdfSwiZ2xvYmFsX3dpdGhkcmF3YWxfaW5kZXhfaGFzaF9rZXkiOnsicyI6ImUzNjlhYzgwLTQ1NzctMTFlOS1hZTA4LTliZWRjNDc5MGI4NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX0sInRpbWVzdGFtcCI6eyJuIjoiMTU4ODY3NDU5MzQ2NCIsInNzIjpbXSwibnMiOltdLCJicyI6W10sIm0iOnt9LCJsIjpbXX19", + // "max_page_size": 2 + // } + // + const withdrawalHistory = this.safeValue (response, 'withdrawal_history', []); + return this.parseTransactions (withdrawalHistory, currency, since, limit, { 'type': 'withdrawal' }); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': code, + 'amount': this.currencyToPrecision (code, amount), + // 'payout_account_id': '66756a10-3e86-48f4-9678-b634c4b135b2', // fiat only + // 'recipient': { // crypto only + // 'address': address, + // // 'destination_tag': '', + // }, + }; + const options = this.safeValue (this.options, 'fiat', []); + const isFiat = this.inArray (code, options); + const method = isFiat ? 'privatePostAccountWithdrawFiat' : 'privatePostAccountWithdrawCrypto'; + if (isFiat) { + const payoutAccountId = this.safeString (params, 'payout_account_id'); + if (payoutAccountId === undefined) { + throw ArgumentsRequired (this.id + ' withdraw() requires a payout_account_id param for fiat ' + code + ' withdrawals'); + } + } else { + const recipient = { 'address': address }; + if (tag !== undefined) { + recipient['destination_tag'] = tag; + } + request['recipient'] = recipient; + } + const response = await this[method] (this.extend (request, params)); + // + // crypto + // + // { + // "amount": "1234.5678", + // "fee": "1234.5678", + // "recipient": "3NacQ7rzZdhfyAtfJ5a11k8jFPdcMP2Bq7", + // "destination_tag": "", + // "transaction_id": "d0f8529f-f832-4e6a-9dc5-b8d5797badb2" + // } + // + // fiat + // + // { + // "transaction_id": "54236cd0-4413-11e9-93fb-5fea7e5b5df6" + // } + // + return this.parseTransaction (response, currency); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchWithdrawals + // + // { + // "transaction_id": "C2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "type": "FIAT", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "1234.5678", + // "time": "2019-08-24T14:15:22Z", + // "funds_source": "INTERNAL", + // "currency": "BTC", + // "fee_amount": "1234.5678", + // "fee_currency": "BTC", + // "blockchain_transaction_id": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + // "related_transaction_id": "e298341a-3855-405e-bce3-92db368a3157" + // } + // + // withdraw + // + // + // crypto + // + // { + // "amount": "1234.5678", + // "fee": "1234.5678", + // "recipient": "3NacQ7rzZdhfyAtfJ5a11k8jFPdcMP2Bq7", + // "destination_tag": "", + // "transaction_id": "d0f8529f-f832-4e6a-9dc5-b8d5797badb2" + // } + // + // fiat + // + // { + // "transaction_id": "54236cd0-4413-11e9-93fb-5fea7e5b5df6" + // } + // + const id = this.safeString (transaction, 'transaction_id'); + const amount = this.safeFloat (transaction, 'amount'); + const timestamp = this.parse8601 (this.safeString (transaction, 'time')); + const currencyId = this.safeString (transaction, 'currency'); + currency = this.safeCurrency (currencyId, currency); + const status = 'ok'; // the exchange returns cleared transactions only + const feeCost = this.safeFloat2 (transaction, 'fee_amount', 'fee'); + let fee = undefined; + const addressTo = this.safeString (transaction, 'recipient'); + const tagTo = this.safeString (transaction, 'destination_tag'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (transaction, 'fee_currency', currencyId); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': transaction, + 'id': id, + 'currency': currency['code'], + 'amount': amount, + 'address': addressTo, + 'addressFrom': undefined, + 'addressTo': addressTo, + 'tag': tagTo, + 'tagFrom': undefined, + 'tagTo': tagTo, + 'status': status, + 'type': undefined, + 'updated': undefined, + 'txid': this.safeString (transaction, 'blockchain_transaction_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'FILLED': 'open', + 'FILLED_FULLY': 'closed', + 'FILLED_CLOSED': 'canceled', + 'FILLED_REJECTED': 'rejected', + 'OPEN': 'open', + 'REJECTED': 'rejected', + 'CLOSED': 'canceled', + 'FAILED': 'failed', + 'STOP_TRIGGERED': 'triggered', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "order_id": "d5492c24-2995-4c18-993a-5b8bf8fffc0d", + // "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "instrument_code": "BTC_EUR", + // "time": "2019-08-01T08:00:44.026Z", + // "side": "BUY", + // "price": "5000", + // "amount": "1", + // "filled_amount": "0.5", + // "type": "LIMIT", + // "time_in_force": "GOOD_TILL_CANCELLED" + // } + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "order": { + // "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2", + // "account_id": "1eb2ad5d-55f1-40b5-bc92-7dc05869e905", + // "instrument_code": "BTC_EUR", + // "amount": "1234.5678", + // "filled_amount": "1234.5678", + // "side": "BUY", + // "type": "LIMIT", + // "status": "OPEN", + // "sequence": 123456789, + // "price": "1234.5678", + // "average_price": "1234.5678", + // "reason": "INSUFFICIENT_FUNDS", + // "time": "2019-08-24T14:15:22Z", + // "time_in_force": "GOOD_TILL_CANCELLED", + // "time_last_updated": "2019-08-24T14:15:22Z", + // "expire_after": "2019-08-24T14:15:22Z", + // "is_post_only": false, + // "time_triggered": "2019-08-24T14:15:22Z", + // "trigger_price": "1234.5678" + // }, + // "trades": [ + // { + // "fee": { + // "fee_amount": "0.0014", + // "fee_currency": "BTC", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664", + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "amount": "1.4", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "7341.4", + // "time": "2019-09-27T15:05:32.564Z", + // "sequence": 48670 + // } + // } + // ] + // } + // + const rawTrades = this.safeValue (order, 'trades', []); + order = this.safeValue (order, 'order', order); + const id = this.safeString (order, 'order_id'); + const clientOrderId = this.safeString (order, 'client_id'); + const timestamp = this.parse8601 (this.safeString (order, 'time')); + let status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'instrument_code'); + const symbol = this.safeSymbol (marketId, market, '_'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + const filled = this.safeFloat (order, 'filled_amount'); + let remaining = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + if (status === undefined) { + if (remaining > 0) { + status = 'open'; + } else { + status = 'closed'; + } + } + } + } + const side = this.safeStringLower (order, 'side'); + const type = this.safeStringLower (order, 'type'); + const trades = this.parseTrades (rawTrades, market, undefined, undefined); + const fees = []; + const numTrades = trades.length; + let lastTradeTimestamp = undefined; + let tradeCost = undefined; + let tradeAmount = undefined; + if (numTrades > 0) { + lastTradeTimestamp = trades[0]['timestamp']; + tradeCost = 0; + tradeAmount = 0; + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + fees.push (trade['fee']); + lastTradeTimestamp = Math.max (lastTradeTimestamp, trade['timestamp']); + tradeCost = this.sum (tradeCost, trade['cost']); + tradeAmount = this.sum (tradeAmount, trade['amount']); + } + } + let average = this.safeFloat (order, 'average_price'); + if (average === undefined) { + if ((tradeCost !== undefined) && (tradeAmount !== undefined) && (tradeAmount !== 0)) { + average = tradeCost / tradeAmount; + } + } + if (cost === undefined) { + if ((average !== undefined) && (filled !== undefined)) { + cost = average * filled; + } + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force')); + const stopPrice = this.safeFloat (order, 'trigger_price'); + const result = { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + // 'fee': undefined, + 'trades': trades, + }; + const numFees = fees.length; + if (numFees > 0) { + if (numFees === 1) { + result['fee'] = fees[0]; + } else { + const feesByCurrency = this.groupBy (fees, 'currency'); + const feeCurrencies = Object.keys (feesByCurrency); + const numFeesByCurrency = feeCurrencies.length; + if (numFeesByCurrency === 1) { + const feeCurrency = feeCurrencies[0]; + const feeArray = this.safeValue (feesByCurrency, feeCurrency); + let feeCost = 0; + for (let i = 0; i < feeArray.length; i++) { + feeCost = this.sum (feeCost, feeArray[i]['cost']); + } + result['fee'] = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } else { + result['fees'] = fees; + } + } + } else { + result['fee'] = undefined; + } + return result; + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GOOD_TILL_CANCELLED': 'GTC', + 'GOOD_TILL_TIME': 'GTT', + 'IMMEDIATE_OR_CANCELLED': 'IOC', + 'FILL_OR_KILL': 'FOK', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const request = { + 'instrument_code': market['id'], + 'type': uppercaseType, // LIMIT, MARKET, STOP + 'side': side.toUpperCase (), // or SELL + 'amount': this.amountToPrecision (symbol, amount), + // "price": "1234.5678", // required for LIMIT and STOP orders + // "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206", // optional + // "time_in_force": "GOOD_TILL_CANCELLED", // limit orders only, GOOD_TILL_CANCELLED, GOOD_TILL_TIME, IMMEDIATE_OR_CANCELLED and FILL_OR_KILL + // "expire_after": "2020-07-02T19:40:13Z", // required for GOOD_TILL_TIME + // "is_post_only": false, // limit orders only, optional + // "trigger_price": "1234.5678" // required for stop orders + }; + let priceIsRequired = false; + if (uppercaseType === 'LIMIT' || uppercaseType === 'STOP') { + priceIsRequired = true; + } + if (uppercaseType === 'STOP') { + const triggerPrice = this.safeFloat (params, 'trigger_price'); + if (triggerPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a trigger_price param for ' + type + ' orders'); + } + request['trigger_price'] = this.priceToPrecision (symbol, triggerPrice); + params = this.omit (params, 'trigger_price'); + } + if (priceIsRequired) { + request['price'] = this.priceToPrecision (symbol, price); + } + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_id'); + if (clientOrderId !== undefined) { + request['client_id'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'client_id' ]); + } + const response = await this.privatePostAccountOrders (this.extend (request, params)); + // + // { + // "order_id": "d5492c24-2995-4c18-993a-5b8bf8fffc0d", + // "client_id": "d75fb03b-b599-49e9-b926-3f0b6d103206", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "instrument_code": "BTC_EUR", + // "time": "2019-08-01T08:00:44.026Z", + // "side": "BUY", + // "price": "5000", + // "amount": "1", + // "filled_amount": "0.5", + // "type": "LIMIT", + // "time_in_force": "GOOD_TILL_CANCELLED" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_id'); + params = this.omit (params, [ 'clientOrderId', 'client_id' ]); + let method = 'privateDeleteAccountOrdersOrderId'; + const request = {}; + if (clientOrderId !== undefined) { + method = 'privateDeleteAccountOrdersClientClientId'; + request['client_id'] = clientOrderId; + } else { + request['order_id'] = id; + } + const response = await this[method] (this.extend (request, params)); + // + // responds with an empty body + // + return response; + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbol !== undefined) { + const market = this.market (symbol); + request['instrument_code'] = market['id']; + } + const response = await this.privateDeleteAccountOrders (this.extend (request, params)); + // + // [ + // "a10e9bd1-8f72-4cfe-9f1b-7f1c8a9bd8ee" + // ] + // + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'ids': ids.join (','), + }; + const response = await this.privateDeleteAccountOrders (this.extend (request, params)); + // + // [ + // "a10e9bd1-8f72-4cfe-9f1b-7f1c8a9bd8ee" + // ] + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetAccountOrdersOrderId (this.extend (request, params)); + // + // { + // "order": { + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "time_last_updated": "2019-09-27T15:05:35.096Z", + // "sequence": 48782, + // "price": "7349.2", + // "filled_amount": "100.0", + // "status": "FILLED_FULLY", + // "amount": "100.0", + // "instrument_code": "BTC_EUR", + // "side": "BUY", + // "time": "2019-09-27T15:05:32.063Z", + // "type": "MARKET" + // }, + // "trades": [ + // { + // "fee": { + // "fee_amount": "0.0014", + // "fee_currency": "BTC", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "fdff2bcc-37d6-4a2d-92a5-46e09c868664", + // "order_id": "36bb2437-7402-4794-bf26-4bdf03526439", + // "account_id": "a4c699f6-338d-4a26-941f-8f9853bfc4b9", + // "amount": "1.4", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "7341.4", + // "time": "2019-09-27T15:05:32.564Z", + // "sequence": 48670 + // } + // } + // ] + // } + // + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), // max range is 100 days + // 'instrument_code': market['id'], + // 'with_cancelled_and_rejected': false, // default is false, orders which have been cancelled by the user before being filled or rejected by the system as invalid, additionally, all inactive filled orders which would return with "with_just_filled_inactive" + // 'with_just_filled_inactive': false, // orders which have been filled and are no longer open, use of "with_cancelled_and_rejected" extends "with_just_filled_inactive" and in case both are specified the latter is ignored + // 'with_just_orders': false, // do not return any trades corresponsing to the orders, it may be significanly faster and should be used if user is not interesting in trade information + // 'max_page_size': 100, + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument_code'] = market['id']; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a "to" iso8601 string param with the since argument is specified, max range is 100 days'); + } + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + const response = await this.privateGetAccountOrders (this.extend (request, params)); + // + // { + // "order_history": [ + // { + // "order": { + // "trigger_price": "12089.88", + // "order_id": "d453ca12-c650-46dd-9dee-66910d96bfc0", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "instrument_code": "BTC_USDT", + // "time": "2019-08-23T10:02:31.663Z", + // "side": "SELL", + // "price": "10159.76", + // "average_price": "10159.76", + // "amount": "0.2", + // "filled_amount": "0.2", + // "type": "STOP", + // "sequence": 8, + // "status": "FILLED_FULLY" + // }, + // "trades": [ + // { + // "fee": { + // "fee_amount": "0.4188869", + // "fee_currency": "USDT", + // "fee_percentage": "0.1", + // "fee_group_id": "default", + // "fee_type": "TAKER", + // "running_trading_volume": "0.0" + // }, + // "trade": { + // "trade_id": "ec82896f-fd1b-4cbb-89df-a9da85ccbb4b", + // "order_id": "d453ca12-c650-46dd-9dee-66910d96bfc0", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "amount": "0.2", + // "side": "SELL", + // "instrument_code": "BTC_USDT", + // "price": "10159.76", + // "time": "2019-08-23T10:02:32.663Z", + // "sequence": 9 + // } + // } + // ] + // }, + // { + // "order": { + // "order_id": "5151a99e-f414-418f-8cf1-2568d0a63ea5", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "instrument_code": "BTC_USDT", + // "time": "2019-08-23T10:01:36.773Z", + // "side": "SELL", + // "price": "12289.88", + // "amount": "0.5", + // "filled_amount": "0.0", + // "type": "LIMIT", + // "sequence": 7, + // "status": "OPEN" + // }, + // "trades": [] + // }, + // { + // "order": { + // "order_id": "ac80d857-75e1-4733-9070-fd4288395fdc", + // "account_id": "ef3a5f4c-cfcd-415e-ba89-5a9abf47b28a", + // "instrument_code": "BTC_USDT", + // "time": "2019-08-23T10:01:25.031Z", + // "side": "SELL", + // "price": "11089.88", + // "amount": "0.1", + // "filled_amount": "0.0", + // "type": "LIMIT", + // "sequence": 6, + // "status": "OPEN" + // }, + // "trades": [] + // } + // ], + // "max_page_size": 100 + // } + // + const orderHistory = this.safeValue (response, 'order_history', []); + return this.parseOrders (orderHistory, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'with_cancelled_and_rejected': true, // default is false, orders which have been cancelled by the user before being filled or rejected by the system as invalid, additionally, all inactive filled orders which would return with "with_just_filled_inactive" + }; + return await this.fetchOpenOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + // 'max_page_size': 100, + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + if (limit !== undefined) { + request['max_page_size'] = limit; + } + const response = await this.privateGetAccountOrdersOrderIdTrades (this.extend (request, params)); + // + // { + // "trade_history": [ + // { + // "trade": { + // "trade_id": "2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "1234.5678", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "1234.5678", + // "time": "2019-08-24T14:15:22Z", + // "price_tick_sequence": 0, + // "sequence": 123456789 + // }, + // "fee": { + // "fee_amount": "1234.5678", + // "fee_percentage": "1234.5678", + // "fee_group_id": "default", + // "running_trading_volume": "1234.5678", + // "fee_currency": "BTC", + // "fee_type": "TAKER" + // } + // } + // ], + // "max_page_size": 0, + // "cursor": "string" + // } + // + const tradeHistory = this.safeValue (response, 'trade_history', []); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (tradeHistory, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), // max range is 100 days + // 'instrument_code': market['id'], + // 'max_page_size': 100, + // 'cursor': 'string', // pointer specifying the position from which the next pages should be returned + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument_code'] = market['id']; + } + if (since !== undefined) { + const to = this.safeString (params, 'to'); + if (to === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a "to" iso8601 string param with the since argument is specified, max range is 100 days'); + } + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['max_page_size'] = limit; + } + const response = await this.privateGetAccountTrades (this.extend (request, params)); + // + // { + // "trade_history": [ + // { + // "trade": { + // "trade_id": "2b42efcd-d5b7-4a56-8e12-b69ffd68c5ef", + // "order_id": "66756a10-3e86-48f4-9678-b634c4b135b2", + // "account_id": "c2d0076a-c20d-41f8-9e9a-1a1d028b2b58", + // "amount": "1234.5678", + // "side": "BUY", + // "instrument_code": "BTC_EUR", + // "price": "1234.5678", + // "time": "2019-08-24T14:15:22Z", + // "price_tick_sequence": 0, + // "sequence": 123456789 + // }, + // "fee": { + // "fee_amount": "1234.5678", + // "fee_percentage": "1234.5678", + // "fee_group_id": "default", + // "running_trading_volume": "1234.5678", + // "fee_currency": "BTC", + // "fee_type": "TAKER" + // } + // } + // ], + // "max_page_size": 0, + // "cursor": "string" + // } + // + const tradeHistory = this.safeValue (response, 'trade_history', []); + return this.parseTrades (tradeHistory, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer ' + this.apiKey, + }; + if (method === 'POST') { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error":"MISSING_FROM_PARAM"} + // {"error":"MISSING_TO_PARAM"} + // {"error":"CANDLESTICKS_TIME_RANGE_TOO_BIG"} + // + const message = this.safeString (response, 'error'); + if (message !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitso.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitso.js new file mode 100644 index 000000000..8ef7dc93c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitso.js @@ -0,0 +1,622 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidNonce, AuthenticationError, OrderNotFound } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bitso extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitso', + 'name': 'Bitso', + 'countries': [ 'MX' ], // Mexico + 'rateLimit': 2000, // 30 requests per minute + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295554-11f98280-c50e-11ea-80d6-15b3bafa8cbf.jpg', + 'api': 'https://api.bitso.com', + 'www': 'https://bitso.com', + 'doc': 'https://bitso.com/api_info', + 'fees': 'https://bitso.com/fees', + 'referral': 'https://bitso.com/?ref=itej', + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'precision': { + 'XRP': 0.000001, + 'MXN': 0.01, + 'TUSD': 0.01, + }, + 'defaultPrecision': 0.00000001, + }, + 'api': { + 'public': { + 'get': [ + 'available_books', + 'ticker', + 'order_book', + 'trades', + ], + }, + 'private': { + 'get': [ + 'account_status', + 'balance', + 'fees', + 'fundings', + 'fundings/{fid}', + 'funding_destination', + 'kyc_documents', + 'ledger', + 'ledger/trades', + 'ledger/fees', + 'ledger/fundings', + 'ledger/withdrawals', + 'mx_bank_codes', + 'open_orders', + 'order_trades/{oid}', + 'orders/{oid}', + 'user_trades', + 'user_trades/{tid}', + 'withdrawals/', + 'withdrawals/{wid}', + ], + 'post': [ + 'bitcoin_withdrawal', + 'debit_card_withdrawal', + 'ether_withdrawal', + 'ripple_withdrawal', + 'bcash_withdrawal', + 'litecoin_withdrawal', + 'orders', + 'phone_number', + 'phone_verification', + 'phone_withdrawal', + 'spei_withdrawal', + 'ripple_withdrawal', + 'bcash_withdrawal', + 'litecoin_withdrawal', + ], + 'delete': [ + 'orders/{oid}', + 'orders/all', + ], + }, + }, + 'exceptions': { + '0201': AuthenticationError, // Invalid Nonce or Invalid Credentials + '104': InvalidNonce, // Cannot perform request - nonce must be higher than 1520307203724237 + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAvailableBooks (params); + // + // { + // "success":true, + // "payload":[ + // { + // "book":"btc_mxn", + // "minimum_price":"500", + // "maximum_price":"10000000", + // "minimum_amount":"0.00005", + // "maximum_amount":"500", + // "minimum_value":"5", + // "maximum_value":"10000000", + // "tick_size":"0.01", + // "fees":{ + // "flat_rate":{"maker":"0.500","taker":"0.650"}, + // "structure":[ + // {"volume":"1500000","maker":"0.00500","taker":"0.00650"}, + // {"volume":"2000000","maker":"0.00490","taker":"0.00637"}, + // {"volume":"5000000","maker":"0.00480","taker":"0.00624"}, + // {"volume":"7000000","maker":"0.00440","taker":"0.00572"}, + // {"volume":"10000000","maker":"0.00420","taker":"0.00546"}, + // {"volume":"15000000","maker":"0.00400","taker":"0.00520"}, + // {"volume":"35000000","maker":"0.00370","taker":"0.00481"}, + // {"volume":"50000000","maker":"0.00300","taker":"0.00390"}, + // {"volume":"150000000","maker":"0.00200","taker":"0.00260"}, + // {"volume":"250000000","maker":"0.00100","taker":"0.00130"}, + // {"volume":"9999999999","maker":"0.00000","taker":"0.00130"}, + // ] + // } + // }, + // ] + // } + const markets = this.safeValue (response, 'payload'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'book'); + const [ baseId, quoteId ] = id.split ('_'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_amount'), + 'max': this.safeFloat (market, 'maximum_amount'), + }, + 'price': { + 'min': this.safeFloat (market, 'minimum_price'), + 'max': this.safeFloat (market, 'maximum_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'minimum_value'), + 'max': this.safeFloat (market, 'maximum_value'), + }, + }; + const defaultPricePrecision = this.safeFloat (this.options['precision'], quote, this.options['defaultPrecision']); + const pricePrecision = this.safeFloat (market, 'tick_size', defaultPricePrecision); + const precision = { + 'amount': this.safeFloat (this.options['precision'], base, this.options['defaultPrecision']), + 'price': pricePrecision, + }; + const fees = this.safeValue (market, 'fees', {}); + const flatRate = this.safeValue (fees, 'flat_rate', {}); + const maker = this.safeFloat (flatRate, 'maker'); + const taker = this.safeFloat (flatRate, 'taker'); + const feeTiers = this.safeValue (fees, 'structure', []); + const fee = { + 'maker': maker, + 'taker': taker, + 'percentage': true, + 'tierBased': true, + }; + const takerFees = []; + const makerFees = []; + for (let i = 0; i < feeTiers.length; i++) { + const tier = feeTiers[i]; + const volume = this.safeFloat (tier, 'volume'); + const takerFee = this.safeFloat (tier, 'taker'); + const makerFee = this.safeFloat (tier, 'maker'); + takerFees.push ([ volume, takerFee ]); + makerFees.push ([ volume, makerFee ]); + if (i === 0) { + fee['taker'] = taker; + fee['maker'] = maker; + } + } + const tiers = { + 'taker': takerFees, + 'maker': makerFees, + }; + fee['tiers'] = tiers; + result.push (this.extend ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'limits': limits, + 'precision': precision, + 'active': undefined, + }, fee)); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const balances = this.safeValue (response['payload'], 'balances'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'locked'), + 'total': this.safeFloat (balance, 'total'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'book': this.marketId (symbol), + }; + const response = await this.publicGetOrderBook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'payload'); + const timestamp = this.parse8601 (this.safeString (orderbook, 'updated_at')); + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'book': this.marketId (symbol), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'payload'); + const timestamp = this.parse8601 (this.safeString (ticker, 'created_at')); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const marketId = this.safeString (trade, 'book'); + const symbol = this.safeSymbol (marketId, market, '_'); + const side = this.safeString2 (trade, 'side', 'maker_side'); + let amount = this.safeFloat2 (trade, 'amount', 'major'); + if (amount !== undefined) { + amount = Math.abs (amount); + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fees_amount'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fees_currency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + let cost = this.safeFloat (trade, 'minor'); + if (cost !== undefined) { + cost = Math.abs (cost); + } + const price = this.safeFloat (trade, 'price'); + const orderId = this.safeString (trade, 'oid'); + const id = this.safeString (trade, 'tid'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'book': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response['payload'], market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = 25, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // the don't support fetching trades starting from a date yet + // use the `marker` extra param for that + // this is not a typo, the variable name is 'marker' (don't confuse with 'market') + const markerInParams = ('marker' in params); + // warn the user with an exception if the user wants to filter + // starting from since timestamp, but does not set the trade id with an extra 'marker' param + if ((since !== undefined) && !markerInParams) { + throw new ExchangeError (this.id + ' fetchMyTrades does not support fetching trades starting from a timestamp with the `since` argument, use the `marker` extra param to filter starting from an integer trade id'); + } + // convert it to an integer unconditionally + if (markerInParams) { + params = this.extend (params, { + 'marker': parseInt (params['marker']), + }); + } + const request = { + 'book': market['id'], + 'limit': limit, // default = 25, max = 100 + // 'sort': 'desc', // default = desc + // 'marker': id, // integer id to start from + }; + const response = await this.privateGetUserTrades (this.extend (request, params)); + return this.parseTrades (response['payload'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'book': this.marketId (symbol), + 'side': side, + 'type': type, + 'major': this.amountToPrecision (symbol, amount), + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + const id = this.safeString (response['payload'], 'oid'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'oid': id, + }; + return await this.privateDeleteOrdersOid (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'partial-fill': 'open', // this is a common substitution in ccxt + 'completed': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'oid'); + const side = this.safeString (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'book'); + const symbol = this.safeSymbol (marketId, market, '_'); + const orderType = this.safeString (order, 'type'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'original_amount'); + const remaining = this.safeFloat (order, 'unfilled_amount'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const clientOrderId = this.safeString (order, 'client_id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': undefined, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = 25, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // the don't support fetching trades starting from a date yet + // use the `marker` extra param for that + // this is not a typo, the variable name is 'marker' (don't confuse with 'market') + const markerInParams = ('marker' in params); + // warn the user with an exception if the user wants to filter + // starting from since timestamp, but does not set the trade id with an extra 'marker' param + if ((since !== undefined) && !markerInParams) { + throw new ExchangeError (this.id + ' fetchOpenOrders does not support fetching orders starting from a timestamp with the `since` argument, use the `marker` extra param to filter starting from an integer trade id'); + } + // convert it to an integer unconditionally + if (markerInParams) { + params = this.extend (params, { + 'marker': parseInt (params['marker']), + }); + } + const request = { + 'book': market['id'], + 'limit': limit, // default = 25, max = 100 + // 'sort': 'desc', // default = desc + // 'marker': id, // integer id to start from + }; + const response = await this.privateGetOpenOrders (this.extend (request, params)); + const orders = this.parseOrders (response['payload'], market, since, limit); + return orders; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetOrdersOid ({ + 'oid': id, + }); + const payload = this.safeValue (response, 'payload'); + if (Array.isArray (payload)) { + const numOrders = response['payload'].length; + if (numOrders === 1) { + return this.parseOrder (payload[0]); + } + } + throw new OrderNotFound (this.id + ': The order ' + id + ' not found.'); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'oid': id, + }; + const response = await this.privateGetOrderTradesOid (this.extend (request, params)); + return this.parseTrades (response['payload'], market); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'fund_currency': currency['id'], + }; + const response = await this.privateGetFundingDestination (this.extend (request, params)); + let address = this.safeString (response['payload'], 'account_identifier'); + let tag = undefined; + if (address.indexOf ('?dt=') >= 0) { + const parts = address.split ('?dt='); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const methods = { + 'BTC': 'Bitcoin', + 'ETH': 'Ether', + 'XRP': 'Ripple', + 'BCH': 'Bcash', + 'LTC': 'Litecoin', + }; + const method = (code in methods) ? methods[code] : undefined; + if (method === undefined) { + throw new ExchangeError (this.id + ' not valid withdraw coin: ' + code); + } + const request = { + 'amount': amount, + 'address': address, + 'destination_tag': tag, + }; + const classMethod = 'privatePost' + method + 'Withdrawal'; + const response = await this[classMethod] (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response['payload'], 'wid'), + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let endpoint = '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + endpoint += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + endpoint; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let request = [ nonce, method, endpoint ].join (''); + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + request += body; + } + } + const signature = this.hmac (this.encode (request), this.encode (this.secret)); + const auth = this.apiKey + ':' + nonce + ':' + signature; + headers = { + 'Authorization': 'Bitso ' + auth, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // {"success":false,"error":{"code":104,"message":"Cannot perform request - nonce must be higher than 1520307203724237"}} + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const feedback = this.id + ' ' + this.json (response); + const error = this.safeValue (response, 'error'); + if (error === undefined) { + throw new ExchangeError (feedback); + } + const code = this.safeString (error, 'code'); + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('success' in response) { + if (response['success']) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitstamp.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitstamp.js new file mode 100644 index 000000000..1b4a33a46 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitstamp.js @@ -0,0 +1,1605 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, NotSupported, PermissionDenied, InvalidNonce, OrderNotFound, InsufficientFunds, InvalidAddress, InvalidOrder, ArgumentsRequired, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitstamp extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitstamp', + 'name': 'Bitstamp', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'version': 'v2', + 'userAgent': this.userAgents['chrome'], + 'pro': true, + 'has': { + 'CORS': true, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchFundingFees': true, + 'fetchFees': true, + 'fetchLedger': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg', + 'api': { + 'public': 'https://www.bitstamp.net/api', + 'private': 'https://www.bitstamp.net/api', + 'v1': 'https://www.bitstamp.net/api', + }, + 'www': 'https://www.bitstamp.net', + 'doc': 'https://www.bitstamp.net/api', + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '259200', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'ohlc/{pair}/', + 'order_book/{pair}/', + 'ticker_hour/{pair}/', + 'ticker/{pair}/', + 'transactions/{pair}/', + 'trading-pairs-info/', + ], + }, + 'private': { + 'post': [ + 'balance/', + 'balance/{pair}/', + 'bch_withdrawal/', + 'bch_address/', + 'user_transactions/', + 'user_transactions/{pair}/', + 'open_orders/all/', + 'open_orders/{pair}/', + 'order_status/', + 'cancel_order/', + 'buy/{pair}/', + 'buy/market/{pair}/', + 'buy/instant/{pair}/', + 'sell/{pair}/', + 'sell/market/{pair}/', + 'sell/instant/{pair}/', + 'ltc_withdrawal/', + 'ltc_address/', + 'eth_withdrawal/', + 'eth_address/', + 'xrp_withdrawal/', + 'xrp_address/', + 'xlm_withdrawal/', + 'xlm_address/', + 'pax_withdrawal/', + 'pax_address/', + 'link_withdrawal/', + 'link_address/', + 'usdc_withdrawal/', + 'usdc_address/', + 'omg_withdrawal/', + 'omg_address/', + 'transfer-to-main/', + 'transfer-from-main/', + 'withdrawal-requests/', + 'withdrawal/open/', + 'withdrawal/status/', + 'withdrawal/cancel/', + 'liquidation_address/new/', + 'liquidation_address/info/', + ], + }, + 'v1': { + 'post': [ + 'bitcoin_deposit_address/', + 'unconfirmed_btc/', + 'bitcoin_withdrawal/', + 'ripple_withdrawal/', + 'ripple_address/', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.5 / 100, + 'maker': 0.5 / 100, + 'tiers': { + 'taker': [ + [0, 0.5 / 100], + [20000, 0.25 / 100], + [100000, 0.24 / 100], + [200000, 0.22 / 100], + [400000, 0.20 / 100], + [600000, 0.15 / 100], + [1000000, 0.14 / 100], + [2000000, 0.13 / 100], + [4000000, 0.12 / 100], + [20000000, 0.11 / 100], + [50000000, 0.10 / 100], + [100000000, 0.07 / 100], + [500000000, 0.05 / 100], + [2000000000, 0.03 / 100], + [6000000000, 0.01 / 100], + [10000000000, 0.005 / 100], + [10000000001, 0.0], + ], + 'maker': [ + [0, 0.5 / 100], + [20000, 0.25 / 100], + [100000, 0.24 / 100], + [200000, 0.22 / 100], + [400000, 0.20 / 100], + [600000, 0.15 / 100], + [1000000, 0.14 / 100], + [2000000, 0.13 / 100], + [4000000, 0.12 / 100], + [20000000, 0.11 / 100], + [50000000, 0.10 / 100], + [100000000, 0.07 / 100], + [500000000, 0.05 / 100], + [2000000000, 0.03 / 100], + [6000000000, 0.01 / 100], + [10000000000, 0.005 / 100], + [10000000001, 0.0], + ], + }, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.0005, + 'BCH': 0.0001, + 'LTC': 0.001, + 'ETH': 0.001, + 'XRP': 0.02, + 'XLM': 0.005, + 'PAX': 0.5, + 'USD': 25, + 'EUR': 3.0, + }, + 'deposit': { + 'BTC': 0, + 'BCH': 0, + 'LTC': 0, + 'ETH': 0, + 'XRP': 0, + 'XLM': 0, + 'PAX': 0, + 'USD': 7.5, + 'EUR': 0, + }, + }, + }, + 'exceptions': { + 'exact': { + 'No permission found': PermissionDenied, + 'API key not found': AuthenticationError, + 'IP address not allowed': PermissionDenied, + 'Invalid nonce': InvalidNonce, + 'Invalid signature': AuthenticationError, + 'Authentication failed': AuthenticationError, + 'Missing key, signature and nonce parameters': AuthenticationError, + 'Your account is frozen': PermissionDenied, + 'Please update your profile with your FATCA information, before using API.': PermissionDenied, + 'Order not found': OrderNotFound, + 'Price is more than 20% below market price.': InvalidOrder, + 'Bitstamp.net is under scheduled maintenance.': OnMaintenance, // { "error": "Bitstamp.net is under scheduled maintenance. We'll be back soon." } + }, + 'broad': { + 'Minimum order size is': InvalidOrder, // Minimum order size is 5.0 EUR. + 'Check your account balance for details.': InsufficientFunds, // You have only 0.00100000 BTC available. Check your account balance for details. + 'Ensure this value has at least': InvalidAddress, // Ensure this value has at least 25 characters (it has 4). + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.fetchMarketsFromCache (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const name = this.safeString (market, 'name'); + let [ base, quote ] = name.split ('/'); + const baseId = base.toLowerCase (); + const quoteId = quote.toLowerCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const symbolId = baseId + '_' + quoteId; + const id = this.safeString (market, 'url_symbol'); + const precision = { + 'amount': market['base_decimals'], + 'price': market['counter_decimals'], + }; + const parts = market['minimum_order'].split (' '); + const cost = parts[0]; + // let [ cost, currency ] = market['minimum_order'].split (' '); + const active = (market['trading'] === 'Enabled'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'symbolId': symbolId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': parseFloat (cost), + 'max': undefined, + }, + }, + }); + } + return result; + } + + constructCurrencyObject (id, code, name, precision, minCost, originalPayload) { + let currencyType = 'crypto'; + const description = this.describe (); + if (this.isFiat (code)) { + currencyType = 'fiat'; + } + return { + 'id': id, + 'code': code, + 'info': originalPayload, // the original payload + 'type': currencyType, + 'name': name, + 'active': true, + 'fee': this.safeFloat (description['fees']['funding']['withdraw'], code), + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + + async fetchMarketsFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchMarkets', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetTradingPairsInfo (params); + this.options['fetchMarkets'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchMarkets'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchMarketsFromCache (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const name = this.safeString (market, 'name'); + let [ base, quote ] = name.split ('/'); + const baseId = base.toLowerCase (); + const quoteId = quote.toLowerCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const description = this.safeString (market, 'description'); + const [ baseDescription, quoteDescription ] = description.split (' / '); + const parts = market['minimum_order'].split (' '); + const cost = parts[0]; + if (!(base in result)) { + result[base] = this.constructCurrencyObject (baseId, base, baseDescription, market['base_decimals'], undefined, market); + } + if (!(quote in result)) { + result[quote] = this.constructCurrencyObject (quoteId, quote, quoteDescription, market['counter_decimals'], parseFloat (cost), market); + } + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetOrderBookPair (this.extend (request, params)); + // + // { + // "timestamp": "1583652948", + // "microtimestamp": "1583652948955826", + // "bids": [ + // [ "8750.00", "1.33685271" ], + // [ "8749.39", "0.07700000" ], + // [ "8746.98", "0.07400000" ], + // ] + // "asks": [ + // [ "8754.10", "1.51995636" ], + // [ "8754.71", "1.40000000" ], + // [ "8754.72", "2.50000000" ], + // ] + // } + // + const microtimestamp = this.safeInteger (response, 'microtimestamp'); + const timestamp = parseInt (microtimestamp / 1000); + const orderbook = this.parseOrderBook (response, timestamp); + orderbook['nonce'] = microtimestamp; + return orderbook; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const ticker = await this.publicGetTickerPair (this.extend (request, params)); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + getCurrencyIdFromTransaction (transaction) { + // + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "datetime": XXX, + // "usd": 0.0, + // "btc": 0.0, + // "eth": "0.05000000", + // "type": "0", + // "id": XXX, + // "eur": 0.0 + // } + // + const currencyId = this.safeStringLower (transaction, 'currency'); + if (currencyId !== undefined) { + return currencyId; + } + transaction = this.omit (transaction, [ + 'fee', + 'price', + 'datetime', + 'type', + 'status', + 'id', + ]); + const ids = Object.keys (transaction); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (id.indexOf ('_') < 0) { + const value = this.safeFloat (transaction, id); + if ((value !== undefined) && (value !== 0)) { + return id; + } + } + } + return undefined; + } + + getMarketFromTrade (trade) { + trade = this.omit (trade, [ + 'fee', + 'price', + 'datetime', + 'tid', + 'type', + 'order_id', + 'side', + ]); + const currencyIds = Object.keys (trade); + const numCurrencyIds = currencyIds.length; + if (numCurrencyIds > 2) { + throw new ExchangeError (this.id + ' getMarketFromTrade too many keys: ' + this.json (currencyIds) + ' in the trade: ' + this.json (trade)); + } + if (numCurrencyIds === 2) { + let marketId = currencyIds[0] + currencyIds[1]; + if (marketId in this.markets_by_id) { + return this.markets_by_id[marketId]; + } + marketId = currencyIds[1] + currencyIds[0]; + if (marketId in this.markets_by_id) { + return this.markets_by_id[marketId]; + } + } + return undefined; + } + + getMarketFromTrades (trades) { + const tradesBySymbol = this.indexBy (trades, 'symbol'); + const symbols = Object.keys (tradesBySymbol); + const numSymbols = symbols.length; + if (numSymbols === 1) { + return this.markets[symbols[0]]; + } + return undefined; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // date: '1551814435', + // tid: '83581898', + // price: '0.03532850', + // type: '1', + // amount: '0.85945907' + // }, + // + // fetchMyTrades, trades returned within fetchOrder (private) + // + // { + // "usd": "6.0134400000000000", + // "price": "4008.96000000", + // "datetime": "2019-03-28 23:07:37.233599", + // "fee": "0.02", + // "btc": "0.00150000", + // "tid": 84452058, + // "type": 2 + // } + // + // from fetchOrder: + // { fee: '0.000019', + // price: '0.00015803', + // datetime: '2018-01-07 10:45:34.132551', + // btc: '0.0079015000000000', + // tid: 42777395, + // type: 2, //(0 - deposit; 1 - withdrawal; 2 - market trade) NOT buy/sell + // xrp: '50.00000000' } + const id = this.safeString2 (trade, 'id', 'tid'); + let symbol = undefined; + let side = undefined; + let price = this.safeFloat (trade, 'price'); + let amount = this.safeFloat (trade, 'amount'); + const orderId = this.safeString (trade, 'order_id'); + const type = undefined; + let cost = this.safeFloat (trade, 'cost'); + if (market === undefined) { + const keys = Object.keys (trade); + for (let i = 0; i < keys.length; i++) { + if (keys[i].indexOf ('_') >= 0) { + const marketId = keys[i].replace ('_', ''); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + } + // if the market is still not defined + // try to deduce it from used keys + if (market === undefined) { + market = this.getMarketFromTrade (trade); + } + } + const feeCost = this.safeFloat (trade, 'fee'); + let feeCurrency = undefined; + if (market !== undefined) { + price = this.safeFloat (trade, market['symbolId'], price); + amount = this.safeFloat (trade, market['baseId'], amount); + cost = this.safeFloat (trade, market['quoteId'], cost); + feeCurrency = market['quote']; + symbol = market['symbol']; + } + let timestamp = this.safeString2 (trade, 'date', 'datetime'); + if (timestamp !== undefined) { + if (timestamp.indexOf (' ') >= 0) { + // iso8601 + timestamp = this.parse8601 (timestamp); + } else { + // string unix epoch in seconds + timestamp = parseInt (timestamp); + timestamp = timestamp * 1000; + } + } + // if it is a private trade + if ('id' in trade) { + if (amount !== undefined) { + if (amount < 0) { + side = 'sell'; + amount = -amount; + } else { + side = 'buy'; + } + } + } else { + side = this.safeString (trade, 'type'); + if (side === '1') { + side = 'sell'; + } else if (side === '0') { + side = 'buy'; + } + } + if (cost === undefined) { + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + } + if (cost !== undefined) { + cost = Math.abs (cost); + } + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseTradingFee (balances, symbol) { + const market = this.market (symbol); + const tradeFee = this.safeFloat (balances, market['id'] + '_fee'); + return { + 'symbol': symbol, + 'maker': tradeFee, + 'taker': tradeFee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'time': 'hour', + }; + const response = await this.publicGetTransactionsPair (this.extend (request, params)); + // + // [ + // { + // date: '1551814435', + // tid: '83581898', + // price: '0.03532850', + // type: '1', + // amount: '0.85945907' + // }, + // { + // date: '1551814434', + // tid: '83581896', + // price: '0.03532851', + // type: '1', + // amount: '11.34130961' + // }, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "high": "9064.77", + // "timestamp": "1593961440", + // "volume": "18.49436608", + // "low": "9040.87", + // "close": "9064.77", + // "open": "9040.87" + // } + // + return [ + this.safeTimestamp (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'step': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + if (limit === undefined) { + if (since === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + limit = 1000; + const start = parseInt (since / 1000); + request['start'] = start; + request['end'] = this.sum (start, limit * duration); + request['limit'] = limit; + } + } else { + if (since !== undefined) { + const start = parseInt (since / 1000); + request['start'] = start; + request['end'] = this.sum (start, limit * duration); + } + request['limit'] = Math.min (limit, 1000); // min 1, max 1000 + } + const response = await this.publicGetOhlcPair (this.extend (request, params)); + // + // { + // "data": { + // "pair": "BTC/USD", + // "ohlc": [ + // {"high": "9064.77", "timestamp": "1593961440", "volume": "18.49436608", "low": "9040.87", "close": "9064.77", "open": "9040.87"}, + // {"high": "9071.59", "timestamp": "1593961500", "volume": "3.48631711", "low": "9058.76", "close": "9061.07", "open": "9064.66"}, + // {"high": "9067.33", "timestamp": "1593961560", "volume": "0.04142833", "low": "9061.94", "close": "9061.94", "open": "9067.33"}, + // ], + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const ohlc = this.safeValue (data, 'ohlc', []); + return this.parseOHLCVs (ohlc, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + const result = { 'info': balance }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const account = this.account (); + account['free'] = this.safeFloat (balance, currencyId + '_available'); + account['used'] = this.safeFloat (balance, currencyId + '_reserved'); + account['total'] = this.safeFloat (balance, currencyId + '_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'privatePostBalance'; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + method += 'Pair'; + } + const balance = await this[method] (this.extend (request, params)); + return { + 'info': balance, + 'symbol': symbol, + 'maker': balance.fee, + 'taker': balance.fee, + }; + } + + praseTradingFees (balance) { + const result = { 'info': balance }; + const markets = Object.keys (this.markets); + for (let i = 0; i < markets.length; i++) { + const symbol = markets[i]; + const fee = this.parseTradingFee (balance, symbol); + result[symbol] = fee; + } + return result; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + return this.praseTradingFees (balance); + } + + parseFundingFees (balance) { + const withdraw = {}; + const ids = Object.keys (balance); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (id.indexOf ('_withdrawal_fee') >= 0) { + const currencyId = id.split ('_')[0]; + const code = this.safeCurrencyCode (currencyId); + withdraw[code] = this.safeFloat (balance, id); + } + } + return { + 'info': balance, + 'withdraw': withdraw, + 'deposit': {}, + }; + } + + async fetchFundingFees (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + return this.parseFundingFees (balance); + } + + async fetchFees (params = {}) { + await this.loadMarkets (); + const balance = await this.privatePostBalance (params); + const tradingFees = this.praseTradingFees (balance); + delete tradingFees['info']; + const fundingFees = this.parseFundingFees (balance); + delete fundingFees['info']; + return { + 'info': balance, + 'trading': tradingFees, + 'funding': fundingFees, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = 'privatePost' + this.capitalize (side); + const request = { + 'pair': market['id'], + 'amount': this.amountToPrecision (symbol, amount), + }; + if (type === 'market') { + method += 'Market'; + } else if (type === 'instant') { + method += 'Instant'; + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + method += 'Pair'; + const response = await this[method] (this.extend (request, params)); + const order = this.parseOrder (response, market); + return this.extend (order, { + 'type': type, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'In Queue': 'open', + 'Open': 'open', + 'Finished': 'closed', + 'Canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrderStatus (this.safeString (response, 'status')); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { 'id': id }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + // + // { + // "status": "Finished", + // "id": 3047704374, + // "transactions": [ + // { + // "usd": "6.0134400000000000", + // "price": "4008.96000000", + // "datetime": "2019-03-28 23:07:37.233599", + // "fee": "0.02", + // "btc": "0.00150000", + // "tid": 84452058, + // "type": 2 + // } + // ] + // } + return this.parseOrder (response, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'privatePostUserTransactions'; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + method += 'Pair'; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + const result = this.filterBy (response, 'type', '2'); + return this.parseTrades (result, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostUserTransactions (this.extend (request, params)); + // + // [ + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1234567894, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-08 09:00:31", + // "type": "1", + // "xrp": "-20.00000000", + // "eur": 0, + // }, + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1134567891, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-07 18:47:52", + // "type": "0", + // "xrp": "20.00000000", + // "eur": 0, + // }, + // ] + // + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const transactions = this.filterByArray (response, 'type', [ '0', '1' ], false); + return this.parseTransactions (transactions, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['timedelta'] = this.milliseconds () - since; + } else { + request['timedelta'] = 50000000; // use max bitstamp approved value + } + const response = await this.privatePostWithdrawalRequests (this.extend (request, params)); + // + // [ + // { + // status: 2, + // datetime: '2018-10-17 10:58:13', + // currency: 'BTC', + // amount: '0.29669259', + // address: 'aaaaa', + // type: 1, + // id: 111111, + // transaction_id: 'xxxx', + // }, + // { + // status: 2, + // datetime: '2018-10-17 10:55:17', + // currency: 'ETH', + // amount: '1.11010664', + // address: 'aaaa', + // type: 16, + // id: 222222, + // transaction_id: 'xxxxx', + // }, + // ] + // + return this.parseTransactions (response, undefined, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchTransactions + // + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1234567894, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-08 09:00:31", + // "type": "1", + // "xrp": "-20.00000000", + // "eur": 0, + // } + // + // fetchWithdrawals + // + // { + // status: 2, + // datetime: '2018-10-17 10:58:13', + // currency: 'BTC', + // amount: '0.29669259', + // address: 'aaaaa', + // type: 1, + // id: 111111, + // transaction_id: 'xxxx', + // } + // + // { + // "id": 3386432, + // "type": 14, + // "amount": "863.21332500", + // "status": 2, + // "address": "rE1sdh25BJQ3qFwngiTBwaq3zPGGYcrjp1?dt=1455", + // "currency": "XRP", + // "datetime": "2018-01-05 15:27:55", + // "transaction_id": "001743B03B0C79BA166A064AC0142917B050347B4CB23BA2AB4B91B3C5608F4C" + // } + // + const timestamp = this.parse8601 (this.safeString (transaction, 'datetime')); + const id = this.safeString (transaction, 'id'); + const currencyId = this.getCurrencyIdFromTransaction (transaction); + const code = this.safeCurrencyCode (currencyId, currency); + const feeCost = this.safeFloat (transaction, 'fee'); + let feeCurrency = undefined; + let amount = undefined; + if ('amount' in transaction) { + amount = this.safeFloat (transaction, 'amount'); + } else if (currency !== undefined) { + amount = this.safeFloat (transaction, currency['id'], amount); + feeCurrency = currency['code']; + } else if ((code !== undefined) && (currencyId !== undefined)) { + amount = this.safeFloat (transaction, currencyId, amount); + feeCurrency = code; + } + if (amount !== undefined) { + // withdrawals have a negative amount + amount = Math.abs (amount); + } + let status = 'ok'; + if ('status' in transaction) { + status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + } + let type = undefined; + if ('type' in transaction) { + // from fetchTransactions + const rawType = this.safeString (transaction, 'type'); + if (rawType === '0') { + type = 'deposit'; + } else if (rawType === '1') { + type = 'withdrawal'; + } + } else { + // from fetchWithdrawals + type = 'withdrawal'; + } + const txid = this.safeString (transaction, 'transaction_id'); + let tag = undefined; + let address = this.safeString (transaction, 'address'); + if (address !== undefined) { + // dt (destination tag) is embedded into the address field + const addressParts = address.split ('?dt='); + const numParts = addressParts.length; + if (numParts > 1) { + address = addressParts[0]; + tag = addressParts[1]; + } + } + const addressFrom = undefined; + const addressTo = address; + const tagFrom = undefined; + const tagTo = tag; + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'currency': feeCurrency, + 'cost': feeCost, + 'rate': undefined, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': tagFrom, + 'tagTo': tagTo, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + // withdrawals: + // 0 (open), 1 (in process), 2 (finished), 3 (canceled) or 4 (failed). + const statuses = { + '0': 'pending', // Open + '1': 'pending', // In process + '2': 'ok', // Finished + '3': 'canceled', // Canceled + '4': 'failed', // Failed + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // from fetch order: + // { status: 'Finished', + // id: 731693945, + // transactions: + // [ { fee: '0.000019', + // price: '0.00015803', + // datetime: '2018-01-07 10:45:34.132551', + // btc: '0.0079015000000000', + // tid: 42777395, + // type: 2, + // xrp: '50.00000000' } ] } + // + // partially filled order: + // { "id": 468646390, + // "status": "Canceled", + // "transactions": [{ + // "eth": "0.23000000", + // "fee": "0.09", + // "tid": 25810126, + // "usd": "69.8947000000000000", + // "type": 2, + // "price": "303.89000000", + // "datetime": "2017-11-11 07:22:20.710567" + // }]} + // + // from create order response: + // { + // price: '0.00008012', + // currency_pair: 'XRP/BTC', + // datetime: '2019-01-31 21:23:36', + // amount: '15.00000000', + // type: '0', + // id: '2814205012' + // } + // + const id = this.safeString (order, 'id'); + let side = this.safeString (order, 'type'); + if (side !== undefined) { + side = (side === '1') ? 'sell' : 'buy'; + } + // there is no timestamp from fetchOrder + const timestamp = this.parse8601 (this.safeString (order, 'datetime')); + let lastTradeTimestamp = undefined; + let symbol = undefined; + let marketId = this.safeStringLower (order, 'currency_pair'); + if (marketId !== undefined) { + marketId = marketId.replace ('/', ''); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } + } + let amount = this.safeFloat (order, 'amount'); + let filled = 0.0; + const trades = []; + const transactions = this.safeValue (order, 'transactions', []); + let feeCost = undefined; + let cost = undefined; + const numTransactions = transactions.length; + if (numTransactions > 0) { + feeCost = 0.0; + for (let i = 0; i < numTransactions; i++) { + const trade = this.parseTrade (this.extend ({ + 'order_id': id, + 'side': side, + }, transactions[i]), market); + filled = this.sum (filled, trade['amount']); + feeCost = this.sum (feeCost, trade['fee']['cost']); + if (cost === undefined) { + cost = 0.0; + } + cost = this.sum (cost, trade['cost']); + trades.push (trade); + } + lastTradeTimestamp = trades[numTransactions - 1]['timestamp']; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + if ((status === 'closed') && (amount === undefined)) { + amount = filled; + } + let remaining = undefined; + if (amount !== undefined) { + remaining = amount - filled; + } + let price = this.safeFloat (order, 'price'); + if (market === undefined) { + market = this.getMarketFromTrades (trades); + } + let feeCurrency = undefined; + if (market !== undefined) { + if (symbol === undefined) { + symbol = market['symbol']; + } + feeCurrency = market['quote']; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } else if (price === undefined) { + if (filled > 0) { + price = cost / filled; + } + } + let fee = undefined; + if (feeCost !== undefined) { + if (feeCurrency !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + parseLedgerEntryType (type) { + const types = { + '0': 'transaction', + '1': 'transaction', + '2': 'trade', + '14': 'transfer', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // [ + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1234567894, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-08 09:00:31", + // "type": "1", + // "xrp": "-20.00000000", + // "eur": 0, + // }, + // { + // "fee": "0.00000000", + // "btc_usd": "0.00", + // "id": 1134567891, + // "usd": 0, + // "btc": 0, + // "datetime": "2018-09-07 18:47:52", + // "type": "0", + // "xrp": "20.00000000", + // "eur": 0, + // }, + // ] + // + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + if (type === 'trade') { + const parsedTrade = this.parseTrade (item); + let market = undefined; + const keys = Object.keys (item); + for (let i = 0; i < keys.length; i++) { + if (keys[i].indexOf ('_') >= 0) { + const marketId = keys[i].replace ('_', ''); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + } + } + // if the market is still not defined + // try to deduce it from used keys + if (market === undefined) { + market = this.getMarketFromTrade (item); + } + const direction = parsedTrade['side'] === 'buy' ? 'in' : 'out'; + return { + 'id': parsedTrade['id'], + 'info': item, + 'timestamp': parsedTrade['timestamp'], + 'datetime': parsedTrade['datetime'], + 'direction': direction, + 'account': undefined, + 'referenceId': parsedTrade['order'], + 'referenceAccount': undefined, + 'type': type, + 'currency': market['base'], + 'amount': parsedTrade['amount'], + 'before': undefined, + 'after': undefined, + 'status': 'ok', + 'fee': parsedTrade['fee'], + }; + } else { + const parsedTransaction = this.parseTransaction (item); + let direction = undefined; + if ('amount' in item) { + const amount = this.safeFloat (item, 'amount'); + direction = amount > 0 ? 'in' : 'out'; + } else if (('currency' in parsedTransaction) && parsedTransaction['currency'] !== undefined) { + const currencyId = this.currencyId (parsedTransaction['currency']); + const amount = this.safeFloat (item, currencyId); + direction = amount > 0 ? 'in' : 'out'; + } + return { + 'id': parsedTransaction['id'], + 'info': item, + 'timestamp': parsedTransaction['timestamp'], + 'datetime': parsedTransaction['datetime'], + 'direction': direction, + 'account': undefined, + 'referenceId': parsedTransaction['txid'], + 'referenceAccount': undefined, + 'type': type, + 'currency': parsedTransaction['currency'], + 'amount': parsedTransaction['amount'], + 'before': undefined, + 'after': undefined, + 'status': parsedTransaction['status'], + 'fee': parsedTransaction['fee'], + }; + } + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostUserTransactions (this.extend (request, params)); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseLedger (response, currency, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + let market = undefined; + await this.loadMarkets (); + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privatePostOpenOrdersAll (params); + // [ + // { + // price: '0.00008012', + // currency_pair: 'XRP/BTC', + // datetime: '2019-01-31 21:23:36', + // amount: '15.00000000', + // type: '0', + // id: '2814205012', + // } + // ] + // + return this.parseOrders (response, market, since, limit, { + 'status': 'open', + 'type': 'limit', + }); + } + + getCurrencyName (code) { + if (code === 'BTC') { + return 'bitcoin'; + } + return code.toLowerCase (); + } + + isFiat (code) { + return code === 'USD' || code === 'EUR' || code === 'GBP'; + } + + async fetchDepositAddress (code, params = {}) { + if (this.isFiat (code)) { + throw new NotSupported (this.id + ' fiat fetchDepositAddress() for ' + code + ' is not supported!'); + } + const name = this.getCurrencyName (code); + const v1 = (code === 'BTC'); + let method = v1 ? 'v1' : 'private'; // v1 or v2 + method += 'Post' + this.capitalize (name); + method += v1 ? 'Deposit' : ''; + method += 'Address'; + let response = await this[method] (params); + if (v1) { + response = JSON.parse (response); + } + const address = v1 ? response : this.safeString (response, 'address'); + const tag = v1 ? undefined : this.safeString2 (response, 'memo_id', 'destination_tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // For fiat withdrawals please provide all required additional parameters in the 'params' + // Check https://www.bitstamp.net/api/ under 'Open bank withdrawal' for list and description. + await this.loadMarkets (); + this.checkAddress (address); + const request = { + 'amount': amount, + }; + let method = undefined; + if (!this.isFiat (code)) { + const name = this.getCurrencyName (code); + const v1 = (code === 'BTC'); + method = v1 ? 'v1' : 'private'; // v1 or v2 + method += 'Post' + this.capitalize (name) + 'Withdrawal'; + if (code === 'XRP') { + if (tag !== undefined) { + request['destination_tag'] = tag; + } + } + request['address'] = address; + } else { + method = 'privatePostWithdrawalOpen'; + const currency = this.currency (code); + request['iban'] = address; + request['account_currency'] = currency['id']; + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + if (api !== 'v1') { + url += this.version + '/'; + } + url += this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const authVersion = this.safeValue (this.options, 'auth', 'v2'); + if ((authVersion === 'v1') || (api === 'v1')) { + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret))); + query = this.extend ({ + 'key': this.apiKey, + 'signature': signature.toUpperCase (), + 'nonce': nonce, + }, query); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } else { + const xAuth = 'BITSTAMP ' + this.apiKey; + const xAuthNonce = this.uuid (); + const xAuthTimestamp = this.milliseconds ().toString (); + const xAuthVersion = 'v2'; + let contentType = ''; + headers = { + 'X-Auth': xAuth, + 'X-Auth-Nonce': xAuthNonce, + 'X-Auth-Timestamp': xAuthTimestamp, + 'X-Auth-Version': xAuthVersion, + }; + if (method === 'POST') { + if (Object.keys (query).length) { + body = this.urlencode (query); + contentType = 'application/x-www-form-urlencoded'; + headers['Content-Type'] = contentType; + } else { + // sending an empty POST request will trigger + // an API0020 error returned by the exchange + // therefore for empty requests we send a dummy object + // https://github.com/ccxt/ccxt/issues/6846 + body = this.urlencode ({ 'foo': 'bar' }); + contentType = 'application/x-www-form-urlencoded'; + headers['Content-Type'] = contentType; + } + } + const authBody = body ? body : ''; + const auth = xAuth + method + url.replace ('https://', '') + contentType + xAuthNonce + xAuthTimestamp + xAuthVersion + authBody; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers['X-Auth-Signature'] = signature; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error": "No permission found"} // fetchDepositAddress returns this on apiKeys that don't have the permission required + // {"status": "error", "reason": {"__all__": ["Minimum order size is 5.0 EUR."]}} + // reuse of a nonce gives: { status: 'error', reason: 'Invalid nonce', code: 'API0004' } + const status = this.safeString (response, 'status'); + const error = this.safeValue (response, 'error'); + if ((status === 'error') || (error !== undefined)) { + let errors = []; + if (typeof error === 'string') { + errors.push (error); + } else if (error !== undefined) { + const keys = Object.keys (error); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = this.safeValue (error, key); + if (Array.isArray (value)) { + errors = this.arrayConcat (errors, value); + } else { + errors.push (value); + } + } + } + const reason = this.safeValue (response, 'reason', {}); + if (typeof reason === 'string') { + errors.push (reason); + } else { + const all = this.safeValue (reason, '__all__', []); + for (let i = 0; i < all.length; i++) { + errors.push (all[i]); + } + } + const code = this.safeString (response, 'code'); + if (code === 'API0005') { + throw new AuthenticationError (this.id + ' invalid signature, use the uid for the main account if you have subaccounts'); + } + const feedback = this.id + ' ' + body; + for (let i = 0; i < errors.length; i++) { + const value = errors[i]; + this.throwExactlyMatchedException (this.exceptions['exact'], value, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], value, feedback); + } + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitstamp1.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitstamp1.js new file mode 100644 index 000000000..8d8de5db1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitstamp1.js @@ -0,0 +1,299 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitstamp1 extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitstamp1', + 'name': 'Bitstamp', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMyTrades': true, + 'fetchOrder': false, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg', + 'api': 'https://www.bitstamp.net/api', + 'www': 'https://www.bitstamp.net', + 'doc': 'https://www.bitstamp.net/api', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'ticker', + 'ticker_hour', + 'order_book', + 'transactions', + 'eur_usd', + ], + }, + 'private': { + 'post': [ + 'balance', + 'user_transactions', + 'open_orders', + 'order_status', + 'cancel_order', + 'cancel_all_orders', + 'buy', + 'sell', + 'bitcoin_deposit_address', + 'unconfirmed_btc', + 'ripple_withdrawal', + 'ripple_address', + 'withdrawal_requests', + 'bitcoin_withdrawal', + ], + }, + }, + 'markets': { + 'BTC/USD': { 'id': 'btcusd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'BTC/EUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'EUR/USD': { 'id': 'eurusd', 'symbol': 'EUR/USD', 'base': 'EUR', 'quote': 'USD', 'baseId': 'eur', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'XRP/USD': { 'id': 'xrpusd', 'symbol': 'XRP/USD', 'base': 'XRP', 'quote': 'USD', 'baseId': 'xrp', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'XRP/EUR': { 'id': 'xrpeur', 'symbol': 'XRP/EUR', 'base': 'XRP', 'quote': 'EUR', 'baseId': 'xrp', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'XRP/BTC': { 'id': 'xrpbtc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 }, + 'LTC/USD': { 'id': 'ltcusd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'ltc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'LTC/EUR': { 'id': 'ltceur', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'ltc', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'LTC/BTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 }, + 'ETH/USD': { 'id': 'ethusd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'eth', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 }, + 'ETH/EUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'eth', 'quoteId': 'eur', 'maker': 0.005, 'taker': 0.005 }, + 'ETH/BTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 }, + }, + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + if (symbol !== 'BTC/USD') { + throw new ExchangeError (this.id + ' ' + this.version + " fetchOrderBook doesn't support " + symbol + ', use it for BTC/USD only'); + } + await this.loadMarkets (); + const orderbook = await this.publicGetOrderBook (params); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + async fetchTicker (symbol, params = {}) { + if (symbol !== 'BTC/USD') { + throw new ExchangeError (this.id + ' ' + this.version + " fetchTicker doesn't support " + symbol + ', use it for BTC/USD only'); + } + await this.loadMarkets (); + const ticker = await this.publicGetTicker (params); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp2 (trade, 'date', 'datetime'); + const side = (trade['type'] === 0) ? 'buy' : 'sell'; + const orderId = this.safeString (trade, 'order_id'); + if ('currency_pair' in trade) { + if (trade['currency_pair'] in this.markets_by_id) { + market = this.markets_by_id[trade['currency_pair']]; + } + } + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + if (symbol !== 'BTC/USD') { + throw new BadSymbol (this.id + ' ' + this.version + " fetchTrades doesn't support " + symbol + ', use it for BTC/USD only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'time': 'minute', + }; + const response = await this.publicGetTransactions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + const balance = await this.privatePostBalance (params); + const result = { 'info': balance }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const account = this.account (); + account['free'] = this.safeFloat (balance, currencyId + '_available'); + account['used'] = this.safeFloat (balance, currencyId + '_reserved'); + account['total'] = this.safeFloat (balance, currencyId + '_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new ExchangeError (this.id + ' ' + this.version + ' accepts limit orders only'); + } + if (symbol !== 'BTC/USD') { + throw new ExchangeError (this.id + ' v1 supports BTC/USD orders only'); + } + await this.loadMarkets (); + const method = 'privatePost' + this.capitalize (side); + const request = { + 'amount': amount, + 'price': price, + }; + const response = await this[method] (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + return await this.privatePostCancelOrder ({ 'id': id }); + } + + parseOrderStatus (status) { + const statuses = { + 'In Queue': 'open', + 'Open': 'open', + 'Finished': 'closed', + 'Canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrderStatus (response); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const pair = market ? market['id'] : 'all'; + const request = { + 'id': pair, + }; + const response = await this.privatePostOpenOrdersId (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' fetchOrder is not implemented yet'); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.encode (this.hmac (this.encode (auth), this.encode (this.secret))); + query = this.extend ({ + 'key': this.apiKey, + 'signature': signature.toUpperCase (), + 'nonce': nonce, + }, query); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('status' in response) { + if (response['status'] === 'error') { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bittrex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bittrex.js new file mode 100644 index 000000000..36d09cbb8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bittrex.js @@ -0,0 +1,1373 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError, ExchangeNotAvailable, AuthenticationError, InvalidOrder, InsufficientFunds, OrderNotFound, DDoSProtection, PermissionDenied, AddressPending, OnMaintenance, BadRequest } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bittrex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bittrex', + 'name': 'Bittrex', + 'countries': [ 'US' ], + 'version': 'v3', + 'rateLimit': 1500, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDeposits': true, + 'fetchDepositAddress': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': 'emulated', + 'fetchOHLCV': true, + 'fetchOrder': true, + 'fetchOrderTrades': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'MINUTE_1', + '5m': 'MINUTE_5', + '1h': 'HOUR_1', + '1d': 'DAY_1', + }, + 'hostname': 'bittrex.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg', + 'api': { + 'public': 'https://api.bittrex.com', + 'private': 'https://api.bittrex.com', + }, + 'www': 'https://bittrex.com', + 'doc': [ + 'https://bittrex.github.io/api/v3', + ], + 'fees': [ + 'https://bittrex.zendesk.com/hc/en-us/articles/115003684371-BITTREX-SERVICE-FEES-AND-WITHDRAWAL-LIMITATIONS', + 'https://bittrex.zendesk.com/hc/en-us/articles/115000199651-What-fees-does-Bittrex-charge-', + ], + 'referral': 'https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B', + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'currencies', + 'currencies/{symbol}', + 'markets', + 'markets/tickers', + 'markets/summaries', + 'markets/{marketSymbol}', + 'markets/{marketSymbol}/summary', + 'markets/{marketSymbol}/orderbook', + 'markets/{marketSymbol}/trades', + 'markets/{marketSymbol}/ticker', + 'markets/{marketSymbol}/candles/{candleInterval}/recent', + 'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}/{month}/{day}', + 'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}/{month}', + 'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}', + ], + }, + 'private': { + 'get': [ + 'account', + 'account/volume', + 'addresses', + 'addresses/{currencySymbol}', + 'balances', + 'balances/{currencySymbol}', + 'deposits/open', + 'deposits/closed', + 'deposits/ByTxId/{txId}', + 'deposits/{depositId}', + 'orders/closed', + 'orders/open', + 'orders/{orderId}', + 'orders/{orderId}/executions', + 'ping', + 'subaccounts/{subaccountId}', + 'subaccounts', + 'withdrawals/open', + 'withdrawals/closed', + 'withdrawals/ByTxId/{txId}', + 'withdrawals/{withdrawalId}', + 'withdrawals/whitelistAddresses', + 'conditional-orders/{conditionalOrderId}', + 'conditional-orders/closed', + 'conditional-orders/open', + 'transfers/sent', + 'transfers/received', + 'transfers/{transferId}', + ], + 'post': [ + 'addresses', + 'orders', + 'subaccounts', + 'withdrawals', + 'conditional-orders', + 'transfers', + ], + 'delete': [ + 'orders/open', + 'orders/{orderId}', + 'withdrawals/{withdrawalId}', + 'conditional-orders/{conditionalOrderId}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0025, + 'taker': 0.0025, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + }, + }, + 'exceptions': { + 'exact': { + 'BAD_REQUEST': BadRequest, // {"code":"BAD_REQUEST","detail":"Refer to the data field for specific field validation failures.","data":{"invalidRequestParameter":"day"}} + 'STARTDATE_OUT_OF_RANGE': BadRequest, // {"code":"STARTDATE_OUT_OF_RANGE"} + // 'Call to Cancel was throttled. Try again in 60 seconds.': DDoSProtection, + // 'Call to GetBalances was throttled. Try again in 60 seconds.': DDoSProtection, + 'APISIGN_NOT_PROVIDED': AuthenticationError, + 'INVALID_SIGNATURE': AuthenticationError, + 'INVALID_CURRENCY': ExchangeError, + 'INVALID_PERMISSION': AuthenticationError, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'INVALID_CEILING_MARKET_BUY': InvalidOrder, + 'INVALID_FIAT_ACCOUNT': InvalidOrder, + 'INVALID_ORDER_TYPE': InvalidOrder, + 'QUANTITY_NOT_PROVIDED': InvalidOrder, + 'MIN_TRADE_REQUIREMENT_NOT_MET': InvalidOrder, + 'ORDER_NOT_OPEN': OrderNotFound, + 'INVALID_ORDER': InvalidOrder, + 'UUID_INVALID': OrderNotFound, + 'RATE_NOT_PROVIDED': InvalidOrder, // createLimitBuyOrder ('ETH/BTC', 1, 0) + 'INVALID_MARKET': BadSymbol, // {"success":false,"message":"INVALID_MARKET","result":null,"explanation":null} + 'WHITELIST_VIOLATION_IP': PermissionDenied, + 'DUST_TRADE_DISALLOWED_MIN_VALUE': InvalidOrder, + 'RESTRICTED_MARKET': BadSymbol, + 'We are down for scheduled maintenance, but we\u2019ll be back up shortly.': OnMaintenance, // {"success":false,"message":"We are down for scheduled maintenance, but we\u2019ll be back up shortly.","result":null,"explanation":null} + }, + 'broad': { + 'throttled': DDoSProtection, + 'problem': ExchangeNotAvailable, + }, + }, + 'options': { + 'fetchTicker': { + 'method': 'publicGetMarketsMarketSymbolTicker', // publicGetMarketsMarketSymbolSummary + }, + 'fetchTickers': { + 'method': 'publicGetMarketsTickers', // publicGetMarketsSummaries + }, + 'parseOrderStatus': false, + 'hasAlreadyAuthenticatedSuccessfully': false, // a workaround for APIKEY_INVALID + // With certain currencies, like + // AEON, BTS, GXS, NXT, SBD, STEEM, STR, XEM, XLM, XMR, XRP + // an additional tag / memo / payment id is usually required by exchanges. + // With Bittrex some currencies imply the "base address + tag" logic. + // The base address for depositing is stored on this.currencies[code] + // The base address identifies the exchange as the recipient + // while the tag identifies the user account within the exchange + // and the tag is retrieved with fetchDepositAddress. + 'tag': { + 'NXT': true, // NXT, BURST + 'CRYPTO_NOTE_PAYMENTID': true, // AEON, XMR + 'BITSHAREX': true, // BTS + 'RIPPLE': true, // XRP + 'NEM': true, // XEM + 'STELLAR': true, // XLM + 'STEEM': true, // SBD, GOLOS + // https://github.com/ccxt/ccxt/issues/4794 + // 'LISK': true, // LSK + }, + 'subaccountId': undefined, + // see the implementation of fetchClosedOrdersV3 below + // 'fetchClosedOrdersMethod': 'fetch_closed_orders_v3', + 'fetchClosedOrdersFilterBySince': true, + // 'createOrderMethod': 'create_order_v1', + }, + 'commonCurrencies': { + 'REPV2': 'REP', + }, + }); + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); + } + + feeToPrecision (symbol, fee) { + return this.decimalToPrecision (fee, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "symbol":"LTC-BTC", + // "baseCurrencySymbol":"LTC", + // "quoteCurrencySymbol":"BTC", + // "minTradeSize":"0.01686767", + // "precision":8, + // "status":"ONLINE", // "OFFLINE" + // "createdAt":"2014-02-13T00:00:00Z" + // }, + // { + // "symbol":"VDX-USDT", + // "baseCurrencySymbol":"VDX", + // "quoteCurrencySymbol":"USDT", + // "minTradeSize":"300.00000000", + // "precision":8, + // "status":"ONLINE", // "OFFLINE" + // "createdAt":"2019-05-23T00:41:21.843Z", + // "notice":"USDT has swapped to an ERC20-based token as of August 5, 2019." + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const baseId = this.safeString (market, 'baseCurrencySymbol'); + const quoteId = this.safeString (market, 'quoteCurrencySymbol'); + const id = this.safeString (market, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const pricePrecision = this.safeInteger (market, 'precision', 8); + const precision = { + 'amount': 8, + 'price': pricePrecision, + }; + const status = this.safeString (market, 'status'); + const active = (status === 'ONLINE'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minTradeSize'), + 'max': undefined, + }, + 'price': { + 'min': 1 / Math.pow (10, precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privateGetBalances (params); + const result = { 'info': balances }; + const indexed = this.indexBy (balances, 'currencySymbol'); + const currencyIds = Object.keys (indexed); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + const balance = indexed[currencyId]; + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'marketSymbol': this.marketId (symbol), + }; + if (limit !== undefined) { + if ((limit !== 1) && (limit !== 25) && (limit !== 500)) { + throw new BadRequest (this.id + ' fetchOrderBook() limit argument must be undefined, 1, 25 or 500, default is 25'); + } + request['depth'] = limit; + } + const response = await this.publicGetMarketsMarketSymbolOrderbook (this.extend (request, params)); + // + // { + // "bid":[ + // {"quantity":"0.01250000","rate":"10718.56200003"}, + // {"quantity":"0.10000000","rate":"10718.56200002"}, + // {"quantity":"0.39648292","rate":"10718.56200001"}, + // ], + // "ask":[ + // {"quantity":"0.05100000","rate":"10724.30099631"}, + // {"quantity":"0.10000000","rate":"10724.30099632"}, + // {"quantity":"0.26000000","rate":"10724.30099634"}, + // ] + // } + // + const sequence = this.safeInteger (this.last_response_headers, 'Sequence'); + const orderbook = this.parseOrderBook (response, undefined, 'bid', 'ask', 'rate', 'quantity'); + orderbook['nonce'] = sequence; + return orderbook; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // "symbol":"1ST", + // "name":"Firstblood", + // "coinType":"ETH_CONTRACT", + // "status":"ONLINE", + // "minConfirmations":36, + // "notice":"", + // "txFee":"4.50000000", + // "logoUrl":"https://bittrexblobstorage.blob.core.windows.net/public/5685a7be-1edf-4ba0-a313-b5309bb204f8.png", + // "prohibitedIn":[], + // "baseAddress":"0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98", + // "associatedTermsOfService":[] + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const precision = 8; // default precision, todo: fix "magic constants" + const fee = this.safeFloat (currency, 'txFee'); // todo: redesign + const isActive = this.safeString (currency, 'status'); + result[code] = { + 'id': id, + 'code': code, + 'address': this.safeString (currency, 'baseAddress'), + 'info': currency, + 'type': this.safeString (currency, 'coinType'), + 'name': this.safeString (currency, 'name'), + 'active': (isActive === 'ONLINE'), + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'price': { + 'min': 1 / Math.pow (10, precision), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': fee, + 'max': undefined, + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // ticker + // + // { + // "symbol":"ETH-BTC", + // "lastTradeRate":"0.03284496", + // "bidRate":"0.03284523", + // "askRate":"0.03286857" + // } + // + // summary + // + // { + // "symbol":"ETH-BTC", + // "high":"0.03369528", + // "low":"0.03282442", + // "volume":"4307.83794556", + // "quoteVolume":"143.08608869", + // "percentChange":"0.79", + // "updatedAt":"2020-09-29T07:36:57.823Z" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'updatedAt')); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const percentage = this.safeFloat (ticker, 'percentChange'); + const last = this.safeFloat (ticker, 'lastTradeRate'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bidRate'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'askRate'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchTickers', {}); + const defaultMethod = this.safeString (options, 'method', 'publicGetMarketsTickers'); + const method = this.safeString (params, 'method', defaultMethod); + params = this.omit (params, 'method'); + const response = await this[method] (params); + // + // publicGetMarketsTickers + // + // [ + // { + // "symbol":"4ART-BTC", + // "lastTradeRate":"0.00000210", + // "bidRate":"0.00000210", + // "askRate":"0.00000215" + // } + // ] + // + // publicGetMarketsSummaries + // + // [ + // { + // "symbol":"4ART-BTC", + // "high":"0.00000206", + // "low":"0.00000196", + // "volume":"14871.32000233", + // "quoteVolume":"0.02932756", + // "percentChange":"1.48", + // "updatedAt":"2020-09-29T07:34:32.757Z" + // } + // ] + // + const tickers = []; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + tickers.push (ticker); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketSymbol': market['id'], + }; + const options = this.safeValue (this.options, 'fetchTicker', {}); + const defaultMethod = this.safeString (options, 'method', 'publicGetMarketsMarketSymbolTicker'); + const method = this.safeString (params, 'method', defaultMethod); + params = this.omit (params, 'method'); + const response = await this[method] (this.extend (request, params)); + // + // publicGetMarketsMarketSymbolTicker + // + // { + // "symbol":"ETH-BTC", + // "lastTradeRate":"0.03284496", + // "bidRate":"0.03284523", + // "askRate":"0.03286857" + // } + // + // + // publicGetMarketsMarketSymbolSummary + // + // { + // "symbol":"ETH-BTC", + // "high":"0.03369528", + // "low":"0.03282442", + // "volume":"4307.83794556", + // "quoteVolume":"143.08608869", + // "percentChange":"0.79", + // "updatedAt":"2020-09-29T07:36:57.823Z" + // } + // + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "id":"9c5589db-42fb-436c-b105-5e2edcb95673", + // "executedAt":"2020-10-03T11:48:43.38Z", + // "quantity":"0.17939626", + // "rate":"0.03297952", + // "takerSide":"BUY" + // } + // + // private fetchOrderTrades + // + // { + // "id": "aaa3e9bd-5b86-4a21-8b3d-1275c1d30b8e", + // "marketSymbol": "OMG-BTC", + // "executedAt": "2020-10-02T16:00:30.3Z", + // "quantity": "7.52710000", + // "rate": "0.00034907", + // "orderId": "3a3dbd33-3a30-4ae5-a41d-68d3c1ac537e", + // "commission": "0.00000525", + // "isTaker": false + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'executedAt')); + const id = this.safeString (trade, 'id'); + const order = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'marketSymbol'); + market = this.safeMarket (marketId, market, '-'); + let cost = undefined; + const price = this.safeFloat (trade, 'rate'); + const amount = this.safeFloat (trade, 'quantity'); + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + let takerOrMaker = undefined; + const isTaker = this.safeValue (trade, 'isTaker'); + if (isTaker !== undefined) { + takerOrMaker = isTaker ? 'taker' : 'maker'; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + const side = this.safeStringLower (trade, 'takerSide'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'id': id, + 'order': order, + 'takerOrMaker': takerOrMaker, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { + // "serverTime": 1594596023162 + // } + // + return this.safeInteger (response, 'serverTime'); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketSymbol': this.marketId (symbol), + }; + const response = await this.publicGetMarketsMarketSymbolTrades (this.extend (request, params)); + // + // [ + // { + // "id":"9c5589db-42fb-436c-b105-5e2edcb95673", + // "executedAt":"2020-10-03T11:48:43.38Z", + // "quantity":"0.17939626", + // "rate":"0.03297952", + // "takerSide":"BUY" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "startsAt":"2020-06-12T02:35:00Z", + // "open":"0.02493753", + // "high":"0.02493753", + // "low":"0.02493753", + // "close":"0.02493753", + // "volume":"0.09590123", + // "quoteVolume":"0.00239153" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'startsAt')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const reverseId = market['baseId'] + '-' + market['quoteId']; + const request = { + 'candleInterval': this.timeframes[timeframe], + 'marketSymbol': reverseId, + }; + let method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalRecent'; + if (since !== undefined) { + const now = this.milliseconds (); + const difference = Math.abs (now - since); + const sinceDate = this.ymd (since); + const parts = sinceDate.split ('-'); + const sinceYear = this.safeInteger (parts, 0); + const sinceMonth = this.safeInteger (parts, 1); + const sinceDay = this.safeInteger (parts, 2); + if (timeframe === '1d') { + // if the since argument is beyond one year into the past + if (difference > 31622400000) { + method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYear'; + request['year'] = sinceYear; + } + // request['year'] = year; + } else if (timeframe === '1h') { + // if the since argument is beyond 31 days into the past + if (difference > 2678400000) { + method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYearMonth'; + request['year'] = sinceYear; + request['month'] = sinceMonth; + } + } else { + // if the since argument is beyond 1 day into the past + if (difference > 86400000) { + method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYearMonthDay'; + request['year'] = sinceYear; + request['month'] = sinceMonth; + request['day'] = sinceDay; + } + } + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // {"startsAt":"2020-06-12T02:35:00Z","open":"0.02493753","high":"0.02493753","low":"0.02493753","close":"0.02493753","volume":"0.09590123","quoteVolume":"0.00239153"}, + // {"startsAt":"2020-06-12T02:40:00Z","open":"0.02491874","high":"0.02491874","low":"0.02490970","close":"0.02490970","volume":"0.04515695","quoteVolume":"0.00112505"}, + // {"startsAt":"2020-06-12T02:45:00Z","open":"0.02490753","high":"0.02493143","low":"0.02490753","close":"0.02493143","volume":"0.17769640","quoteVolume":"0.00442663"} + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketSymbol'] = market['id']; + } + const response = await this.privateGetOrdersOpen (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateGetOrdersOrderIdExecutions (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + // A ceiling order is a market or limit order that allows you to specify + // the amount of quote currency you want to spend (or receive, if selling) + // instead of the quantity of the market currency (e.g. buy $100 USD of BTC + // at the current market BTC price) + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const reverseId = market['baseId'] + '-' + market['quoteId']; + const request = { + 'marketSymbol': reverseId, + 'direction': side.toUpperCase (), + 'type': uppercaseType, // LIMIT, MARKET, CEILING_LIMIT, CEILING_MARKET + // 'quantity': this.amountToPrecision (symbol, amount), // required for limit orders, excluded for ceiling orders + // 'ceiling': this.priceToPrecision (symbol, price), // required for ceiling orders, excluded for non-ceiling orders + // 'limit': this.priceToPrecision (symbol, price), // required for limit orders, excluded for market orders + // 'timeInForce': 'GOOD_TIL_CANCELLED', // IMMEDIATE_OR_CANCEL, FILL_OR_KILL, POST_ONLY_GOOD_TIL_CANCELLED + // 'useAwards': false, // optional + }; + const isCeilingLimit = (uppercaseType === 'CEILING_LIMIT'); + const isCeilingMarket = (uppercaseType === 'CEILING_MARKET'); + const isCeilingOrder = isCeilingLimit || isCeilingMarket; + if (isCeilingOrder) { + request['ceiling'] = this.priceToPrecision (symbol, price); + // bittrex only accepts IMMEDIATE_OR_CANCEL or FILL_OR_KILL for ceiling orders + request['timeInForce'] = 'IMMEDIATE_OR_CANCEL'; + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + if (uppercaseType === 'LIMIT') { + request['limit'] = this.priceToPrecision (symbol, price); + request['timeInForce'] = 'GOOD_TIL_CANCELLED'; + } else { + // bittrex does not allow GOOD_TIL_CANCELLED for market orders + request['timeInForce'] = 'IMMEDIATE_OR_CANCEL'; + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // id: 'f03d5e98-b5ac-48fb-8647-dd4db828a297', + // marketSymbol: 'BTC-USDT', + // direction: 'SELL', + // type: 'LIMIT', + // quantity: '0.01', + // limit: '6000', + // timeInForce: 'GOOD_TIL_CANCELLED', + // fillQuantity: '0.00000000', + // commission: '0.00000000', + // proceeds: '0.00000000', + // status: 'OPEN', + // createdAt: '2020-03-18T02:37:33.42Z', + // updatedAt: '2020-03-18T02:37:33.42Z' + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateDeleteOrdersOrderId (this.extend (request, params)); + return this.extend (this.parseOrder (response), { + 'id': id, + 'info': response, + 'status': 'canceled', + }); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketSymbol'] = market['id']; + } + const response = await this.privateDeleteOrdersOpen (this.extend (request, params)); + // + // [ + // { + // "id":"66582be0-5337-4d8c-b212-c356dd525801", + // "statusCode":"SUCCESS", + // "result":{ + // "id":"66582be0-5337-4d8c-b212-c356dd525801", + // "marketSymbol":"BTC-USDT", + // "direction":"BUY", + // "type":"LIMIT", + // "quantity":"0.01000000", + // "limit":"3000.00000000", + // "timeInForce":"GOOD_TIL_CANCELLED", + // "fillQuantity":"0.00000000", + // "commission":"0.00000000", + // "proceeds":"0.00000000", + // "status":"CLOSED", + // "createdAt":"2020-10-06T12:31:53.39Z", + // "updatedAt":"2020-10-06T12:54:28.8Z", + // "closedAt":"2020-10-06T12:54:28.8Z" + // } + // } + // ] + // + const orders = []; + for (let i = 0; i < response.length; i++) { + const result = this.safeValue (response[i], 'result', {}); + orders.push (result); + } + return this.parseOrders (orders, market); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // https://support.bittrex.com/hc/en-us/articles/115003723911 + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currencySymbol'] = currency['id']; + } + const response = await this.privateGetDepositsClosed (this.extend (request, params)); + // we cannot filter by `since` timestamp, as it isn't set by Bittrex + // see https://github.com/ccxt/ccxt/issues/4067 + // return this.parseTransactions (response, currency, since, limit); + return this.parseTransactions (response, currency, undefined, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // https://support.bittrex.com/hc/en-us/articles/115003723911 + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currencySymbol'] = currency['id']; + } + const response = await this.privateGetWithdrawalsClosed (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // { + // "id": "d00fdf2e-df9e-48f1-....", + // "currencySymbol": "BTC", + // "quantity": "0.00550000", + // "cryptoAddress": "1PhmYjnJPZH5NUwV8AU...", + // "txId": "d1f1afffe1b9b6614eaee7e8133c85d98...", + // "confirmations": 2, + // "updatedAt": "2020-01-12T16:49:30.41Z", + // "completedAt": "2020-01-12T16:49:30.41Z", + // "status": "COMPLETED", + // "source": "BLOCKCHAIN" + // } + // + // fetchWithdrawals + // { + // "PaymentUuid" : "e293da98-788c-4188-a8f9-8ec2c33fdfcf", + // "Currency" : "XC", + // "Amount" : 7513.75121715, + // "Address" : "EVnSMgAd7EonF2Dgc4c9K14L12RBaW5S5J", + // "Opened" : "2014-07-08T23:13:31.83", + // "Authorized" : true, + // "PendingPayment" : false, + // "TxCost" : 0.00002000, + // "TxId" : "b4a575c2a71c7e56d02ab8e26bb1ef0a2f6cf2094f6ca2116476a569c1e84f6e", + // "Canceled" : false, + // "InvalidAddress" : false + // } + // + const id = this.safeString (transaction, 'id'); + const amount = this.safeFloat (transaction, 'quantity'); + const address = this.safeString (transaction, 'cryptoAddress'); + const txid = this.safeString (transaction, 'txId'); + const updated = this.parse8601 (this.safeString (transaction, 'updatedAt')); + const opened = this.parse8601 (this.safeString (transaction, 'createdAt')); + const timestamp = opened ? opened : updated; + const type = (opened === undefined) ? 'deposit' : 'withdrawal'; + const currencyId = this.safeString (transaction, 'currencySymbol'); + const code = this.safeCurrencyCode (currencyId, currency); + let status = 'pending'; + if (type === 'deposit') { + // + // deposits numConfirmations never reach the minConfirmations number + // we set all of them to 'ok', otherwise they'd all be 'pending' + // + // const numConfirmations = this.safeInteger (transaction, 'Confirmations', 0); + // const minConfirmations = this.safeInteger (currency['info'], 'MinConfirmation'); + // if (numConfirmations >= minConfirmations) { + // status = 'ok'; + // } + // + status = 'ok'; + } else { + const responseStatus = this.safeString (transaction, 'status'); + if (responseStatus === 'ERROR_INVALID_ADDRESS') { + status = 'failed'; + } else if (responseStatus === 'CANCELLED') { + status = 'canceled'; + } else if (responseStatus === 'PENDING') { + status = 'pending'; + } else if (responseStatus === 'COMPLETED') { + status = 'ok'; + } else if (responseStatus === 'AUTHORIZED' && (txid !== undefined)) { + status = 'ok'; + } + } + let feeCost = this.safeFloat (transaction, 'txCost'); + if (feeCost === undefined) { + if (type === 'deposit') { + // according to https://support.bittrex.com/hc/en-us/articles/115000199651-What-fees-does-Bittrex-charge- + feeCost = 0; + } + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GOOD_TIL_CANCELLED': 'GTC', + 'IMMEDIATE_OR_CANCEL': 'IOC', + 'FILL_OR_KILL': 'FOK', + 'POST_ONLY_GOOD_TIL_CANCELLED': 'POST_ONLY_GOOD_TIL_CANCELLED', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + // + // { + // id: '1be35109-b763-44ce-b6ea-05b6b0735c0c', + // marketSymbol: 'LTC-ETH', + // direction: 'BUY', + // type: 'LIMIT', + // quantity: '0.50000000', + // limit: '0.17846699', + // timeInForce: 'GOOD_TIL_CANCELLED', + // fillQuantity: '0.50000000', + // commission: '0.00022286', + // proceeds: '0.08914915', + // status: 'CLOSED', + // createdAt: '2018-06-23T13:14:28.613Z', + // updatedAt: '2018-06-23T13:14:30.19Z', + // closedAt: '2018-06-23T13:14:30.19Z' + // } + // + const marketSymbol = this.safeString (order, 'marketSymbol'); + let symbol = undefined; + let feeCurrency = undefined; + if (marketSymbol !== undefined) { + const [ baseId, quoteId ] = marketSymbol.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + feeCurrency = quote; + } + const direction = this.safeStringLower (order, 'direction'); + const createdAt = this.safeString (order, 'createdAt'); + const updatedAt = this.safeString (order, 'updatedAt'); + const closedAt = this.safeString (order, 'closedAt'); + let lastTradeTimestamp = undefined; + if (closedAt !== undefined) { + lastTradeTimestamp = this.parse8601 (closedAt); + } else if (updatedAt) { + lastTradeTimestamp = this.parse8601 (updatedAt); + } + const timestamp = this.parse8601 (createdAt); + const type = this.safeStringLower (order, 'type'); + const quantity = this.safeFloat (order, 'quantity'); + const limit = this.safeFloat (order, 'limit'); + const fillQuantity = this.safeFloat (order, 'fillQuantity'); + const commission = this.safeFloat (order, 'commission'); + const proceeds = this.safeFloat (order, 'proceeds'); + let average = undefined; + let remaining = undefined; + if (fillQuantity !== undefined) { + if (proceeds !== undefined) { + if (fillQuantity > 0) { + average = proceeds / fillQuantity; + } else if (proceeds === 0) { + average = 0; + } + } + if (quantity !== undefined) { + remaining = quantity - fillQuantity; + } + } + let status = this.safeStringLower (order, 'status'); + if ((status === 'closed') && (remaining !== undefined) && (remaining > 0)) { + status = 'canceled'; + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': direction, + 'price': limit, + 'stopPrice': undefined, + 'cost': proceeds, + 'average': average, + 'amount': quantity, + 'filled': fillQuantity, + 'remaining': remaining, + 'status': status, + 'fee': { + 'cost': commission, + 'currency': feeCurrency, + }, + 'info': order, + 'trades': undefined, + }; + } + + parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { + if (this.options['fetchClosedOrdersFilterBySince']) { + return super.parseOrders (orders, market, since, limit, params); + } else { + return super.parseOrders (orders, market, undefined, limit, params); + } + } + + parseOrderStatus (status) { + const statuses = { + 'CLOSED': 'closed', + 'OPEN': 'open', + 'CANCELLED': 'canceled', + 'CANCELED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let response = undefined; + try { + const request = { + 'orderId': id, + }; + response = await this.privateGetOrdersOrderId (this.extend (request, params)); + } catch (e) { + if (this.last_json_response) { + const message = this.safeString (this.last_json_response, 'message'); + if (message === 'UUID_INVALID') { + throw new OrderNotFound (this.id + ' fetchOrder() error: ' + this.last_http_response); + } + } + throw e; + } + return this.parseOrder (response); + } + + orderToTrade (order) { + // this entire method should be moved to the base class + const timestamp = this.safeInteger2 (order, 'lastTradeTimestamp', 'timestamp'); + return { + 'id': this.safeString (order, 'id'), + 'side': this.safeString (order, 'side'), + 'order': this.safeString (order, 'id'), + 'type': this.safeString (order, 'type'), + 'price': this.safeFloat (order, 'average'), + 'amount': this.safeFloat (order, 'filled'), + 'cost': this.safeFloat (order, 'cost'), + 'symbol': this.safeString (order, 'symbol'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': this.safeValue (order, 'fee'), + 'info': order, + 'takerOrMaker': undefined, + }; + } + + ordersToTrades (orders) { + // this entire method should be moved to the base class + const result = []; + for (let i = 0; i < orders.length; i++) { + result.push (this.orderToTrade (orders[i])); + } + return result; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['pageSize'] = limit; + } + if (since !== undefined) { + request['startDate'] = this.ymdhms (since, 'T') + 'Z'; + } + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + // because of this line we will have to rethink the entire v3 + // in other words, markets define all the rest of the API + // and v3 market ids are reversed in comparison to v1 + // v3 has to be a completely separate implementation + // otherwise we will have to shuffle symbols and currencies everywhere + // which is prone to errors, as was shown here + // https://github.com/ccxt/ccxt/pull/5219#issuecomment-499646209 + request['marketSymbol'] = market['base'] + '-' + market['quote']; + } + const response = await this.privateGetOrdersClosed (this.extend (request, params)); + const orders = this.parseOrders (response, market); + const trades = this.ordersToTrades (orders); + return this.filterBySymbolSinceLimit (trades, symbol, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['pageSize'] = limit; + } + if (since !== undefined) { + request['startDate'] = this.ymdhms (since, 'T') + 'Z'; + } + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + // because of this line we will have to rethink the entire v3 + // in other words, markets define all the rest of the API + // and v3 market ids are reversed in comparison to v1 + // v3 has to be a completely separate implementation + // otherwise we will have to shuffle symbols and currencies everywhere + // which is prone to errors, as was shown here + // https://github.com/ccxt/ccxt/pull/5219#issuecomment-499646209 + request['marketSymbol'] = market['base'] + '-' + market['quote']; + } + const response = await this.privateGetOrdersClosed (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencySymbol': currency['id'], + }; + const response = await this.privateGetAddressesCurrencySymbol (this.extend (request, params)); + // { + // "status": "PROVISIONED", + // "currencySymbol": "BTC", + // "cryptoAddress": "1PhmYjnJPZH5NUwV8AUjqkeDkCBpbE2xqX" + // } + let address = this.safeString (response, 'cryptoAddress'); + const message = this.safeString (response, 'status'); + if (!address || message === 'REQUESTED') { + throw new AddressPending (this.id + ' the address for ' + code + ' is being generated (pending, not ready yet, retry again later)'); + } + let tag = undefined; + if (currency['type'] in this.options['tag']) { + tag = address; + address = currency['address']; + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencySymbol': currency['id'], + 'quantity': amount, + 'cryptoAddress': address, + }; + if (tag !== undefined) { + request['cryptoAddressTag'] = tag; + } + const response = await this.privatePostWithdrawals (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'v3', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }) + '/'; + if (api === 'private') { + url += this.version + '/'; + this.checkRequiredCredentials (); + url += this.implodeParams (path, params); + params = this.omit (params, this.extractParams (path)); + let hashString = ''; + if (method === 'POST') { + body = this.json (params); + hashString = body; + } else { + if (Object.keys (params).length) { + url += '?' + this.rawencode (params); + } + } + const contentHash = this.hash (this.encode (hashString), 'sha512', 'hex'); + const timestamp = this.milliseconds ().toString (); + let auth = timestamp + url + method + contentHash; + const subaccountId = this.safeValue (this.options, 'subaccountId'); + if (subaccountId !== undefined) { + auth += subaccountId; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'); + headers = { + 'Api-Key': this.apiKey, + 'Api-Timestamp': timestamp, + 'Api-Content-Hash': contentHash, + 'Api-Signature': signature, + }; + if (subaccountId !== undefined) { + headers['Api-Subaccount-Id'] = subaccountId; + } + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + } + } else { + if (api === 'public') { + url += this.version + '/'; + } + url += this.implodeParams (path, params); + params = this.omit (params, this.extractParams (path)); + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { success: false, message: "message" } + // + if (body[0] === '{') { + const feedback = this.id + ' ' + body; + let success = this.safeValue (response, 'success'); + if (success === undefined) { + const code = this.safeString (response, 'code'); + if (code !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], code, feedback); + } + // throw new ExchangeError (this.id + ' malformed response ' + this.json (response)); + return; + } + if (typeof success === 'string') { + // bleutrade uses string instead of boolean + success = (success === 'true'); + } + if (!success) { + const message = this.safeString (response, 'message'); + if (message === 'APIKEY_INVALID') { + if (this.options['hasAlreadyAuthenticatedSuccessfully']) { + throw new DDoSProtection (feedback); + } else { + throw new AuthenticationError (feedback); + } + } + // https://github.com/ccxt/ccxt/issues/4932 + // the following two lines are now redundant, see line 171 in describe() + // + // if (message === 'DUST_TRADE_DISALLOWED_MIN_VALUE_50K_SAT') + // throw new InvalidOrder (this.id + ' order cost should be over 50k satoshi ' + this.json (response)); + // + if (message === 'INVALID_ORDER') { + // Bittrex will return an ambiguous INVALID_ORDER message + // upon canceling already-canceled and closed orders + // therefore this special case for cancelOrder + // let url = 'https://bittrex.com/api/v1.1/market/cancel?apikey=API_KEY&uuid=ORDER_UUID' + const cancel = 'cancel'; + const indexOfCancel = url.indexOf (cancel); + if (indexOfCancel >= 0) { + const urlParts = url.split ('?'); + const numParts = urlParts.length; + if (numParts > 1) { + const query = urlParts[1]; + const params = query.split ('&'); + const numParams = params.length; + let orderId = undefined; + for (let i = 0; i < numParams; i++) { + const param = params[i]; + const keyValue = param.split ('='); + if (keyValue[0] === 'uuid') { + orderId = keyValue[1]; + break; + } + } + if (orderId !== undefined) { + throw new OrderNotFound (this.id + ' cancelOrder ' + orderId + ' ' + this.json (response)); + } else { + throw new OrderNotFound (this.id + ' cancelOrder ' + this.json (response)); + } + } + } + } + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + if (message !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitvavo.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitvavo.js new file mode 100644 index 000000000..10d62cfeb --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitvavo.js @@ -0,0 +1,1495 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, InvalidAddress, BadRequest, RateLimitExceeded, PermissionDenied, ExchangeNotAvailable, AccountSuspended, OnMaintenance } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class bitvavo extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitvavo', + 'name': 'Bitvavo', + 'countries': [ 'NL' ], // Netherlands + 'rateLimit': 500, + 'version': 'v2', + 'certified': true, + 'pro': true, + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderBook': true, + 'fetchMarkets': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/83165440-2f1cf200-a116-11ea-9046-a255d09fb2ed.jpg', + 'api': { + 'public': 'https://api.bitvavo.com', + 'private': 'https://api.bitvavo.com', + }, + 'www': 'https://bitvavo.com/', + 'doc': 'https://docs.bitvavo.com/', + 'fees': 'https://bitvavo.com/en/fees', + 'referral': 'https://bitvavo.com/?a=24F34952F7', + }, + 'api': { + 'public': { + 'get': [ + 'time', + 'markets', + 'assets', + '{market}/book', + '{market}/trades', + '{market}/candles', + 'ticker/price', + 'ticker/book', + 'ticker/24h', + ], + }, + 'private': { + 'get': [ + 'order', + 'orders', + 'ordersOpen', + 'trades', + 'balance', + 'deposit', + 'depositHistory', + 'withdrawalHistory', + ], + 'post': [ + 'order', + 'withdrawal', + ], + 'put': [ + 'order', + ], + 'delete': [ + 'order', + 'orders', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.25 / 100, + 'maker': 0.20 / 100, + 'tiers': { + 'taker': [ + [ 0, 0.0025 ], + [ 50000, 0.0024 ], + [ 100000, 0.0022 ], + [ 250000, 0.0020 ], + [ 500000, 0.0018 ], + [ 1000000, 0.0016 ], + [ 2500000, 0.0014 ], + [ 5000000, 0.0012 ], + [ 10000000, 0.0010 ], + ], + 'maker': [ + [ 0, 0.0020 ], + [ 50000, 0.0015 ], + [ 100000, 0.0010 ], + [ 250000, 0.0006 ], + [ 500000, 0.0003 ], + [ 1000000, 0.0001 ], + [ 2500000, -0.0001 ], + [ 5000000, -0.0003 ], + [ 10000000, -0.0005 ], + ], + }, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'exceptions': { + 'exact': { + '101': ExchangeError, // Unknown error. Operation may or may not have succeeded. + '102': BadRequest, // Invalid JSON. + '103': RateLimitExceeded, // You have been rate limited. Please observe the Bitvavo-Ratelimit-AllowAt header to see when you can send requests again. Failure to respect this limit will result in an IP ban. The default value is 1000 weighted requests per minute. Please contact support if you wish to increase this limit. + '104': RateLimitExceeded, // You have been rate limited by the number of new orders. The default value is 100 new orders per second or 100.000 new orders per day. Please update existing orders instead of cancelling and creating orders. Please contact support if you wish to increase this limit. + '105': PermissionDenied, // Your IP or API key has been banned for not respecting the rate limit. The ban expires at ${expiryInMs}. + '107': ExchangeNotAvailable, // The matching engine is overloaded. Please wait 500ms and resubmit your order. + '108': ExchangeNotAvailable, // The matching engine could not process your order in time. Please consider increasing the access window or resubmit your order. + '109': ExchangeNotAvailable, // The matching engine did not respond in time. Operation may or may not have succeeded. + '110': BadRequest, // Invalid endpoint. Please check url and HTTP method. + '200': BadRequest, // ${param} url parameter is not supported. Please note that parameters are case-sensitive and use body parameters for PUT and POST requests. + '201': BadRequest, // ${param} body parameter is not supported. Please note that parameters are case-sensitive and use url parameters for GET and DELETE requests. + '202': BadRequest, // ${param} order parameter is not supported. Please note that certain parameters are only allowed for market or limit orders. + '203': BadSymbol, // {"errorCode":203,"error":"symbol parameter is required."} + '204': BadRequest, // ${param} parameter is not supported. + '205': BadRequest, // ${param} parameter is invalid. + '206': BadRequest, // Use either ${paramA} or ${paramB}. The usage of both parameters at the same time is not supported. + '210': InvalidOrder, // Amount exceeds the maximum allowed amount (1000000000). + '211': InvalidOrder, // Price exceeds the maximum allowed amount (100000000000). + '212': InvalidOrder, // Amount is below the minimum allowed amount for this asset. + '213': InvalidOrder, // Price is below the minimum allowed amount (0.000000000000001). + '214': InvalidOrder, // Price is too detailed + '215': InvalidOrder, // Price is too detailed. A maximum of 15 digits behind the decimal point are allowed. + '216': InsufficientFunds, // {"errorCode":216,"error":"You do not have sufficient balance to complete this operation."} + '217': InvalidOrder, // {"errorCode":217,"error":"Minimum order size in quote currency is 5 EUR or 0.001 BTC."} + '230': ExchangeError, // The order is rejected by the matching engine. + '231': ExchangeError, // The order is rejected by the matching engine. TimeInForce must be GTC when markets are paused. + '232': BadRequest, // You must change at least one of amount, amountRemaining, price, timeInForce, selfTradePrevention or postOnly. + '233': InvalidOrder, // {"errorCode":233,"error":"Order must be active (status new or partiallyFilled) to allow updating/cancelling."} + '234': InvalidOrder, // Market orders cannot be updated. + '235': ExchangeError, // You can only have 100 open orders on each book. + '236': BadRequest, // You can only update amount or amountRemaining, not both. + '240': OrderNotFound, // {"errorCode":240,"error":"No order found. Please be aware that simultaneously updating the same order may return this error."} + '300': AuthenticationError, // Authentication is required for this endpoint. + '301': AuthenticationError, // {"errorCode":301,"error":"API Key must be of length 64."} + '302': AuthenticationError, // Timestamp is invalid. This must be a timestamp in ms. See Bitvavo-Access-Timestamp header or timestamp parameter for websocket. + '303': AuthenticationError, // Window must be between 100 and 60000 ms. + '304': AuthenticationError, // Request was not received within acceptable window (default 30s, or custom with Bitvavo-Access-Window header) of Bitvavo-Access-Timestamp header (or timestamp parameter for websocket). + // '304': AuthenticationError, // Authentication is required for this endpoint. + '305': AuthenticationError, // {"errorCode":305,"error":"No active API key found."} + '306': AuthenticationError, // No active API key found. Please ensure that you have confirmed the API key by e-mail. + '307': PermissionDenied, // This key does not allow access from this IP. + '308': AuthenticationError, // {"errorCode":308,"error":"The signature length is invalid (HMAC-SHA256 should return a 64 length hexadecimal string)."} + '309': AuthenticationError, // {"errorCode":309,"error":"The signature is invalid."} + '310': PermissionDenied, // This key does not allow trading actions. + '311': PermissionDenied, // This key does not allow showing account information. + '312': PermissionDenied, // This key does not allow withdrawal of funds. + '315': BadRequest, // Websocket connections may not be used in a browser. Please use REST requests for this. + '317': AccountSuspended, // This account is locked. Please contact support. + '400': ExchangeError, // Unknown error. Please contact support with a copy of your request. + '401': ExchangeError, // Deposits for this asset are not available at this time. + '402': PermissionDenied, // You need to verify your identitiy before you can deposit and withdraw digital assets. + '403': PermissionDenied, // You need to verify your phone number before you can deposit and withdraw digital assets. + '404': OnMaintenance, // Could not complete this operation, because our node cannot be reached. Possibly under maintenance. + '405': ExchangeError, // You cannot withdraw digital assets during a cooldown period. This is the result of newly added bank accounts. + '406': BadRequest, // {"errorCode":406,"error":"Your withdrawal is too small."} + '407': ExchangeError, // Internal transfer is not possible. + '408': InsufficientFunds, // {"errorCode":408,"error":"You do not have sufficient balance to complete this operation."} + '409': InvalidAddress, // {"errorCode":409,"error":"This is not a verified bank account."} + '410': ExchangeError, // Withdrawals for this asset are not available at this time. + '411': BadRequest, // You can not transfer assets to yourself. + '412': InvalidAddress, // {"errorCode":412,"error":"eth_address_invalid."} + '413': InvalidAddress, // This address violates the whitelist. + '414': ExchangeError, // You cannot withdraw assets within 2 minutes of logging in. + }, + 'broad': { + 'start parameter is invalid': BadRequest, // {"errorCode":205,"error":"start parameter is invalid."} + 'symbol parameter is invalid': BadSymbol, // {"errorCode":205,"error":"symbol parameter is invalid."} + 'amount parameter is invalid': InvalidOrder, // {"errorCode":205,"error":"amount parameter is invalid."} + 'orderId parameter is invalid': InvalidOrder, // {"errorCode":205,"error":"orderId parameter is invalid."} + }, + }, + 'options': { + 'BITVAVO-ACCESS-WINDOW': 10000, // default 10 sec + 'fetchCurrencies': { + 'expires': 1000, // 1 second + }, + }, + 'commonCurrencies': { + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { "time": 1590379519148 } + // + return this.safeInteger (response, 'time'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const currencies = await this.fetchCurrenciesFromCache (params); + const currenciesById = this.indexBy (currencies, 'symbol'); + // + // [ + // { + // "market":"ADA-BTC", + // "status":"trading", // "trading" "halted" "auction" + // "base":"ADA", + // "quote":"BTC", + // "pricePrecision":5, + // "minOrderInBaseAsset":"100", + // "minOrderInQuoteAsset":"0.001", + // "orderTypes": [ "market", "limit" ] + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'market'); + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const status = this.safeString (market, 'status'); + const active = (status === 'trading'); + const baseCurrency = this.safeValue (currenciesById, baseId); + let amountPrecision = undefined; + if (baseCurrency !== undefined) { + amountPrecision = this.safeInteger (baseCurrency, 'decimals', 8); + } + const precision = { + 'price': this.safeInteger (market, 'pricePrecision'), + 'amount': amountPrecision, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minOrderInBaseAsset'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minOrderInQuoteAsset'), + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchCurrenciesFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetAssets (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchCurrencies'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + // + // [ + // { + // "symbol":"ADA", + // "name":"Cardano", + // "decimals":6, + // "depositFee":"0", + // "depositConfirmations":15, + // "depositStatus":"OK", // "OK", "MAINTENANCE", "DELISTED" + // "withdrawalFee":"0.2", + // "withdrawalMinAmount":"0.2", + // "withdrawalStatus":"OK", // "OK", "MAINTENANCE", "DELISTED" + // "networks": [ "Mainnet" ], // "ETH", "NEO", "ONT", "SEPA", "VET" + // "message":"", + // }, + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const depositStatus = this.safeValue (currency, 'depositStatus'); + const deposit = (depositStatus === 'OK'); + const withdrawalStatus = this.safeValue (currency, 'withdrawalStatus'); + const withdrawal = (withdrawalStatus === 'OK'); + const active = deposit && withdrawal; + const name = this.safeString (currency, 'name'); + const precision = this.safeInteger (currency, 'decimals', 8); + result[code] = { + 'id': id, + 'info': currency, + 'code': code, + 'name': name, + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawalFee'), + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'withdrawalMinAmount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTicker24h (this.extend (request, params)); + // + // { + // "market":"ETH-BTC", + // "open":"0.022578", + // "high":"0.023019", + // "low":"0.022573", + // "last":"0.023019", + // "volume":"25.16366324", + // "volumeQuote":"0.57333305", + // "bid":"0.023039", + // "bidSize":"0.53500578", + // "ask":"0.023041", + // "askSize":"0.47859202", + // "timestamp":1590381666900 + // } + // + return this.parseTicker (response, market); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "market":"ETH-BTC", + // "open":"0.022578", + // "high":"0.023019", + // "low":"0.022573", + // "last":"0.023019", + // "volume":"25.16366324", + // "volumeQuote":"0.57333305", + // "bid":"0.023039", + // "bidSize":"0.53500578", + // "ask":"0.023041", + // "askSize":"0.47859202", + // "timestamp":1590381666900 + // } + // + const marketId = this.safeString (ticker, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.safeInteger (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + const baseVolume = this.safeFloat (ticker, 'volume'); + const quoteVolume = this.safeFloat (ticker, 'volumeQuote'); + const vwap = this.vwap (baseVolume, quoteVolume); + let change = undefined; + let percentage = undefined; + let average = undefined; + const open = this.safeFloat (ticker, 'open'); + if ((open !== undefined) && (last !== undefined)) { + change = last - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, last) / 2; + } + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': this.safeFloat (ticker, 'bidSize'), + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': this.safeFloat (ticker, 'askSize'), + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + return result; + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker24h (params); + // + // [ + // { + // "market":"ADA-BTC", + // "open":"0.0000059595", + // "high":"0.0000059765", + // "low":"0.0000059595", + // "last":"0.0000059765", + // "volume":"2923.172", + // "volumeQuote":"0.01743483", + // "bid":"0.0000059515", + // "bidSize":"1117.630919", + // "ask":"0.0000059585", + // "askSize":"809.999739", + // "timestamp":1590382266324 + // } + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + // 'limit': 500, // default 500, max 1000 + // 'start': since, + // 'end': this.milliseconds (), + // 'tradeIdFrom': '57b1159b-6bf5-4cde-9e2c-6bd6a5678baf', + // 'tradeIdTo': '57b1159b-6bf5-4cde-9e2c-6bd6a5678baf', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['start'] = since; + } + const response = await this.publicGetMarketTrades (this.extend (request, params)); + // + // [ + // { + // "id":"94154c98-6e8b-4e33-92a8-74e33fc05650", + // "timestamp":1590382761859, + // "amount":"0.06026079", + // "price":"8095.3", + // "side":"buy" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":"94154c98-6e8b-4e33-92a8-74e33fc05650", + // "timestamp":1590382761859, + // "amount":"0.06026079", + // "price":"8095.3", + // "side":"buy" + // } + // + // createOrder, fetchOpenOrders, fetchOrders, editOrder (private) + // + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // + // fetchMyTrades (private) + // + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "timestamp":1590505649245, + // "market":"ETH-EUR", + // "side":"sell", + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // + // watchMyTrades (private) + // + // { + // event: 'fill', + // timestamp: 1590964470132, + // market: 'ETH-EUR', + // orderId: '85d082e1-eda4-4209-9580-248281a29a9a', + // fillId: '861d2da5-aa93-475c-8d9a-dce431bd4211', + // side: 'sell', + // amount: '0.1', + // price: '211.46', + // taker: true, + // fee: '0.056', + // feeCurrency: 'EUR' + // } + // + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const timestamp = this.safeInteger (trade, 'timestamp'); + const side = this.safeString (trade, 'side'); + const id = this.safeString2 (trade, 'id', 'fillId'); + const marketId = this.safeInteger (trade, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const taker = this.safeValue (trade, 'taker'); + let takerOrMaker = undefined; + if (taker !== undefined) { + takerOrMaker = taker ? 'taker' : 'maker'; + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const orderId = this.safeString (trade, 'orderId'); + return { + 'info': trade, + 'id': id, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetMarketBook (this.extend (request, params)); + // + // { + // "market":"BTC-EUR", + // "nonce":35883831, + // "bids":[ + // ["8097.4","0.6229099"], + // ["8097.2","0.64151283"], + // ["8097.1","0.24966294"], + // ], + // "asks":[ + // ["8097.5","1.36916911"], + // ["8098.8","0.33462248"], + // ["8099.3","1.12908646"], + // ] + // } + // + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'nonce'); + return orderbook; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1590383700000, + // "8088.5", + // "8088.5", + // "8088.5", + // "8088.5", + // "0.04788623" + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'interval': this.timeframes[timeframe], + // 'limit': 1440, // default 1440, max 1440 + // 'start': since, + // 'end': this.milliseconds (), + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 1440, max 1440 + } + const response = await this.publicGetMarketCandles (this.extend (request, params)); + // + // [ + // [1590383700000,"8088.5","8088.5","8088.5","8088.5","0.04788623"], + // [1590383580000,"8091.3","8091.5","8091.3","8091.5","0.04931221"], + // [1590383520000,"8090.3","8092.7","8090.3","8092.5","0.04001286"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + // + // [ + // { + // "symbol": "BTC", + // "available": "1.57593193", + // "inOrder": "0.74832374" + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'symbol'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'inOrder'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'symbol': currency['id'], + }; + const response = await this.privateGetDeposit (this.extend (request, params)); + // + // { + // "address": "0x449889e3234514c45d57f7c5a571feba0c7ad567", + // "paymentId": "10002653" + // } + // + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'paymentId'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': side, + 'orderType': type, + // 'amount': this.amountToPrecision (symbol, amount), + // 'price': this.priceToPrecision (symbol, price), + // 'amountQuote': this.costToPrecision (symbol, cost), + // 'timeInForce': 'GTC', // "GTC" "IOC" "FOK" + // 'selfTradePrevention': "decrementAndCancel", // "decrementAndCancel" "cancelOldest" "cancelNewest" "cancelBoth" + // 'postOnly': false, + // 'disableMarketProtection': false, // don't cancel if the next fill price is 10% worse than the best fill price + // 'responseRequired': true, // false is faster + }; + if (type === 'market') { + let cost = undefined; + if (price !== undefined) { + cost = amount * price; + } else { + cost = this.safeFloat2 (params, 'cost', 'amountQuote'); + } + if (cost !== undefined) { + const precision = market['precision']['price']; + request['amountQuote'] = this.decimalToPrecision (cost, TRUNCATE, precision, this.precisionMode); + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + params = this.omit (params, [ 'cost', 'amountQuote' ]); + } else if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + request['amount'] = this.amountToPrecision (symbol, amount); + } + const response = await this.privatePostOrder (this.extend (request, params)); + // + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // + return this.parseOrder (response, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let request = {}; + const amountRemaining = this.safeFloat (params, 'amountRemaining'); + params = this.omit (params, 'amountRemaining'); + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + if (amount !== undefined) { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if (amountRemaining !== undefined) { + request['amountRemaining'] = this.amountToPrecision (symbol, amountRemaining); + } + request = this.extend (request, params); + if (Object.keys (request).length) { + request['orderId'] = id; + request['market'] = market['id']; + const response = await this.privatePutOrder (this.extend (request, params)); + return this.parseOrder (response, market); + } else { + throw new ArgumentsRequired (this.id + ' editOrder requires an amount argument, or a price argument, or non-empty params'); + } + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'orderId': id, + 'market': market['id'], + }; + const response = await this.privateDeleteOrder (this.extend (request, params)); + // + // { + // "orderId": "2e7ce7fc-44e2-4d80-a4a7-d079c4750b61" + // } + // + return this.parseOrder (response, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.privateDeleteOrders (this.extend (request, params)); + // + // [ + // { + // "orderId": "1be6d0df-d5dc-4b53-a250-3376f3b393e6" + // } + // ] + // + return this.parseOrders (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'orderId': id, + 'market': market['id'], + }; + const response = await this.privateGetOrder (this.extend (request, params)); + // + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // + return this.parseOrder (response, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + // 'limit': 500, + // 'start': since, + // 'end': this.milliseconds (), + // 'orderIdFrom': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + // 'orderIdTo': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // [ + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'market': market['id'], + // 'limit': 500, + // 'start': since, + // 'end': this.milliseconds (), + // 'orderIdFrom': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + // 'orderIdTo': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetOrdersOpen (this.extend (request, params)); + // + // [ + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'new': 'open', + 'canceled': 'canceled', + 'canceledAuction': 'canceled', + 'canceledSelfTradePrevention': 'canceled', + 'canceledIOC': 'canceled', + 'canceledFOK': 'canceled', + 'canceledMarketProtection': 'canceled', + 'canceledPostOnly': 'canceled', + 'filled': 'closed', + 'partiallyFilled': 'open', + 'expired': 'canceled', + 'rejected': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // cancelOrder, cancelAllOrders + // + // { + // "orderId": "2e7ce7fc-44e2-4d80-a4a7-d079c4750b61" + // } + // + // createOrder, fetchOrder, fetchOpenOrders, fetchOrders, editOrder + // + // { + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "market":"ETH-EUR", + // "created":1590505649241, + // "updated":1590505649241, + // "status":"filled", + // "side":"sell", + // "orderType":"market", + // "amount":"0.249825", + // "amountRemaining":"0", + // "price": "183.49", // limit orders only + // "onHold":"0", + // "onHoldCurrency":"ETH", + // "filledAmount":"0.249825", + // "filledAmountQuote":"45.84038925", + // "feePaid":"0.12038925", + // "feeCurrency":"EUR", + // "fills":[ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "timestamp":1590505649245, + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ], + // "selfTradePrevention":"decrementAndCancel", + // "visible":false, + // "disableMarketProtection":false + // "timeInForce": "GTC", + // "postOnly": true, + // } + // + const id = this.safeString (order, 'orderId'); + const timestamp = this.safeInteger (order, 'created'); + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'orderType'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let remaining = this.safeFloat (order, 'amountRemaining'); + let filled = this.safeFloat (order, 'filledAmount'); + const remainingCost = this.safeFloat (order, 'remainingCost'); + if ((remainingCost !== undefined) && (remainingCost === 0.0)) { + remaining = 0; + } + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + const cost = this.safeFloat (order, 'filledAmountQuote'); + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'feePaid'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (order, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + let lastTradeTimestamp = undefined; + const rawTrades = this.safeValue (order, 'fills'); + let trades = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'symbol': symbol, + 'order': id, + 'side': side, + }); + const numTrades = trades.length; + if (numTrades > 0) { + const lastTrade = this.safeValue (trades, numTrades - 1); + lastTradeTimestamp = lastTrade['timestamp']; + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + // 'limit': 500, + // 'start': since, + // 'end': this.milliseconds (), + // 'tradeIdFrom': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + // 'tradeIdTo': 'af76d6ce-9f7c-4006-b715-bb5d430652d0', + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // [ + // { + // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", + // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", + // "timestamp":1590505649245, + // "market":"ETH-EUR", + // "side":"sell", + // "amount":"0.249825", + // "price":"183.49", + // "taker":true, + // "fee":"0.12038925", + // "feeCurrency":"EUR", + // "settled":true + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'symbol': currency['id'], + 'amount': this.currencyToPrecision (code, amount), + 'address': address, // address or IBAN + // 'internal': false, // transfer to another Bitvavo user address, no fees + // 'addWithdrawalFee': false, // true = add the fee on top, otherwise the fee is subtracted from the amount + }; + if (tag !== undefined) { + request['paymentId'] = tag; + } + const response = await this.privatePostWithdrawal (this.extend (request, params)); + // + // { + // "success": true, + // "symbol": "BTC", + // "amount": "1.5" + // } + // + return this.parseTransaction (response, currency); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': currency['id'], + // 'limit': 500, // default 500, max 1000 + // 'start': since, + // 'end': this.milliseconds (), + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetWithdrawalHistory (this.extend (request, params)); + // + // [ + // { + // "timestamp":1590531212000, + // "symbol":"ETH", + // "amount":"0.091", + // "fee":"0.009", + // "status":"awaiting_bitvavo_inspection", + // "address":"0xe42b309f1eE9F0cbf7f54CcF3bc2159eBfA6735b", + // "paymentId": "10002653", + // "txId": "927b3ea50c5bb52c6854152d305dfa1e27fc01d10464cf10825d96d69d235eb3", + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': currency['id'], + // 'limit': 500, // default 500, max 1000 + // 'start': since, + // 'end': this.milliseconds (), + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetDepositHistory (this.extend (request, params)); + // + // [ + // { + // "timestamp":1590492401000, + // "symbol":"ETH", + // "amount":"0.249825", + // "fee":"0", + // "status":"completed", + // "txId":"0x5167b473fd37811f9ef22364c3d54726a859ef9d98934b3a1e11d7baa8d2c2e2" + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'awaiting_processing': 'pending', + 'awaiting_email_confirmation': 'pending', + 'awaiting_bitvavo_inspection': 'pending', + 'approved': 'pending', + 'sending': 'pending', + 'in_mempool': 'pending', + 'processed': 'pending', + 'completed': 'ok', + 'canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "success": true, + // "symbol": "BTC", + // "amount": "1.5" + // } + // + // fetchWithdrawals + // + // { + // "timestamp": 1542967486256, + // "symbol": "BTC", + // "amount": "0.99994", + // "address": "BitcoinAddress", + // "paymentId": "10002653", + // "txId": "927b3ea50c5bb52c6854152d305dfa1e27fc01d10464cf10825d96d69d235eb3", + // "fee": "0.00006", + // "status": "awaiting_processing" + // } + // + // fetchDeposits + // + // { + // "timestamp":1590492401000, + // "symbol":"ETH", + // "amount":"0.249825", + // "fee":"0", + // "status":"completed", + // "txId":"0x5167b473fd37811f9ef22364c3d54726a859ef9d98934b3a1e11d7baa8d2c2e2" + // } + // + const id = undefined; + const timestamp = this.safeInteger (transaction, 'timestamp'); + const currencyId = this.safeString (transaction, 'symbol'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const txid = this.safeString (transaction, 'txId'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + let type = undefined; + if ('success' in transaction) { + type = 'withdrawal'; + } else { + type = (status === undefined) ? 'deposit' : 'withdrawal'; + } + const tag = this.safeString (transaction, 'paymentId'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = '/' + this.version + '/' + this.implodeParams (path, params); + const getOrDelete = (method === 'GET') || (method === 'DELETE'); + if (getOrDelete) { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + let payload = ''; + if (!getOrDelete) { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + const timestamp = this.milliseconds ().toString (); + const auth = timestamp + method + url + payload; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + const accessWindow = this.safeString (this.options, 'BITVAVO-ACCESS-WINDOW', '10000'); + headers = { + 'BITVAVO-ACCESS-KEY': this.apiKey, + 'BITVAVO-ACCESS-SIGNATURE': signature, + 'BITVAVO-ACCESS-TIMESTAMP': timestamp, + 'BITVAVO-ACCESS-WINDOW': accessWindow, + }; + if (!getOrDelete) { + headers['Content-Type'] = 'application/json'; + } + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"errorCode":308,"error":"The signature length is invalid (HMAC-SHA256 should return a 64 length hexadecimal string)."} + // {"errorCode":203,"error":"symbol parameter is required."} + // {"errorCode":205,"error":"symbol parameter is invalid."} + // + const errorCode = this.safeString (response, 'errorCode'); + const error = this.safeString (response, 'error'); + if (errorCode !== undefined) { + const feedback = this.id + ' ' + body; + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitz.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitz.js new file mode 100644 index 000000000..9b28cd463 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bitz.js @@ -0,0 +1,1285 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, PermissionDenied, InvalidOrder, AuthenticationError, InsufficientFunds, OrderNotFound, DDoSProtection, OnMaintenance, RateLimitExceeded } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bitz extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bitz', + 'name': 'Bit-Z', + 'countries': [ 'HK' ], + 'rateLimit': 2000, + 'version': 'v2', + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'createOrder': true, + 'createMarketOrder': false, + 'fetchBalance': true, + 'fetchDeposits': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '60min', + '4h': '4hour', + '1d': '1day', + '5d': '5day', + '1w': '1week', + '1M': '1mon', + }, + 'hostname': 'apiv2.bitz.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443304-fec5e000-c5fd-11ea-98f8-ba8e67f7eaff.jpg', + 'api': { + 'market': 'https://{hostname}', + 'trade': 'https://{hostname}', + 'assets': 'https://{hostname}', + }, + 'www': 'https://www.bitz.com', + 'doc': 'https://apidoc.bitz.com/en/', + 'fees': 'https://www.bitz.com/fee?type=1', + 'referral': 'https://u.bitz.com/register?invite_code=1429193', + }, + 'api': { + 'market': { + 'get': [ + 'ticker', + 'depth', + 'order', // trades + 'tickerall', + 'kline', + 'symbolList', + 'getServerTime', + 'currencyRate', + 'currencyCoinRate', + 'coinRate', + ], + }, + 'trade': { + 'post': [ + 'addEntrustSheet', + 'cancelEntrustSheet', + 'cancelAllEntrustSheet', + 'coinOut', // withdraw + 'getUserHistoryEntrustSheet', // closed orders + 'getUserNowEntrustSheet', // open orders + 'getEntrustSheetInfo', // order + 'depositOrWithdraw', // transactions + ], + }, + 'assets': { + 'post': [ + 'getUserAssets', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.002, + 'taker': 0.002, + }, + 'funding': { + 'withdraw': { + 'BTC': '0.5%', + 'DKKT': '0.5%', + 'ETH': 0.01, + 'USDT': '0.5%', + 'LTC': '0.5%', + 'FCT': '0.5%', + 'LSK': '0.5%', + 'HXI': '0.8%', + 'ZEC': '0.5%', + 'DOGE': '0.5%', + 'MZC': '0.5%', + 'ETC': '0.5%', + 'GXS': '0.5%', + 'XPM': '0.5%', + 'PPC': '0.5%', + 'BLK': '0.5%', + 'XAS': '0.5%', + 'HSR': '0.5%', + 'NULS': 5.0, + 'VOISE': 350.0, + 'PAY': 1.5, + 'EOS': 0.6, + 'YBCT': 35.0, + 'OMG': 0.3, + 'OTN': 0.4, + 'BTX': '0.5%', + 'QTUM': '0.5%', + 'DASH': '0.5%', + 'GAME': '0.5%', + 'BCH': '0.5%', + 'GNT': 9.0, + 'SSS': 1500.0, + 'ARK': '0.5%', + 'PART': '0.5%', + 'LEO': '0.5%', + 'DGB': '0.5%', + 'ZSC': 130.0, + 'VIU': 350.0, + 'BTG': '0.5%', + 'ARN': 10.0, + 'VTC': '0.5%', + 'BCD': '0.5%', + 'TRX': 200.0, + 'HWC': '0.5%', + 'UNIT': '0.5%', + 'OXY': '0.5%', + 'MCO': 0.3500, + 'SBTC': '0.5%', + 'BCX': '0.5%', + 'ETF': '0.5%', + 'PYLNT': 0.4000, + 'XRB': '0.5%', + 'ETP': '0.5%', + }, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'options': { + 'fetchOHLCVVolume': true, + 'fetchOHLCVWarning': true, + 'lastNonceTimestamp': 0, + }, + 'commonCurrencies': { + // https://github.com/ccxt/ccxt/issues/3881 + // https://support.bit-z.pro/hc/en-us/articles/360007500654-BOX-BOX-Token- + 'BOX': 'BOX Token', + 'LEO': 'LeoCoin', + 'XRB': 'NANO', + 'PXC': 'Pixiecoin', + 'VTC': 'VoteCoin', + 'TTC': 'TimesChain', + }, + 'exceptions': { + // '200': Success + '-102': ExchangeError, // Invalid parameter + '-103': AuthenticationError, // Verification failed + '-104': ExchangeNotAvailable, // Network Error-1 + '-105': AuthenticationError, // Invalid api signature + '-106': ExchangeNotAvailable, // Network Error-2 + '-109': AuthenticationError, // Invalid scretKey + '-110': DDoSProtection, // The number of access requests exceeded + '-111': PermissionDenied, // Current IP is not in the range of trusted IP + '-112': OnMaintenance, // Service is under maintenance + '-114': RateLimitExceeded, // The number of daily requests has reached the limit + '-117': AuthenticationError, // The apikey expires + '-100015': AuthenticationError, // Trade password error + '-100044': ExchangeError, // Fail to request data + '-100101': ExchangeError, // Invalid symbol + '-100201': ExchangeError, // Invalid symbol + '-100301': ExchangeError, // Invalid symbol + '-100401': ExchangeError, // Invalid symbol + '-100302': ExchangeError, // Type of K-line error + '-100303': ExchangeError, // Size of K-line error + '-200003': AuthenticationError, // Please set trade password + '-200005': PermissionDenied, // This account can not trade + '-200025': ExchangeNotAvailable, // Temporary trading halt + '-200027': InvalidOrder, // Price Error + '-200028': InvalidOrder, // Amount must be greater than 0 + '-200029': InvalidOrder, // Number must be between %s and %d + '-200030': InvalidOrder, // Over price range + '-200031': InsufficientFunds, // Insufficient assets + '-200032': ExchangeError, // System error. Please contact customer service + '-200033': ExchangeError, // Fail to trade + '-200034': OrderNotFound, // The order does not exist + '-200035': OrderNotFound, // Cancellation error, order filled + '-200037': InvalidOrder, // Trade direction error + '-200038': ExchangeError, // Trading Market Error + '-200055': OrderNotFound, // Order record does not exist + '-300069': AuthenticationError, // api_key is illegal + '-300101': ExchangeError, // Transaction type error + '-300102': InvalidOrder, // Price or number cannot be less than 0 + '-300103': AuthenticationError, // Trade password error + '-301001': ExchangeNotAvailable, // Network Error-3 + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.marketGetSymbolList (params); + // + // { status: 200, + // msg: "", + // data: { ltc_btc: { id: "1", + // name: "ltc_btc", + // coinFrom: "ltc", + // coinTo: "btc", + // numberFloat: "4", + // priceFloat: "8", + // status: "1", + // minTrade: "0.010", + // maxTrade: "500000000.000" }, + // qtum_usdt: { id: "196", + // name: "qtum_usdt", + // coinFrom: "qtum", + // coinTo: "usdt", + // numberFloat: "4", + // priceFloat: "2", + // status: "1", + // minTrade: "0.100", + // maxTrade: "500000000.000" }, }, + // time: 1535969146, + // microtime: "0.66955600 1535969146", + // source: "api" } + // + const markets = this.safeValue (response, 'data'); + const ids = Object.keys (markets); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = markets[id]; + const numericId = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'coinFrom'); + const quoteId = this.safeString (market, 'coinTo'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'numberFloat'), + 'price': this.safeInteger (market, 'priceFloat'), + }; + result.push ({ + 'info': market, + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minTrade'), + 'max': this.safeFloat (market, 'maxTrade'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.assetsPostGetUserAssets (params); + // + // { + // status: 200, + // msg: "", + // data: { + // cny: 0, + // usd: 0, + // btc_total: 0, + // info: [{ + // "name": "zpr", + // "num": "37.49067275", + // "over": "37.49067275", + // "lock": "0.00000000", + // "btc": "0.00000000", + // "usd": "0.00000000", + // "cny": "0.00000000", + // }], + // }, + // time: 1535983966, + // microtime: "0.70400500 1535983966", + // source: "api", + // } + // + const balances = this.safeValue (response['data'], 'info'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'name'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'lock'); + account['total'] = this.safeFloat (balance, 'num'); + account['free'] = this.safeFloat (balance, 'over'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTicker (ticker, market = undefined) { + // + // { symbol: "eth_btc", + // quoteVolume: "3905.72", + // volume: "97058.21", + // priceChange: "-1.72", + // priceChange24h: "-1.65", + // askPrice: "0.03971272", + // askQty: "0.0663", + // bidPrice: "0.03961469", + // bidQty: "19.5451", + // open: "0.04036769", + // high: "0.04062988", + // low: "0.03956123", + // now: "0.03970100", + // firstId: 115567767, + // lastId: 115795316, + // dealCount: 14078, + // numberPrecision: 4, + // pricePrecision: 8, + // cny: "1959.05", + // usd: "287.10", + // krw: "318655.82" } + // + const timestamp = undefined; + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat (ticker, 'now'); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': this.safeFloat (ticker, 'priceChange24h'), + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + parseMicrotime (microtime) { + if (microtime === undefined) { + return microtime; + } + const parts = microtime.split (' '); + const milliseconds = parseFloat (parts[0]); + const seconds = parseInt (parts[1]); + const total = this.sum (seconds, milliseconds); + return parseInt (total * 1000); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetTicker (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: { symbol: "eth_btc", + // quoteVolume: "3905.72", + // volume: "97058.21", + // priceChange: "-1.72", + // priceChange24h: "-1.65", + // askPrice: "0.03971272", + // askQty: "0.0663", + // bidPrice: "0.03961469", + // bidQty: "19.5451", + // open: "0.04036769", + // high: "0.04062988", + // low: "0.03956123", + // now: "0.03970100", + // firstId: 115567767, + // lastId: 115795316, + // dealCount: 14078, + // numberPrecision: 4, + // pricePrecision: 8, + // cny: "1959.05", + // usd: "287.10", + // krw: "318655.82" }, + // time: 1535970397, + // microtime: "0.76341900 1535970397", + // source: "api" } + // + const ticker = this.parseTicker (response['data'], market); + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + return this.extend (ticker, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + }); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['symbols'] = ids.join (','); + } + const response = await this.marketGetTickerall (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: { ela_btc: { symbol: "ela_btc", + // quoteVolume: "0.00", + // volume: "3.28", + // priceChange: "0.00", + // priceChange24h: "0.00", + // askPrice: "0.00147984", + // askQty: "5.4580", + // bidPrice: "0.00120230", + // bidQty: "12.5384", + // open: "0.00149078", + // high: "0.00149078", + // low: "0.00149078", + // now: "0.00149078", + // firstId: 115581219, + // lastId: 115581219, + // dealCount: 1, + // numberPrecision: 4, + // pricePrecision: 8, + // cny: "73.66", + // usd: "10.79", + // krw: "11995.03" } }, + // time: 1535971578, + // microtime: "0.39854200 1535971578", + // source: "api" } + // + const tickers = this.safeValue (response, 'data'); + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + const result = {}; + const ids = Object.keys (tickers); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let ticker = tickers[id]; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + } + ticker = this.parseTicker (tickers[id], market); + let symbol = ticker['symbol']; + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } else { + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if (symbol !== undefined) { + result[symbol] = this.extend (ticker, { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + }); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTime (params = {}) { + const response = await this.marketGetGetServerTime (params); + // + // { + // "status":200, + // "msg":"", + // "data":[], + // "time":1555490875, + // "microtime":"0.35994200 1555490875", + // "source":"api" + // } + // + return this.safeTimestamp (response, 'time'); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.marketGetDepth (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: { asks: [ ["10.00000000", "0.4426", "4.4260"], + // ["1.00000000", "0.8339", "0.8339"], + // ["0.91700000", "0.0500", "0.0458"], + // ["0.20000000", "0.1000", "0.0200"], + // ["0.03987120", "16.1262", "0.6429"], + // ["0.03986120", "9.7523", "0.3887"] ], + // bids: [ ["0.03976145", "0.0359", "0.0014"], + // ["0.03973401", "20.9493", "0.8323"], + // ["0.03967970", "0.0328", "0.0013"], + // ["0.00000002", "10000.0000", "0.0002"], + // ["0.00000001", "231840.7500", "0.0023"] ], + // coinPair: "eth_btc" }, + // time: 1535974778, + // microtime: "0.04017400 1535974778", + // source: "api" } + // + const orderbook = this.safeValue (response, 'data'); + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { id: 115807453, + // t: "19:36:24", + // T: 1535974584, + // p: "0.03983296", + // n: "0.1000", + // s: "buy" }, + // + const id = this.safeString (trade, 'id'); + const timestamp = this.safeTimestamp (trade, 'T'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const price = this.safeFloat (trade, 'p'); + const amount = this.safeFloat (trade, 'n'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = this.priceToPrecision (symbol, amount * price); + } + } + const side = this.safeString (trade, 's'); + return { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': undefined, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetOrder (this.extend (request, params)); + // + // { status: 200, + // msg: "", + // data: [ { id: 115807453, + // t: "19:36:24", + // T: 1535974584, + // p: "0.03983296", + // n: "0.1000", + // s: "buy" }, + // { id: 115806811, + // t: "19:33:19", + // T: 1535974399, + // p: "0.03981135", + // n: "9.4612", + // s: "sell" } ], + // time: 1535974583, + // microtime: "0.57118100 1535974583", + // source: "api" } + // + return this.parseTrades (response['data'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // time: "1535973420000", + // open: "0.03975084", + // high: "0.03975084", + // low: "0.03967700", + // close: "0.03967700", + // volume: "12.4733", + // datetime: "2018-09-03 19:17:00" + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const duration = this.parseTimeframe (timeframe) * 1000; + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['size'] = Math.min (limit, 300); // 1-300 + if (since !== undefined) { + request['to'] = this.sum (since, limit * duration * 1000); + } + } else { + if (since !== undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a limit argument if the since argument is specified'); + } + } + const response = await this.marketGetKline (this.extend (request, params)); + // + // { + // status: 200, + // msg: "", + // data: { + // bars: [ + // { time: "1535973420000", open: "0.03975084", high: "0.03975084", low: "0.03967700", close: "0.03967700", volume: "12.4733", datetime: "2018-09-03 19:17:00" }, + // { time: "1535955480000", open: "0.04009900", high: "0.04016745", low: "0.04009900", close: "0.04012074", volume: "74.4803", datetime: "2018-09-03 14:18:00" }, + // ], + // resolution: "1min", + // symbol: "eth_btc", + // from: "1535973420000", + // to: "1535955480000", + // size: 300 + // }, + // time: 1535973435, + // microtime: "0.56462100 1535973435", + // source: "api" + // } + // + const data = this.safeValue (response, 'data', {}); + const bars = this.safeValue (data, 'bars', []); + return this.parseOHLCVs (bars, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', // partially filled + '2': 'closed', // filled + '3': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": "693248739", // order id + // "uId": "2074056", // uid + // "price": "100", // price + // "number": "10", // number + // "numberOver": "10", // undealed + // "flag": "sale", // flag + // "status": "0", // unfilled + // "coinFrom": "vtc", + // "coinTo": "dkkt", + // "numberDeal": "0" // dealed + // } + // + const id = this.safeString (order, 'id'); + let symbol = undefined; + if (market === undefined) { + const baseId = this.safeString (order, 'coinFrom'); + const quoteId = this.safeString (order, 'coinTo'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const marketId = baseId + '_' + quoteId; + if (marketId in this.markets_by_id) { + market = this.safeValue (this.markets_by_id, marketId); + } else { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let side = this.safeString (order, 'flag'); + if (side !== undefined) { + side = (side === 'sale') ? 'sell' : 'buy'; + } + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'number'); + const remaining = this.safeFloat (order, 'numberOver'); + const filled = this.safeFloat (order, 'numberDeal'); + let timestamp = this.safeInteger (order, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.safeTimestamp (order, 'created'); + } + let cost = this.safeFloat (order, 'orderTotalPrice'); + if (price !== undefined) { + if (filled !== undefined) { + cost = filled * price; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type !== 'limit') { + throw new ExchangeError (this.id + ' createOrder allows limit orders only'); + } + const market = this.market (symbol); + const orderType = (side === 'buy') ? '1' : '2'; + if (!this.password) { + throw new ExchangeError (this.id + ' createOrder() requires you to set exchange.password = "YOUR_TRADING_PASSWORD" (a trade password is NOT THE SAME as your login password)'); + } + const request = { + 'symbol': market['id'], + 'type': orderType, + 'price': this.priceToPrecision (symbol, price), + 'number': this.amountToPrecision (symbol, amount), + 'tradePwd': this.password, + }; + const response = await this.tradePostAddEntrustSheet (this.extend (request, params)); + // + // { + // "status": 200, + // "msg": "", + // "data": { + // "id": "693248739", // order id + // "uId": "2074056", // uid + // "price": "100", // price + // "number": "10", // number + // "numberOver": "10", // undealed + // "flag": "sale", // flag + // "status": "0", // unfilled + // "coinFrom": "vtc", + // "coinTo": "dkkt", + // "numberDeal": "0" // dealed + // }, + // "time": "1533035297", + // "microtime": "0.41892000 1533035297", + // "source": "api", + // } + // + const timestamp = this.parseMicrotime (this.safeString (response, 'microtime')); + const order = this.extend ({ + 'timestamp': timestamp, + }, response['data']); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'entrustSheetId': id, + }; + const response = await this.tradePostCancelEntrustSheet (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "updateAssetsData":{ + // "coin":"bz", + // "over":"1000.00000000", + // "lock":"-1000.00000000" + // }, + // "assetsInfo":{ + // "coin":"bz", + // "over":"9999.99999999", + // "lock":"9999.99999999" + // } + // }, + // "time":"1535464383", + // "microtime":"0.91558000 1535464383", + // "source":"api" + // } + // + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'ids': ids.join (','), + }; + const response = await this.tradePostCancelEntrustSheet (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "744173808":{ + // "updateAssetsData":{ + // "coin":"bz", + // "over":"100.00000000", + // "lock":"-100.00000000" + // }, + // "assetsInfo":{ + // "coin":"bz", + // "over":"899.99999999", + // "lock":"19099.99999999" + // } + // }, + // "744173809":{ + // "updateAssetsData":{ + // "coin":"bz", + // "over":"100.00000000", + // "lock":"-100.00000000" + // }, + // "assetsInfo":{ + // "coin":"bz", + // "over":"999.99999999", + // "lock":"18999.99999999" + // } + // } + // }, + // "time":"1535525649", + // "microtime":"0.05009400 1535525649", + // "source":"api" + // } + // + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'entrustSheetId': id, + }; + const response = await this.tradePostGetEntrustSheetInfo (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "id":"708279852", + // "uId":"2074056", + // "price":"100.00000000", + // "number":"10.0000", + // "total":"0.00000000", + // "numberOver":"10.0000", + // "numberDeal":"0.0000", + // "flag":"sale", + // "status":"0", //0:unfilled, 1:partial deal, 2:all transactions, 3:already cancelled + // "coinFrom":"bz", + // "coinTo":"usdt", + // "orderTotalPrice":"0", + // "created":"1533279876" + // }, + // "time":"1533280294", + // "microtime":"0.36859200 1533280294", + // "source":"api" + // } + // + return this.parseOrder (response['data']); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coinFrom': market['baseId'], + 'coinTo': market['quoteId'], + // 'type': 1, // optional integer, 1 = buy, 2 = sell + // 'page': 1, // optional integer + // 'pageSize': 100, // optional integer, max 100 + // 'startTime': 1510235730, // optional integer timestamp in seconds + // 'endTime': 1510235730, // optional integer timestamp in seconds + }; + if (limit !== undefined) { + request['page'] = 1; + request['pageSize'] = limit; + } + if (since !== undefined) { + request['startTime'] = parseInt (since / 1000); + // request['endTime'] = parseInt (since / 1000); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "status": 200, + // "msg": "", + // "data": { + // "data": [ + // { + // "id": "693248739", + // "uid": "2074056", + // "price": "100.00000000", + // "number": "10.0000", + // "total": "0.00000000", + // "numberOver": "0.0000", + // "numberDeal": "0.0000", + // "flag": "sale", + // "status": "3", // 0:unfilled, 1:partial deal, 2:all transactions, 3:already cancelled + // "isNew": "N", + // "coinFrom": "vtc", + // "coinTo": "dkkt", + // "created": "1533035300", + // }, + // { + // "id": "723086996", + // "uid": "2074056", + // "price": "100.00000000", + // "number": "10.0000", + // "total": "0.00000000", + // "numberOver": "0.0000", + // "numberDeal": "0.0000", + // "flag": "sale", + // "status": "3", + // "isNew": "N", + // "coinFrom": "bz", + // "coinTo": "usdt", + // "created": "1533523568", + // }, + // ], + // "pageInfo": { + // "limit": "10", + // "offest": "0", + // "current_page": "1", + // "page_size": "10", + // "total_count": "17", + // "page_count": "2", + // } + // }, + // "time": "1533279329", + // "microtime": "0.15305300 1533279329", + // "source": "api" + // } + // + const orders = this.safeValue (response['data'], 'data', []); + return this.parseOrders (orders, undefined, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('tradePostGetUserHistoryEntrustSheet', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('tradePostGetUserNowEntrustSheet', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('tradePostGetUserHistoryEntrustSheet', symbol, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + '1': 'pending', + '2': 'pending', + '3': 'pending', + '4': 'ok', + '5': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": '96275', + // "uid": '2109073', + // "wallet": '0xf4c4141c0127bc37b1d0c409a091920eba13ada7', + // "txid": '0xb7adfa52aa566f9ac112e3c01f77bd91179b19eab12092a9a5a8b33d5086e31d', + // "confirm": '12', + // "number": '0.50000000', + // "status": 4, + // "updated": '1534944168605', + // "addressUrl": 'https://etherscan.io/address/', + // "txidUrl": 'https://etherscan.io/tx/', + // "description": 'Ethereum', + // "coin": 'eth', + // "memo": '' + // } + // + // { + // "id":"397574", + // "uid":"2033056", + // "wallet":"1AG1gZvQAYu3WBvgg7p4BMMghQD2gE693k", + // "txid":"", + // "confirm":"0", + // "number":"1000.00000000", + // "status":1, + // "updated":"0", + // "addressUrl":"http://omniexplorer.info/lookupadd.aspx?address=", + // "txidUrl":"http://omniexplorer.info/lookuptx.aspx?txid=", + // "description":"Tether", + // "coin":"usdt", + // "memo":"" + // } + // + // { + // "id":"153606", + // "uid":"2033056", + // "wallet":"1AG1gZvQAYu3WBvgg7p4BMMghQD2gE693k", + // "txid":"aa2b179f84cd6dedafd41845e0fbf7f01e14c0d71ea3140d03d6f5a9ccd93199", + // "confirm":"0", + // "number":"761.11110000", + // "status":4, + // "updated":"1536726133579", + // "addressUrl":"http://omniexplorer.info/lookupadd.aspx?address=", + // "txidUrl":"http://omniexplorer.info/lookuptx.aspx?txid=", + // "description":"Tether", + // "coin":"usdt", + // "memo":"" + // } + // + // withdraw + // + // { + // "id":397574, + // "email":"***@email.com", + // "coin":"usdt", + // "network_fee":"", + // "eid":23112 + // } + // + let timestamp = this.safeInteger (transaction, 'updated'); + if (timestamp === 0) { + timestamp = undefined; + } + const currencyId = this.safeString (transaction, 'coin'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeStringLower (transaction, 'type'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'network_fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'code': code, + }; + } + return { + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'txid'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'wallet'), + 'tag': this.safeString (transaction, 'memo'), + 'type': type, + 'amount': this.safeFloat (transaction, 'number'), + 'currency': code, + 'status': status, + 'updated': timestamp, + 'fee': fee, + 'info': transaction, + }; + } + + parseTransactionsByType (type, transactions, code = undefined, since = undefined, limit = undefined) { + const result = []; + for (let i = 0; i < transactions.length; i++) { + const transaction = this.parseTransaction (this.extend ({ + 'type': type, + }, transactions[i])); + result.push (transaction); + } + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + parseTransactionType (type) { + const types = { + 'deposit': 1, + 'withdrawal': 2, + }; + return this.safeInteger (types, type, type); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsForType ('deposit', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsForType ('withdrawal', code, since, limit, params); + } + + async fetchTransactionsForType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'type': this.parseTransactionType (type), + }; + if (since !== undefined) { + request['startTime'] = parseInt (since / 1000).toString (); + } + if (limit !== undefined) { + request['page'] = 1; + request['pageSize'] = limit; + } + const response = await this.tradePostDepositOrWithdraw (this.extend (request, params)); + const transactions = this.safeValue (response['data'], 'data', []); + return this.parseTransactionsByType (type, transactions, code, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'number': this.currencyToPrecision (code, amount), + 'address': address, + // 'type': 'erc20', // omni, trc20, optional + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const response = await this.tradePostCoinOut (this.extend (request, params)); + // + // { + // "status":200, + // "msg":"", + // "data":{ + // "id":397574, + // "email":"***@email.com", + // "coin":"usdt", + // "network_fee":"", + // "eid":23112 + // }, + // "time":1552641646, + // "microtime":"0.70304500 1552641646", + // "source":"api" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransaction (data, currency); + } + + nonce () { + const currentTimestamp = this.seconds (); + if (currentTimestamp > this.options['lastNonceTimestamp']) { + this.options['lastNonceTimestamp'] = currentTimestamp; + this.options['lastNonce'] = 100000; + } + this.options['lastNonce'] = this.sum (this.options['lastNonce'], 1); + return this.options['lastNonce']; + } + + sign (path, api = 'market', method = 'GET', params = {}, headers = undefined, body = undefined) { + const baseUrl = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }); + let url = baseUrl + '/' + this.capitalize (api) + '/' + path; + let query = undefined; + if (api === 'market') { + query = this.urlencode (params); + if (query.length) { + url += '?' + query; + } + } else { + this.checkRequiredCredentials (); + body = this.rawencode (this.keysort (this.extend ({ + 'apiKey': this.apiKey, + 'timeStamp': this.seconds (), + 'nonce': this.nonce (), + }, params))); + body += '&sign=' + this.hash (this.encode (body + this.secret)); + headers = { 'Content-type': 'application/x-www-form-urlencoded' }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const status = this.safeString (response, 'status'); + if (status !== undefined) { + const feedback = this.id + ' ' + body; + // + // {"status":-107,"msg":"","data":"","time":1535968848,"microtime":"0.89092200 1535968848","source":"api"} + // + if (status === '200') { + // + // {"status":200,"msg":"","data":-200031,"time":1535999806,"microtime":"0.85476800 1535999806","source":"api"} + // + const code = this.safeInteger (response, 'data'); + if (code !== undefined) { + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); + } else { + return; // no error + } + } + this.throwExactlyMatchedException (this.exceptions, status, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bl3p.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bl3p.js new file mode 100644 index 000000000..bb84442ec --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bl3p.js @@ -0,0 +1,236 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class bl3p extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bl3p', + 'name': 'BL3P', + 'countries': [ 'NL', 'EU' ], // Netherlands, EU + 'rateLimit': 1000, + 'version': '1', + 'comment': 'An exchange market by BitonicNL', + 'has': { + 'CORS': false, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28501752-60c21b82-6feb-11e7-818b-055ee6d0e754.jpg', + 'api': 'https://api.bl3p.eu', + 'www': 'https://bl3p.eu', // 'https://bitonic.nl' + 'doc': [ + 'https://github.com/BitonicNL/bl3p-api/tree/master/docs', + 'https://bl3p.eu/api', + 'https://bitonic.nl/en/api', + ], + }, + 'api': { + 'public': { + 'get': [ + '{market}/ticker', + '{market}/orderbook', + '{market}/trades', + ], + }, + 'private': { + 'post': [ + '{market}/money/depth/full', + '{market}/money/order/add', + '{market}/money/order/cancel', + '{market}/money/order/result', + '{market}/money/orders', + '{market}/money/orders/history', + '{market}/money/trades/fetch', + 'GENMKT/money/info', + 'GENMKT/money/deposit_address', + 'GENMKT/money/new_deposit_address', + 'GENMKT/money/wallet/history', + 'GENMKT/money/withdraw', + ], + }, + }, + 'markets': { + 'BTC/EUR': { 'id': 'BTCEUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'BTC', 'quoteId': 'EUR', 'maker': 0.0025, 'taker': 0.0025 }, + 'LTC/EUR': { 'id': 'LTCEUR', 'symbol': 'LTC/EUR', 'base': 'LTC', 'quote': 'EUR', 'baseId': 'LTC', 'quoteId': 'EUR', 'maker': 0.0025, 'taker': 0.0025 }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGENMKTMoneyInfo (params); + const data = this.safeValue (response, 'data', {}); + const wallets = this.safeValue (data, 'wallets'); + const result = { 'info': data }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const wallet = this.safeValue (wallets, currencyId, {}); + const available = this.safeValue (wallet, 'available', {}); + const balance = this.safeValue (wallet, 'balance', {}); + const account = this.account (); + account['free'] = this.safeFloat (available, 'value'); + account['total'] = this.safeFloat (balance, 'value'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + return [ + bidask[priceKey] / 100000.0, + bidask[amountKey] / 100000000.0, + ]; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketOrderbook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'data'); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price_int', 'amount_int'); + } + + async fetchTicker (symbol, params = {}) { + const request = { + 'market': this.marketId (symbol), + }; + const ticker = await this.publicGetMarketTicker (this.extend (request, params)); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker['volume'], '24h'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const id = this.safeString (trade, 'trade_id'); + const timestamp = this.safeInteger (trade, 'date'); + let price = this.safeFloat (trade, 'price_int'); + if (price !== undefined) { + price /= 100000.0; + } + let amount = this.safeFloat (trade, 'amount_int'); + if (amount !== undefined) { + amount /= 100000000.0; + } + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + const market = this.market (symbol); + const response = await this.publicGetMarketTrades (this.extend ({ + 'market': market['id'], + }, params)); + const result = this.parseTrades (response['data']['trades'], market, since, limit); + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + const market = this.market (symbol); + const order = { + 'market': market['id'], + 'amount_int': parseInt (amount * 100000000), + 'fee_currency': market['quote'], + 'type': (side === 'buy') ? 'bid' : 'ask', + }; + if (type === 'limit') { + order['price_int'] = parseInt (price * 100000.0); + } + const response = await this.privatePostMarketMoneyOrderAdd (this.extend (order, params)); + const orderId = this.safeString (response['data'], 'order_id'); + return { + 'info': response, + 'id': orderId, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.privatePostMarketMoneyOrderCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = this.implodeParams (path, params); + let url = this.urls['api'] + '/' + this.version + '/' + request; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, query)); + const secret = this.base64ToBinary (this.secret); + // eslint-disable-next-line quotes + const auth = request + "\0" + body; + const signature = this.hmac (this.encode (auth), secret, 'sha512', 'base64'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Rest-Key': this.apiKey, + 'Rest-Sign': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bleutrade.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bleutrade.js new file mode 100644 index 000000000..385e7e455 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bleutrade.js @@ -0,0 +1,813 @@ +'use strict'; + +// --------------------------------------------------------------------------- +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidOrder, AuthenticationError, InsufficientFunds, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bleutrade extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bleutrade', + 'name': 'Bleutrade', + 'countries': ['BR'], // Brazil + 'rateLimit': 1000, + 'certified': false, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createLimitOrder': false, + 'createMarketOrder': false, + 'createOrder': true, + 'editOrder': false, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': false, + 'fetchWithdrawals': true, + 'withdraw': false, + }, + 'timeframes': { + '1h': '1h', + '4h': '4h', + '8h': '8h', + '1d': '1d', + '1w': '1w', + }, + 'hostname': 'bleutrade.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30303000-b602dbe6-976d-11e7-956d-36c5049c01e7.jpg', + 'api': { + 'v3Private': 'https://{hostname}/api/v3/private', + 'v3Public': 'https://{hostname}/api/v3/public', + }, + 'www': 'https://bleutrade.com', + 'doc': [ + 'https://app.swaggerhub.com/apis-docs/bleu/white-label/3.0.0', + ], + 'fees': 'https://bleutrade.com/fees/', + }, + 'api': { + 'v3Public': { + 'get': [ + 'getassets', + 'getmarkets', + 'getticker', + 'getmarketsummary', + 'getmarketsummaries', + 'getorderbook', + 'getmarkethistory', + 'getcandles', + ], + }, + 'v3Private': { + 'get': [ + 'statement', + ], + 'post': [ + 'getbalance', + 'getbalances', + 'buylimit', + 'selllimit', + 'buylimitami', + 'selllimitami', + 'buystoplimit', + 'sellstoplimit', + 'ordercancel', + 'getopenorders', + 'getcloseorders', + 'getdeposithistory', + 'getdepositaddress', + 'getmytransactions', + 'withdraw', + 'directtransfer', + 'getwithdrawhistory', + 'getlimits', + ], + }, + }, + 'commonCurrencies': { + 'EPC': 'Epacoin', + }, + 'exceptions': { + 'exact': { + 'ERR_INSUFICIENT_BALANCE': InsufficientFunds, + 'ERR_LOW_VOLUME': BadRequest, + 'Invalid form': BadRequest, + }, + 'broad': { + 'Order is not open': InvalidOrder, + 'Invalid Account / Api KEY / Api Secret': AuthenticationError, // also happens when an invalid nonce is used + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.25 / 100, + 'maker': 0.25 / 100, + }, + }, + 'options': { + 'parseOrderStatus': true, + }, + }); + // undocumented api calls + // https://bleutrade.com/api/v3/public/tradingview/symbols?symbol=ETH_BTC + // https://bleutrade.com/api/v3/public/tradingview/config + // https://bleutrade.com/api/v3/public/tradingview/time + // https://bleutrade.com/api/v3/private/getcloseorders?market=ETH_BTC + // https://bleutrade.com/config contains the fees + } + + async fetchCurrencies (params = {}) { + const response = await this.v3PublicGetGetassets (params); + const items = response['result']; + const result = {}; + for (let i = 0; i < items.length; i++) { + // { Asset: 'USDT', + // AssetLong: 'Tether', + // MinConfirmation: 4, + // WithdrawTxFee: 1, + // WithdrawTxFeePercent: 0, + // SystemProtocol: 'ETHERC20', + // IsActive: true, + // InfoMessage: '', + // MaintenanceMode: false, + // MaintenanceMessage: '', + // FormatPrefix: '', + // FormatSufix: '', + // DecimalSeparator: '.', + // ThousandSeparator: ',', + // DecimalPlaces: 8, + // Currency: 'USDT', + // CurrencyLong: 'Tether', + // CoinType: 'ETHERC20' } + const item = items[i]; + const id = this.safeString (item, 'Asset'); + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'name': this.safeString (item, 'AssetLong'), + 'active': this.safeValue (item, 'IsActive') && !this.safeValue (item, 'MaintenanceMode'), + 'fee': this.safeFloat (item, 'WithdrawTxFee'), + 'precision': this.safeFloat (item, 'DecimalPlaces'), + 'info': item, + 'limits': this.limits, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + // https://github.com/ccxt/ccxt/issues/5668 + const response = await this.v3PublicGetGetmarkets (params); + const result = []; + const markets = this.safeValue (response, 'result'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + // { MarketName: 'LTC_USDT', + // MarketAsset: 'LTC', + // BaseAsset: 'USDT', + // MarketAssetLong: 'Litecoin', + // BaseAssetLong: 'Tether', + // IsActive: true, + // MinTradeSize: 0.0001, + // InfoMessage: '', + // MarketCurrency: 'LTC', + // BaseCurrency: 'USDT', + // MarketCurrencyLong: 'Litecoin', + // BaseCurrencyLong: 'Tether' } + const id = this.safeString (market, 'MarketName'); + const baseId = this.safeString (market, 'MarketAsset'); + const quoteId = this.safeString (market, 'BaseAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const active = this.safeValue (market, 'IsActive', false); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'maker': this.fees['trading']['maker'], + 'taker': this.fees['trading']['taker'], + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'MinTradeSize'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'type': 'ALL', + }; + if (limit !== undefined) { + request['depth'] = limit; // 50 + } + const response = await this.v3PublicGetGetorderbook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'result'); + if (!orderbook) { + throw new ExchangeError (this.id + ' no orderbook data in ' + this.json (response)); + } + return this.parseOrderBook (orderbook, undefined, 'buy', 'sell', 'Rate', 'Quantity'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.v3PublicGetGetmarketsummary (this.extend (request, params)); + const ticker = this.safeValue (response, 'result', {}); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.v3PublicGetGetmarketsummaries (params); + const result = this.safeValue (response, 'result'); + const tickers = []; + for (let i = 0; i < result.length; i++) { + const ticker = this.parseTicker (result[i]); + tickers.push (ticker); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // { TimeStamp: '2020-01-14 14:32:28', + // MarketName: 'LTC_USDT', + // MarketAsset: 'LTC', + // BaseAsset: 'USDT', + // MarketAssetName: 'Litecoin', + // BaseAssetName: 'Tether', + // PrevDay: 49.2867503, + // High: 56.78622664, + // Low: 49.27384025, + // Last: 53.94, + // Average: 51.37509368, + // Volume: 1.51282404, + // BaseVolume: 77.72147677, + // Bid: 53.62070218, + // Ask: 53.94, + // IsActive: 'true', + // InfoMessage: '', + // MarketCurrency: 'Litecoin', + // BaseCurrency: 'Tether' } + const timestamp = this.parse8601 (this.safeString (ticker, 'TimeStamp')); + const marketId = this.safeString (ticker, 'MarketName'); + const symbol = this.safeSymbol (marketId, market, '_'); + const previous = this.safeFloat (ticker, 'PrevDay'); + const last = this.safeFloat (ticker, 'Last'); + let change = undefined; + let percentage = undefined; + if (last !== undefined) { + if (previous !== undefined) { + change = last - previous; + if (previous > 0) { + percentage = (change / previous) * 100; + } + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'High'), + 'low': this.safeFloat (ticker, 'Low'), + 'bid': this.safeFloat (ticker, 'Bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'Ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': previous, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'Volume'), + 'quoteVolume': this.safeFloat (ticker, 'BaseVolume'), + 'info': ticker, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.parse8601 (ohlcv['TimeStamp'] + '+00:00'), + this.safeFloat (ohlcv, 'Open'), + this.safeFloat (ohlcv, 'High'), + this.safeFloat (ohlcv, 'Low'), + this.safeFloat (ohlcv, 'Close'), + this.safeFloat (ohlcv, 'Volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '15m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'period': this.timeframes[timeframe], + 'market': market['id'], + 'count': limit, + }; + const response = await this.v3PublicGetGetcandles (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + // todo: STOP-LIMIT and AMI order types are supported + throw new InvalidOrder (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const request = { + 'rate': this.priceToPrecision (symbol, price), + 'quantity': this.amountToPrecision (symbol, amount), + 'tradeType': (side === 'buy') ? '1' : '0', + 'market': this.marketId (symbol), + }; + let response = undefined; + if (side === 'buy') { + response = await this.v3PrivatePostBuylimit (this.extend (request, params)); + } else { + response = await this.v3PrivatePostSelllimit (this.extend (request, params)); + } + // { success: true, + // message: "", + // result: "161105236" }, + return { + 'info': response, + 'id': this.safeString (response, 'result'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'orderid': id, + }; + const response = await this.v3PrivatePostOrdercancel (this.extend (request, params)); + // { success: true, message: '', result: '' } + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.v3PrivatePostGetopenorders (this.extend (request, params)); + const items = this.safeValue (response, 'result', []); + return this.parseOrders (items, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.v3PrivatePostGetbalances (params); + const result = { 'info': response }; + const items = response['result']; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const currencyId = this.safeString (item, 'Asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (item, 'Available'); + account['total'] = this.safeFloat (item, 'Balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.v3PrivatePostGetcloseorders (this.extend (request, params)); + const orders = this.safeValue (response, 'result', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this[method] (params); + const transactions = this.safeValue (response, 'result', []); + return this.parseTransactions (transactions, code, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('v3PrivatePostGetdeposithistory', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('v3PrivatePostGetwithdrawhistory', code, since, limit, params); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.v3PrivatePostGetdepositaddress (this.extend (request, params)); + // { success: true, + // message: '', + // result: + // { Asset: 'ETH', + // AssetName: 'Ethereum', + // DepositAddress: '0x748c5c8jhksjdfhd507d3aa9', + // Currency: 'ETH', + // CurrencyName: 'Ethereum' } } + const item = response['result']; + const address = this.safeString (item, 'DepositAddress'); + return { + 'currency': code, + 'address': this.checkAddress (address), + // 'tag': tag, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + // deposits don't seem to appear in here + const types = { + 'TRADE': 'trade', + 'WITHDRAW': 'transaction', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // trade (both sides) + // + // { + // ID: 109660527, + // TimeStamp: '2018-11-14 15:12:57.140776', + // Asset: 'ETH', + // AssetName: 'Ethereum', + // Amount: 0.01, + // Type: 'TRADE', + // Description: 'Trade +, order id 133111123', + // Comments: '', + // CoinSymbol: 'ETH', + // CoinName: 'Ethereum' + // } + // + // { + // ID: 109660526, + // TimeStamp: '2018-11-14 15:12:57.140776', + // Asset: 'BTC', + // AssetName: 'Bitcoin', + // Amount: -0.00031776, + // Type: 'TRADE', + // Description: 'Trade -, order id 133111123, fee -0.00000079', + // Comments: '', + // CoinSymbol: 'BTC', + // CoinName: 'Bitcoin' + // } + // + // withdrawal + // + // { + // ID: 104672316, + // TimeStamp: '2018-05-03 08:18:19.031831', + // Asset: 'DOGE', + // AssetName: 'Dogecoin', + // Amount: -61893.87864686, + // Type: 'WITHDRAW', + // Description: 'Withdraw: 61883.87864686 to address DD8tgehNNyYB2iqVazi2W1paaztgcWXtF6; fee 10.00000000', + // Comments: '', + // CoinSymbol: 'DOGE', + // CoinName: 'Dogecoin' + // } + // + const code = this.safeCurrencyCode (this.safeString (item, 'CoinSymbol'), currency); + const description = this.safeString (item, 'Description'); + const type = this.parseLedgerEntryType (this.safeString (item, 'Type')); + let referenceId = undefined; + let fee = undefined; + const delimiter = (type === 'trade') ? ', ' : '; '; + const parts = description.split (delimiter); + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; + if (part.indexOf ('fee') === 0) { + part = part.replace ('fee ', ''); + let feeCost = parseFloat (part); + if (feeCost < 0) { + feeCost = -feeCost; + } + fee = { + 'cost': feeCost, + 'currency': code, + }; + } else if (part.indexOf ('order id') === 0) { + referenceId = part.replace ('order id ', ''); + } + // + // does not belong to Ledger, related to parseTransaction + // + // if (part.indexOf ('Withdraw') === 0) { + // const details = part.split (' to address '); + // if (details.length > 1) { + // address = details[1]; + // } + // + } + const timestamp = this.parse8601 (this.safeString (item, 'TimeStamp')); + let amount = this.safeFloat (item, 'Amount'); + let direction = undefined; + if (amount !== undefined) { + direction = 'in'; + if (amount < 0) { + direction = 'out'; + amount = -amount; + } + } + const id = this.safeString (item, 'ID'); + return { + 'id': id, + 'info': item, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'direction': direction, + 'account': undefined, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': undefined, + 'after': undefined, + 'status': 'ok', + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // only seems to return 100 items and there is no documented way to change page size or offset + const request = { + }; + const response = await this.v3PrivatePostGetmytransactions (this.extend (request, params)); + const items = response['result']; + return this.parseLedger (items, code, since, limit); + } + + parseOrder (order, market = undefined) { + // + // fetchClosedOrders + // + // { OrderID: 89742658, + // Exchange: 'DOGE_BTC', + // Type: 'BUY', + // Quantity: 10000, + // QuantityRemaining: 0, + // QuantityBaseTraded: 0, + // Price: 6.6e-7, + // Status: 'OK', + // Created: '2018-02-16 08:55:36', + // Comments: '' } + // + // fetchOpenOrders + // + // { OrderID: 161105302, + // Exchange: 'ETH_BTC', + // Type: 'SELL', + // Quantity: 0.4, + // QuantityRemaining: 0.4, + // QuantityBaseTraded: 0, + // Price: 0.04, + // Status: 'OPEN', + // Created: '2020-01-22 09:21:27', + // Comments: { String: '', Valid: true } + const side = this.safeString (order, 'Type').toLowerCase (); + const status = this.parseOrderStatus (this.safeString (order, 'Status')); + const marketId = this.safeString (order, 'Exchange'); + const symbol = this.safeSymbol (marketId, market, '_'); + let timestamp = undefined; + if ('Created' in order) { + timestamp = this.parse8601 (order['Created'] + '+00:00'); + } + let price = this.safeFloat (order, 'Price'); + let cost = undefined; + const amount = this.safeFloat (order, 'Quantity'); + const remaining = this.safeFloat (order, 'QuantityRemaining'); + let filled = undefined; + if (amount !== undefined && remaining !== undefined) { + filled = amount - remaining; + } + if (!cost) { + if (price && filled) { + cost = price * filled; + } + } + if (!price) { + if (cost && filled) { + price = cost / filled; + } + } + const average = this.safeFloat (order, 'PricePerUnit'); + const id = this.safeString (order, 'OrderID'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'OK': 'closed', + 'OPEN': 'open', + 'CANCELED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // deposit: + // + // { ID: 118698752, + // Timestamp: '2020-01-21 11:16:09', + // Asset: 'ETH', + // Amount: 1, + // TransactionID: '', + // Status: 'CONFIRMED', + // Label: '0x748c5c8228d0c596f4d07f338blah', + // Symbol: 'ETH' } + // + // withdrawal: + // + // { ID: 689281, + // Timestamp: '2019-07-05 13:14:43', + // Asset: 'BTC', + // Amount: -0.108959, + // TransactionID: 'da48d6901fslfjsdjflsdjfls852b87e362cad1', + // Status: 'CONFIRMED', + // Label: '0.1089590;35wztHPMgrebFvvblah;0.00100000', + // Symbol: 'BTC' } + // + const id = this.safeString (transaction, 'ID'); + let amount = this.safeFloat (transaction, 'Amount'); + let type = 'deposit'; + if (amount < 0) { + amount = Math.abs (amount); + type = 'withdrawal'; + } + const currencyId = this.safeString (transaction, 'Asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const label = this.safeString (transaction, 'Label'); + const timestamp = this.parse8601 (this.safeString (transaction, 'Timestamp')); + let txid = this.safeString (transaction, 'TransactionID'); + let address = undefined; + let feeCost = undefined; + const labelParts = label.split (';'); + if (labelParts.length === 3) { + amount = parseFloat (labelParts[0]); + address = labelParts[1]; + feeCost = parseFloat (labelParts[2]); + } else { + address = label; + } + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'currency': code, + 'cost': feeCost, + }; + } + let status = 'ok'; + if (txid === 'CANCELED') { + txid = undefined; + status = 'canceled'; + } + return { + 'info': transaction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'fee': fee, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }) + '/'; + if (api === 'v3Private') { + this.checkRequiredCredentials (); + const request = { + 'apikey': this.apiKey, + 'nonce': this.nonce (), + }; + url += path + '?' + this.urlencode (this.extend (request, params)); + const signature = this.hmac (this.encode (url), this.encode (this.secret), 'sha512'); + headers = { 'apisign': signature }; + } else { + url += path + '?' + this.urlencode (params); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // examples... + // {"success":false,"message":"Erro: Order is not open.","result":""} <-- 'error' is spelt wrong + // {"success":false,"message":"Error: Very low volume.","result":"ERR_LOW_VOLUME"} + // {"success":false,"message":"Error: Insuficient Balance","result":"ERR_INSUFICIENT_BALANCE"} + // {"success":false,"message":"Invalid form","result":null} + // + const success = this.safeValue (response, 'success'); + if (success === undefined) { + throw new ExchangeError (this.id + ': malformed response: ' + this.json (response)); + } + if (!success) { + const feedback = this.id + ' ' + body; + const errorCode = this.safeString (response, 'result'); + if (errorCode !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + const errorMessage = this.safeString (response, 'message'); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorMessage, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], errorMessage, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/braziliex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/braziliex.js new file mode 100644 index 000000000..ac9d460c0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/braziliex.js @@ -0,0 +1,641 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidOrder, AuthenticationError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class braziliex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'braziliex', + 'name': 'Braziliex', + 'countries': [ 'BR' ], + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/34703593-c4498674-f504-11e7-8d14-ff8e44fb78c1.jpg', + 'api': 'https://braziliex.com/api/v1', + 'www': 'https://braziliex.com/', + 'doc': 'https://braziliex.com/exchange/api.php', + 'fees': 'https://braziliex.com/exchange/fees.php', + 'referral': 'https://braziliex.com/?ref=5FE61AB6F6D67DA885BC98BA27223465', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'ticker', + 'ticker/{market}', + 'orderbook/{market}', + 'tradehistory/{market}', + ], + }, + 'private': { + 'post': [ + 'balance', + 'complete_balance', + 'open_orders', + 'trade_history', + 'deposit_address', + 'sell', + 'buy', + 'cancel_order', + 'order_status', + ], + }, + }, + 'commonCurrencies': { + 'EPC': 'Epacoin', + 'ABC': 'Anti Bureaucracy Coin', + }, + 'fees': { + 'trading': { + 'maker': 0.005, + 'taker': 0.005, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'options': { + 'fetchCurrencies': { + 'expires': 1000, // 1 second + }, + }, + }); + } + + async fetchCurrenciesFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetCurrencies (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchCurrencies'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + // + // { + // brl: { + // name: "Real", + // withdrawal_txFee: 0.0075, + // txWithdrawalFee: 9, + // MinWithdrawal: 30, + // minConf: 1, + // minDeposit: 0, + // txDepositFee: 0, + // txDepositPercentageFee: 0, + // minAmountTradeFIAT: 5, + // minAmountTradeBTC: 0.0001, + // minAmountTradeUSDT: 0.0001, + // decimal: 8, + // decimal_withdrawal: 8, + // active: 1, + // dev_active: 1, + // under_maintenance: 0, + // order: "010", + // is_withdrawal_active: 1, + // is_deposit_active: 1, + // is_token_erc20: 0, + // is_fiat: 1, + // gateway: 0, + // }, + // btc: { + // name: "Bitcoin", + // txWithdrawalMinFee: 0.000125, + // txWithdrawalFee: 0.00015625, + // MinWithdrawal: 0.0005, + // minConf: 1, + // minDeposit: 0, + // txDepositFee: 0, + // txDepositPercentageFee: 0, + // minAmountTradeFIAT: 5, + // minAmountTradeBTC: 0.0001, + // minAmountTradeUSDT: 0.0001, + // decimal: 8, + // decimal_withdrawal: 8, + // active: 1, + // dev_active: 1, + // under_maintenance: 0, + // order: "011", + // is_withdrawal_active: 1, + // is_deposit_active: 1, + // is_token_erc20: 0, + // is_fiat: 0, + // gateway: 1, + // } + // } + // + this.options['currencies'] = { + 'timestamp': this.milliseconds (), + 'response': response, + }; + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = response[id]; + const precision = this.safeInteger (currency, 'decimal'); + const code = this.safeCurrencyCode (id); + let active = this.safeInteger (currency, 'active') === 1; + const maintenance = this.safeInteger (currency, 'under_maintenance'); + if (maintenance !== 0) { + active = false; + } + const canWithdraw = this.safeInteger (currency, 'is_withdrawal_active') === 1; + const canDeposit = this.safeInteger (currency, 'is_deposit_active') === 1; + if (!canWithdraw || !canDeposit) { + active = false; + } + result[code] = { + 'id': id, + 'code': code, + 'name': currency['name'], + 'active': active, + 'precision': precision, + 'funding': { + 'withdraw': { + 'active': canWithdraw, + 'fee': this.safeFloat (currency, 'txWithdrawalFee'), + }, + 'deposit': { + 'active': canDeposit, + 'fee': this.safeFloat (currency, 'txDepositFee'), + }, + }, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'MinWithdrawal'), + 'max': Math.pow (10, precision), + }, + 'deposit': { + 'min': this.safeFloat (currency, 'minDeposit'), + 'max': undefined, + }, + }, + 'info': currency, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const currencies = await this.fetchCurrenciesFromCache (params); + const response = await this.publicGetTicker (); + // + // { + // btc_brl: { + // active: 1, + // market: 'btc_brl', + // last: 14648, + // percentChange: -0.95, + // baseVolume24: 27.856, + // quoteVolume24: 409328.039, + // baseVolume: 27.856, + // quoteVolume: 409328.039, + // highestBid24: 14790, + // lowestAsk24: 14450.01, + // highestBid: 14450.37, + // lowestAsk: 14699.98 + // }, + // ... + // } + // + const ids = Object.keys (response); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = response[id]; + const [ baseId, quoteId ] = id.split ('_'); + const uppercaseBaseId = baseId.toUpperCase (); + const uppercaseQuoteId = quoteId.toUpperCase (); + const base = this.safeCurrencyCode (uppercaseBaseId); + const quote = this.safeCurrencyCode (uppercaseQuoteId); + const symbol = base + '/' + quote; + const baseCurrency = this.safeValue (currencies, baseId, {}); + const quoteCurrency = this.safeValue (currencies, quoteId, {}); + const quoteIsFiat = this.safeInteger (quoteCurrency, 'is_fiat', 0); + let minCost = undefined; + if (quoteIsFiat) { + minCost = this.safeFloat (baseCurrency, 'minAmountTradeFIAT'); + } else { + minCost = this.safeFloat (baseCurrency, 'minAmountTrade' + uppercaseQuoteId); + } + const isActive = this.safeInteger (market, 'active'); + const active = (isActive === 1); + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highestBid24'), + 'low': this.safeFloat (ticker, 'lowestAsk24'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'percentChange'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume24'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume24'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTickerMarket (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const result = {}; + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const marketId = ids[i]; + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (response[marketId], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + const response = await this.publicGetOrderbookMarket (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString2 (trade, 'date_exec', 'date')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const cost = this.safeFloat (trade, 'total'); + const orderId = this.safeString (trade, 'order_number'); + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, '_id'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTradehistoryMarket (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privatePostCompleteBalance (params); + const result = { 'info': balances }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // { + // "order_number":"58ee441d05f8233fadabfb07", + // "type":"buy", + // "market":"ltc_btc", + // "price":"0.01000000", + // "amount":"0.00200000", + // "total":"0.00002000", + // "progress":"1.0000", + // "date":"2017-03-12 15:13:33" + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '_'); + let timestamp = this.safeInteger (order, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'date')); + } + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'total', 0.0); + const amount = this.safeFloat (order, 'amount'); + const filledPercentage = this.safeFloat (order, 'progress'); + const filled = amount * filledPercentage; + const remaining = parseFloat (this.amountToPrecision (symbol, amount - filled)); + let info = order; + if ('info' in info) { + info = order['info']; + } + const id = this.safeString (order, 'order_number'); + const fee = this.safeValue (order, 'fee'); // propagated from createOrder + const status = (filledPercentage === 1.0) ? 'closed' : 'open'; + const side = this.safeString (order, 'type'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': info, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = 'privatePost' + this.capitalize (side); + const request = { + 'market': market['id'], + // 'price': this.priceToPrecision (symbol, price), + // 'amount': this.amountToPrecision (symbol, amount), + 'price': price, + 'amount': amount, + }; + const response = await this[method] (this.extend (request, params)); + // + // sell + // + // { + // "success":1, + // "message":" ##RESERVED FOR ORDER / SELL / XMR_BTC / AMOUNT: 0.01 XMR / PRICE: 0.017 BTC / TOTAL: 0.00017000 BTC / FEE: 0.00002500 XMR ", + // "order_number":"590b962ba5b98335965fa0a8" + // } + // + // buy + // + // { + // "success":1, + // "message":" ##RESERVED FOR ORDER / BUY / XMR_BTC / AMOUNT: 0.005 XMR / PRICE: 0.017 BTC / TOTAL: 0.00008500 BTC / FEE: 0.00000021 BTC ", + // "order_number":"590b962ba5b98335965fa0c0" + // } + // + const success = this.safeInteger (response, 'success'); + if (success !== 1) { + throw new InvalidOrder (this.id + ' ' + this.json (response)); + } + const message = this.safeString (response, 'message'); + let parts = message.split (' / '); + parts = parts.slice (1); + const feeParts = parts[5].split (' '); + const amountParts = parts[2].split (' '); + const priceParts = parts[3].split (' '); + const totalParts = parts[4].split (' '); + const order = this.parseOrder ({ + 'timestamp': this.milliseconds (), + 'order_number': response['order_number'], + 'type': this.safeStringLower (parts, 0), + 'market': parts[0].toLowerCase (), + 'amount': this.safeString (amountParts, 1), + 'price': this.safeString (priceParts, 1), + 'total': this.safeString (totalParts, 1), + 'fee': { + 'cost': this.safeFloat (feeParts, 1), + 'currency': this.safeString (feeParts, 2), + }, + 'progress': '0.0', + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_number': id, + 'market': market['id'], + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_number': id, + 'market': market['id'], + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privatePostOpenOrders (this.extend (request, params)); + const orders = this.safeValue (response, 'order_open', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const trades = this.safeValue (response, 'trade_history', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostDepositAddress (this.extend (request, params)); + const address = this.safeString (response, 'deposit_address'); + this.checkAddress (address); + const tag = this.safeString (response, 'payment_id'); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + api; + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + query = this.extend ({ + 'command': path, + 'nonce': this.nonce (), + }, query); + body = this.urlencode (query); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Content-type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ((typeof response === 'string') && (response.length < 1)) { + throw new ExchangeError (this.id + ' returned empty response'); + } + if ('success' in response) { + const success = this.safeInteger (response, 'success'); + if (success === 0) { + const message = this.safeString (response, 'message'); + if (message === 'Invalid APIKey') { + throw new AuthenticationError (message); + } + throw new ExchangeError (message); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcalpha.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcalpha.js new file mode 100644 index 000000000..441947aa1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcalpha.js @@ -0,0 +1,502 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, DDoSProtection, InvalidOrder, InsufficientFunds } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcalpha extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcalpha', + 'name': 'BTC-Alpha', + 'countries': [ 'US' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': false, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '4h': '240', + '1d': 'D', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/42625213-dabaa5da-85cf-11e8-8f99-aa8f8f7699f0.jpg', + 'api': 'https://btc-alpha.com/api', + 'www': 'https://btc-alpha.com', + 'doc': 'https://btc-alpha.github.io/api-docs', + 'fees': 'https://btc-alpha.com/fees/', + 'referral': 'https://btc-alpha.com/?r=123788', + }, + 'api': { + 'public': { + 'get': [ + 'currencies/', + 'pairs/', + 'orderbook/{pair_name}/', + 'exchanges/', + 'charts/{pair}/{type}/chart/', + ], + }, + 'private': { + 'get': [ + 'wallets/', + 'orders/own/', + 'order/{id}/', + 'exchanges/own/', + 'deposits/', + 'withdraws/', + ], + 'post': [ + 'order/', + 'order-cancel/', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.00135, + 'LTC': 0.0035, + 'XMR': 0.018, + 'ZEC': 0.002, + 'ETH': 0.01, + 'ETC': 0.01, + 'SIB': 1.5, + 'CCRB': 4, + 'PZM': 0.05, + 'ITI': 0.05, + 'DCY': 5, + 'R': 5, + 'ATB': 0.05, + 'BRIA': 0.05, + 'KZC': 0.05, + 'HWC': 1, + 'SPA': 1, + 'SMS': 0.001, + 'REC': 0.01, + 'SUP': 1, + 'BQ': 100, + 'GDS': 0.1, + 'EVN': 300, + 'TRKC': 0.01, + 'UNI': 1, + 'STN': 1, + 'BCH': undefined, + 'QBIC': 0.5, + }, + }, + }, + 'commonCurrencies': { + 'CBC': 'Cashbery', + }, + 'exceptions': { + 'exact': {}, + 'broad': { + 'Out of balance': InsufficientFunds, // {"date":1570599531.4814300537,"error":"Out of balance -9.99243661 BTC"} + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPairs (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'currency1'); + const quoteId = this.safeString (market, 'currency2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': this.safeInteger (market, 'price_precision'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minimum_order_size'), + 'max': this.safeFloat (market, 'maximum_order_size'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + 'baseId': undefined, + 'quoteId': undefined, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair_name': this.marketId (symbol), + }; + if (limit) { + request['limit_sell'] = limit; + request['limit_buy'] = limit; + } + const response = await this.publicGetOrderbookPairName (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'buy', 'sell', 'price', 'amount'); + } + + parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) { + const result = []; + for (let i = 0; i < bidasks.length; i++) { + const bidask = bidasks[i]; + if (bidask) { + result.push (this.parseBidAsk (bidask, priceKey, amountKey)); + } + } + return result; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market === undefined) { + market = this.safeValue (this.marketsById, trade['pair']); + } + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (trade, 'timestamp'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const id = this.safeString2 (trade, 'id', 'tid'); + const side = this.safeString2 (trade, 'my_side', 'side'); + const orderId = this.safeString (trade, 'o_id'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const trades = await this.publicGetExchanges (this.extend (request, params)); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":1591296000, + // "open":0.024746, + // "close":0.024728, + // "low":0.024728, + // "high":0.024753, + // "volume":16.624 + // } + // + return [ + this.safeTimestamp (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.publicGetChartsPairTypeChart (this.extend (request, params)); + // + // [ + // {"time":1591296000,"open":0.024746,"close":0.024728,"low":0.024728,"high":0.024753,"volume":16.624}, + // {"time":1591295700,"open":0.024718,"close":0.02475,"low":0.024711,"high":0.02475,"volume":31.645}, + // {"time":1591295400,"open":0.024721,"close":0.024717,"low":0.024711,"high":0.02473,"volume":65.071} + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWallets (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'reserve'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + '1': 'open', + '2': 'canceled', + '3': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + let symbol = undefined; + if (market === undefined) { + market = this.safeValue (this.marketsById, order['pair']); + } + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (order, 'date'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString2 (order, 'oid', 'id'); + let trades = this.safeValue (order, 'trades', []); + trades = this.parseTrades (trades, market); + const side = this.safeString2 (order, 'my_side', 'type'); + let filled = undefined; + const numTrades = trades.length; + if (numTrades > 0) { + filled = 0.0; + for (let i = 0; i < numTrades; i++) { + filled = this.sum (filled, trades[i]['amount']); + } + } + let remaining = undefined; + if ((amount !== undefined) && (amount > 0) && (filled !== undefined)) { + remaining = Math.max (0, amount - filled); + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': undefined, + 'info': order, + 'lastTradeTimestamp': undefined, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'amount': amount, + 'price': this.priceToPrecision (symbol, price), + }; + const response = await this.privatePostOrder (this.extend (request, params)); + if (!response['success']) { + throw new InvalidOrder (this.id + ' ' + this.json (response)); + } + const order = this.parseOrder (response, market); + amount = (order['amount'] > 0) ? order['amount'] : amount; + return this.extend (order, { + 'amount': amount, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order': id, + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const order = await this.privateGetOrderId (this.extend (request, params)); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const orders = await this.privateGetOrdersOwn (this.extend (request, params)); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': '1', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': '3', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbol !== undefined) { + const market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const trades = await this.privateGetExchangesOwn (this.extend (request, params)); + return this.parseTrades (trades, undefined, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.urlencode (this.keysort (this.omit (params, this.extractParams (path)))); + let url = this.urls['api'] + '/'; + if (path !== 'charts/{pair}/{type}/chart/') { + url += 'v1/'; + } + url += this.implodeParams (path, params); + headers = { 'Accept': 'application/json' }; + if (api === 'public') { + if (query.length) { + url += '?' + query; + } + } else { + this.checkRequiredCredentials (); + let payload = this.apiKey; + if (method === 'POST') { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + body = query; + payload += body; + } else if (query.length) { + url += '?' + query; + } + headers['X-KEY'] = this.apiKey; + headers['X-SIGN'] = this.hmac (this.encode (payload), this.encode (this.secret)); + headers['X-NONCE'] = this.nonce ().toString (); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"date":1570599531.4814300537,"error":"Out of balance -9.99243661 BTC"} + // + const error = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + if (error !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + } + if (code === 401 || code === 403) { + throw new AuthenticationError (feedback); + } else if (code === 429) { + throw new DDoSProtection (feedback); + } + if (code < 400) { + return; + } + throw new ExchangeError (feedback); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcbox.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcbox.js new file mode 100644 index 000000000..af988de09 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcbox.js @@ -0,0 +1,425 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InsufficientFunds, InvalidOrder, AuthenticationError, PermissionDenied, InvalidNonce, OrderNotFound, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcbox extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcbox', + 'name': 'BtcBox', + 'countries': [ 'JP' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg', + 'api': 'https://www.btcbox.co.jp/api', + 'www': 'https://www.btcbox.co.jp/', + 'doc': 'https://blog.btcbox.jp/en/archives/8762', + 'fees': 'https://support.btcbox.co.jp/hc/en-us/articles/360001235694-Fees-introduction', + }, + 'api': { + 'public': { + 'get': [ + 'depth', + 'orders', + 'ticker', + ], + }, + 'private': { + 'post': [ + 'balance', + 'trade_add', + 'trade_cancel', + 'trade_list', + 'trade_view', + 'wallet', + ], + }, + }, + 'markets': { + 'BTC/JPY': { 'id': 'btc', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy', 'taker': 0.05 / 100, 'maker': 0.05 / 100 }, + 'ETH/JPY': { 'id': 'eth', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 }, + 'LTC/JPY': { 'id': 'ltc', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 }, + 'BCH/JPY': { 'id': 'bch', 'symbol': 'BCH/JPY', 'base': 'BCH', 'quote': 'JPY', 'baseId': 'bch', 'quoteId': 'jpy', 'taker': 0.10 / 100, 'maker': 0.10 / 100 }, + }, + 'exceptions': { + '104': AuthenticationError, + '105': PermissionDenied, + '106': InvalidNonce, + '107': InvalidOrder, // price should be an integer + '200': InsufficientFunds, + '201': InvalidOrder, // amount too small + '202': InvalidOrder, // price should be [0 : 1000000] + '203': OrderNotFound, + '401': OrderNotFound, // cancel canceled, closed or non-existent order + '402': DDoSProtection, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const currencyId = currency['id']; + const free = currencyId + '_balance'; + if (free in response) { + const account = this.account (); + const used = currencyId + '_lock'; + account['free'] = this.safeFloat (response, free); + account['used'] = this.safeFloat (response, used); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + const numSymbols = this.symbols.length; + if (numSymbols > 1) { + request['coin'] = market['baseId']; + } + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + const numSymbols = this.symbols.length; + if (numSymbols > 1) { + request['coin'] = market['baseId']; + } + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const type = undefined; + const side = this.safeString (trade, 'type'); + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + const numSymbols = this.symbols.length; + if (numSymbols > 1) { + request['coin'] = market['baseId']; + } + const response = await this.publicGetOrders (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amount': amount, + 'price': price, + 'type': side, + 'coin': market['baseId'], + }; + const response = await this.privatePostTradeAdd (this.extend (request, params)); + // + // { + // "result":true, + // "id":"11" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // a special case for btcbox – default symbol is BTC/JPY + if (symbol === undefined) { + symbol = 'BTC/JPY'; + } + const market = this.market (symbol); + const request = { + 'id': id, + 'coin': market['baseId'], + }; + const response = await this.privatePostTradeCancel (this.extend (request, params)); + // + // {"result":true, "id":"11"} + // + return this.parseOrder (response, market); + } + + parseOrderStatus (status) { + const statuses = { + // TODO: complete list + 'part': 'open', // partially or not at all executed + 'all': 'closed', // fully executed + 'cancelled': 'canceled', + 'closed': 'closed', // never encountered, seems to be bug in the doc + 'no': 'closed', // not clarified in the docs... + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id":11, + // "datetime":"2014-10-21 10:47:20", + // "type":"sell", + // "price":42000, + // "amount_original":1.2, + // "amount_outstanding":1.2, + // "status":"closed", + // "trades":[] + // } + // + const id = this.safeString (order, 'id'); + const datetimeString = this.safeString (order, 'datetime'); + let timestamp = undefined; + if (datetimeString !== undefined) { + timestamp = this.parse8601 (order['datetime'] + '+09:00'); // Tokyo time + } + const amount = this.safeFloat (order, 'amount_original'); + const remaining = this.safeFloat (order, 'amount_outstanding'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const price = this.safeFloat (order, 'price'); + let cost = undefined; + if (price !== undefined) { + if (filled !== undefined) { + cost = filled * price; + } + } + // status is set by fetchOrder method only + let status = this.parseOrderStatus (this.safeString (order, 'status')); + // fetchOrders do not return status, use heuristic + if (status === undefined) { + if (remaining !== undefined && remaining === 0) { + status = 'closed'; + } + } + const trades = undefined; // todo: this.parseTrades (order['trades']); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (order, 'type'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'side': side, + 'type': undefined, + 'timeInForce': undefined, + 'status': status, + 'symbol': symbol, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'trades': trades, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // a special case for btcbox – default symbol is BTC/JPY + if (symbol === undefined) { + symbol = 'BTC/JPY'; + } + const market = this.market (symbol); + const request = this.extend ({ + 'id': id, + 'coin': market['baseId'], + }, params); + const response = await this.privatePostTradeView (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOrdersByType (type, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // a special case for btcbox – default symbol is BTC/JPY + if (symbol === undefined) { + symbol = 'BTC/JPY'; + } + const market = this.market (symbol); + const request = { + 'type': type, // 'open' or 'all' + 'coin': market['baseId'], + }; + const response = await this.privatePostTradeList (this.extend (request, params)); + const orders = this.parseOrders (response, market, since, limit); + // status (open/closed/canceled) is undefined + // btcbox does not return status, but we know it's 'open' as we queried for open orders + if (type === 'open') { + for (let i = 0; i < orders.length; i++) { + orders[i]['status'] = 'open'; + } + } + return orders; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByType ('all', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByType ('open', symbol, since, limit, params); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const query = this.extend ({ + 'key': this.apiKey, + 'nonce': nonce, + }, params); + const request = this.urlencode (query); + const secret = this.hash (this.encode (this.secret)); + query['signature'] = this.hmac (this.encode (request), this.encode (secret)); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // resort to defaultErrorHandler + } + // typical error response: {"result":false,"code":"401"} + if (httpCode >= 400) { + return; // resort to defaultErrorHandler + } + const result = this.safeValue (response, 'result'); + if (result === undefined || result === true) { + return; // either public API (no error codes expected) or success + } + const code = this.safeValue (response, 'code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, code, feedback); + throw new ExchangeError (feedback); // unknown message + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let response = await this.fetch2 (path, api, method, params, headers, body); + if (typeof response === 'string') { + // sometimes the exchange returns whitespace prepended to json + response = this.strip (response); + if (!this.isJsonEncodedObject (response)) { + throw new ExchangeError (this.id + ' ' + response); + } + response = JSON.parse (response); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcmarkets.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcmarkets.js new file mode 100644 index 000000000..15bbd9610 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcmarkets.js @@ -0,0 +1,1055 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, OrderNotFound, InvalidOrder, InsufficientFunds, DDoSProtection, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcmarkets extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcmarkets', + 'name': 'BTC Markets', + 'countries': [ 'AU' ], // Australia + 'rateLimit': 1000, // market data cached for 1 second (trades cached for 2 seconds) + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': 'emulated', + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/89731817-b3fb8480-da52-11ea-817f-783b08aaf32b.jpg', + 'api': { + 'public': 'https://api.btcmarkets.net', + 'private': 'https://api.btcmarkets.net', + }, + 'www': 'https://btcmarkets.net', + 'doc': [ + 'https://api.btcmarkets.net/doc/v3', + 'https://github.com/BTCMarkets/API', + ], + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'markets/{marketId}/ticker', + 'markets/{marketId}/trades', + 'markets/{marketId}/orderbook', + 'markets/{marketId}/candles', + 'markets/tickers', + 'markets/orderbooks', + 'time', + ], + }, + 'private': { + 'get': [ + 'orders', + 'orders/{id}', + 'batchorders/{ids}', + 'trades', + 'trades/{id}', + 'withdrawals', + 'withdrawals/{id}', + 'deposits', + 'deposits/{id}', + 'transfers', + 'transfers/{id}', + 'addresses', + 'withdrawal-fees', + 'assets', + 'accounts/me/trading-fees', + 'accounts/me/withdrawal-limits', + 'accounts/me/balances', + 'accounts/me/transactions', + 'reports/{id}', + ], + 'post': [ + 'orders', + 'batchorders', + 'withdrawals', + 'reports', + ], + 'delete': [ + 'orders', + 'orders/{id}', + 'batchorders/{ids}', + ], + 'put': [ + 'orders/{id}', + ], + }, + }, + 'timeframes': { + '1m': '1m', + '1h': '1h', + '1d': '1d', + }, + 'exceptions': { + '3': InvalidOrder, + '6': DDoSProtection, + 'InsufficientFund': InsufficientFunds, + 'InvalidPrice': InvalidOrder, + 'InvalidAmount': InvalidOrder, + 'MissingArgument': InvalidOrder, + 'OrderAlreadyCancelled': InvalidOrder, + 'OrderNotFound': OrderNotFound, + 'OrderStatusIsFinal': InvalidOrder, + 'InvalidPaginationParameter': BadRequest, + }, + 'fees': { + 'percentage': true, + 'tierBased': true, + 'maker': -0.05 / 100, + 'taker': 0.20 / 100, + }, + 'options': { + 'fees': { + 'AUD': { + 'maker': 0.85 / 100, + 'taker': 0.85 / 100, + }, + }, + }, + }); + } + + async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['after'] = since; + } + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('privateGetTransfers', code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('privateGetDeposits', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsWithMethod ('privateGetWithdrawals', code, since, limit, params); + } + + parseTransactionStatus (status) { + // todo: find more statuses + const statuses = { + 'Complete': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const statuses = { + 'Withdraw': 'withdrawal', + 'Deposit': 'deposit', + }; + return this.safeString (statuses, type, type); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": "6500230339", + // "assetName": "XRP", + // "amount": "500", + // "type": "Deposit", + // "creationTime": "2020-07-27T07:52:08.640000Z", + // "status": "Complete", + // "description": "RIPPLE Deposit, XRP 500", + // "fee": "0", + // "lastUpdate": "2020-07-27T07:52:08.665000Z", + // "paymentDetail": { + // "txId": "lsjflsjdfljsd", + // "address": "kjasfkjsdf?dt=873874545" + // } + // } + // + // { + // "id": "500985282", + // "assetName": "BTC", + // "amount": "0.42570126", + // "type": "Withdraw", + // "creationTime": "2017-07-29T12:49:03.931000Z", + // "status": "Complete", + // "description": "BTC withdraw from [nick-btcmarkets@snowmonkey.co.uk] to Address: 1B9DsnSYQ54VMqFHVJYdGoLMCYzFwrQzsj amount: 0.42570126 fee: 0.00000000", + // "fee": "0.0005", + // "lastUpdate": "2017-07-29T12:52:20.676000Z", + // "paymentDetail": { + // "txId": "fkjdsfjsfljsdfl", + // "address": "a;daddjas;djas" + // } + // } + // + // { + // "id": "505102262", + // "assetName": "XRP", + // "amount": "979.836", + // "type": "Deposit", + // "creationTime": "2017-07-31T08:50:01.053000Z", + // "status": "Complete", + // "description": "Ripple Deposit, X 979.8360", + // "fee": "0", + // "lastUpdate": "2017-07-31T08:50:01.290000Z" + // } + // + const timestamp = this.parse8601 (this.safeString (transaction, 'creationTime')); + const lastUpdate = this.parse8601 (this.safeString (transaction, 'lastUpdate')); + let type = this.parseTransactionType (this.safeStringLower (transaction, 'type')); + if (type === 'withdraw') { + type = 'withdrawal'; + } + const cryptoPaymentDetail = this.safeValue (transaction, 'paymentDetail', {}); + const txid = this.safeString (cryptoPaymentDetail, 'txId'); + let address = this.safeString (cryptoPaymentDetail, 'address'); + let tag = undefined; + if (address !== undefined) { + const addressParts = address.split ('?dt='); + const numParts = addressParts.length; + if (numParts > 1) { + address = addressParts[0]; + tag = addressParts[1]; + } + } + const addressTo = address; + const tagTo = tag; + const addressFrom = undefined; + const tagFrom = undefined; + const fee = this.safeFloat (transaction, 'fee'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const currencyId = this.safeString (transaction, 'assetName'); + const code = this.safeCurrencyCode (currencyId); + let amount = this.safeFloat (transaction, 'amount'); + if (fee) { + amount -= fee; + } + return { + 'id': this.safeString (transaction, 'id'), + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': addressTo, + 'addressFrom': addressFrom, + 'tag': tag, + 'tagTo': tagTo, + 'tagFrom': tagFrom, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': lastUpdate, + 'fee': { + 'currency': code, + 'cost': fee, + }, + 'info': transaction, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "marketId":"COMP-AUD", + // "baseAssetName":"COMP", + // "quoteAssetName":"AUD", + // "minOrderAmount":"0.00007", + // "maxOrderAmount":"1000000", + // "amountDecimals":"8", + // "priceDecimals":"2" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const baseId = this.safeString (market, 'baseAssetName'); + const quoteId = this.safeString (market, 'quoteAssetName'); + const id = this.safeString (market, 'marketId'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const fees = this.safeValue (this.safeValue (this.options, 'fees', {}), quote, this.fees); + const pricePrecision = this.safeFloat (market, 'priceDecimals'); + const amountPrecision = this.safeFloat (market, 'amountDecimals'); + const minAmount = this.safeFloat (market, 'minOrderAmount'); + const maxAmount = this.safeFloat (market, 'maxOrderAmount'); + let minPrice = undefined; + if (quote === 'AUD') { + minPrice = Math.pow (10, -pricePrecision); + } + const precision = { + 'amount': amountPrecision, + 'price': pricePrecision, + }; + const limits = { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': minPrice, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'info': market, + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'maker': fees['maker'], + 'taker': fees['taker'], + 'limits': limits, + 'precision': precision, + }); + } + return result; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "timestamp": "2019-09-01T18:34:27.045000Z" + // } + // + return this.parse8601 (this.safeString (response, 'timestamp')); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountsMeBalances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'assetName'); + const code = this.safeCurrencyCode (currencyId); + const total = this.safeFloat (balance, 'balance'); + const used = this.safeFloat (balance, 'locked'); + const account = this.account (); + account['used'] = used; + account['total'] = total; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "2020-09-12T18:30:00.000000Z", + // "14409.45", // open + // "14409.45", // high + // "14403.91", // low + // "14403.91", // close + // "0.01571701" // volume + // ] + // + return [ + this.parse8601 (this.safeString (ohlcv, 0)), + this.safeFloat (ohlcv, 1), // open + this.safeFloat (ohlcv, 2), // high + this.safeFloat (ohlcv, 3), // low + this.safeFloat (ohlcv, 4), // close + this.safeFloat (ohlcv, 5), // volume + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'timeWindow': this.timeframes[timeframe], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + // 'before': 1234567890123, + // 'after': 1234567890123, + // 'limit': limit, // default 10, max 200 + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; // default is 10, max 200 + } + const response = await this.publicGetMarketsMarketIdCandles (this.extend (request, params)); + // + // [ + // ["2020-09-12T18:30:00.000000Z","14409.45","14409.45","14403.91","14403.91","0.01571701"], + // ["2020-09-12T18:21:00.000000Z","14409.45","14409.45","14409.45","14409.45","0.0035"], + // ["2020-09-12T18:03:00.000000Z","14361.37","14361.37","14361.37","14361.37","0.00345221"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + const response = await this.publicGetMarketsMarketIdOrderbook (this.extend (request, params)); + // + // { + // "marketId":"BTC-AUD", + // "snapshotId":1599936148941000, + // "asks":[ + // ["14459.45","0.00456475"], + // ["14463.56","2"], + // ["14470.91","0.98"], + // ], + // "bids":[ + // ["14421.01","0.52"], + // ["14421","0.75"], + // ["14418","0.3521"], + // ] + // } + // + const timestamp = this.safeIntegerProduct (response, 'snapshotId', 0.001); + const orderbook = this.parseOrderBook (response, timestamp); + orderbook['nonce'] = this.safeInteger (response, 'snapshotId'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "marketId":"BAT-AUD", + // "bestBid":"0.3751", + // "bestAsk":"0.377", + // "lastPrice":"0.3769", + // "volume24h":"56192.97613335", + // "volumeQte24h":"21179.13270465", + // "price24h":"0.0119", + // "pricePct24h":"3.26", + // "low24h":"0.3611", + // "high24h":"0.3799", + // "timestamp":"2020-08-09T18:28:23.280000Z" + // } + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'marketId'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const last = this.safeFloat (ticker, 'lastPrice'); + const baseVolume = this.safeFloat (ticker, 'volume24h'); + const quoteVolume = this.safeFloat (ticker, 'volumeQte24h'); + const vwap = this.vwap (baseVolume, quoteVolume); + const change = this.safeFloat (ticker, 'price24h'); + const percentage = this.safeFloat (ticker, 'pricePct24h'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24h'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'bestAsk'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + const response = await this.publicGetMarketsMarketIdTicker (this.extend (request, params)); + // + // { + // "marketId":"BAT-AUD", + // "bestBid":"0.3751", + // "bestAsk":"0.377", + // "lastPrice":"0.3769", + // "volume24h":"56192.97613335", + // "volumeQte24h":"21179.13270465", + // "price24h":"0.0119", + // "pricePct24h":"3.26", + // "low24h":"0.3611", + // "high24h":"0.3799", + // "timestamp":"2020-08-09T18:28:23.280000Z" + // } + // + return this.parseTicker (response, market); + } + + async fetchTicker2 (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetMarketIdTick (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "id":"6191646611", + // "price":"539.98", + // "amount":"0.5", + // "timestamp":"2020-08-09T15:21:05.016000Z", + // "side":"Ask" + // } + // + // private fetchMyTrades + // + // { + // "id": "36014819", + // "marketId": "XRP-AUD", + // "timestamp": "2019-06-25T16:01:02.977000Z", + // "price": "0.67", + // "amount": "1.50533262", + // "side": "Ask", + // "fee": "0.00857285", + // "orderId": "3648306", + // "liquidityType": "Taker", + // "clientOrderId": "48" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const marketId = this.safeString (trade, 'marketId'); + let symbol = undefined; + let base = undefined; + let quote = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let feeCurrencyCode = undefined; + if (quote === 'AUD') { + feeCurrencyCode = quote; + } else { + feeCurrencyCode = base; + } + let side = this.safeString (trade, 'side'); + if (side === 'Bid') { + side = 'buy'; + } else if (side === 'Ask') { + side = 'sell'; + } + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const orderId = this.safeString (trade, 'orderId'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const takerOrMaker = this.safeStringLower (trade, 'liquidityType'); + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + // 'since': 59868345231, + 'marketId': market['id'], + }; + const response = await this.publicGetMarketsMarketIdTrades (this.extend (request, params)); + // + // [ + // {"id":"6191646611","price":"539.98","amount":"0.5","timestamp":"2020-08-09T15:21:05.016000Z","side":"Ask"}, + // {"id":"6191646610","price":"539.99","amount":"0.5","timestamp":"2020-08-09T15:21:05.015000Z","side":"Ask"}, + // {"id":"6191646590","price":"540","amount":"0.00233785","timestamp":"2020-08-09T15:21:04.171000Z","side":"Bid"}, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + // 'price': this.priceToPrecision (symbol, price), + 'amount': this.amountToPrecision (symbol, amount), + // 'type': 'Limit', // "Limit", "Market", "Stop Limit", "Stop", "Take Profit" + 'side': (side === 'buy') ? 'Bid' : 'Ask', + // 'triggerPrice': this.priceToPrecision (symbol, triggerPrice), // required for Stop, Stop Limit, Take Profit orders + // 'targetAmount': this.amountToPrecision (symbol, targetAmount), // target amount when a desired target outcome is required for order execution + // 'timeInForce': 'GTC', // GTC, FOK, IOC + // 'postOnly': false, // boolean if this is a post-only order + // 'selfTrade': 'A', // A = allow, P = prevent + // 'clientOrderId': this.uuid (), + }; + const lowercaseType = type.toLowerCase (); + const orderTypes = this.safeValue (this.options, 'orderTypes', { + 'limit': 'Limit', + 'market': 'Market', + 'stop': 'Stop', + 'stop limit': 'Stop Limit', + 'take profit': 'Take Profit', + }); + request['type'] = this.safeString (orderTypes, lowercaseType, type); + let priceIsRequired = false; + let triggerPriceIsRequired = false; + if (lowercaseType === 'limit') { + priceIsRequired = true; + // } else if (lowercaseType === 'market') { + // ... + // } + } else if (lowercaseType === 'stop limit') { + triggerPriceIsRequired = true; + priceIsRequired = true; + } else if (lowercaseType === 'take profit') { + triggerPriceIsRequired = true; + } else if (lowercaseType === 'stop') { + triggerPriceIsRequired = true; + } + if (priceIsRequired) { + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a price argument for a ' + type + 'order'); + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + } + if (triggerPriceIsRequired) { + const triggerPrice = this.safeFloat (params, 'triggerPrice'); + params = this.omit (params, 'triggerPrice'); + if (triggerPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a triggerPrice parameter for a ' + type + 'order'); + } else { + request['triggerPrice'] = this.priceToPrecision (symbol, triggerPrice); + } + } + const clientOrderId = this.safeString (params, 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clientOrderId'] = clientOrderId; + } + params = this.omit (params, 'clientOrderId'); + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "orderId": "7524", + // "marketId": "BTC-AUD", + // "side": "Bid", + // "type": "Limit", + // "creationTime": "2019-08-30T11:08:21.956000Z", + // "price": "100.12", + // "amount": "1.034", + // "openAmount": "1.034", + // "status": "Accepted", + // "clientOrderId": "1234-5678", + // "timeInForce": "IOC", + // "postOnly": false, + // "selfTrade": "P", + // "triggerAmount": "105", + // "targetAmount": "1000" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + for (let i = 0; i < ids.length; i++) { + ids[i] = parseInt (ids[i]); + } + const request = { + 'ids': ids, + }; + return await this.privateDeleteBatchordersIds (this.extend (request, params)); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privateDeleteOrdersId (this.extend (request, params)); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let currency = undefined; + let cost = undefined; + if (market['quote'] === 'AUD') { + currency = market['quote']; + cost = parseFloat (this.costToPrecision (symbol, amount * price)); + } else { + currency = market['base']; + cost = parseFloat (this.amountToPrecision (symbol, amount)); + } + return { + 'type': takerOrMaker, + 'currency': currency, + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, rate * cost)), + }; + } + + parseOrderStatus (status) { + const statuses = { + 'Accepted': 'open', + 'Placed': 'open', + 'Partially Matched': 'open', + 'Fully Matched': 'closed', + 'Cancelled': 'canceled', + 'Partially Cancelled': 'canceled', + 'Failed': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "orderId": "7524", + // "marketId": "BTC-AUD", + // "side": "Bid", + // "type": "Limit", + // "creationTime": "2019-08-30T11:08:21.956000Z", + // "price": "100.12", + // "amount": "1.034", + // "openAmount": "1.034", + // "status": "Accepted", + // "clientOrderId": "1234-5678", + // "timeInForce": "IOC", + // "postOnly": false, + // "selfTrade": "P", + // "triggerAmount": "105", + // "targetAmount": "1000" + // } + // + const timestamp = this.parse8601 (this.safeString (order, 'creationTime')); + const marketId = this.safeString (order, 'marketId'); + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let side = this.safeString (order, 'side'); + if (side === 'Bid') { + side = 'buy'; + } else if (side === 'Ask') { + side = 'sell'; + } + const type = this.safeStringLower (order, 'type'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const remaining = this.safeFloat (order, 'openAmount'); + let filled = undefined; + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let cost = undefined; + if (price !== undefined) { + if (filled !== undefined) { + cost = price * filled; + } + } + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'triggerPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': undefined, + 'status': status, + 'trades': undefined, + 'fee': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': 'all', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (since !== undefined) { + request['after'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'status': 'open' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (since !== undefined) { + request['after'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // [ + // { + // "id": "36014819", + // "marketId": "XRP-AUD", + // "timestamp": "2019-06-25T16:01:02.977000Z", + // "price": "0.67", + // "amount": "1.50533262", + // "side": "Ask", + // "fee": "0.00857285", + // "orderId": "3648306", + // "liquidityType": "Taker", + // "clientOrderId": "48" + // }, + // { + // "id": "3568960", + // "marketId": "GNT-AUD", + // "timestamp": "2019-06-20T08:44:04.488000Z", + // "price": "0.1362", + // "amount": "0.85", + // "side": "Bid", + // "fee": "0.00098404", + // "orderId": "3543015", + // "liquidityType": "Maker" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + lookupSymbolFromMarketId (marketId) { + let market = undefined; + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return symbol; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.keysort (this.omit (params, this.extractParams (path))); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const secret = this.base64ToBinary (this.encode (this.secret)); + let auth = method + request + nonce; + if ((method === 'GET') || (method === 'DELETE')) { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + auth += body; + } + const signature = this.hmac (this.encode (auth), secret, 'sha512', 'base64'); + headers = { + 'Accept': 'application/json', + 'Accept-Charset': 'UTF-8', + 'Content-Type': 'application/json', + 'BM-AUTH-APIKEY': this.apiKey, + 'BM-AUTH-TIMESTAMP': nonce, + 'BM-AUTH-SIGNATURE': signature, + }; + } else if (api === 'public') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'][api] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + if (!response['success']) { + const error = this.safeString (response, 'errorCode'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, error, feedback); + throw new ExchangeError (feedback); + } + } + // v3 api errors + if (code >= 400) { + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btctradeua.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btctradeua.js new file mode 100644 index 000000000..4d6743bf5 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btctradeua.js @@ -0,0 +1,393 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btctradeua extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btctradeua', + 'name': 'BTC Trade UA', + 'countries': [ 'UA' ], // Ukraine, + 'rateLimit': 3000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'signIn': true, + }, + 'urls': { + 'referral': 'https://btc-trade.com.ua/registration/22689', + 'logo': 'https://user-images.githubusercontent.com/1294454/27941483-79fc7350-62d9-11e7-9f61-ac47f28fcd96.jpg', + 'api': 'https://btc-trade.com.ua/api', + 'www': 'https://btc-trade.com.ua', + 'doc': 'https://docs.google.com/document/d/1ocYA0yMy_RXd561sfG3qEPZ80kyll36HUxvCRe5GbhE/edit', + }, + 'api': { + 'public': { + 'get': [ + 'deals/{symbol}', + 'trades/sell/{symbol}', + 'trades/buy/{symbol}', + 'japan_stat/high/{symbol}', + ], + }, + 'private': { + 'post': [ + 'auth', + 'ask/{symbol}', + 'balance', + 'bid/{symbol}', + 'buy/{symbol}', + 'my_orders/{symbol}', + 'order/status/{id}', + 'remove/order/{id}', + 'sell/{symbol}', + ], + }, + }, + 'markets': { + 'BCH/UAH': { 'id': 'bch_uah', 'symbol': 'BCH/UAH', 'base': 'BCH', 'quote': 'UAH', 'baseId': 'bch', 'quoteId': 'uah' }, + 'BTC/UAH': { 'id': 'btc_uah', 'symbol': 'BTC/UAH', 'base': 'BTC', 'quote': 'UAH', 'baseId': 'btc', 'quoteId': 'uah', 'precision': { 'price': 1 }, 'limits': { 'amount': { 'min': 0.0000000001 }}}, + 'DASH/BTC': { 'id': 'dash_btc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC', 'baseId': 'dash', 'quoteId': 'btc' }, + 'DASH/UAH': { 'id': 'dash_uah', 'symbol': 'DASH/UAH', 'base': 'DASH', 'quote': 'UAH', 'baseId': 'dash', 'quoteId': 'uah' }, + 'DOGE/BTC': { 'id': 'doge_btc', 'symbol': 'DOGE/BTC', 'base': 'DOGE', 'quote': 'BTC', 'baseId': 'doge', 'quoteId': 'btc' }, + 'DOGE/UAH': { 'id': 'doge_uah', 'symbol': 'DOGE/UAH', 'base': 'DOGE', 'quote': 'UAH', 'baseId': 'doge', 'quoteId': 'uah' }, + 'ETH/UAH': { 'id': 'eth_uah', 'symbol': 'ETH/UAH', 'base': 'ETH', 'quote': 'UAH', 'baseId': 'eth', 'quoteId': 'uah' }, + 'ITI/UAH': { 'id': 'iti_uah', 'symbol': 'ITI/UAH', 'base': 'ITI', 'quote': 'UAH', 'baseId': 'iti', 'quoteId': 'uah' }, + 'KRB/UAH': { 'id': 'krb_uah', 'symbol': 'KRB/UAH', 'base': 'KRB', 'quote': 'UAH', 'baseId': 'krb', 'quoteId': 'uah' }, + 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' }, + 'LTC/UAH': { 'id': 'ltc_uah', 'symbol': 'LTC/UAH', 'base': 'LTC', 'quote': 'UAH', 'baseId': 'ltc', 'quoteId': 'uah' }, + 'NVC/BTC': { 'id': 'nvc_btc', 'symbol': 'NVC/BTC', 'base': 'NVC', 'quote': 'BTC', 'baseId': 'nvc', 'quoteId': 'btc' }, + 'NVC/UAH': { 'id': 'nvc_uah', 'symbol': 'NVC/UAH', 'base': 'NVC', 'quote': 'UAH', 'baseId': 'nvc', 'quoteId': 'uah' }, + 'PPC/BTC': { 'id': 'ppc_btc', 'symbol': 'PPC/BTC', 'base': 'PPC', 'quote': 'BTC', 'baseId': 'ppc', 'quoteId': 'btc' }, + 'SIB/UAH': { 'id': 'sib_uah', 'symbol': 'SIB/UAH', 'base': 'SIB', 'quote': 'UAH', 'baseId': 'sib', 'quoteId': 'uah' }, + 'XMR/UAH': { 'id': 'xmr_uah', 'symbol': 'XMR/UAH', 'base': 'XMR', 'quote': 'UAH', 'baseId': 'xmr', 'quoteId': 'uah' }, + 'ZEC/UAH': { 'id': 'zec_uah', 'symbol': 'ZEC/UAH', 'base': 'ZEC', 'quote': 'UAH', 'baseId': 'zec', 'quoteId': 'uah' }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.0006, + 'LTC': 0.01, + 'NVC': 0.01, + 'DOGE': 10, + }, + }, + }, + }); + } + + async signIn (params = {}) { + return await this.privatePostAuth (params); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'accounts'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const bids = await this.publicGetTradesBuySymbol (this.extend (request, params)); + const asks = await this.publicGetTradesSellSymbol (this.extend (request, params)); + const orderbook = { + 'bids': [], + 'asks': [], + }; + if (bids) { + if ('list' in bids) { + orderbook['bids'] = bids['list']; + } + } + if (asks) { + if ('list' in asks) { + orderbook['asks'] = asks['list']; + } + } + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'currency_trade'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetJapanStatHighSymbol (this.extend (request, params)); + const ticker = this.safeValue (response, 'trades'); + const timestamp = this.milliseconds (); + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': undefined, + 'last': undefined, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': ticker, + }; + const tickerLength = ticker.length; + if (tickerLength > 0) { + const start = Math.max (tickerLength - 48, 0); + for (let i = start; i < ticker.length; i++) { + const candle = ticker[i]; + if (result['open'] === undefined) { + result['open'] = candle[1]; + } + if ((result['high'] === undefined) || (result['high'] < candle[2])) { + result['high'] = candle[2]; + } + if ((result['low'] === undefined) || (result['low'] > candle[3])) { + result['low'] = candle[3]; + } + if (result['baseVolume'] === undefined) { + result['baseVolume'] = -candle[5]; + } else { + result['baseVolume'] -= candle[5]; + } + } + const last = tickerLength - 1; + result['last'] = ticker[last][4]; + result['close'] = result['last']; + result['baseVolume'] = -1 * result['baseVolume']; + } + return result; + } + + convertCyrillicMonthNameToString (cyrillic) { + const months = { + 'января': '01', + 'февраля': '02', + 'марта': '03', + 'апреля': '04', + 'мая': '05', + 'июня': '06', + 'июля': '07', + 'августа': '08', + 'сентября': '09', + 'октября': '10', + 'ноября': '11', + 'декабря': '12', + }; + return this.safeString (months, cyrillic); + } + + parseCyrillicDatetime (cyrillic) { + const parts = cyrillic.split (' '); + let day = parts[0]; + const month = this.convertCyrillicMonthNameToString (parts[1]); + if (!month) { + throw new ExchangeError (this.id + ' parseTrade() undefined month name: ' + cyrillic); + } + const year = parts[2]; + let hms = parts[4]; + const hmsLength = hms.length; + if (hmsLength === 7) { + hms = '0' + hms; + } + if (day.length === 1) { + day = '0' + day; + } + const ymd = [ year, month, day ].join ('-'); + const ymdhms = ymd + 'T' + hms; + const timestamp = this.parse8601 (ymdhms); + // server reports local time, adjust to UTC + let md = [ month, day ].join (''); + md = parseInt (md); + // a special case for DST + // subtract 2 hours during winter + if (md < 325 || md > 1028) { + return timestamp - 7200000; + } + // subtract 3 hours during summer + return timestamp - 10800000; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parseCyrillicDatetime (this.safeString (trade, 'pub_date')); + const id = this.safeString (trade, 'id'); + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amnt_trade'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetDealsSymbol (this.extend (request, params)); + // they report each trade twice (once for both of the two sides of the fill) + // deduplicate trades for that reason + const trades = []; + for (let i = 0; i < response.length; i++) { + if (response[i]['id'] % 2) { + trades.push (response[i]); + } + } + return this.parseTrades (trades, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = 'privatePost' + this.capitalize (side) + 'Id'; + const request = { + 'count': amount, + 'currency1': market['quoteId'], + 'currency': market['baseId'], + 'price': price, + }; + return this[method] (this.extend (request, params)); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privatePostRemoveOrderId (this.extend (request, params)); + } + + parseOrder (order, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'timestamp': timestamp, // until they fix their timestamp + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': 'open', + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': this.safeString (order, 'type'), + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'amount': this.safeFloat (order, 'amnt_trade'), + 'filled': 0, + 'remaining': this.safeFloat (order, 'amnt_trade'), + 'trades': undefined, + 'info': order, + 'cost': undefined, + 'average': undefined, + 'fee': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privatePostMyOrdersSymbol (this.extend (request, params)); + const orders = this.safeValue (response, 'your_open_orders'); + return this.parseOrders (orders, market, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += this.implodeParams (path, query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'out_order_id': nonce, + 'nonce': nonce, + }, query)); + const auth = body + this.secret; + headers = { + 'public-key': this.apiKey, + 'api-sign': this.hash (this.encode (auth), 'sha256'), + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcturk.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcturk.js new file mode 100644 index 000000000..24f79880b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/btcturk.js @@ -0,0 +1,327 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class btcturk extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'btcturk', + 'name': 'BTCTurk', + 'countries': [ 'TR' ], // Turkey + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1d': '1d', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153926-efbef500-c2c0-11ea-9842-05b63612c4b9.jpg', + 'api': 'https://www.btcturk.com/api', + 'www': 'https://www.btcturk.com', + 'doc': 'https://github.com/BTCTrader/broker-api-docs', + }, + 'api': { + 'public': { + 'get': [ + 'ohlcdata', // ?last=COUNT + 'orderbook', + 'ticker', + 'trades', // ?last=COUNT (max 50) + ], + }, + 'private': { + 'get': [ + 'balance', + 'openOrders', + 'userTransactions', // ?offset=0&limit=25&sort=asc + ], + 'post': [ + 'exchange', + 'cancelOrder', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.002 * 1.18, + 'taker': 0.003 * 1.18, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTicker (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'pair'); + let baseId = id.slice (0, 3); + let quoteId = id.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + baseId = baseId.toLowerCase (); + quoteId = quoteId.toLowerCase (); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const active = true; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const result = { 'info': response }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currencies[code]; + const free = currency['id'] + '_available'; + const total = currency['id'] + '_balance'; + const used = currency['id'] + '_reserved'; + if (free in response) { + const account = this.account (); + account['free'] = this.safeFloat (response, free); + account['total'] = this.safeFloat (response, total); + account['used'] = this.safeFloat (response, used); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pairSymbol': market['id'], + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'average'), + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const tickers = await this.publicGetTicker (params); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const marketId = this.safeString (ticker, 'pair'); + let symbol = marketId; + let market = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[symbol]; + symbol = market['symbol']; + } + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tickers = await this.fetchTickers (params); + return this.safeValue2 (tickers, market['id'], symbol); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // let maxCount = 50; + const request = { + 'pairSymbol': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.parse8601 (this.safeString (ohlcv, 'Time')), + this.safeFloat (ohlcv, 'Open'), + this.safeFloat (ohlcv, 'High'), + this.safeFloat (ohlcv, 'Low'), + this.safeFloat (ohlcv, 'Close'), + this.safeFloat (ohlcv, 'Volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1d', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + if (limit !== undefined) { + request['last'] = limit; + } + const response = await this.publicGetOhlcdata (this.extend (request, params)); + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'PairSymbol': this.marketId (symbol), + 'OrderType': (side === 'buy') ? 0 : 1, + 'OrderMethod': (type === 'market') ? 1 : 0, + }; + if (type === 'market') { + if (!('Total' in params)) { + throw new ExchangeError (this.id + ' createOrder requires the "Total" extra parameter for market orders (amount and price are both ignored)'); + } + } else { + request['Price'] = price; + request['Amount'] = amount; + } + const response = await this.privatePostExchange (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (this.id === 'btctrader') { + throw new ExchangeError (this.id + ' is an abstract base API for BTCExchange, BTCTurk'); + } + let url = this.urls['api'] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + body = this.urlencode (params); + const secret = this.base64ToBinary (this.secret); + const auth = this.apiKey + nonce; + headers = { + 'X-PCK': this.apiKey, + 'X-Stamp': nonce, + 'X-Signature': this.hmac (this.encode (auth), secret, 'sha256', 'base64'), + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/buda.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/buda.js new file mode 100644 index 000000000..96da5d093 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/buda.js @@ -0,0 +1,788 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AddressPending, AuthenticationError, ExchangeError, NotSupported, PermissionDenied, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class buda extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'buda', + 'name': 'Buda', + 'countries': [ 'AR', 'CL', 'CO', 'PE' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTrades': true, + 'fetchTicker': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/47380619-8a029200-d706-11e8-91e0-8a391fe48de3.jpg', + 'api': 'https://www.buda.com/api', + 'www': 'https://www.buda.com', + 'doc': 'https://api.buda.com', + 'fees': 'https://www.buda.com/comisiones', + }, + 'status': { + 'status': 'error', + 'updated': undefined, + 'eta': undefined, + 'url': undefined, + }, + 'api': { + 'public': { + 'get': [ + 'pairs', + 'markets', + 'currencies', + 'markets/{market}', + 'markets/{market}/ticker', + 'markets/{market}/volume', + 'markets/{market}/order_book', + 'markets/{market}/trades', + 'currencies/{currency}/fees/deposit', + 'currencies/{currency}/fees/withdrawal', + 'tv/history', + ], + 'post': [ + 'markets/{market}/quotations', + ], + }, + 'private': { + 'get': [ + 'balances', + 'balances/{currency}', + 'currencies/{currency}/balances', + 'orders', + 'orders/{id}', + 'markets/{market}/orders', + 'deposits', + 'currencies/{currency}/deposits', + 'withdrawals', + 'currencies/{currency}/withdrawals', + 'currencies/{currency}/receive_addresses', + 'currencies/{currency}/receive_addresses/{id}', + ], + 'post': [ + 'markets/{market}/orders', + 'currencies/{currency}/deposits', + 'currencies/{currency}/withdrawals', + 'currencies/{currency}/simulated_withdrawals', + 'currencies/{currency}/receive_addresses', + ], + 'put': [ + 'orders/{id}', + ], + }, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '30m': '30', + '1h': '60', + '2h': '120', + '1d': 'D', + '1w': 'W', + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.008, // 0.8% + 'maker': 0.004, // 0.4% + 'tiers': { + 'taker': [ + [0, 0.008], // 0.8% + [2000, 0.007], // 0.7% + [20000, 0.006], // 0.6% + [100000, 0.005], // 0.5% + [500000, 0.004], // 0.4% + [2500000, 0.003], // 0.3% + [12500000, 0.002], // 0.2% + ], + 'maker': [ + [0, 0.004], // 0.4% + [2000, 0.0035], // 0.35% + [20000, 0.003], // 0.3% + [100000, 0.0025], // 0.25% + [500000, 0.002], // 0.2% + [2500000, 0.0015], // 0.15% + [12500000, 0.001], // 0.1% + ], + }, + }, + }, + 'exceptions': { + 'not_authorized': AuthenticationError, // { message: 'Invalid credentials', code: 'not_authorized' } + 'forbidden': PermissionDenied, // { message: 'You dont have access to this resource', code: 'forbidden' } + 'invalid_record': ExchangeError, // { message: 'Validation Failed', code: 'invalid_record', errors: [] } + 'not_found': ExchangeError, // { message: 'Not found', code: 'not_found' } + 'parameter_missing': ExchangeError, // { message: 'Parameter missing', code: 'parameter_missing' } + 'bad_parameter': ExchangeError, // { message: 'Bad Parameter format', code: 'bad_parameter' } + }, + }); + } + + async fetchCurrencyInfo (currency, currencies = undefined) { + if (!currencies) { + const response = await this.publicGetCurrencies (); + currencies = this.safeValue (response, 'currencies'); + } + for (let i = 0; i < currencies.length; i++) { + const currencyInfo = currencies[i]; + if (currencyInfo['id'] === currency) { + return currencyInfo; + } + } + return undefined; + } + + async fetchMarkets (params = {}) { + const marketsResponse = await this.publicGetMarkets (params); + const markets = this.safeValue (marketsResponse, 'markets'); + const currenciesResponse = await this.publicGetCurrencies (); + const currencies = this.safeValue (currenciesResponse, 'currencies'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const baseInfo = await this.fetchCurrencyInfo (baseId, currencies); + const quoteInfo = await this.fetchCurrencyInfo (quoteId, currencies); + const symbol = base + '/' + quote; + const precision = { + 'amount': baseInfo['input_decimals'], + 'price': quoteInfo['input_decimals'], + }; + const limits = { + 'amount': { + 'min': parseFloat (market['minimum_order_amount'][0]), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }; + limits['cost'] = { + 'min': limits['amount']['min'] * limits['price']['min'], + 'max': undefined, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (); + const currencies = response['currencies']; + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + if (!currency['managed']) { + continue; + } + const id = this.safeString (currency, 'id'); + const code = this.safeCurrencyCode (id); + const precision = this.safeFloat (currency, 'input_decimals'); + const minimum = Math.pow (10, -precision); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': undefined, + 'active': true, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minimum, + 'max': undefined, + }, + 'price': { + 'min': minimum, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'deposit': { + 'min': parseFloat (currency['deposit_minimum'][0]), + 'max': undefined, + }, + 'withdraw': { + 'min': parseFloat (currency['withdrawal_minimum'][0]), + }, + }, + }; + } + return result; + } + + async fetchFundingFees (codes = undefined, params = {}) { + // by default it will try load withdrawal fees of all currencies (with separate requests) + // however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those + await this.loadMarkets (); + const withdrawFees = {}; + const depositFees = {}; + const info = {}; + if (codes === undefined) { + codes = Object.keys (this.currencies); + } + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currency = this.currency (code); + const request = { 'currency': currency['id'] }; + const withdrawResponse = await this.publicGetCurrenciesCurrencyFeesWithdrawal (request); + const depositResponse = await this.publicGetCurrenciesCurrencyFeesDeposit (request); + withdrawFees[code] = this.parseFundingFee (withdrawResponse['fee']); + depositFees[code] = this.parseFundingFee (depositResponse['fee']); + info[code] = { + 'withdraw': withdrawResponse, + 'deposit': depositResponse, + }; + } + return { + 'withdraw': withdrawFees, + 'deposit': depositFees, + 'info': info, + }; + } + + parseFundingFee (fee, type = undefined) { + if (type === undefined) { + type = fee['name']; + } + if (type === 'withdrawal') { + type = 'withdraw'; + } + return { + 'type': type, + 'currency': fee['base'][1], + 'rate': fee['percent'], + 'cost': parseFloat (fee['base'][0]), + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketsMarketTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'ticker'); + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = parseFloat (ticker['last_price'][0]); + const percentage = parseFloat (ticker['price_variation_24h']); + const open = parseFloat (this.priceToPrecision (symbol, last / (percentage + 1))); + const change = last - open; + const average = this.sum (last, open) / 2; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': parseFloat (ticker['max_bid'][0]), + 'bidVolume': undefined, + 'ask': parseFloat (ticker['min_ask'][0]), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': open, + 'change': change, + 'percentage': percentage * 100, + 'average': average, + 'baseVolume': parseFloat (ticker['volume'][0]), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + // the since argument works backwards – returns trades up to the specified timestamp + // therefore not implemented here + // the method is still available for users to be able to traverse backwards in time + // by using the timestamp from the first received trade upon each iteration + if (limit !== undefined) { + request['limit'] = limit; // 50 max + } + const response = await this.publicGetMarketsMarketTrades (this.extend (request, params)); + // + // { trades: { market_id: "ETH-BTC", + // timestamp: null, + // last_timestamp: "1536901277302", + // entries: [ [ "1540077456791", "0.0063767", "0.03", "sell", 479842 ], + // [ "1539916642772", "0.01888263", "0.03019563", "sell", 479438 ], + // [ "1539834081787", "0.023718648", "0.031001", "sell", 479069 ], + // ... ] + // + return this.parseTrades (response['trades']['entries'], market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // [ "1540077456791", "0.0063767", "0.03", "sell", 479842 ] + // + let timestamp = undefined; + let side = undefined; + const type = undefined; + let price = undefined; + let amount = undefined; + let id = undefined; + const order = undefined; + const fee = undefined; + let symbol = undefined; + let cost = undefined; + if (market) { + symbol = market['symbol']; + } + if (Array.isArray (trade)) { + timestamp = parseInt (trade[0]); + price = parseFloat (trade[1]); + amount = parseFloat (trade[2]); + cost = price * amount; + side = trade[3]; + id = trade[4].toString (); + } + return { + 'id': id, + 'order': order, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketsMarketOrderBook (this.extend (request, params)); + const orderbook = this.safeValue (response, 'order_book'); + return this.parseOrderBook (orderbook); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (since === undefined) { + since = this.milliseconds () - 86400000; + } + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + 'from': since / 1000, + 'to': this.seconds (), + }; + const response = await this.publicGetTvHistory (this.extend (request, params)); + return this.parseTradingViewOHLCV (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalances (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'balances'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'id'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = parseFloat (balance['available_amount'][0]); + account['total'] = parseFloat (balance['amount'][0]); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': parseInt (id), + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + const order = this.safeValue (response, 'order'); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'market': market['id'], + 'per': limit, + }; + const response = await this.privateGetMarketsMarketOrders (this.extend (request, params)); + const orders = this.safeValue (response, 'orders'); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'pending', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'state': 'traded', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + side = (side === 'buy') ? 'Bid' : 'Ask'; + const request = { + 'market': this.marketId (symbol), + 'price_type': type, + 'type': side, + 'amount': this.amountToPrecision (symbol, amount), + }; + if (type === 'limit') { + request['limit'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostMarketsMarketOrders (this.extend (request, params)); + const order = this.safeValue (response, 'order'); + return this.parseOrder (order); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': parseInt (id), + 'state': 'canceling', + }; + const response = await this.privatePutOrdersId (this.extend (request, params)); + const order = this.safeValue (response, 'order'); + return this.parseOrder (order); + } + + parseOrderStatus (status) { + const statuses = { + 'traded': 'closed', + 'received': 'open', + 'canceling': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const marketId = this.safeString (order, 'market_id'); + const symbol = this.safeSymbol (marketId, market); + const type = this.safeString (order, 'price_type'); + const side = this.safeStringLower (order, 'type'); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const amount = parseFloat (order['original_amount'][0]); + const remaining = parseFloat (order['amount'][0]); + const filled = parseFloat (order['traded_amount'][0]); + const cost = parseFloat (order['total_exchanged'][0]); + let price = this.safeFloat (order, 'limit'); + if (price !== undefined) { + price = parseFloat (price[0]); + } + if (cost > 0 && filled > 0) { + price = this.priceToPrecision (symbol, cost / filled); + } + const fee = { + 'cost': parseFloat (order['paid_fee'][0]), + 'currency': order['paid_fee'][1], + }; + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + isFiat (code) { + const fiats = { + 'ARS': true, + 'CLP': true, + 'COP': true, + 'PEN': true, + }; + return this.safeValue (fiats, code, false); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + if (this.isFiat (code)) { + throw new NotSupported (this.id + ' fetchDepositAddress() for fiat ' + code + ' is not supported'); + } + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetCurrenciesCurrencyReceiveAddresses (this.extend (request, params)); + const receiveAddresses = this.safeValue (response, 'receive_addresses'); + const addressPool = []; + for (let i = 1; i < receiveAddresses.length; i++) { + const receiveAddress = receiveAddresses[i]; + if (receiveAddress['ready']) { + const address = receiveAddress['address']; + this.checkAddress (address); + addressPool.push (address); + } + } + const addressPoolLength = addressPool.length; + if (addressPoolLength < 1) { + throw new AddressPending (this.id + ': there are no addresses ready for receiving ' + code + ', retry again later)'); + } + const address = addressPool[0]; + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': receiveAddresses, + }; + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + if (this.isFiat (code)) { + throw new NotSupported (this.id + ': fiat fetchDepositAddress() for ' + code + ' is not supported'); + } + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostCurrenciesCurrencyReceiveAddresses (this.extend (request, params)); + const address = this.safeString (response['receive_address'], 'address'); // the creation is async and returns a null address, returns only the id + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'rejected': 'failed', + 'confirmed': 'ok', + 'anulled': 'canceled', + 'retained': 'canceled', + 'pending_confirmation': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + const id = this.safeString (transaction, 'id'); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const amount = parseFloat (transaction['amount'][0]); + const fee = parseFloat (transaction['fee'][0]); + const feeCurrency = transaction['fee'][1]; + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const type = ('deposit_data' in transaction) ? 'deposit' : 'withdrawal'; + const data = this.safeValue (transaction, type + '_data', {}); + const address = this.safeValue (data, 'target_address'); + const txid = this.safeString (data, 'tx_hash'); + const updated = this.parse8601 (this.safeString (data, 'updated_at')); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'cost': fee, + 'rate': feeCurrency, + }, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (code === undefined) { + throw new ArgumentsRequired (this.id + ': fetchDeposits() requires a currency code argument'); + } + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'per': limit, + }; + const response = await this.privateGetCurrenciesCurrencyDeposits (this.extend (request, params)); + const deposits = this.safeValue (response, 'deposits'); + return this.parseTransactions (deposits, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (code === undefined) { + throw new ArgumentsRequired (this.id + ': fetchDeposits() requires a currency code argument'); + } + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'per': limit, + }; + const response = await this.privateGetCurrenciesCurrencyWithdrawals (this.extend (request, params)); + const withdrawals = this.safeValue (response, 'withdrawals'); + return this.parseTransactions (withdrawals, currency, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'withdrawal_data': { + 'target_address': address, + }, + }; + const response = await this.privatePostCurrenciesCurrencyWithdrawals (this.extend (request, params)); + const withdrawal = this.safeValue (response, 'withdrawal'); + return this.parseTransaction (withdrawal); + } + + nonce () { + return this.microseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + if (method === 'GET') { + request += '?' + this.urlencode (query); + } else { + body = this.json (query); + } + } + const url = this.urls['api'] + '/' + this.version + '/' + request; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const components = [ method, '/api/' + this.version + '/' + request ]; + if (body) { + const base64Body = this.stringToBase64 (body); + components.push (this.decode (base64Body)); + } + components.push (nonce); + const message = components.join (' '); + const signature = this.hmac (this.encode (message), this.encode (this.secret), 'sha384'); + headers = { + 'X-SBTC-APIKEY': this.apiKey, + 'X-SBTC-SIGNATURE': signature, + 'X-SBTC-NONCE': nonce, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (code >= 400) { + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message', body); + const feedback = this.id + ' ' + message; + if (errorCode !== undefined) { + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bw.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bw.js new file mode 100644 index 000000000..57a8a64c7 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bw.js @@ -0,0 +1,1205 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { RateLimitExceeded, BadSymbol, OrderNotFound, ExchangeError, AuthenticationError, ArgumentsRequired, ExchangeNotAvailable } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bw extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bw', + 'name': 'BW', + 'countries': [ 'CN' ], + 'rateLimit': 1500, + 'version': 'v1', + 'has': { + 'cancelAllOrders': false, + 'cancelOrder': true, + 'cancelOrders': false, + 'CORS': false, + 'createDepositAddress': false, + 'createLimitOrder': true, + 'createMarketOrder': false, + 'createOrder': true, + 'deposit': false, + 'editOrder': false, + 'fetchBalance': true, + 'fetchBidsAsks': false, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': false, + 'fetchL2OrderBook': false, + 'fetchLedger': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': false, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': false, + 'fetchTradingFees': false, + 'fetchTradingLimits': false, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'privateAPI': false, + 'publicAPI': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': '1M', + '5m': '5M', + '15m': '15M', + '30m': '30M', + '1h': '1H', + '1w': '1W', + }, + 'hostname': 'bw.com', // set to 'bw.io' for China mainland + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/69436317-31128c80-0d52-11ea-91d1-eb7bb5818812.jpg', + 'api': 'https://www.{hostname}', + 'www': 'https://www.bw.com', + 'doc': 'https://github.com/bw-exchange/api_docs_en/wiki', + 'fees': 'https://www.bw.com/feesRate', + 'referral': 'https://www.bw.com/regGetCommission/N3JuT1R3bWxKTE0', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.2 / 100, + 'maker': 0.2 / 100, + }, + 'funding': { + }, + }, + 'exceptions': { + 'exact': { + '999': AuthenticationError, + '1000': ExchangeNotAvailable, // {"datas":null,"resMsg":{"message":"getKlines error:data not exitsts\uff0cplease wait ,dataType=4002_KLINE_1M","method":null,"code":"1000"}} + '2012': OrderNotFound, // {"datas":null,"resMsg":{"message":"entrust not exists or on dealing with system","method":null,"code":"2012"}} + '5017': BadSymbol, // {"datas":null,"resMsg":{"message":"market not exist","method":null,"code":"5017"}} + '10001': RateLimitExceeded, // {"resMsg":{"code":"10001","message":"API frequency limit"}} + }, + }, + 'api': { + 'public': { + 'get': [ + 'api/data/v1/klines', + 'api/data/v1/ticker', + 'api/data/v1/tickers', + 'api/data/v1/trades', + 'api/data/v1/entrusts', + 'exchange/config/controller/website/marketcontroller/getByWebId', + 'exchange/config/controller/website/currencycontroller/getCurrencyList', + ], + }, + 'private': { + 'get': [ + 'exchange/entrust/controller/website/EntrustController/getEntrustById', + 'exchange/entrust/controller/website/EntrustController/getUserEntrustRecordFromCacheWithPage', + 'exchange/entrust/controller/website/EntrustController/getUserEntrustList', + 'exchange/fund/controller/website/fundwebsitecontroller/getwithdrawaddress', + 'exchange/fund/controller/website/fundwebsitecontroller/getpayoutcoinrecord', + 'exchange/entrust/controller/website/EntrustController/getUserEntrustList', + // the docs say that the following URLs are HTTP POST + // in the docs header and HTTP GET in the docs body + // the docs contradict themselves, a typo most likely + // the actual HTTP method is POST for this endpoint + // 'exchange/fund/controller/website/fundcontroller/getPayinAddress', + // 'exchange/fund/controller/website/fundcontroller/getPayinCoinRecord', + ], + 'post': [ + 'exchange/fund/controller/website/fundcontroller/getPayinAddress', // see the comment above + 'exchange/fund/controller/website/fundcontroller/getPayinCoinRecord', // see the comment above + 'exchange/fund/controller/website/fundcontroller/findbypage', + 'exchange/entrust/controller/website/EntrustController/addEntrust', + 'exchange/entrust/controller/website/EntrustController/cancelEntrust', + ], + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeConfigControllerWebsiteMarketcontrollerGetByWebId (params); + // + // { + // "datas": [ + // { + // "orderNum":null, + // "leverEnable":true, + // "leverMultiple":10, + // "marketId":"291", + // "webId":"102", + // "serverId":"entrust_bw_23", + // "name":"eos_usdt", + // "leverType":"2", + // "buyerCurrencyId":"11", + // "sellerCurrencyId":"7", + // "amountDecimal":4, + // "priceDecimal":3, + // "minAmount":"0.0100000000", + // "state":1, + // "openTime":1572537600000, + // "defaultFee":"0.00200000", + // "createUid":null, + // "createTime":0, + // "modifyUid":null, + // "modifyTime":1574160113735, + // "combineMarketId":"", + // "isCombine":0, + // "isMining":0 + // } + // ], + // "resMsg": { "message":"success !", "method":null, "code":"1" } + // } + // + const markets = this.safeValue (response, 'datas', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'marketId'); + const numericId = parseInt (id); + const name = this.safeStringUpper (market, 'name'); + let [ base, quote ] = name.split ('_'); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const baseId = this.safeString (market, 'sellerCurrencyId'); + const quoteId = this.safeString (market, 'buyerCurrencyId'); + const baseNumericId = parseInt (baseId); + const quoteNumericId = parseInt (quoteId); + const symbol = base + '/' + quote; + const state = this.safeInteger (market, 'state'); + const active = (state === 1); + const fee = this.safeFloat (market, 'defaultFee'); + result.push ({ + 'id': id, + 'active': active, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'baseNumericId': baseNumericId, + 'quoteNumericId': quoteNumericId, + 'maker': fee, + 'taker': fee, + 'info': market, + 'precision': { + 'amount': this.safeInteger (market, 'amountDecimal'), + 'price': this.safeInteger (market, 'priceDecimal'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minAmount'), + 'max': undefined, + }, + 'price': { + 'min': 0, + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetExchangeConfigControllerWebsiteCurrencycontrollerGetCurrencyList (params); + // + // { + // "datas":[ + // { + // "currencyId":"456", + // "name":"pan", + // "alias":"pan", + // "logo":"pan.svg", + // "description":"pan", + // "descriptionEnglish":"pan", + // "defaultDecimal":2, + // "createUid":null, + // "createTime":1574068133762, + // "modifyUid":null, + // "modifyTime":0, + // "state":1, + // "mark":"pan", + // "totalNumber":"0", + // "publishNumber":"0", + // "marketValue":"0", + // "isLegalCoin":0, + // "needBlockUrl":1, + // "blockChainUrl":"https://etherscan.io/tx/", + // "tradeSearchUrl":null, + // "tokenCoinsId":0, + // "isMining":"0", + // "arithmetic":null, + // "founder":"bw_nxwal", + // "teamAddress":null, + // "remark":null, + // "tokenName":"ethw2", + // "isMemo":0, + // "websiteCurrencyId":"7rhqoHLohkG", + // "drawFlag":0, + // "rechargeFlag":1, + // "drawFee":"0.03000000", + // "onceDrawLimit":100, + // "dailyDrawLimit":500, + // "timesFreetrial":"0", + // "hourFreetrial":"0", + // "dayFreetrial":"0", + // "minFee":"0", + // "inConfigTimes":7, + // "outConfigTimes":7, + // "minCash":"0.06000000", + // "limitAmount":"0", + // "zbExist":false, + // "zone":1 + // }, + // ], + // "resMsg": { "message":"success !", "method":null, "code":"1" } + // } + // + const currencies = this.safeValue (response, 'datas', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currencyId'); + const code = this.safeCurrencyCode (this.safeStringUpper (currency, 'name')); + const state = this.safeInteger (currency, 'state'); + const active = state === 1; + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': this.safeFloat (currency, 'drawFee'), + 'precision': undefined, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'limitAmount', 0), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': this.safeFloat (currency, 'onceDrawLimit'), + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // [ + // "281", // market id + // "9754.4", // last + // "9968.8", // high + // "9631.5", // low + // "47865.6432", // base volume + // "-2.28", // change + // // closing price for last 6 hours + // "[[1, 9750.1], [2, 9737.1], [3, 9727.5], [4, 9722], [5, 9722.1], [6, 9754.4]]", + // "9752.12", // bid + // "9756.69", // ask + // "469849357.2364" // quote volume + // ] + // + const marketId = this.safeString (ticker, 0); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.milliseconds (); + const close = parseFloat (this.safeValue (ticker, 1)); + const bid = this.safeValue (ticker, 'bid', {}); + const ask = this.safeValue (ticker, 'ask', {}); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': parseFloat (this.safeValue (ticker, 2)), + 'low': parseFloat (this.safeValue (ticker, 3)), + 'bid': parseFloat (this.safeValue (ticker, 7)), + 'bidVolume': this.safeFloat (bid, 'quantity'), + 'ask': parseFloat (this.safeValue (ticker, 8)), + 'askVolume': this.safeFloat (ask, 'quantity'), + 'vwap': undefined, + 'open': undefined, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': parseFloat (this.safeValue (ticker, 5)), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': parseFloat (this.safeValue (ticker, 4)), + 'quoteVolume': parseFloat (this.safeValue (ticker, 9)), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + const response = await this.publicGetApiDataV1Ticker (this.extend (request, params)); + // + // { + // "datas": [ + // "281", + // "7601.99", + // "8126.5", + // "7474.68", + // "47004.8708", + // "-6.18", + // "[[1, 7800.34], [2, 7626.41], [3, 7609.97], [4, 7569.04], [5, 7577.93], [6, 7601.99]]", + // "7600.24", + // "7603.69", + // "371968300.0119", + // ], + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + const ticker = this.safeValue (response, 'datas', []); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetApiDataV1Tickers (params); + // + // { + // "datas": [ + // [ + // "4051", + // "0.00194", + // "0.00863", + // "0.0012", + // "1519020", + // "-38.22", + // "[[1, 0.0023], [2, 0.00198], [3, 0.00199], [4, 0.00195], [5, 0.00199], [6, 0.00194]]", + // "0.00123", + // "0.0045", + // "4466.8104", + // ], + // ], + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const datas = this.safeValue (response, 'datas', []); + const result = {}; + for (let i = 0; i < datas.length; i++) { + const ticker = this.parseTicker (datas[i]); + const symbol = ticker['symbol']; + if ((symbols === undefined) || this.inArray (symbol, symbols)) { + result[symbol] = ticker; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + if (limit !== undefined) { + request['dataSize'] = limit; + } + const response = await this.publicGetApiDataV1Entrusts (this.extend (request, params)); + // + // { + // "datas": { + // "asks": [ + // [ "9740.43", "0.0083" ], + // ], + // "bids": [ + // [ "9734.33", "0.0133" ], + // ], + // "timestamp": "1569303520", + // }, + // "resMsg": { + // "message": "success !", + // "method": null, + // "code": "1", + // }, + // } + // + const orderbook = this.safeValue (response, 'datas', []); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // "T", // trade + // "281", // market id + // "1569303302", // timestamp + // "BTC_USDT", // market name + // "ask", // side + // "9745.08", // price + // "0.0026" // amount + // ] + // + // fetchMyTrades (private) + // + // ... + // + const timestamp = this.safeTimestamp (trade, 2); + const price = this.safeFloat (trade, 5); + const amount = this.safeFloat (trade, 6); + const marketId = this.safeString (trade, 1); + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const marketName = this.safeString (trade, 3); + const [ baseId, quoteId ] = marketName.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = this.costToPrecision (symbol, price * amount); + } + } + const sideString = this.safeString (trade, 4); + const side = (sideString === 'ask') ? 'sell' : 'buy'; + return { + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': parseFloat (cost), + 'fee': undefined, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + if (limit !== undefined) { + request['dataSize'] = limit; // max 20 + } + const response = await this.publicGetApiDataV1Trades (this.extend (request, params)); + // + // { + // "datas": [ + // [ + // "T", // trade + // "281", // market id + // "1569303302", // timestamp + // "BTC_USDT", // market name + // "ask", // side + // "9745.08", // price + // "0.0026" // amount + // ], + // ], + // "resMsg": { "code": "1", "method": null, "message": "success !" }, + // } + // + const trades = this.safeValue (response, 'datas', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "K", + // "305", + // "eth_btc", + // "1591511280", + // "0.02504", + // "0.02504", + // "0.02504", + // "0.02504", + // "0.0123", + // "0", + // "285740.17", + // "1M", + // "false", + // "0.000308" + // ] + // + return [ + this.safeTimestamp (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + this.safeFloat (ohlcv, 6), + this.safeFloat (ohlcv, 7), + this.safeFloat (ohlcv, 8), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'type': this.timeframes[timeframe], + 'dataSize': 500, + }; + if (limit !== undefined) { + request['dataSize'] = limit; + } + const response = await this.publicGetApiDataV1Klines (this.extend (request, params)); + // + // { + // "datas":[ + // ["K","305","eth_btc","1591511280","0.02504","0.02504","0.02504","0.02504","0.0123","0","285740.17","1M","false","0.000308"], + // ["K","305","eth_btc","1591511220","0.02504","0.02504","0.02504","0.02504","0.0006","0","285740.17","1M","false","0.00001502"], + // ["K","305","eth_btc","1591511100","0.02505","0.02505","0.02504","0.02504","0.0012","-0.0399","285740.17","1M","false","0.00003005"], + // ], + // "resMsg":{"code":"1","method":null,"message":"success !"} + // } + // + const data = this.safeValue (response, 'datas', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostExchangeFundControllerWebsiteFundcontrollerFindbypage (params); + // + // { + // "datas": { + // "totalRow": 6, + // "pageSize": 99, + // "list": [ + // { + // "amount": "0.000090000000000000", // The current number of tokens available + // "currencyTypeId": 2, // Token ID + // "freeze": "0.009900000000000000", // Current token freezing quantity + // }, + // ], + // "pageNum": 1, + // }, + // "resMsg": { "code": "1", "message": "success !" } + // } + // + const data = this.safeValue (response, 'datas', {}); + const balances = this.safeValue (data, 'list', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currencyTypeId'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'amount'); + account['used'] = this.safeFloat (balance, 'freeze'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (price === undefined) { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amount': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), + 'type': (side === 'buy') ? 1 : 0, + 'rangeType': 0, // limit order + 'marketId': market['id'], + }; + const response = await this.privatePostExchangeEntrustControllerWebsiteEntrustControllerAddEntrust (this.extend (request, params)); + // + // { + // "datas": { + // "entrustId": "E6581105708337483776", + // }, + // "resMsg": { + // "message": "success !", + // "method": null, + // "code": "1" + // } + // } + // + const data = this.safeValue (response, 'datas'); + const id = this.safeString (data, 'entrustId'); + return { + 'id': id, + 'info': response, + 'timestamp': undefined, + 'datetime': undefined, + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': 'open', + 'fee': undefined, + 'trades': undefined, + 'clientOrderId': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + '-3': 'canceled', + '-2': 'canceled', + '-1': 'canceled', + '0': 'open', + '1': 'canceled', + '2': 'closed', + '3': 'open', + '4': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "entrustId": "E6581108027628212224", // Order id + // "price": "1450", // price + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "amount": "14.05", // Order quantity + // "totalMoney": "20372.50", // Total order amount + // "completeAmount": "0", // Quantity sold + // "completeTotalMoney": "0", // Total dealt amount + // "type": 1, // 0 = sell, 1 = buy, -1 = cancel + // "entrustType": 0, // 0 = ordinary current price commission, 1 = lever commission + // "status": 0, // + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "availabelAmount": "14.05" // Outstanding quantity, typo in the docs or in the API, availabel vs available + // } + // + const marketId = this.safeString (order, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (order, 'createTime'); + let side = this.safeString (order, 'type'); + if (side === '0') { + side = 'sell'; + } else if (side === '1') { + side = 'buy'; + } + const amount = this.safeFloat (order, 'amount'); + const price = this.safeFloat (order, 'price'); + const filled = this.safeFloat (order, 'completeAmount'); + let remaining = this.safeFloat2 (order, 'availabelAmount', 'availableAmount'); // typo in the docs or in the API, availabel vs available + let cost = this.safeFloat (order, 'totalMoney'); + if (filled !== undefined) { + if (amount !== undefined) { + if (remaining === undefined) { + remaining = amount - filled; + } + } + if (cost === undefined) { + if (price !== undefined) { + cost = filled * cost; + } + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + return { + 'info': order, + 'id': this.safeString (order, 'entrustId'), + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'entrustId': id, + }; + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetEntrustById (this.extend (request, params)); + // + // { + // "datas": { + // "entrustId": "E6581108027628212224", // Order id + // "price": "1450", // price + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "amount": "14.05", // Order quantity + // "totalMoney": "20372.50", // Total order amount + // "completeAmount": "0", // Quantity sold + // "completeTotalMoney": "0", // Total dealt amount + // "type": 1, // Trade direction, 0: sell, 1: buy, -1: cancel + // "entrustType": 0, // Commission type, 0: ordinary current price commission, 1: lever commission + // "status": 0, // Order status,-3:fund Freeze exception,Order status to be confirmed -2: fund freeze failure, order failure, -1: insufficient funds, order failure, 0: pending order, 1: cancelled, 2: dealt, 3: partially dealt + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "availabelAmount": "14.05" // Outstanding quantity + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + const order = this.safeValue (response, 'datas', {}); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + 'entrustId': id, + }; + const response = await this.privatePostExchangeEntrustControllerWebsiteEntrustControllerCancelEntrust (this.extend (request, params)); + // + // { + // "datas": null, + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + return { + 'info': response, + 'id': id, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageIndex': 0, // also works without it, most likely a typo in the docs + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default limit is 20 + } + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustRecordFromCacheWithPage (this.extend (request, params)); + // + // { + // "datas": { + // "pageNum": 1, + // "pageSize": 2, + // "totalPage": 20, + // "totalRow": 40, + // "entrustList": [ + // { + // "amount": "14.050000000000000000", // Order quantity + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "totalMoney": "20372.500000000000000000", // Total order amount + // "entrustId": "E6581108027628212224", // Order id + // "type": 1, // Trade direction, 0: sell, 1: buy, -1: cancel + // "completeAmount": "0", // Quantity sold + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "price": "1450.000000000", // price + // "completeTotalMoney": "0", // Quantity sold + // "entrustType": 0, // Commission type, 0: ordinary current price commission, 1: lever commission + // "status": 0 // Order status,-3:fund Freeze exception,Order status to be confirmed -2: fund freeze failure, order failure, -1: insufficient funds, order failure, 0: pending order, 1: cancelled, 2: dealt, 3: partially dealt + // }, + // ], + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const orders = this.safeValue (data, 'entrustList', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default limit is 20 + } + if (since !== undefined) { + request['startDateTime'] = since; + } + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustList (this.extend (request, params)); + const data = this.safeValue (response, 'datas', {}); + const orders = this.safeValue (data, 'entrustList', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'marketId': market['id'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageIndex': 0, // also works without it, most likely a typo in the docs + // 'type': 0, // 0 = sell, 1 = buy, -1 = cancel + // 'status': -1, // -1 = insufficient funds, failed orders, 0 = pending orders, 1 = canceled, 2 = closed, 3 = partial + // 'startDateTime': since, + // 'endDateTime': this.milliseconds (), + }; + if (since !== undefined) { + request['startDateTime'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; // default limit is 20 + } + const response = await this.privateGetExchangeEntrustControllerWebsiteEntrustControllerGetUserEntrustList (this.extend (request, params)); + // + // { + // "datas": { + // "pageNum": 1, + // "pageSize": 2, + // "totalPage": 20, + // "totalRow": 40, + // "entrustList": [ + // { + // "amount": "14.050000000000000000", // Order quantity + // "rangeType": 0, // Commission type 0: limit price commission 1: interval commission + // "totalMoney": "20372.500000000000000000", // Total order amount + // "entrustId": "E6581108027628212224", // Order id + // "type": 1, // Trade direction, 0: sell, 1: buy, -1: cancel + // "completeAmount": "0", // Quantity sold + // "marketId": "318", // The market id + // "createTime": 1569058424861, // Create time + // "price": "1450.000000000", // price + // "completeTotalMoney": "0", // Quantity sold + // "entrustType": 0, // Commission type, 0: ordinary current price commission, 1: lever commission + // "status": 0 // Order status,-3:fund Freeze exception,Order status to be confirmed -2: fund freeze failure, order failure, -1: insufficient funds, order failure, 0: pending order, 1: cancelled, 2: dealt, 3: partially dealt + // }, + // ], + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const orders = this.safeValue (data, 'entrustList', []); + return this.parseOrders (orders, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'], { 'hostname': this.hostname }) + '/' + path; + if (method === 'GET') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + body = this.json (params); + } + if (api === 'private') { + const ms = this.milliseconds ().toString (); + let content = ''; + if (method === 'GET') { + const sortedParams = this.keysort (params); + const keys = Object.keys (sortedParams); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + content += key + sortedParams[key].toString (); + } + } else { + content = body; + } + const signature = this.apiKey + ms + content + this.secret; + const hash = this.hash (this.encode (signature), 'md5'); + if (!headers) { + headers = {}; + } + headers['Apiid'] = this.apiKey; + headers['Timestamp'] = ms; + headers['Sign'] = hash; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyTypeName': currency['name'], + }; + const response = await this.privatePostExchangeFundControllerWebsiteFundcontrollerGetPayinAddress (this.extend (request, params)); + // + // { + // "datas": { + // "isMemo": true, // 是否为memo 格式,false:否,true :是 + // "address": "bweosdeposit_787928102918558272", // 充币地址 + // "memo": "787928102918558272", // 币种memo + // "account": "bweosdeposit" // 币种账户 + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" } + // } + // + const data = this.safeValue (response, 'datas', {}); + const address = this.safeString (data, 'address'); + const tag = this.safeString (data, 'memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + '-1': 'canceled', // or auditing failed + '0': 'pending', + '1': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "depositId": "D6574268549744189441", // Deposit ID + // "amount": "54.753589700000000000", // Deposit amount + // "txId": "INNER_SYSTEM_TRANSFER_1198941", // Trading ID + // "confirmTimes": 0, // Confirmation number + // "depositAddress": "bweosdeposit_787928102918558272", // Deposit address + // "createTime": "2019-09-02 20:36:08.0", // Deposit time + // "status": 1, // Deposit status, 0: not received, 1: received + // "currencyTypeId": 7, // Token ID + // } + // + // fetchWithdrawals + // + // { + // "withdrawalId": "W6527498439872634880", // Withdrawal ID + // "fees": "0.500000000000000000", // Withdrawal fee + // "withdrawalAddress": "okbtothemoon_941657", // Withdrawal address + // "currencyId": "7", // Token ID + // "amount": "10.000000000000000000", // Withdrawal amount + // "state": 1, // Status, 1: normal, -1: delete + // "verifyStatus": 1, // Audit status, 0: to be audited, 1: auditing passed, -1: auditing failed + // "createTime": 1556276903656, // WIthdrawal time + // "actuallyAmount": "9.500000000000000000", // Actual amount received + // } + // + const id = this.safeString (transaction, 'depositId', 'withdrawalId'); + const address = this.safeString2 (transaction, 'depositAddress', 'withdrawalAddress'); + const currencyId = this.safeString2 (transaction, 'currencyId', 'currencyTypeId'); + let code = undefined; + if (currencyId in this.currencies_by_id) { + currency = this.currencies_by_id[currencyId]; + } + if ((code === undefined) && (currency !== undefined)) { + code = currency['code']; + } + const type = ('depositId' in transaction) ? 'deposit' : 'withdrawal'; + const amount = this.safeFloat2 (transaction, 'actuallyAmount', 'amount'); + const status = this.parseTransactionStatus (this.safeString2 (transaction, 'verifyStatus', 'state')); + const timestamp = this.safeInteger (transaction, 'createTime'); + const txid = this.safeString (transaction, 'txId'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fees'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': undefined, + 'tagFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyTypeName': currency['name'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageNum': 0, // also works without it, most likely a typo in the docs + // 'sort': 1, // 1 = asc, 0 = desc + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default 50 + } + const response = await this.privatePostExchangeFundControllerWebsiteFundcontrollerGetPayinCoinRecord (this.extend (request, params)); + // + // { + // "datas": { + // "totalRow":2, + // "totalPage": 1, + // "pageSize": 2, + // "pageNum": 1, + // "list": [ + // { + // "depositId": "D6574268549744189441", // Deposit ID + // "amount": "54.753589700000000000", // Deposit amount + // "txId": "INNER_SYSTEM_TRANSFER_1198941", // Trading ID + // "confirmTimes": 0, // Confirmation number + // "depositAddress": "bweosdeposit_787928102918558272", // Deposit address + // "createTime": "2019-09-02 20:36:08.0", // Deposit time + // "status": 1, // Deposit status, 0: not received, 1: received + // "currencyTypeId": 7, // Token ID + // }, + // ] + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const deposits = this.safeValue (data, 'list', []); + return this.parseTransactions (deposits, code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyId': currency['id'], + // 'pageSize': limit, // documented as required, but it works without it + // 'pageIndex': 0, // also works without it, most likely a typo in the docs + // 'tab': 'all', // all, wait (submitted, not audited), success (auditing passed), fail (auditing failed), cancel (canceled by user) + }; + if (limit !== undefined) { + request['pageSize'] = limit; // default 50 + } + const response = await this.privateGetExchangeFundControllerWebsiteFundwebsitecontrollerGetpayoutcoinrecord (this.extend (request, params)); + // + // { + // "datas": { + // "totalRow": 1, + // "totalPage": 1, + // "pageSize": 2, + // "pageNum": 1, + // "list": [ + // { + // "withdrawalId": "W6527498439872634880", // Withdrawal ID + // "fees": "0.500000000000000000", // Withdrawal fee + // "withdrawalAddress": "okbtothemoon_941657", // Withdrawal address + // "currencyId": "7", // Token ID + // "amount": "10.000000000000000000", // Withdrawal amount + // "state": 1, // Status, 1: normal, -1: delete + // "verifyStatus": 1, // Audit status, 0: to be audited, 1: auditing passed, -1: auditing failed + // "createTime": 1556276903656, // WIthdrawal time + // "actuallyAmount": "9.500000000000000000", // Actual amount received + // }, + // ], + // }, + // "resMsg": { "message": "success !", "method": null, "code": "1" }, + // } + // + const data = this.safeValue (response, 'datas', {}); + const withdrawals = this.safeValue (data, 'list', []); + return this.parseTransactions (withdrawals, code, since, limit); + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // default error handler + } + const resMsg = this.safeValue (response, 'resMsg'); + const errorCode = this.safeString (resMsg, 'code'); + if (errorCode !== '1') { + const feedback = this.id + ' ' + this.json (response); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); // unknown error + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bybit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bybit.js new file mode 100644 index 000000000..7a9159515 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bybit.js @@ -0,0 +1,2229 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, InsufficientFunds, BadRequest, RateLimitExceeded, InvalidNonce } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class bybit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bybit', + 'name': 'Bybit', + 'countries': [ 'VG' ], // British Virgin Islands + 'version': 'v2', + 'userAgent': undefined, + 'rateLimit': 100, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'cancelAllOrders': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '1', + '3m': '3', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '6h': '360', + '12h': '720', + '1d': 'D', + '1w': 'W', + '1M': 'M', + '1y': 'Y', + }, + 'urls': { + 'test': 'https://api-testnet.bybit.com', + 'logo': 'https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg', + 'api': 'https://api.bybit.com', + 'www': 'https://www.bybit.com', + 'doc': [ + 'https://bybit-exchange.github.io/docs/inverse/', + 'https://bybit-exchange.github.io/docs/linear/', + 'https://github.com/bybit-exchange', + ], + 'fees': 'https://help.bybit.com/hc/en-us/articles/360039261154', + 'referral': 'https://www.bybit.com/app/register?ref=X7Prm', + }, + 'api': { + 'public': { + 'get': [ + 'orderBook/L2', + 'kline/list', + 'tickers', + 'trading-records', + 'symbols', + 'liq-records', + 'mark-price-kline', + 'open-interest', + 'big-deal', + 'account-ratio', + 'time', + 'announcement', + ], + }, + 'private': { + 'get': [ + 'order/list', + 'order', + 'stop-order/list', + 'stop-order', + 'position/list', + 'wallet/balance', + 'execution/list', + 'trade/closed-pnl/list', + 'account/lcp', + 'exchange-order/list', + ], + 'post': [ + 'order/create', + 'order/cancel', + 'order/cancelAll', + 'order/replace', + 'stop-order/create', + 'stop-order/cancel', + 'stop-order/cancelAll', + 'stop-order/replace', + ], + }, + 'openapi': { + 'get': [ + 'order/list', // deprecated + 'stop-order/list', // deprecated + 'wallet/risk-limit/list', + 'funding/prev-funding-rate', + 'funding/prev-funding', + 'funding/predicted-funding', + 'api-key', + 'wallet/fund/records', + 'wallet/withdraw/list', + ], + 'post': [ + 'order/replace', // deprecated + 'stop-order/create', // deprecated + 'stop-order/cancel', // deprecated + 'stop-order/replace', // deprecated + 'position/trading-stop', + 'wallet/risk-limit', + ], + }, + 'publicLinear': { + 'get': [ + 'kline', + 'recent-trading-records', + 'funding/prev-funding-rate', + 'mark-price-kline', + 'risk-limit', + ], + }, + 'privateLinear': { + 'get': [ + 'order/list', + 'order/search', + 'stop-order/list', + 'stop-order/search', + 'position/list', + 'trade/execution/list', + 'trade/closed-pnl/list', + 'funding/prev-funding', + 'funding/predicted-funding', + ], + 'post': [ + 'order/create', + 'order/cancel', + 'order/cancel-all', + 'order/replace', + 'stop-order/create', + 'stop-order/cancel', + 'stop-order/cancel-all', + 'stop-order/replace', + 'position/switch-isolated', + 'position/set-auto-add-margin', + 'tpsl/switch-mode', + 'position/set-leverage', + 'position/trading-stop', + 'position/add-margin', + ], + }, + 'position': { + 'post': [ + 'change-position-margin', + ], + }, + 'user': { + 'get': [ + 'leverage', // deprecated + ], + 'post': [ + 'leverage/save', + ], + }, + }, + 'httpExceptions': { + '403': RateLimitExceeded, // Forbidden -- You request too many times + }, + 'exceptions': { + 'exact': { + '10001': BadRequest, // parameter error + '10002': InvalidNonce, // request expired, check your timestamp and recv_window + '10003': AuthenticationError, // Invalid apikey + '10004': AuthenticationError, // invalid sign + '10005': PermissionDenied, // permission denied for current apikey + '10006': RateLimitExceeded, // too many requests + '10007': AuthenticationError, // api_key not found in your request parameters + '10010': PermissionDenied, // request ip mismatch + '10017': BadRequest, // request path not found or request method is invalid + '20001': OrderNotFound, // Order not exists + '20003': InvalidOrder, // missing parameter side + '20004': InvalidOrder, // invalid parameter side + '20005': InvalidOrder, // missing parameter symbol + '20006': InvalidOrder, // invalid parameter symbol + '20007': InvalidOrder, // missing parameter order_type + '20008': InvalidOrder, // invalid parameter order_type + '20009': InvalidOrder, // missing parameter qty + '20010': InvalidOrder, // qty must be greater than 0 + '20011': InvalidOrder, // qty must be an integer + '20012': InvalidOrder, // qty must be greater than zero and less than 1 million + '20013': InvalidOrder, // missing parameter price + '20014': InvalidOrder, // price must be greater than 0 + '20015': InvalidOrder, // missing parameter time_in_force + '20016': InvalidOrder, // invalid value for parameter time_in_force + '20017': InvalidOrder, // missing parameter order_id + '20018': InvalidOrder, // invalid date format + '20019': InvalidOrder, // missing parameter stop_px + '20020': InvalidOrder, // missing parameter base_price + '20021': InvalidOrder, // missing parameter stop_order_id + '20022': BadRequest, // missing parameter leverage + '20023': BadRequest, // leverage must be a number + '20031': BadRequest, // leverage must be greater than zero + '20070': BadRequest, // missing parameter margin + '20071': BadRequest, // margin must be greater than zero + '20084': BadRequest, // order_id or order_link_id is required + '30001': BadRequest, // order_link_id is repeated + '30003': InvalidOrder, // qty must be more than the minimum allowed + '30004': InvalidOrder, // qty must be less than the maximum allowed + '30005': InvalidOrder, // price exceeds maximum allowed + '30007': InvalidOrder, // price exceeds minimum allowed + '30008': InvalidOrder, // invalid order_type + '30009': ExchangeError, // no position found + '30010': InsufficientFunds, // insufficient wallet balance + '30011': PermissionDenied, // operation not allowed as position is undergoing liquidation + '30012': PermissionDenied, // operation not allowed as position is undergoing ADL + '30013': PermissionDenied, // position is in liq or adl status + '30014': InvalidOrder, // invalid closing order, qty should not greater than size + '30015': InvalidOrder, // invalid closing order, side should be opposite + '30016': ExchangeError, // TS and SL must be cancelled first while closing position + '30017': InvalidOrder, // estimated fill price cannot be lower than current Buy liq_price + '30018': InvalidOrder, // estimated fill price cannot be higher than current Sell liq_price + '30019': InvalidOrder, // cannot attach TP/SL params for non-zero position when placing non-opening position order + '30020': InvalidOrder, // position already has TP/SL params + '30021': InvalidOrder, // cannot afford estimated position_margin + '30022': InvalidOrder, // estimated buy liq_price cannot be higher than current mark_price + '30023': InvalidOrder, // estimated sell liq_price cannot be lower than current mark_price + '30024': InvalidOrder, // cannot set TP/SL/TS for zero-position + '30025': InvalidOrder, // trigger price should bigger than 10% of last price + '30026': InvalidOrder, // price too high + '30027': InvalidOrder, // price set for Take profit should be higher than Last Traded Price + '30028': InvalidOrder, // price set for Stop loss should be between Liquidation price and Last Traded Price + '30029': InvalidOrder, // price set for Stop loss should be between Last Traded Price and Liquidation price + '30030': InvalidOrder, // price set for Take profit should be lower than Last Traded Price + '30031': InsufficientFunds, // insufficient available balance for order cost + '30032': InvalidOrder, // order has been filled or cancelled + '30033': RateLimitExceeded, // The number of stop orders exceeds maximum limit allowed + '30034': OrderNotFound, // no order found + '30035': RateLimitExceeded, // too fast to cancel + '30036': ExchangeError, // the expected position value after order execution exceeds the current risk limit + '30037': InvalidOrder, // order already cancelled + '30041': ExchangeError, // no position found + '30042': InsufficientFunds, // insufficient wallet balance + '30043': PermissionDenied, // operation not allowed as position is undergoing liquidation + '30044': PermissionDenied, // operation not allowed as position is undergoing AD + '30045': PermissionDenied, // operation not allowed as position is not normal status + '30049': InsufficientFunds, // insufficient available balance + '30050': ExchangeError, // any adjustments made will trigger immediate liquidation + '30051': ExchangeError, // due to risk limit, cannot adjust leverage + '30052': ExchangeError, // leverage can not less than 1 + '30054': ExchangeError, // position margin is invalid + '30057': ExchangeError, // requested quantity of contracts exceeds risk limit + '30063': ExchangeError, // reduce-only rule not satisfied + '30067': InsufficientFunds, // insufficient available balance + '30068': ExchangeError, // exit value must be positive + '34026': ExchangeError, // the limit is no change + }, + 'broad': { + 'unknown orderInfo': OrderNotFound, // {"ret_code":-1,"ret_msg":"unknown orderInfo","ext_code":"","ext_info":"","result":null,"time_now":"1584030414.005545","rate_limit_status":99,"rate_limit_reset_ms":1584030414003,"rate_limit":100} + 'invalid api_key': AuthenticationError, // {"ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797"} + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'marketTypes': { + 'BTC/USDT': 'linear', + 'ETH/USDT': 'linear', + 'LTC/USDT': 'linear', + 'XTZ/USDT': 'linear', + 'LINK/USDT': 'linear', + }, + 'code': 'BTC', + 'cancelAllOrders': { + 'method': 'privatePostOrderCancelAll', // privatePostStopOrderCancelAll + }, + 'recvWindow': 5 * 1000, // 5 sec default + 'timeDifference': 0, // the difference between system clock and Binance clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.00075, + 'maker': -0.00025, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async loadTimeDifference (params = {}) { + const serverTime = await this.fetchTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = after - serverTime; + return this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: {}, + // time_now: '1583933682.448826' + // } + // + return this.safeTimestamp (response, 'time_now'); + } + + async fetchMarkets (params = {}) { + if (this.options['adjustForTimeDifference']) { + await this.loadTimeDifference (); + } + const response = await this.publicGetSymbols (params); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // name: 'BTCUSD', + // base_currency: 'BTC', + // quote_currency: 'USD', + // price_scale: 2, + // taker_fee: '0.00075', + // maker_fee: '-0.00025', + // leverage_filter: { min_leverage: 1, max_leverage: 100, leverage_step: '0.01' }, + // price_filter: { min_price: '0.5', max_price: '999999.5', tick_size: '0.5' }, + // lot_size_filter: { max_trading_qty: 1000000, min_trading_qty: 1, qty_step: 1 } + // }, + // ], + // time_now: '1583930495.454196' + // } + // + // sandbox/testnet + // + // { + // "ret_code":0, + // "ret_msg":"OK", + // "ext_code":"", + // "ext_info":"", + // "result":[ + // { + // "symbol":"BTCUSD", + // "symbol_alias":"BTCUSD", + // "status":"Trading", + // "base_currency":"BTC", + // "quote_currency":"USD", + // "price_scale":2, + // "taker_fee":"0.00075", + // "maker_fee":"-0.00025", + // "leverage_filter":{"min_leverage":1,"max_leverage":100,"leverage_step":"0.01"}, + // "price_filter":{"min_price":"0.5","max_price":"999999.5","tick_size":"0.5"}, + // "lot_size_filter":{"max_trading_qty":1000000,"min_trading_qty":1,"qty_step":1} + // } + // ], + // "time_now":"1605916574.118500" + // } + // + const markets = this.safeValue (response, 'result', []); + const options = this.safeValue (this.options, 'fetchMarkets', {}); + const linearQuoteCurrencies = this.safeValue (options, 'linear', { 'USDT': true }); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString2 (market, 'name', 'symbol'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const linear = (quote in linearQuoteCurrencies); + const inverse = !linear; + let symbol = base + '/' + quote; + const baseQuote = base + quote; + if (baseQuote !== id) { + symbol = id; + } + const lotSizeFilter = this.safeValue (market, 'lot_size_filter', {}); + const priceFilter = this.safeValue (market, 'price_filter', {}); + const precision = { + 'amount': this.safeFloat (lotSizeFilter, 'qty_step'), + 'price': this.safeFloat (priceFilter, 'tick_size'), + }; + const status = this.safeString (market, 'status'); + let active = undefined; + if (status !== undefined) { + active = (status === 'Trading'); + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'taker': this.safeFloat (market, 'taker_fee'), + 'maker': this.safeFloat (market, 'maker_fee'), + 'type': 'future', + 'spot': false, + 'future': true, + 'option': false, + 'linear': linear, + 'inverse': inverse, + 'limits': { + 'amount': { + 'min': this.safeFloat (lotSizeFilter, 'min_trading_qty'), + 'max': this.safeFloat (lotSizeFilter, 'max_trading_qty'), + }, + 'price': { + 'min': this.safeFloat (priceFilter, 'min_price'), + 'max': this.safeFloat (priceFilter, 'max_price'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = {}; + const coin = this.safeString (params, 'coin'); + const code = this.safeString (params, 'code'); + if (coin !== undefined) { + request['coin'] = coin; + } else if (code !== undefined) { + const currency = this.currency (code); + request['coin'] = currency['id']; + } + const response = await this.privateGetWalletBalance (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: { + // BTC: { + // equity: 0, + // available_balance: 0, + // used_margin: 0, + // order_margin: 0, + // position_margin: 0, + // occ_closing_fee: 0, + // occ_funding_fee: 0, + // wallet_balance: 0, + // realised_pnl: 0, + // unrealised_pnl: 0, + // cum_realised_pnl: 0, + // given_cash: 0, + // service_cash: 0 + // } + // }, + // time_now: '1583937810.370020', + // rate_limit_status: 119, + // rate_limit_reset_ms: 1583937810367, + // rate_limit: 120 + // } + // + const result = { + 'info': response, + }; + const balances = this.safeValue (response, 'result', {}); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available_balance'); + account['used'] = this.safeFloat (balance, 'used_margin'); + account['total'] = this.safeFloat (balance, 'equity'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // symbol: 'BTCUSD', + // bid_price: '7680', + // ask_price: '7680.5', + // last_price: '7680.00', + // last_tick_direction: 'MinusTick', + // prev_price_24h: '7870.50', + // price_24h_pcnt: '-0.024204', + // high_price_24h: '8035.00', + // low_price_24h: '7671.00', + // prev_price_1h: '7780.00', + // price_1h_pcnt: '-0.012853', + // mark_price: '7683.27', + // index_price: '7682.74', + // open_interest: 188829147, + // open_value: '23670.06', + // total_turnover: '25744224.90', + // turnover_24h: '102997.83', + // total_volume: 225448878806, + // volume_24h: 809919408, + // funding_rate: '0.0001', + // predicted_funding_rate: '0.0001', + // next_funding_time: '2020-03-12T00:00:00Z', + // countdown_hour: 7 + // } + // + const timestamp = undefined; + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'last_price'); + const open = this.safeFloat (ticker, 'prev_price_24h'); + let percentage = this.safeFloat (ticker, 'price_24h_pcnt'); + if (percentage !== undefined) { + percentage *= 100; + } + let change = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + average = this.sum (open, last) / 2; + } + const baseVolume = this.safeFloat (ticker, 'turnover_24h'); + const quoteVolume = this.safeFloat (ticker, 'volume_24h'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_price_24h'), + 'low': this.safeFloat (ticker, 'low_price_24h'), + 'bid': this.safeFloat (ticker, 'bid_price'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask_price'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // symbol: 'BTCUSD', + // bid_price: '7680', + // ask_price: '7680.5', + // last_price: '7680.00', + // last_tick_direction: 'MinusTick', + // prev_price_24h: '7870.50', + // price_24h_pcnt: '-0.024204', + // high_price_24h: '8035.00', + // low_price_24h: '7671.00', + // prev_price_1h: '7780.00', + // price_1h_pcnt: '-0.012853', + // mark_price: '7683.27', + // index_price: '7682.74', + // open_interest: 188829147, + // open_value: '23670.06', + // total_turnover: '25744224.90', + // turnover_24h: '102997.83', + // total_volume: 225448878806, + // volume_24h: 809919408, + // funding_rate: '0.0001', + // predicted_funding_rate: '0.0001', + // next_funding_time: '2020-03-12T00:00:00Z', + // countdown_hour: 7 + // } + // ], + // time_now: '1583948195.818255' + // } + // + const result = this.safeValue (response, 'result', []); + const first = this.safeValue (result, 0); + const timestamp = this.safeTimestamp (response, 'time_now'); + const ticker = this.parseTicker (first, market); + ticker['timestamp'] = timestamp; + ticker['datetime'] = this.iso8601 (timestamp); + return ticker; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // symbol: 'BTCUSD', + // bid_price: '7680', + // ask_price: '7680.5', + // last_price: '7680.00', + // last_tick_direction: 'MinusTick', + // prev_price_24h: '7870.50', + // price_24h_pcnt: '-0.024204', + // high_price_24h: '8035.00', + // low_price_24h: '7671.00', + // prev_price_1h: '7780.00', + // price_1h_pcnt: '-0.012853', + // mark_price: '7683.27', + // index_price: '7682.74', + // open_interest: 188829147, + // open_value: '23670.06', + // total_turnover: '25744224.90', + // turnover_24h: '102997.83', + // total_volume: 225448878806, + // volume_24h: 809919408, + // funding_rate: '0.0001', + // predicted_funding_rate: '0.0001', + // next_funding_time: '2020-03-12T00:00:00Z', + // countdown_hour: 7 + // } + // ], + // time_now: '1583948195.818255' + // } + // + const result = this.safeValue (response, 'result', []); + const tickers = {}; + for (let i = 0; i < result.length; i++) { + const ticker = this.parseTicker (result[i]); + const symbol = ticker['symbol']; + tickers[symbol] = ticker; + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // inverse perpetual BTC/USD + // + // { + // symbol: 'BTCUSD', + // interval: '1', + // open_time: 1583952540, + // open: '7760.5', + // high: '7764', + // low: '7757', + // close: '7763.5', + // volume: '1259766', + // turnover: '162.32773718999994' + // } + // + // linear perpetual BTC/USDT + // + // { + // "id":143536, + // "symbol":"BTCUSDT", + // "period":"15", + // "start_at":1587883500, + // "volume":1.035, + // "open":7540.5, + // "high":7541, + // "low":7540.5, + // "close":7541 + // } + // + return [ + this.safeTimestamp2 (ohlcv, 'open_time', 'start_at'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat2 (ohlcv, 'turnover', 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + const now = this.seconds (); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + request['from'] = now - limit * duration; + } + } else { + request['from'] = parseInt (since / 1000); + } + if (limit !== undefined) { + request['limit'] = limit; // max 200, default 200 + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const method = (marketType === 'linear') ? 'publicLinearGetKline' : 'publicGetKlineList'; + const response = await this[method] (this.extend (request, params)); + // + // inverse perpetual BTC/USD + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // symbol: 'BTCUSD', + // interval: '1', + // open_time: 1583952540, + // open: '7760.5', + // high: '7764', + // low: '7757', + // close: '7763.5', + // volume: '1259766', + // turnover: '162.32773718999994' + // }, + // ], + // time_now: '1583953082.397330' + // } + // + // linear perpetual BTC/USDT + // + // { + // "ret_code":0, + // "ret_msg":"OK", + // "ext_code":"", + // "ext_info":"", + // "result":[ + // { + // "id":143536, + // "symbol":"BTCUSDT", + // "period":"15", + // "start_at":1587883500, + // "volume":1.035, + // "open":7540.5, + // "high":7541, + // "low":7540.5, + // "close":7541 + // } + // ], + // "time_now":"1587884120.168077" + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // id: 43785688, + // symbol: 'BTCUSD', + // price: 7786, + // qty: 67, + // side: 'Sell', + // time: '2020-03-11T19:18:30.123Z' + // } + // + // fetchMyTrades, fetchOrderTrades (private) + // + // { + // "closed_size": 0, + // "cross_seq": 277136382, + // "exec_fee": "0.0000001", + // "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68", + // "exec_price": "8178.5", + // "exec_qty": 1, + // // the docs say the exec_time field is "abandoned" now + // // the user should use "trade_time_ms" + // "exec_time": "1571676941.70682", + // "exec_type": "Trade", //Exec Type Enum + // "exec_value": "0.00012227", + // "fee_rate": "0.00075", + // "last_liquidity_ind": "RemovedLiquidity", //Liquidity Enum + // "leaves_qty": 0, + // "nth_fill": 2, + // "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029", + // "order_link_id": "", + // "order_price": "8178", + // "order_qty": 1, + // "order_type": "Market", //Order Type Enum + // "side": "Buy", //Side Enum + // "symbol": "BTCUSD", //Symbol Enum + // "user_id": 1, + // "trade_time_ms": 1577480599000 + // } + // + const id = this.safeString2 (trade, 'id', 'exec_id'); + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const amount = this.safeFloat2 (trade, 'qty', 'exec_qty'); + let cost = this.safeFloat (trade, 'exec_value'); + const price = this.safeFloat2 (trade, 'exec_price', 'price'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + let timestamp = this.parse8601 (this.safeString (trade, 'time')); + if (timestamp === undefined) { + timestamp = this.safeInteger (trade, 'trade_time_ms'); + } + const side = this.safeStringLower (trade, 'side'); + const lastLiquidityInd = this.safeString (trade, 'last_liquidity_ind'); + const takerOrMaker = (lastLiquidityInd === 'AddedLiquidity') ? 'maker' : 'taker'; + const feeCost = this.safeFloat (trade, 'exec_fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyCode = market['inverse'] ? market['base'] : market['quote']; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': this.safeFloat (trade, 'fee_rate'), + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': this.safeString (trade, 'order_id'), + 'type': this.safeStringLower (trade, 'order_type'), + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'from': 123, // from id + }; + if (limit !== undefined) { + request['count'] = limit; // default 500, max 1000 + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const method = (marketType === 'linear') ? 'publicLinearGetRecentTradingRecords' : 'publicGetTradingRecords'; + const response = await this[method] (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { + // id: 43785688, + // symbol: 'BTCUSD', + // price: 7786, + // qty: 67, + // side: 'Sell', + // time: '2020-03-11T19:18:30.123Z' + // }, + // ], + // time_now: '1583954313.393362' + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTrades (result, market, since, limit); + } + + parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'Buy', asksKey = 'Sell', priceKey = 'price', amountKey = 'size') { + const bids = []; + const asks = []; + for (let i = 0; i < orderbook.length; i++) { + const bidask = orderbook[i]; + const side = this.safeString (bidask, 'side'); + if (side === 'Buy') { + bids.push (this.parseBidAsk (bidask, priceKey, amountKey)); + } else if (side === 'Sell') { + asks.push (this.parseBidAsk (bidask, priceKey, amountKey)); + } else { + throw new ExchangeError (this.id + ' parseOrderBook encountered an unrecognized bidask format: ' + this.json (bidask)); + } + } + return { + 'bids': this.sortBy (bids, 0, true), + 'asks': this.sortBy (asks, 0), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetOrderBookL2 (this.extend (request, params)); + // + // { + // ret_code: 0, + // ret_msg: 'OK', + // ext_code: '', + // ext_info: '', + // result: [ + // { symbol: 'BTCUSD', price: '7767.5', size: 677956, side: 'Buy' }, + // { symbol: 'BTCUSD', price: '7767', size: 580690, side: 'Buy' }, + // { symbol: 'BTCUSD', price: '7766.5', size: 475252, side: 'Buy' }, + // { symbol: 'BTCUSD', price: '7768', size: 330847, side: 'Sell' }, + // { symbol: 'BTCUSD', price: '7768.5', size: 97159, side: 'Sell' }, + // { symbol: 'BTCUSD', price: '7769', size: 6508, side: 'Sell' }, + // ], + // time_now: '1583954829.874823' + // } + // + const result = this.safeValue (response, 'result', []); + const timestamp = this.safeTimestamp (response, 'time_now'); + return this.parseOrderBook (result, timestamp, 'Buy', 'Sell', 'price', 'size'); + } + + parseOrderStatus (status) { + const statuses = { + // basic orders + 'Created': 'open', + 'Rejected': 'rejected', // order is triggered but failed upon being placed + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'Cancelled': 'canceled', + 'PendingCancel': 'canceling', // the engine has received the cancellation but there is no guarantee that it will be successful + // conditional orders + 'Active': 'open', // order is triggered and placed successfully + 'Untriggered': 'open', // order waits to be triggered + 'Triggered': 'closed', // order is triggered + // 'Cancelled': 'canceled', // order is cancelled + // 'Rejected': 'rejected', // order is triggered but fail to be placed + 'Deactivated': 'canceled', // conditional order was cancelled before triggering + }; + return this.safeString (statuses, status, status); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GoodTillCancel': 'GTC', + 'ImmediateOrCancel': 'IOC', + 'FillOrKill': 'FOK', + 'PostOnly': 'PO', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "user_id": 1, + // "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b", + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8800, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "order_status": "Created", + // "last_exec_time": 0, + // "last_exec_price": 0, + // "leaves_qty": 1, + // "cum_exec_qty": 0, // in contracts, where 1 contract = 1 quote currency unit (USD for inverse contracts) + // "cum_exec_value": 0, // in contract's underlying currency (BTC for inverse contracts) + // "cum_exec_fee": 0, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-11-30T11:03:43.452Z", + // "updated_at": "2019-11-30T11:03:43.455Z" + // } + // + // fetchOrder + // + // { + // "user_id" : 599946, + // "symbol" : "BTCUSD", + // "side" : "Buy", + // "order_type" : "Limit", + // "price" : "7948", + // "qty" : 10, + // "time_in_force" : "GoodTillCancel", + // "order_status" : "Filled", + // "ext_fields" : { + // "o_req_num" : -1600687220498, + // "xreq_type" : "x_create" + // }, + // "last_exec_time" : "1588150113.968422", + // "last_exec_price" : "7948", + // "leaves_qty" : 0, + // "leaves_value" : "0", + // "cum_exec_qty" : 10, + // "cum_exec_value" : "0.00125817", + // "cum_exec_fee" : "-0.00000031", + // "reject_reason" : "", + // "cancel_type" : "", + // "order_link_id" : "", + // "created_at" : "2020-04-29T08:45:24.399146Z", + // "updated_at" : "2020-04-29T08:48:33.968422Z", + // "order_id" : "dd2504b9-0157-406a-99e1-efa522373944" + // } + // + // conditional order + // + // { + // "user_id":##, + // "symbol":"BTCUSD", + // "side":"Buy", + // "order_type":"Market", + // "price":0, + // "qty":10, + // "time_in_force":"GoodTillCancel", + // "stop_order_type":"Stop", + // "trigger_by":"LastPrice", + // "base_price":11833, + // "order_status":"Untriggered", + // "ext_fields":{ + // "stop_order_type":"Stop", + // "trigger_by":"LastPrice", + // "base_price":11833, + // "expected_direction":"Rising", + // "trigger_price":12400, + // "close_on_trigger":true, + // "op_from":"api", + // "remark":"145.53.159.48", + // "o_req_num":0 + // }, + // "leaves_qty":10, + // "leaves_value":0.00080645, + // "reject_reason":null, + // "cross_seq":-1, + // "created_at":"2020-08-21T09:18:48.000Z", + // "updated_at":"2020-08-21T09:18:48.000Z", + // "stop_px":12400, + // "stop_order_id":"3f3b54b1-3379-42c7-8510-44f4d9915be0" + // } + // + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market); + let symbol = undefined; + let base = undefined; + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const id = this.safeString2 (order, 'order_id', 'stop_order_id'); + const type = this.safeStringLower (order, 'order_type'); + let price = this.safeFloat (order, 'price'); + if (price === 0.0) { + price = undefined; + } + let average = this.safeFloat (order, 'average_price'); + const amount = this.safeFloat (order, 'qty'); + let cost = this.safeFloat (order, 'cum_exec_value'); + let filled = this.safeFloat (order, 'cum_exec_qty'); + let remaining = this.safeFloat (order, 'leaves_qty'); + if (market !== undefined) { + symbol = market['symbol']; + base = market['base']; + } + let lastTradeTimestamp = this.safeTimestamp (order, 'last_exec_time'); + if (lastTradeTimestamp === 0) { + lastTradeTimestamp = undefined; + } + if ((filled === undefined) && (amount !== undefined) && (remaining !== undefined)) { + filled = amount - remaining; + } + if (filled !== undefined) { + if ((remaining === undefined) && (amount !== undefined)) { + remaining = amount - filled; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } + if ((type === 'market') && (cost !== undefined) && (cost > 0)) { + price = undefined; + if (average === undefined) { + average = filled / cost; + } + } + } + const status = this.parseOrderStatus (this.safeString2 (order, 'order_status', 'stop_order_status')); + const side = this.safeStringLower (order, 'side'); + let feeCost = this.safeFloat (order, 'cum_exec_fee'); + let fee = undefined; + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + fee = { + 'cost': feeCost, + 'currency': base, + }; + } + let clientOrderId = this.safeString (order, 'order_link_id'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force')); + const stopPrice = this.safeFloat (order, 'stop_px'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'order_link_id': 'string', // one of order_id, stop_order_id or order_link_id is required + // regular orders --------------------------------------------- + // 'order_id': id, // one of order_id or order_link_id is required for regular orders + // conditional orders --------------------------------------------- + // 'stop_order_id': id, // one of stop_order_id or order_link_id is required for conditional orders + }; + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + let method = (marketType === 'linear') ? 'privateLinearGetOrderSearch' : 'privateGetOrder'; + const stopOrderId = this.safeString (params, 'stop_order_id'); + if (stopOrderId === undefined) { + const orderLinkId = this.safeString (params, 'order_link_id'); + if (orderLinkId === undefined) { + request['order_id'] = id; + } + } else { + method = (marketType === 'linear') ? 'privateLinearGetStopOrderSearch' : 'privateGetStopOrder'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Sell", + // "order_type": "Limit", + // "price": "8083", + // "qty": 10, + // "time_in_force": "GoodTillCancel", + // "order_status": "New", + // "ext_fields": { "o_req_num": -308787, "xreq_type": "x_create", "xreq_offset": 4154640 }, + // "leaves_qty": 10, + // "leaves_value": "0.00123716", + // "cum_exec_qty": 0, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-10-21T07:28:19.396246Z", + // "updated_at": "2019-10-21T07:28:19.396246Z", + // "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93" + // }, + // "time_now": "1571651135.291930", + // "rate_limit_status": 99, // The remaining number of accesses in one minute + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": "8000", + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "order_status": "Untriggered", + // "ext_fields": {}, + // "leaves_qty": 1, + // "leaves_value": "0.00013333", + // "cum_exec_qty": 0, + // "cum_exec_value": null, + // "cum_exec_fee": null, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-12-27T19:56:24.052194Z", + // "updated_at": "2019-12-27T19:56:24.052194Z", + // "order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36" + // }, + // "time_now": "1577476584.386958", + // "rate_limit_status": 99, + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result, market); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let qty = this.amountToPrecision (symbol, amount); + if (market['inverse']) { + qty = parseInt (qty); + } else { + qty = parseFloat (qty); + } + const request = { + // orders --------------------------------------------------------- + 'side': this.capitalize (side), + 'symbol': market['id'], + 'order_type': this.capitalize (type), + 'qty': qty, // order quantity in USD, integer only + // 'price': parseFloat (this.priceToPrecision (symbol, price)), // required for limit orders + 'time_in_force': 'GoodTillCancel', // ImmediateOrCancel, FillOrKill, PostOnly + // 'take_profit': 123.45, // take profit price, only take effect upon opening the position + // 'stop_loss': 123.45, // stop loss price, only take effect upon opening the position + // 'reduce_only': false, // reduce only, required for linear orders + // when creating a closing order, bybit recommends a True value for + // close_on_trigger to avoid failing due to insufficient available margin + // 'close_on_trigger': false, required for linear orders + // 'order_link_id': 'string', // unique client order id, max 36 characters + // conditional orders --------------------------------------------- + // base_price is used to compare with the value of stop_px, to decide + // whether your conditional order will be triggered by crossing trigger + // price from upper side or lower side, mainly used to identify the + // expected direction of the current conditional order + // 'base_price': 123.45, // required for conditional orders + // 'stop_px': 123.45, // trigger price, required for conditional orders + // 'trigger_by': 'LastPrice', // IndexPrice, MarkPrice + }; + let priceIsRequired = false; + if (type === 'limit') { + priceIsRequired = true; + } + if (priceIsRequired) { + if (price !== undefined) { + request['price'] = parseFloat (this.priceToPrecision (symbol, price)); + } else { + throw new ArgumentsRequired (this.id + ' createOrder requires a price argument for a ' + type + ' order'); + } + } + const clientOrderId = this.safeString2 (params, 'order_link_id', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['order_link_id'] = clientOrderId; + params = this.omit (params, [ 'order_link_id', 'clientOrderId' ]); + } + const stopPx = this.safeValue2 (params, 'stop_px', 'stopPrice'); + const basePrice = this.safeValue (params, 'base_price'); + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + let method = (marketType === 'linear') ? 'privateLinearPostOrderCreate' : 'privatePostOrderCreate'; + if (marketType === 'linear') { + method = 'privateLinearPostOrderCreate'; + request['reduce_only'] = false; + request['close_on_trigger'] = false; + } + if (stopPx !== undefined) { + if (basePrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order'); + } else { + method = (marketType === 'linear') ? 'privateLinearPostStopOrderCreate' : 'openapiPostStopOrderCreate'; + request['stop_px'] = parseFloat (this.priceToPrecision (symbol, stopPx)); + request['base_price'] = parseFloat (this.priceToPrecision (symbol, basePrice)); + params = this.omit (params, [ 'stop_px', 'stopPrice', 'base_price' ]); + } + } else if (basePrice !== undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order'); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "user_id": 1, + // "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b", + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8800, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "order_status": "Created", + // "last_exec_time": 0, + // "last_exec_price": 0, + // "leaves_qty": 1, + // "cum_exec_qty": 0, + // "cum_exec_value": 0, + // "cum_exec_fee": 0, + // "reject_reason": "", + // "order_link_id": "", + // "created_at": "2019-11-30T11:03:43.452Z", + // "updated_at": "2019-11-30T11:03:43.455Z" + // }, + // "time_now": "1575111823.458705", + // "rate_limit_status": 98, + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8000, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "stop_order_type": "Stop", + // "trigger_by": "LastPrice", + // "base_price": 7000, + // "order_status": "Untriggered", + // "ext_fields": { + // "stop_order_type": "Stop", + // "trigger_by": "LastPrice", + // "base_price": 7000, + // "expected_direction": "Rising", + // "trigger_price": 7500, + // "op_from": "api", + // "remark": "127.0.01", + // "o_req_num": 0 + // }, + // "leaves_qty": 1, + // "leaves_value": 0.00013333, + // "reject_reason": null, + // "cross_seq": -1, + // "created_at": "2019-12-27T12:48:24.000Z", + // "updated_at": "2019-12-27T12:48:24.000Z", + // "stop_px": 7500, + // "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5" + // }, + // "ext_info": null, + // "time_now": "1577450904.327654", + // "rate_limit_status": 99, + // "rate_limit_reset_ms": 1577450904335, + // "rate_limit": "100" + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires an symbol argument'); + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + // 'order_id': id, // only for non-conditional orders + 'symbol': market['id'], + // 'p_r_qty': this.amountToPrecision (symbol, amount), // new order quantity, optional + // 'p_r_price' this.priceToprecision (symbol, price), // new order price, optional + // ---------------------------------------------------------------- + // conditional orders + // 'stop_order_id': id, // only for conditional orders + // 'p_r_trigger_price': 123.45, // new trigger price also known as stop_px + }; + let method = (marketType === 'linear') ? 'privateLinearPostOrderReplace' : 'openapiPostOrderReplace'; + const stopOrderId = this.safeString (params, 'stop_order_id'); + if (stopOrderId !== undefined) { + method = (marketType === 'linear') ? 'privateLinearPostStopOrderReplace' : 'openapiPostStopOrderReplace'; + request['stop_order_id'] = stopOrderId; + params = this.omit (params, [ 'stop_order_id' ]); + } else { + request['order_id'] = id; + } + if (amount !== undefined) { + request['p_r_qty'] = parseInt (this.amountToPrecision (symbol, amount)); + } + if (price !== undefined) { + request['p_r_price'] = parseFloat (this.priceToPrecision (symbol, price)); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93" }, + // "time_now": "1539778407.210858", + // "rate_limit_status": 99, // remaining number of accesses in one minute + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { "stop_order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36" }, + // "ext_info": null, + // "time_now": "1577475760.604942", + // "rate_limit_status": 96, + // "rate_limit_reset_ms": 1577475760612, + // "rate_limit": "100" + // } + // + const result = this.safeValue (response, 'result', {}); + return { + 'info': response, + 'id': this.safeString2 (result, 'order_id', 'stop_order_id'), + 'order_id': this.safeString (result, 'order_id'), + 'stop_order_id': this.safeString (result, 'stop_order_id'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'order_link_id': 'string', // one of order_id, stop_order_id or order_link_id is required + // regular orders --------------------------------------------- + // 'order_id': id, // one of order_id or order_link_id is required for regular orders + // conditional orders --------------------------------------------- + // 'stop_order_id': id, // one of stop_order_id or order_link_id is required for conditional orders + }; + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeValue (marketTypes, symbol); + let method = (marketType === 'linear') ? 'privateLinearPostOrderCancel' : 'privatePostOrderCancel'; + const stopOrderId = this.safeString (params, 'stop_order_id'); + if (stopOrderId === undefined) { + const orderLinkId = this.safeString (params, 'order_link_id'); + if (orderLinkId === undefined) { + request['order_id'] = id; + } + } else { + method = (marketType === 'linear') ? 'privateLinearPostStopOrderCancel' : 'openapiPostStopOrderCancel'; + } + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const options = this.safeValue (this.options, 'cancelAllOrders'); + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const defaultMethod = (marketType === 'linear') ? 'privateLinearPostOrderCancelAll' : 'privatePostOrderCancelAll'; + const method = this.safeString (options, 'method', defaultMethod); + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'order_id': 'string' + // 'order_link_id': 'string', // unique client order id, max 36 characters + // 'symbol': market['id'], // default BTCUSD + // 'order': 'desc', // asc + // 'page': 1, + // 'limit': 20, // max 50 + // 'order_status': 'Created,New' + // conditional orders --------------------------------------------- + // 'stop_order_id': 'string', + // 'stop_order_status': 'Untriggered', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const options = this.safeValue (this.options, 'fetchOrders', {}); + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + let defaultMethod = (marketType === 'linear') ? 'privateLinearGetOrderList' : 'openapiGetOrderList'; + let query = params; + if (('stop_order_id' in params) || ('stop_order_status' in params)) { + let stopOrderStatus = this.safeValue (params, 'stopOrderStatus'); + if (stopOrderStatus !== undefined) { + if (Array.isArray (stopOrderStatus)) { + stopOrderStatus = stopOrderStatus.join (','); + } + request['stop_order_status'] = stopOrderStatus; + query = this.omit (params, 'stop_order_status'); + } + defaultMethod = (marketType === 'linear') ? 'privateLinearGetStopOrderList' : 'openapiGetStopOrderList'; + } + const method = this.safeString (options, 'method', defaultMethod); + const response = await this[method] (this.extend (request, query)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "current_page": 1, + // "last_page": 6, + // "data": [ + // { + // "user_id": 1, + // "symbol": "BTCUSD", + // "side": "Sell", + // "order_type": "Market", + // "price": 7074, + // "qty": 2, + // "time_in_force": "ImmediateOrCancel", + // "order_status": "Filled", + // "ext_fields": { + // "close_on_trigger": true, + // "orig_order_type": "BLimit", + // "prior_x_req_price": 5898.5, + // "op_from": "pc", + // "remark": "127.0.0.1", + // "o_req_num": -34799032763, + // "xreq_type": "x_create" + // }, + // "last_exec_time": "1577448481.696421", + // "last_exec_price": 7070.5, + // "leaves_qty": 0, + // "leaves_value": 0, + // "cum_exec_qty": 2, + // "cum_exec_value": 0.00028283, + // "cum_exec_fee": 0.00002, + // "reject_reason": "NoError", + // "order_link_id": "", + // "created_at": "2019-12-27T12:08:01.000Z", + // "updated_at": "2019-12-27T12:08:01.000Z", + // "order_id": "f185806b-b801-40ff-adec-52289370ed62" + // } + // ] + // }, + // "ext_info": null, + // "time_now": "1577448922.437871", + // "rate_limit_status": 98, + // "rate_limit_reset_ms": 1580885703683, + // "rate_limit": 100 + // } + // + // conditional orders + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "current_page": 1, + // "last_page": 1, + // "data": [ + // { + // "user_id": 1, + // "stop_order_status": "Untriggered", + // "symbol": "BTCUSD", + // "side": "Buy", + // "order_type": "Limit", + // "price": 8000, + // "qty": 1, + // "time_in_force": "GoodTillCancel", + // "stop_order_type": "Stop", + // "trigger_by": "LastPrice", + // "base_price": 7000, + // "order_link_id": "", + // "created_at": "2019-12-27T12:48:24.000Z", + // "updated_at": "2019-12-27T12:48:24.000Z", + // "stop_px": 7500, + // "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5" + // }, + // ] + // }, + // "ext_info": null, + // "time_now": "1577451658.755468", + // "rate_limit_status": 599, + // "rate_limit_reset_ms": 1577451658762, + // "rate_limit": 600 + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultStatuses = [ + 'Rejected', + 'Filled', + 'Cancelled', + // conditional orders + // 'Active', + // 'Triggered', + // 'Cancelled', + // 'Rejected', + // 'Deactivated', + ]; + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + let status = this.safeValue (options, 'order_status', defaultStatuses); + if (Array.isArray (status)) { + status = status.join (','); + } + const request = {}; + const stopOrderStatus = this.safeValue (params, 'stop_order_status'); + if (stopOrderStatus === undefined) { + request['order_status'] = status; + } else { + request['stop_order_status'] = stopOrderStatus; + } + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultStatuses = [ + 'Created', + 'New', + 'PartiallyFilled', + 'PendingCancel', + // conditional orders + // 'Untriggered', + ]; + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + let status = this.safeValue (options, 'order_status', defaultStatuses); + if (Array.isArray (status)) { + status = status.join (','); + } + const request = {}; + const stopOrderStatus = this.safeValue (params, 'stop_order_status'); + if (stopOrderStatus === undefined) { + request['order_status'] = status; + } else { + request['stop_order_status'] = stopOrderStatus; + } + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params)); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'order_id': 'f185806b-b801-40ff-adec-52289370ed62', // if not provided will return user's trading records + // 'symbol': market['id'], + // 'start_time': parseInt (since / 1000), + // 'page': 1, + // 'limit' 20, // max 50 + }; + let market = undefined; + if (symbol === undefined) { + const orderId = this.safeString (params, 'order_id'); + if (orderId === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument or an order_id param'); + } else { + request['order_id'] = orderId; + params = this.omit (params, 'order_id'); + } + } else { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 20, max 50 + } + const marketTypes = this.safeValue (this.options, 'marketTypes', {}); + const marketType = this.safeString (marketTypes, symbol); + const method = (marketType === 'linear') ? 'privateLinearGetTradeExecutionList' : 'privateGetExecutionList'; + const response = await this[method] (this.extend (request, params)); + // + // inverse + // + // { + // "ret_code": 0, + // "ret_msg": "OK", + // "ext_code": "", + // "ext_info": "", + // "result": { + // "order_id": "Abandoned!!", // Abandoned!! + // "trade_list": [ + // { + // "closed_size": 0, + // "cross_seq": 277136382, + // "exec_fee": "0.0000001", + // "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68", + // "exec_price": "8178.5", + // "exec_qty": 1, + // "exec_time": "1571676941.70682", + // "exec_type": "Trade", //Exec Type Enum + // "exec_value": "0.00012227", + // "fee_rate": "0.00075", + // "last_liquidity_ind": "RemovedLiquidity", //Liquidity Enum + // "leaves_qty": 0, + // "nth_fill": 2, + // "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029", + // "order_link_id": "", + // "order_price": "8178", + // "order_qty": 1, + // "order_type": "Market", //Order Type Enum + // "side": "Buy", //Side Enum + // "symbol": "BTCUSD", //Symbol Enum + // "user_id": 1 + // } + // ] + // }, + // "time_now": "1577483699.281488", + // "rate_limit_status": 118, + // "rate_limit_reset_ms": 1577483699244737, + // "rate_limit": 120 + // } + // + // linear + // + // { + // "ret_code":0, + // "ret_msg":"OK", + // "ext_code":"", + // "ext_info":"", + // "result":{ + // "current_page":1, + // "data":[ + // { + // "order_id":"b59418ec-14d4-4ef9-b9f4-721d5d576974", + // "order_link_id":"", + // "side":"Sell", + // "symbol":"BTCUSDT", + // "exec_id":"0327284d-faec-5191-bd89-acc5b4fafda9", + // "price":0.5, + // "order_price":0.5, + // "order_qty":0.01, + // "order_type":"Market", + // "fee_rate":0.00075, + // "exec_price":9709.5, + // "exec_type":"Trade", + // "exec_qty":0.01, + // "exec_fee":0.07282125, + // "exec_value":97.095, + // "leaves_qty":0, + // "closed_size":0.01, + // "last_liquidity_ind":"RemovedLiquidity", + // "trade_time":1591648052, + // "trade_time_ms":1591648052861 + // } + // ] + // }, + // "time_now":"1591736501.979264", + // "rate_limit_status":119, + // "rate_limit_reset_ms":1591736501974, + // "rate_limit":120 + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue2 (result, 'trade_list', 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetDeposits (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 5611, + // "result": { + // "count": 1, + // "data": [ + // { + // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", + // "amount": 5, + // "currency": "BTC", + // "received_timestamp": 1549295017670, + // "state": "completed", + // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", + // "updated_timestamp": 1549295130159 + // } + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'coin': currency['id'], + // 'start_date': this.iso8601 (since), + // 'end_date': this.iso8601 (till), + // 'status': 'Pending', // ToBeConfirmed, UnderReview, Pending, Success, CancelByUser, Reject, Expire + // 'page': 1, + // 'limit': 20, // max 50 + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['coin'] = currency['id']; + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.openapiGetWalletWithdrawList (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "data": [ + // { + // "id": 137, + // "user_id": 1, + // "coin": "XRP", // Coin Enum + // "status": "Pending", // Withdraw Status Enum + // "amount": "20.00000000", + // "fee": "0.25000000", + // "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM", + // "tx_id": "", + // "submited_at": "2019-06-11T02:20:24.000Z", + // "updated_at": "2019-06-11T02:20:24.000Z" + // }, + // ], + // "current_page": 1, + // "last_page": 1 + // }, + // "ext_info": null, + // "time_now": "1577482295.125488", + // "rate_limit_status": 119, + // "rate_limit_reset_ms": 1577482295132, + // "rate_limit": 120 + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + 'ToBeConfirmed': 'pending', + 'UnderReview': 'pending', + 'Pending': 'pending', + 'Success': 'ok', + 'CancelByUser': 'canceled', + 'Reject': 'rejected', + 'Expire': 'expired', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchWithdrawals + // + // { + // "id": 137, + // "user_id": 1, + // "coin": "XRP", // Coin Enum + // "status": "Pending", // Withdraw Status Enum + // "amount": "20.00000000", + // "fee": "0.25000000", + // "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM", + // "tx_id": "", + // "submited_at": "2019-06-11T02:20:24.000Z", + // "updated_at": "2019-06-11T02:20:24.000Z" + // } + // + const currencyId = this.safeString (transaction, 'coin'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.parse8601 (this.safeString (transaction, 'submited_at')); + const updated = this.parse8601 (this.safeString (transaction, 'updated_at')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const address = this.safeString (transaction, 'address'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'tx_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': undefined, + 'addressFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'tagFrom': undefined, + 'type': 'withdrawal', + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'coin': currency['id'], + // 'currency': currency['id'], // alias + // 'start_date': this.iso8601 (since), + // 'end_date': this.iso8601 (till), + // 'wallet_fund_type': 'Deposit', // Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit + // 'page': 1, + // 'limit': 20, // max 50 + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['coin'] = currency['id']; + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.openapiGetWalletFundRecords (this.extend (request, params)); + // + // { + // "ret_code": 0, + // "ret_msg": "ok", + // "ext_code": "", + // "result": { + // "data": [ + // { + // "id": 234467, + // "user_id": 1, + // "coin": "BTC", + // "wallet_id": 27913, + // "type": "Realized P&L", + // "amount": "-0.00000006", + // "tx_id": "", + // "address": "BTCUSD", + // "wallet_balance": "0.03000330", + // "exec_time": "2019-12-09T00:00:25.000Z", + // "cross_seq": 0 + // } + // ] + // }, + // "ext_info": null, + // "time_now": "1577481867.115552", + // "rate_limit_status": 119, + // "rate_limit_reset_ms": 1577481867122, + // "rate_limit": 120 + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseLedger (data, currency, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "id": 234467, + // "user_id": 1, + // "coin": "BTC", + // "wallet_id": 27913, + // "type": "Realized P&L", + // "amount": "-0.00000006", + // "tx_id": "", + // "address": "BTCUSD", + // "wallet_balance": "0.03000330", + // "exec_time": "2019-12-09T00:00:25.000Z", + // "cross_seq": 0 + // } + // + const currencyId = this.safeString (item, 'coin'); + const code = this.safeCurrencyCode (currencyId, currency); + const amount = this.safeFloat (item, 'amount'); + const after = this.safeFloat (item, 'wallet_balance'); + const direction = (amount < 0) ? 'out' : 'in'; + let before = undefined; + if (after !== undefined && amount !== undefined) { + const difference = (direction === 'out') ? amount : -amount; + before = this.sum (after, difference); + } + const timestamp = this.parse8601 (this.safeString (item, 'exec_time')); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const id = this.safeString (item, 'id'); + const referenceId = this.safeString (item, 'tx_id'); + return { + 'id': id, + 'currency': code, + 'account': this.safeString (item, 'wallet_id'), + 'referenceAccount': undefined, + 'referenceId': referenceId, + 'status': undefined, + 'amount': amount, + 'before': before, + 'after': after, + 'fee': undefined, + 'direction': direction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'type': type, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'Deposit': 'transaction', + 'Withdraw': 'transaction', + 'RealisedPNL': 'trade', + 'Commission': 'fee', + 'Refund': 'cashback', + 'Prize': 'prize', // ? + 'ExchangeOrderWithdraw': 'transaction', + 'ExchangeOrderDeposit': 'transaction', + }; + return this.safeString (types, type, type); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api']; + let request = path; + // public v2 + if (api === 'public') { + request = '/' + this.version + '/' + api + '/' + request; + if (Object.keys (params).length) { + request += '?' + this.rawencode (params); + } + } else if (api === 'publicLinear') { + request = '/public/linear/' + request; + if (Object.keys (params).length) { + request += '?' + this.rawencode (params); + } + } else { + this.checkRequiredCredentials (); + if (api === 'openapi') { + request = '/open-api/' + request; + } else if (api === 'private') { + // private v2 + request = '/' + this.version + '/' + api + '/' + request; + } else if (api === 'privateLinear') { + request = '/private/linear/' + request; + } else { + // position, user + request = '/' + api + '/' + request; + } + const timestamp = this.nonce (); + const query = this.extend (params, { + 'api_key': this.apiKey, + 'recv_window': this.options['recvWindow'], + 'timestamp': timestamp, + }); + let auth = this.rawencode (this.keysort (query)); + // https://github.com/ccxt/ccxt/issues/7377 + // https://github.com/ccxt/ccxt/issues/7515 + // bybit encodes whole floats as integers without .0 for conditional stop-orders only + if (path.indexOf ('stop-order') >= 0) { + auth = auth.replace ('.0&', '&'); + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + if (method === 'POST') { + body = this.json (this.extend (query, { + 'sign': signature, + })); + headers = { + 'Content-Type': 'application/json', + }; + } else { + request += '?' + auth + '&sign=' + signature; + } + } + url += request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + // + // { + // ret_code: 10001, + // ret_msg: 'ReadMapCB: expect { or n, but found \u0000, error ' + + // 'found in #0 byte of ...||..., bigger context ' + + // '...||...', + // ext_code: '', + // ext_info: '', + // result: null, + // time_now: '1583934106.590436' + // } + // + const errorCode = this.safeValue (response, 'ret_code'); + if (errorCode !== 0) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bytetrade.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bytetrade.js new file mode 100644 index 000000000..bbd1af6f6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/bytetrade.js @@ -0,0 +1,1430 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, AuthenticationError, DDoSProtection, BadResponse } = require ('./base/errors'); +const { TRUNCATE, NO_PADDING, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class bytetrade extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'bytetrade', + 'name': 'ByteTrade', + 'countries': ['HK'], + 'rateLimit': 500, + 'requiresWeb3': true, + 'certified': true, + // new metainfo interface + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '1d': '1d', + '5d': '5d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'test': { + 'market': 'https://api-v2-test.byte-trade.com', + 'public': 'https://api-v2-test.byte-trade.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg', + 'api': { + 'market': 'https://api-v2.byte-trade.com', + 'public': 'https://api-v2.byte-trade.com', + }, + 'www': 'https://www.byte-trade.com', + 'doc': 'https://docs.byte-trade.com/#description', + }, + 'api': { + 'market': { + 'get': [ + 'klines', // Kline of a symbol + 'depth', // Market Depth of a symbol + 'trades', // Trade records of a symbol + 'tickers', + ], + }, + 'public': { + 'get': [ + 'symbols', // Reference information of trading instrument, including base currency, quote precision, etc. + 'currencies', // The list of currencies available + 'balance', // Get the balance of an account + 'orders/open', // Get the open orders of an account + 'orders/closed', // Get the closed orders of an account + 'orders/all', // Get the open and closed orders of an account + 'orders', // Get the details of an order of an account + 'orders/trades', // Get detail match results + 'depositaddress', // Get deposit address + 'withdrawals', // Get withdrawals info + 'deposits', // Get deposit info + 'transfers', // Get transfer info + ], + 'post': [ + 'transaction/createorder', // Post create order transaction to blockchain + 'transaction/cancelorder', // Post cancel order transaction to blockchain + 'transaction/withdraw', // Post withdraw transaction to blockchain + 'transaction/transfer', // Post transfer transaction to blockchain + ], + }, + }, + 'fees': { + 'trading': { + 'taker': 0.0008, + 'maker': 0.0008, + }, + }, + 'commonCurrencies': { + '1': 'ByteTrade', + '44': 'ByteHub', + '48': 'Blocktonic', + '133': 'TerraCredit', + }, + 'exceptions': { + 'vertify error': AuthenticationError, // typo on the exchange side, 'vertify' + 'verify error': AuthenticationError, // private key signature is incorrect + 'transaction already in network': BadRequest, // same transaction submited + 'invalid argument': BadRequest, + }, + }); + } + + async fetchCurrencies (params = {}) { + const currencies = await this.publicGetCurrencies (params); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'code'); + let code = undefined; + if (id in this.commonCurrencies) { + code = this.commonCurrencies[id]; + } else { + code = this.safeString (currency, 'name'); + } + const name = this.safeString (currency, 'fullname'); + // in byte-trade.com DEX, request https://api-v2.byte-trade.com/currencies will return currencies, + // the api doc is https://github.com/Bytetrade/bytetrade-official-api-docs/wiki/rest-api#get-currencies-get-currencys-supported-in-bytetradecom + // we can see the coin name is none-unique in the result, the coin which code is 18 is the CyberMiles ERC20, and the coin which code is 35 is the CyberMiles main chain, but their name is same. + // that is because bytetrade is a DEX, supports people create coin with the same name, but the id(code) of coin is unique, so we should use the id or name and id as the identity of coin. + // For coin name and symbol is same with CCXT, I use name@id as the key of commonCurrencies dict. + // [{ + // "name": "CMT", // currency name, non-unique + // "code": "18", // currency id, unique + // "type": "crypto", + // "fullname": "CyberMiles", + // "active": true, + // "chainType": "ethereum", + // "basePrecision": 18, + // "transferPrecision": 10, + // "externalPrecision": 18, + // "chainContractAddress": "0xf85feea2fdd81d51177f6b8f35f0e6734ce45f5f", + // "limits": { + // "deposit": { + // "min": "0", + // "max": "-1" + // }, + // "withdraw": { + // "min": "0", + // "max": "-1" + // } + // } + // }, + // { + // "name": "CMT", + // "code": "35", + // "type": "crypto", + // "fullname": "CyberMiles", + // "active": true, + // "chainType": "cmt", + // "basePrecision": 18, + // "transferPrecision": 10, + // "externalPrecision": 18, + // "chainContractAddress": "0x0000000000000000000000000000000000000000", + // "limits": { + // "deposit": { + // "min": "1", + // "max": "-1" + // }, + // "withdraw": { + // "min": "10", + // "max": "-1" + // } + // } + // } + // ] + const active = this.safeValue (currency, 'active'); + const limits = this.safeValue (currency, 'limits'); + const deposit = this.safeValue (limits, 'deposit'); + const amountPrecision = this.safeInteger (currency, 'basePrecision'); + let maxDeposit = this.safeFloat (deposit, 'max'); + if (maxDeposit === -1.0) { + maxDeposit = undefined; + } + const withdraw = this.safeValue (limits, 'withdraw'); + let maxWithdraw = this.safeFloat (withdraw, 'max'); + if (maxWithdraw === -1.0) { + maxWithdraw = undefined; + } + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'active': active, + 'precision': amountPrecision, + 'fee': undefined, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'deposit': { + 'min': this.safeFloat (deposit, 'min'), + 'max': maxDeposit, + }, + 'withdraw': { + 'min': this.safeFloat (withdraw, 'min'), + 'max': maxWithdraw, + }, + }, + 'info': currency, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetSymbols (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + let base = this.safeString (market, 'baseName'); + let quote = this.safeString (market, 'quoteName'); + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const normalBase = base.split ('@' + baseId)[0]; + let normalQuote = quote.split ('@' + quoteId)[0]; + if (quoteId === '126') { + normalQuote = 'ZAR'; // The id 126 coin is a special coin whose name on the chain is actually ZAR, but it is changed to ZCN after creation, so it must be changed to ZAR when placing the transaction in the chain + } + const normalSymbol = normalBase + '/' + normalQuote; + if (baseId in this.commonCurrencies) { + base = this.commonCurrencies[baseId]; + } + if (quoteId in this.commonCurrencies) { + quote = this.commonCurrencies[quoteId]; + } + const symbol = base + '/' + quote; + const limits = this.safeValue (market, 'limits', {}); + const amount = this.safeValue (limits, 'amount', {}); + const price = this.safeValue (limits, 'price', {}); + const precision = this.safeValue (market, 'precision', {}); + const active = this.safeString (market, 'active'); + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': { + 'amount': this.safeInteger (precision, 'amount'), + 'price': this.safeInteger (precision, 'price'), + }, + 'normalSymbol': normalSymbol, + 'limits': { + 'amount': { + 'min': this.safeFloat (amount, 'min'), + 'max': this.safeFloat (amount, 'max'), + }, + 'price': { + 'min': this.safeFloat (price, 'min'), + 'max': this.safeFloat (price, 'max'), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + result.push (entry); + } + return result; + } + + async fetchBalance (params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const request = { + 'userid': this.apiKey, + }; + const balances = await this.publicGetBalance (this.extend (request, params)); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'code'); + const code = this.safeCurrencyCode (currencyId, undefined); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'used'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = maximum = 100 + } + const response = await this.marketGetDepth (this.extend (request, params)); + const timestamp = this.safeValue (response, 'timestamp'); + const orderbook = this.parseOrderBook (response, timestamp); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeInteger (ticker, 'timestamp'); + // + // [ + // { + // "symbol":"68719476706", + // "name":"ETH/BTC", + // "base":"2", + // "quote":"32", + // "timestamp":1575905991933, + // "datetime":"2019-12-09T15:39:51.933Z", + // "high":"0", + // "low":"0", + // "open":"0", + // "close":"0", + // "last":"0", + // "change":"0", + // "percentage":"0", + // "baseVolume":"0", + // "quoteVolume":"0" + // } + // ] + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'symbol'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (ticker, 'base'); + const quoteId = this.safeString (ticker, 'quote'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), + 'open': this.safeFloat (ticker, 'open'), + 'close': this.safeFloat (ticker, 'close'), + 'last': this.safeFloat (ticker, 'last'), + 'previousClose': undefined, // previous day close + 'change': this.safeFloat (ticker, 'change'), + 'percentage': this.safeFloat (ticker, 'percentage'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetTickers (this.extend (request, params)); + // + // [ + // { + // "symbol":"68719476706", + // "name":"ETH/BTC", + // "base":"2", + // "quote":"32", + // "timestamp":1575905991933, + // "datetime":"2019-12-09T15:39:51.933Z", + // "high":"0", + // "low":"0", + // "open":"0", + // "close":"0", + // "last":"0", + // "change":"0", + // "percentage":"0", + // "baseVolume":"0", + // "quoteVolume":"0" + // } + // ] + // + if (Array.isArray (response)) { + const ticker = this.safeValue (response, 0); + if (ticker === undefined) { + throw new BadResponse (this.id + ' fetchTicker() returned an empty response'); + } + return this.parseTicker (ticker, market); + } + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchBidsAsks (symbols = undefined, params = {}) { + await this.loadMarkets (); + const rawTickers = await this.marketGetDepth (params); + return this.parseTickers (rawTickers, symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const rawTickers = await this.marketGetTickers (params); + return this.parseTickers (rawTickers, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591505760000, + // "242.7", + // "242.76", + // "242.69", + // "242.76", + // "0.1892" + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.marketGetKlines (this.extend (request, params)); + // + // [ + // [1591505760000,"242.7","242.76","242.69","242.76","0.1892"], + // [1591505820000,"242.77","242.83","242.7","242.72","0.6378"], + // [1591505880000,"242.72","242.73","242.61","242.72","0.4141"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeInteger (trade, 'timestamp'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const cost = this.safeFloat (trade, 'cost'); + const id = this.safeString (trade, 'id'); + const type = this.safeString (trade, 'type'); + const takerOrMaker = this.safeString (trade, 'takerOrMaker'); + const side = this.safeString (trade, 'side'); + const datetime = this.iso8601 (timestamp); // this.safeString (trade, 'datetime'); + const order = this.safeString (trade, 'order'); + const fee = this.safeValue (trade, 'fee'); + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeString (trade, 'symbol'); + market = this.safeValue (this.markets_by_id, marketId); + } + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': datetime, + 'symbol': symbol, + 'id': id, + 'order': order, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default = 100, maximum = 500 + } + const response = await this.marketGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOrder (order, market = undefined) { + const status = this.safeString (order, 'status'); + let symbol = undefined; + const marketId = this.safeString (order, 'symbol'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (order, 'base'); + const quoteId = this.safeString (order, 'quote'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (order, 'timestamp'); + const datetime = this.safeString (order, 'datetime'); + const lastTradeTimestamp = this.safeInteger (order, 'lastTradeTimestamp'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled'); + const remaining = this.safeFloat (order, 'remaining'); + const cost = this.safeFloat (order, 'cost'); + const average = this.safeFloat (order, 'average'); + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const fee = this.safeValue (order, 'fee'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': datetime, + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + this.checkRequiredDependencies (); + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('createOrder requires this.apiKey or userid in params'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let sideNum = undefined; + let typeNum = undefined; + if (side === 'sell') { + sideNum = 1; + } else { + sideNum = 2; + } + if (type === 'limit') { + typeNum = 1; + } else { + typeNum = 2; + price = 0; + } + const normalSymbol = market['normalSymbol']; + const baseId = market['baseId']; + const baseCurrency = this.currency (market['base']); + const amountTruncated = this.amountToPrecision (symbol, amount); + const amountChain = this.toWei (amountTruncated, baseCurrency['precision']); + const quoteId = market['quoteId']; + const quoteCurrency = this.currency (market['quote']); + const priceRounded = this.priceToPrecision (symbol, price); + const priceChain = this.toWei (priceRounded, quoteCurrency['precision']); + const now = this.milliseconds (); + const expiration = this.milliseconds (); + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const defaultFee = this.safeString (this.options, 'fee', '300000000000000'); + const totalFeeRate = this.safeString (params, 'totalFeeRate', 8); + const chainFeeRate = this.safeString (params, 'chainFeeRate', 1); + const fee = this.safeString (params, 'fee', defaultFee); + const eightBytes = this.integerPow ('2', '64'); + const allByteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (32, 1), + this.numberToLE (0, 8), + this.numberToLE (fee, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (sideNum, 1), + this.numberToLE (typeNum, 1), + this.numberToLE (normalSymbol.length, 1), + this.stringToBinary (this.encode (normalSymbol)), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (this.integerDivide (priceChain, eightBytes), 8), + this.numberToLE (this.integerModulo (priceChain, eightBytes), 8), + this.numberToLE (0, 2), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (parseInt (chainFeeRate), 2), + this.numberToLE (1, 1), + this.numberToLE (parseInt (totalFeeRate), 2), + this.numberToLE (parseInt (quoteId), 4), + this.numberToLE (parseInt (baseId), 4), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const txByteStringArray = [ + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (32, 1), + this.numberToLE (0, 8), + this.numberToLE (fee, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (sideNum, 1), + this.numberToLE (typeNum, 1), + this.numberToLE (normalSymbol.length, 1), + this.stringToBinary (this.encode (normalSymbol)), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (this.integerDivide (priceChain, eightBytes), 8), + this.numberToLE (this.integerModulo (priceChain, eightBytes), 8), + this.numberToLE (0, 2), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (parseInt (chainFeeRate), 2), + this.numberToLE (1, 1), + this.numberToLE (parseInt (totalFeeRate), 2), + this.numberToLE (parseInt (quoteId), 4), + this.numberToLE (parseInt (baseId), 4), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const txbytestring = this.binaryConcatArray (txByteStringArray); + const txidhash = this.hash (txbytestring, 'sha256', 'hex'); + const txid = txidhash.slice (0, 40); + const orderidByteStringArray = [ + this.numberToLE (txid.length, 1), + this.stringToBinary (this.encode (txid)), + this.numberToBE (0, 4), + ]; + const orderidbytestring = this.binaryConcatArray (orderidByteStringArray); + const orderidhash = this.hash (orderidbytestring, 'sha256', 'hex'); + const orderid = orderidhash.slice (0, 40); + const bytestring = this.binaryConcatArray (allByteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + const operation = { + 'now': datetime, + 'expiration': expirationDatetime, + 'fee': fee, + 'creator': this.apiKey, + 'side': sideNum, + 'order_type': typeNum, + 'market_name': normalSymbol, + 'amount': amountChain, + 'price': priceChain, + 'use_btt_as_fee': false, + 'money_id': parseInt (quoteId), + 'stock_id': parseInt (baseId), + 'custom_no_btt_fee_rate': parseInt (totalFeeRate), + 'custom_btt_fee_rate': parseInt (chainFeeRate), + }; + const fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + 32, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + const request = { + 'trObj': this.json (fatty), + }; + const response = await this.publicPostTransactionCreateorder (request); + const timestamp = this.milliseconds (); + const statusCode = this.safe_string (response, 'code'); + const status = (statusCode === '0') ? 'open' : 'failed'; + return { + 'info': response, + 'id': orderid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': undefined, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchOrder requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const request = { + 'userid': this.apiKey, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.markets[symbol]; + request['symbol'] = market['id']; + } + request['id'] = id; + const response = await this.publicGetOrders (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchOpenOrders requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const request = { + 'userid': this.apiKey, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersOpen (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchClosedOrders requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + let market = undefined; + const request = { + 'userid': this.apiKey, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersClosed (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchOrders requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + let market = undefined; + const request = { + 'userid': this.apiKey, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersAll (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('cancelOrder requires hasAlreadyAuthenticatedSuccessfully'); + } + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const baseId = market['baseId']; + const quoteId = market['quoteId']; + const normalSymbol = market['normalSymbol']; + const feeAmount = '300000000000000'; + const now = this.milliseconds (); + const expiration = 0; + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (expiration, 4), + this.numberToLE (1, 1), + this.numberToLE (33, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (normalSymbol.length, 1), + this.stringToBinary (this.encode (normalSymbol)), + this.base16ToBinary (id), + this.numberToLE (parseInt (quoteId), 4), + this.numberToLE (parseInt (baseId), 4), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const bytestring = this.binaryConcatArray (byteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + const operation = { + 'fee': feeAmount, + 'creator': this.apiKey, + 'order_id': id, + 'market_name': normalSymbol, + 'money_id': parseInt (quoteId), + 'stock_id': parseInt (baseId), + }; + const fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + 33, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + const request = { + 'trObj': this.json (fatty), + }; + const response = await this.publicPostTransactionCancelorder (request); + const timestamp = this.milliseconds (); + const statusCode = this.safe_string (response, 'code'); + const status = (statusCode === '0') ? 'canceled' : 'failed'; + return { + 'info': response, + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': undefined, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async transfer (code, amount, address, message = '', params = {}) { + this.checkRequiredDependencies (); + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('transfer requires this.apiKey'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING); + const amountChain = this.toWei (amountTruncate, currency['precision']); + const assetType = parseInt (currency['id']); + const now = this.milliseconds (); + const expiration = now; + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const feeAmount = '300000000000000'; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const eightBytes = this.integerPow ('2', '64'); + const byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (28, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (address.length, 1), + this.stringToBinary (this.encode (address)), + this.numberToLE (assetType, 4), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (1, 1), + this.numberToLE (message.length, 1), + this.stringToBinary (this.encode (message)), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + const bytestring = this.binaryConcatArray (byteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + const operation = { + 'fee': '300000000000000', + 'from': this.apiKey, + 'to': address, + 'asset_type': parseInt (currency['id']), + 'amount': amountChain.toString (), + 'message': message, + }; + const fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + 28, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + const request = { + 'trObj': this.json (fatty), + }; + const response = await this.publicPostTransactionTransfer (request); + const timestamp = this.milliseconds (); + const statusCode = this.safe_string (response, 'code'); + let status = ''; + if (statusCode === '0') { + status = 'submit success'; + } else { + status = 'submit fail'; + } + return { + 'info': response, + 'id': '', + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': undefined, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchMyTrades requires this.apiKey or userid argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'userid': this.apiKey, + }; + if (symbol !== undefined) { + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrdersTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchDeposits requires this.apiKey or userid argument'); + } + let currency = undefined; + const request = { + 'userid': this.apiKey, + }; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetDeposits (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchWithdrawals requires this.apiKey or userid argument'); + } + let currency = undefined; + const request = { + 'userid': this.apiKey, + }; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetWithdrawals (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'DEPOSIT_FAILED': 'failed', + 'FEE_SEND_FAILED': 'failed', + 'FEE_FAILED': 'failed', + 'PAY_SEND_FAILED': 'failed', + 'PAY_FAILED': 'failed', + 'BTT_FAILED': 'failed', + 'WITHDDRAW_FAILED': 'failed', + 'USER_FAILED': 'failed', + 'FEE_EXECUED': 'pending', + 'PAY_EXECUED': 'pending', + 'WITHDDRAW_EXECUTED': 'pending', + 'USER_EXECUED': 'pending', + 'BTT_SUCCED': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString (transaction, 'tag'); + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const txid = this.safeValue (transaction, 'txid'); + const currencyId = this.safeString (transaction, 'code'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeInteger (transaction, 'timestamp'); + const datetime = this.safeString (transaction, 'datetime'); + const type = this.safeString (transaction, 'type'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const feeInfo = this.safeValue (transaction, 'fee'); + const feeCost = this.safeFloat (feeInfo, 'cost'); + const feeCurrencyId = this.safeString (feeInfo, 'code'); + const feeCode = this.safeCurrencyCode (feeCurrencyId, currency); + const fee = { + 'cost': feeCost, + 'currency': feeCode, + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': datetime, + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + if (!('userid' in params) && (this.apiKey === undefined)) { + throw new ArgumentsRequired ('fetchDepositAddress requires this.apiKey or userid argument'); + } + const currency = this.currency (code); + const request = { + 'userid': this.apiKey, + 'code': currency['id'], + }; + const response = await this.publicGetDepositaddress (request); + const address = this.safeString (response[0], 'address'); + const tag = this.safeString (response[0], 'tag'); + const chainType = this.safeString (response[0], 'chainType'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'chainType': chainType, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkAddress (address); + await this.loadMarkets (); + if (this.apiKey === undefined) { + throw new ArgumentsRequired ('withdraw requires this.apiKey'); + } + const addressResponse = await this.fetchDepositAddress (code); + const chainTypeString = this.safeString (addressResponse, 'chainType'); + const chainId = this.safeString (addressResponse['info'][0], 'chainId'); + let middleAddress = ''; + if (chainTypeString === 'eos') { + middleAddress = address; + } else { + middleAddress = this.safeString (addressResponse, 'address'); + } + let operationId = 18; + if (chainTypeString !== 'ethereum' && chainTypeString !== 'etc' && chainTypeString !== 'eos' && chainTypeString !== 'cmt' && chainTypeString !== 'naka') { + operationId = 26; + } + const now = this.milliseconds (); + const expiration = 0; + let datetime = this.iso8601 (now); + datetime = datetime.split ('.')[0]; + let expirationDatetime = this.iso8601 (expiration); + expirationDatetime = expirationDatetime.split ('.')[0]; + const defaultDappId = 'Sagittarius'; + const dappId = this.safeString (params, 'dappId', defaultDappId); + const feeAmount = '300000000000000'; + const currency = this.currency (code); + const coinId = currency['id']; + const amountTruncate = this.decimalToPrecision (amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING); + const amountChain = this.toWei (amountTruncate, currency['info']['externalPrecision']); + const eightBytes = this.integerPow ('2', '64'); + let assetFee = 0; + let byteStringArray = []; + if (operationId === 26) { + assetFee = currency['info']['fee']; + byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (operationId, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (address.length, 1), + this.stringToBinary (this.encode (address)), + this.numberToLE (parseInt (coinId), 4), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (1, 1), + this.numberToLE (this.integerDivide (assetFee, eightBytes), 8), + this.numberToLE (this.integerModulo (assetFee, eightBytes), 8), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + } else { + byteStringArray = [ + this.numberToBE (1, 32), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (Math.floor (expiration / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (operationId, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), // string for 32 bit php + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (Math.floor (now / 1000), 4), + this.numberToLE (1, 1), + this.numberToLE (4, 1), + this.numberToLE (0, 8), + this.numberToLE (feeAmount, 8), + this.numberToLE (this.apiKey.length, 1), + this.stringToBinary (this.encode (this.apiKey)), + this.numberToLE (middleAddress.length, 1), + this.stringToBinary (this.encode (middleAddress)), + this.numberToLE (parseInt (coinId), 4), + this.numberToLE (this.integerDivide (amountChain, eightBytes), 8), + this.numberToLE (this.integerModulo (amountChain, eightBytes), 8), + this.numberToLE (0, 1), + this.numberToLE (1, 1), + this.numberToLE (dappId.length, 1), + this.stringToBinary (this.encode (dappId)), + this.numberToLE (0, 1), + ]; + } + const bytestring = this.binaryConcatArray (byteStringArray); + const hash = this.hash (bytestring, 'sha256', 'hex'); + const signature = this.ecdsa (hash, this.secret, 'secp256k1', undefined, true); + const recoveryParam = this.binaryToBase16 (this.numberToLE (this.sum (signature['v'], 31), 1)); + const mySignature = recoveryParam + signature['r'] + signature['s']; + let fatty = undefined; + let request = undefined; + let operation = undefined; + const chainContractAddress = this.safeString (currency['info'], 'chainContractAddress'); + if (operationId === 26) { + operation = { + 'fee': feeAmount, + 'from': this.apiKey, + 'to_external_address': address, + 'asset_type': parseInt (coinId), + 'amount': amountChain, + 'asset_fee': assetFee, + }; + fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + operationId, + operation, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + request = { + 'chainType': chainId, + 'trObj': this.json (fatty), + 'chainContractAddress': chainContractAddress, + }; + } else { + operation = { + 'fee': feeAmount, + 'from': this.apiKey, + 'to_external_address': middleAddress, + 'asset_type': parseInt (coinId), + 'amount': amountChain, + 'asset_fee': assetFee, + }; + const middle = { + 'fee': feeAmount, + 'proposaler': this.apiKey, + 'expiration_time': datetime, + 'proposed_ops': [{ + 'op': [4, operation], + }], + }; + fatty = { + 'timestamp': datetime, + 'expiration': expirationDatetime, + 'operations': [ + [ + operationId, + middle, + ], + ], + 'validate_type': 0, + 'dapp': dappId, + 'signatures': [ + mySignature, + ], + }; + if (chainTypeString === 'eos') { + request = { + 'chainType': chainId, + 'toExternalAddress': 'noneed', + 'trObj': this.json (fatty), + 'chainContractAddress': chainContractAddress, + }; + } else { + request = { + 'chainType': chainId, + 'toExternalAddress': address, + 'trObj': this.json (fatty), + 'chainContractAddress': chainContractAddress, + }; + } + } + const response = await this.publicPostTransactionWithdraw (request); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + url += '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code === 503) { + throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body); + } + if (response === undefined) { + return; // fallback to default error handler + } + if ('code' in response) { + const status = this.safeString (response, 'code'); + if (status === '1') { + const message = this.safeString (response, 'msg'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/cex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/cex.js new file mode 100644 index 000000000..082db07c7 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/cex.js @@ -0,0 +1,1348 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, NullResponse, InvalidOrder, NotSupported, InsufficientFunds, InvalidNonce, OrderNotFound, RateLimitExceeded, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class cex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'cex', + 'name': 'CEX.IO', + 'countries': [ 'GB', 'EU', 'CY', 'RU' ], + 'rateLimit': 1500, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1m', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg', + 'api': 'https://cex.io/api', + 'www': 'https://cex.io', + 'doc': 'https://cex.io/cex-api', + 'fees': [ + 'https://cex.io/fee-schedule', + 'https://cex.io/limits-commissions', + ], + 'referral': 'https://cex.io/r/0/up105393824/0/', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'currency_profile', + 'currency_limits/', + 'last_price/{pair}/', + 'last_prices/{currencies}/', + 'ohlcv/hd/{yyyymmdd}/{pair}', + 'order_book/{pair}/', + 'ticker/{pair}/', + 'tickers/{currencies}/', + 'trade_history/{pair}/', + ], + 'post': [ + 'convert/{pair}', + 'price_stats/{pair}', + ], + }, + 'private': { + 'post': [ + 'active_orders_status/', + 'archived_orders/{pair}/', + 'balance/', + 'cancel_order/', + 'cancel_orders/{pair}/', + 'cancel_replace_order/{pair}/', + 'close_position/{pair}/', + 'get_address/', + 'get_myfee/', + 'get_order/', + 'get_order_tx/', + 'open_orders/{pair}/', + 'open_orders/', + 'open_position/{pair}/', + 'open_positions/{pair}/', + 'place_order/{pair}/', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.16 / 100, + 'taker': 0.25 / 100, + }, + 'funding': { + 'withdraw': { + // 'USD': undefined, + // 'EUR': undefined, + // 'RUB': undefined, + // 'GBP': undefined, + 'BTC': 0.001, + 'ETH': 0.01, + 'BCH': 0.001, + 'DASH': 0.01, + 'BTG': 0.001, + 'ZEC': 0.001, + 'XRP': 0.02, + }, + 'deposit': { + // 'USD': amount => amount * 0.035 + 0.25, + // 'EUR': amount => amount * 0.035 + 0.24, + // 'RUB': amount => amount * 0.05 + 15.57, + // 'GBP': amount => amount * 0.035 + 0.2, + 'BTC': 0.0, + 'ETH': 0.0, + 'BCH': 0.0, + 'DASH': 0.0, + 'BTG': 0.0, + 'ZEC': 0.0, + 'XRP': 0.0, + 'XLM': 0.0, + }, + }, + }, + 'exceptions': { + 'exact': {}, + 'broad': { + 'Insufficient funds': InsufficientFunds, + 'Nonce must be incremented': InvalidNonce, + 'Invalid Order': InvalidOrder, + 'Order not found': OrderNotFound, + 'limit exceeded': RateLimitExceeded, // {"error":"rate limit exceeded"} + 'Invalid API key': AuthenticationError, + 'There was an error while placing your order': InvalidOrder, + 'Sorry, too many clients already': DDoSProtection, + }, + }, + 'options': { + 'fetchOHLCVWarning': true, + 'createMarketBuyOrderRequiresPrice': true, + 'order': { + 'status': { + 'c': 'canceled', + 'd': 'closed', + 'cd': 'canceled', + 'a': 'open', + }, + }, + }, + }); + } + + async fetchCurrenciesFromCache (params = {}) { + // this method is now redundant + // currencies are now fetched before markets + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const response = await this.publicGetCurrencyProfile (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'response': response, + 'timestamp': now, + }); + } + return this.safeValue (this.options['fetchCurrencies'], 'response'); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + this.options['currencies'] = { + 'timestamp': this.milliseconds (), + 'response': response, + }; + // + // { + // "e":"currency_profile", + // "ok":"ok", + // "data":{ + // "symbols":[ + // { + // "code":"GHS", + // "contract":true, + // "commodity":true, + // "fiat":false, + // "description":"CEX.IO doesn't provide cloud mining services anymore.", + // "precision":8, + // "scale":0, + // "minimumCurrencyAmount":"0.00000001", + // "minimalWithdrawalAmount":-1 + // }, + // { + // "code":"BTC", + // "contract":false, + // "commodity":false, + // "fiat":false, + // "description":"", + // "precision":8, + // "scale":0, + // "minimumCurrencyAmount":"0.00000001", + // "minimalWithdrawalAmount":0.002 + // }, + // { + // "code":"ETH", + // "contract":false, + // "commodity":false, + // "fiat":false, + // "description":"", + // "precision":8, + // "scale":2, + // "minimumCurrencyAmount":"0.00000100", + // "minimalWithdrawalAmount":0.01 + // } + // ], + // "pairs":[ + // { + // "symbol1":"BTC", + // "symbol2":"USD", + // "pricePrecision":1, + // "priceScale":"/1000000", + // "minLotSize":0.002, + // "minLotSizeS2":20 + // }, + // { + // "symbol1":"ETH", + // "symbol2":"USD", + // "pricePrecision":2, + // "priceScale":"/10000", + // "minLotSize":0.1, + // "minLotSizeS2":20 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', []); + const currencies = this.safeValue (data, 'symbols', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (currency, 'precision'); + const active = true; + result[code] = { + 'id': id, + 'code': code, + 'name': id, + 'active': active, + 'precision': precision, + 'fee': undefined, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'minimumCurrencyAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minimalWithdrawalAmount'), + 'max': undefined, + }, + }, + 'info': currency, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const currenciesResponse = await this.fetchCurrenciesFromCache (params); + const currenciesData = this.safeValue (currenciesResponse, 'data', {}); + const currencies = this.safeValue (currenciesData, 'symbols', []); + const currenciesById = this.indexBy (currencies, 'code'); + const pairs = this.safeValue (currenciesData, 'pairs', []); + const response = await this.publicGetCurrencyLimits (params); + // + // { + // "e":"currency_limits", + // "ok":"ok", + // "data": { + // "pairs":[ + // { + // "symbol1":"BTC", + // "symbol2":"USD", + // "minLotSize":0.002, + // "minLotSizeS2":20, + // "maxLotSize":30, + // "minPrice":"1500", + // "maxPrice":"35000" + // }, + // { + // "symbol1":"BCH", + // "symbol2":"EUR", + // "minLotSize":0.1, + // "minLotSizeS2":20, + // "maxLotSize":null, + // "minPrice":"25", + // "maxPrice":"8192" + // } + // ] + // } + // } + // + const result = []; + const markets = this.safeValue (response['data'], 'pairs'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const baseId = this.safeString (market, 'symbol1'); + const quoteId = this.safeString (market, 'symbol2'); + const id = baseId + '/' + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const baseCurrency = this.safeValue (currenciesById, baseId, {}); + const quoteCurrency = this.safeValue (currenciesById, quoteId, {}); + let pricePrecision = this.safeInteger (quoteCurrency, 'precision', 8); + for (let j = 0; j < pairs.length; j++) { + const pair = pairs[j]; + if ((pair['symbol1'] === baseId) && (pair['symbol2'] === quoteId)) { + // we might need to account for `priceScale` here + pricePrecision = this.safeInteger (pair, 'pricePrecision', pricePrecision); + } + } + const baseCcyPrecision = this.safeInteger (baseCurrency, 'precision', 8); + const baseCcyScale = this.safeInteger (baseCurrency, 'scale', 0); + const amountPrecision = baseCcyPrecision - baseCcyScale; + const precision = { + 'amount': amountPrecision, + 'price': pricePrecision, + }; + result.push ({ + 'id': id, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minLotSize'), + 'max': this.safeFloat (market, 'maxLotSize'), + }, + 'price': { + 'min': this.safeFloat (market, 'minPrice'), + 'max': this.safeFloat (market, 'maxPrice'), + }, + 'cost': { + 'min': this.safeFloat (market, 'minLotSizeS2'), + 'max': undefined, + }, + }, + 'active': undefined, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const ommited = [ 'username', 'timestamp' ]; + const balances = this.omit (response, ommited); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + // https://github.com/ccxt/ccxt/issues/5484 + account['used'] = this.safeFloat (balance, 'orders', 0.0); + const code = this.safeCurrencyCode (currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetOrderBookPair (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591403940, + // 0.024972, + // 0.024972, + // 0.024969, + // 0.024969, + // 0.49999900 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (since === undefined) { + since = this.milliseconds () - 86400000; // yesterday + } else { + if (this.options['fetchOHLCVWarning']) { + throw new ExchangeError (this.id + " fetchOHLCV warning: CEX can return historical candles for a certain date only, this might produce an empty or null reply. Set exchange.options['fetchOHLCVWarning'] = false or add ({ 'options': { 'fetchOHLCVWarning': false }}) to constructor params to suppress this warning message."); + } + } + let ymd = this.ymd (since); + ymd = ymd.split ('-'); + ymd = ymd.join (''); + const request = { + 'pair': market['id'], + 'yyyymmdd': ymd, + }; + try { + const response = await this.publicGetOhlcvHdYyyymmddPair (this.extend (request, params)); + // + // { + // "time":20200606, + // "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]", + // } + // + const key = 'data' + this.timeframes[timeframe]; + const data = this.safeString (response, key); + const ohlcvs = JSON.parse (data); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } catch (e) { + if (e instanceof NullResponse) { + return []; + } + } + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const volume = this.safeFloat (ticker, 'volume'); + const high = this.safeFloat (ticker, 'high'); + const low = this.safeFloat (ticker, 'low'); + const bid = this.safeFloat (ticker, 'bid'); + const ask = this.safeFloat (ticker, 'ask'); + const last = this.safeFloat (ticker, 'last'); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': volume, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const currencies = Object.keys (this.currencies); + const request = { + 'currencies': currencies.join ('/'), + }; + const response = await this.publicGetTickersCurrencies (this.extend (request, params)); + const tickers = response['data']; + const result = {}; + for (let t = 0; t < tickers.length; t++) { + const ticker = tickers[t]; + const symbol = ticker['pair'].replace (':', '/'); + const market = this.markets[symbol]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const ticker = await this.publicGetTickerPair (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const type = undefined; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTradeHistoryPair (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + // for market buy it requires the amount of quote currency to spend + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + amount = amount * price; + } + } + } + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + 'type': side, + 'amount': amount, + }; + if (type === 'limit') { + request['price'] = price; + } else { + request['order_type'] = type; + } + const response = await this.privatePostPlaceOrderPair (this.extend (request, params)); + // + // { + // "id": "12978363524", + // "time": 1586610022259, + // "type": "buy", + // "price": "0.033934", + // "amount": "0.10722802", + // "pending": "0.10722802", + // "complete": false + // } + // + const placedAmount = this.safeFloat (response, 'amount'); + const remaining = this.safeFloat (response, 'pending'); + const timestamp = this.safeValue (response, 'time'); + const complete = this.safeValue (response, 'complete'); + const status = complete ? 'closed' : 'open'; + let filled = undefined; + if ((placedAmount !== undefined) && (remaining !== undefined)) { + filled = Math.max (placedAmount - remaining, 0); + } + return { + 'id': this.safeString (response, 'id'), + 'info': response, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'type': type, + 'side': this.safeString (response, 'type'), + 'symbol': symbol, + 'status': status, + 'price': this.safeFloat (response, 'price'), + 'amount': placedAmount, + 'cost': undefined, + 'average': undefined, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + 'trades': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrder (order, market = undefined) { + // Depending on the call, 'time' can be a unix int, unix string or ISO string + // Yes, really + let timestamp = this.safeValue (order, 'time'); + if (typeof timestamp === 'string' && timestamp.indexOf ('T') >= 0) { + // ISO8601 string + timestamp = this.parse8601 (timestamp); + } else { + // either integer or string integer + timestamp = parseInt (timestamp); + } + let symbol = undefined; + if (market === undefined) { + const baseId = this.safeString (order, 'symbol1'); + const quoteId = this.safeString (order, 'symbol2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + if (symbol in this.markets) { + market = this.market (symbol); + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const price = this.safeFloat (order, 'price'); + let amount = this.safeFloat (order, 'amount'); + // sell orders can have a negative amount + // https://github.com/ccxt/ccxt/issues/5338 + if (amount !== undefined) { + amount = Math.abs (amount); + } + const remaining = this.safeFloat2 (order, 'pending', 'remains'); + const filled = amount - remaining; + let fee = undefined; + let cost = undefined; + if (market !== undefined) { + symbol = market['symbol']; + cost = this.safeFloat (order, 'ta:' + market['quote']); + if (cost === undefined) { + cost = this.safeFloat (order, 'tta:' + market['quote']); + } + const baseFee = 'fa:' + market['base']; + const baseTakerFee = 'tfa:' + market['base']; + const quoteFee = 'fa:' + market['quote']; + const quoteTakerFee = 'tfa:' + market['quote']; + let feeRate = this.safeFloat (order, 'tradingFeeMaker'); + if (!feeRate) { + feeRate = this.safeFloat (order, 'tradingFeeTaker', feeRate); + } + if (feeRate) { + feeRate /= 100.0; // convert to mathematically-correct percentage coefficients: 1.0 = 100% + } + if ((baseFee in order) || (baseTakerFee in order)) { + const baseFeeCost = this.safeFloat2 (order, baseFee, baseTakerFee); + fee = { + 'currency': market['base'], + 'rate': feeRate, + 'cost': baseFeeCost, + }; + } else if ((quoteFee in order) || (quoteTakerFee in order)) { + const quoteFeeCost = this.safeFloat2 (order, quoteFee, quoteTakerFee); + fee = { + 'currency': market['quote'], + 'rate': feeRate, + 'cost': quoteFeeCost, + }; + } + } + if (!cost) { + cost = price * filled; + } + const side = order['type']; + let trades = undefined; + const orderId = order['id']; + if ('vtx' in order) { + trades = []; + for (let i = 0; i < order['vtx'].length; i++) { + const item = order['vtx'][i]; + const tradeSide = this.safeString (item, 'type'); + if (tradeSide === 'cancel') { + // looks like this might represent the cancelled part of an order + // { id: '4426729543', + // type: 'cancel', + // time: '2017-09-22T00:24:30.476Z', + // user: 'up106404164', + // c: 'user:up106404164:a:BCH', + // d: 'order:4426728375:a:BCH', + // a: '0.09935956', + // amount: '0.09935956', + // balance: '0.42580261', + // symbol: 'BCH', + // order: '4426728375', + // buy: null, + // sell: null, + // pair: null, + // pos: null, + // cs: '0.42580261', + // ds: 0 } + continue; + } + const tradePrice = this.safeFloat (item, 'price'); + if (tradePrice === undefined) { + // this represents the order + // { + // "a": "0.47000000", + // "c": "user:up106404164:a:EUR", + // "d": "order:6065499239:a:EUR", + // "cs": "1432.93", + // "ds": "476.72", + // "id": "6065499249", + // "buy": null, + // "pos": null, + // "pair": null, + // "sell": null, + // "time": "2018-04-22T13:07:22.152Z", + // "type": "buy", + // "user": "up106404164", + // "order": "6065499239", + // "amount": "-715.97000000", + // "symbol": "EUR", + // "balance": "1432.93000000" } + continue; + } + // todo: deal with these + if (tradeSide === 'costsNothing') { + continue; + } + // -- + // if (side !== tradeSide) + // throw new Error (JSON.stringify (order, null, 2)); + // if (orderId !== item['order']) + // throw new Error (JSON.stringify (order, null, 2)); + // -- + // partial buy trade + // { + // "a": "0.01589885", + // "c": "user:up106404164:a:BTC", + // "d": "order:6065499239:a:BTC", + // "cs": "0.36300000", + // "ds": 0, + // "id": "6067991213", + // "buy": "6065499239", + // "pos": null, + // "pair": null, + // "sell": "6067991206", + // "time": "2018-04-22T23:09:11.773Z", + // "type": "buy", + // "user": "up106404164", + // "order": "6065499239", + // "price": 7146.5, + // "amount": "0.01589885", + // "symbol": "BTC", + // "balance": "0.36300000", + // "symbol2": "EUR", + // "fee_amount": "0.19" } + // -- + // trade with zero amount, but non-zero fee + // { + // "a": "0.00000000", + // "c": "user:up106404164:a:EUR", + // "d": "order:5840654423:a:EUR", + // "cs": 559744, + // "ds": 0, + // "id": "5840654429", + // "buy": "5807238573", + // "pos": null, + // "pair": null, + // "sell": "5840654423", + // "time": "2018-03-15T03:20:14.010Z", + // "type": "sell", + // "user": "up106404164", + // "order": "5840654423", + // "price": 730, + // "amount": "0.00000000", + // "symbol": "EUR", + // "balance": "5597.44000000", + // "symbol2": "BCH", + // "fee_amount": "0.01" } + // -- + // trade which should have an amount of exactly 0.002BTC + // { + // "a": "16.70000000", + // "c": "user:up106404164:a:GBP", + // "d": "order:9927386681:a:GBP", + // "cs": "86.90", + // "ds": 0, + // "id": "9927401610", + // "buy": "9927401601", + // "pos": null, + // "pair": null, + // "sell": "9927386681", + // "time": "2019-08-21T15:25:37.777Z", + // "type": "sell", + // "user": "up106404164", + // "order": "9927386681", + // "price": 8365, + // "amount": "16.70000000", + // "office": "UK", + // "symbol": "GBP", + // "balance": "86.90000000", + // "symbol2": "BTC", + // "fee_amount": "0.03" + // } + const tradeTimestamp = this.parse8601 (this.safeString (item, 'time')); + const tradeAmount = this.safeFloat (item, 'amount'); + const feeCost = this.safeFloat (item, 'fee_amount'); + let absTradeAmount = (tradeAmount < 0) ? -tradeAmount : tradeAmount; + let tradeCost = undefined; + if (tradeSide === 'sell') { + tradeCost = absTradeAmount; + absTradeAmount = this.sum (feeCost, tradeCost) / tradePrice; + } else { + tradeCost = absTradeAmount * tradePrice; + } + trades.push ({ + 'id': this.safeString (item, 'id'), + 'timestamp': tradeTimestamp, + 'datetime': this.iso8601 (tradeTimestamp), + 'order': orderId, + 'symbol': symbol, + 'price': tradePrice, + 'amount': absTradeAmount, + 'cost': tradeCost, + 'side': tradeSide, + 'fee': { + 'cost': feeCost, + 'currency': market['quote'], + }, + 'info': item, + 'type': undefined, + 'takerOrMaker': undefined, + }); + } + } + return { + 'id': orderId, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': (price === undefined) ? 'market' : 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'privatePostOpenOrders'; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + method += 'Pair'; + } + const orders = await this[method] (this.extend (request, params)); + for (let i = 0; i < orders.length; i++) { + orders[i] = this.extend (orders[i], { 'status': 'open' }); + } + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostArchivedOrdersPair'; + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + const market = this.market (symbol); + const request = { 'pair': market['id'] }; + const response = await this[method] (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id.toString (), + }; + const response = await this.privatePostGetOrderTx (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + // + // { + // "id": "5442731603", + // "type": "sell", + // "time": 1516132358071, + // "lastTxTime": 1516132378452, + // "lastTx": "5442734452", + // "pos": null, + // "user": "up106404164", + // "status": "d", + // "symbol1": "ETH", + // "symbol2": "EUR", + // "amount": "0.50000000", + // "kind": "api", + // "price": "923.3386", + // "tfacf": "1", + // "fa:EUR": "0.55", + // "ta:EUR": "369.77", + // "remains": "0.00000000", + // "tfa:EUR": "0.22", + // "tta:EUR": "91.95", + // "a:ETH:cds": "0.50000000", + // "a:EUR:cds": "461.72", + // "f:EUR:cds": "0.77", + // "tradingFeeMaker": "0.15", + // "tradingFeeTaker": "0.23", + // "tradingFeeStrategy": "userVolumeAmount", + // "tradingFeeUserVolumeAmount": "2896912572", + // "orderId": "5442731603", + // "next": false, + // "vtx": [ + // { + // "id": "5442734452", + // "type": "sell", + // "time": "2018-01-16T19:52:58.452Z", + // "user": "up106404164", + // "c": "user:up106404164:a:EUR", + // "d": "order:5442731603:a:EUR", + // "a": "104.53000000", + // "amount": "104.53000000", + // "balance": "932.71000000", + // "symbol": "EUR", + // "order": "5442731603", + // "buy": "5442734443", + // "sell": "5442731603", + // "pair": null, + // "pos": null, + // "office": null, + // "cs": "932.71", + // "ds": 0, + // "price": 923.3386, + // "symbol2": "ETH", + // "fee_amount": "0.16" + // }, + // { + // "id": "5442731609", + // "type": "sell", + // "time": "2018-01-16T19:52:38.071Z", + // "user": "up106404164", + // "c": "user:up106404164:a:EUR", + // "d": "order:5442731603:a:EUR", + // "a": "91.73000000", + // "amount": "91.73000000", + // "balance": "563.49000000", + // "symbol": "EUR", + // "order": "5442731603", + // "buy": "5442618127", + // "sell": "5442731603", + // "pair": null, + // "pos": null, + // "office": null, + // "cs": "563.49", + // "ds": 0, + // "price": 924.0092, + // "symbol2": "ETH", + // "fee_amount": "0.22" + // }, + // { + // "id": "5442731604", + // "type": "sell", + // "time": "2018-01-16T19:52:38.071Z", + // "user": "up106404164", + // "c": "order:5442731603:a:ETH", + // "d": "user:up106404164:a:ETH", + // "a": "0.50000000", + // "amount": "-0.50000000", + // "balance": "15.80995000", + // "symbol": "ETH", + // "order": "5442731603", + // "buy": null, + // "sell": null, + // "pair": null, + // "pos": null, + // "office": null, + // "cs": "0.50000000", + // "ds": "15.80995000" + // } + // ] + // } + // + return this.parseOrder (data); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'limit': limit, + 'pair': market['id'], + 'dateFrom': since, + }; + const response = await this.privatePostArchivedOrdersPair (this.extend (request, params)); + const results = []; + for (let i = 0; i < response.length; i++) { + // cancelled (unfilled): + // { id: '4005785516', + // type: 'sell', + // time: '2017-07-18T19:08:34.223Z', + // lastTxTime: '2017-07-18T19:08:34.396Z', + // lastTx: '4005785522', + // pos: null, + // status: 'c', + // symbol1: 'ETH', + // symbol2: 'GBP', + // amount: '0.20000000', + // price: '200.5625', + // remains: '0.20000000', + // 'a:ETH:cds': '0.20000000', + // tradingFeeMaker: '0', + // tradingFeeTaker: '0.16', + // tradingFeeUserVolumeAmount: '10155061217', + // orderId: '4005785516' } + // -- + // cancelled (partially filled buy): + // { id: '4084911657', + // type: 'buy', + // time: '2017-08-05T03:18:39.596Z', + // lastTxTime: '2019-03-19T17:37:46.404Z', + // lastTx: '8459265833', + // pos: null, + // status: 'cd', + // symbol1: 'BTC', + // symbol2: 'GBP', + // amount: '0.05000000', + // price: '2241.4692', + // tfacf: '1', + // remains: '0.03910535', + // 'tfa:GBP': '0.04', + // 'tta:GBP': '24.39', + // 'a:BTC:cds': '0.01089465', + // 'a:GBP:cds': '112.26', + // 'f:GBP:cds': '0.04', + // tradingFeeMaker: '0', + // tradingFeeTaker: '0.16', + // tradingFeeUserVolumeAmount: '13336396963', + // orderId: '4084911657' } + // -- + // cancelled (partially filled sell): + // { id: '4426728375', + // type: 'sell', + // time: '2017-09-22T00:24:20.126Z', + // lastTxTime: '2017-09-22T00:24:30.476Z', + // lastTx: '4426729543', + // pos: null, + // status: 'cd', + // symbol1: 'BCH', + // symbol2: 'BTC', + // amount: '0.10000000', + // price: '0.11757182', + // tfacf: '1', + // remains: '0.09935956', + // 'tfa:BTC': '0.00000014', + // 'tta:BTC': '0.00007537', + // 'a:BCH:cds': '0.10000000', + // 'a:BTC:cds': '0.00007537', + // 'f:BTC:cds': '0.00000014', + // tradingFeeMaker: '0', + // tradingFeeTaker: '0.18', + // tradingFeeUserVolumeAmount: '3466715450', + // orderId: '4426728375' } + // -- + // filled: + // { id: '5342275378', + // type: 'sell', + // time: '2018-01-04T00:28:12.992Z', + // lastTxTime: '2018-01-04T00:28:12.992Z', + // lastTx: '5342275393', + // pos: null, + // status: 'd', + // symbol1: 'BCH', + // symbol2: 'BTC', + // amount: '0.10000000', + // kind: 'api', + // price: '0.17', + // remains: '0.00000000', + // 'tfa:BTC': '0.00003902', + // 'tta:BTC': '0.01699999', + // 'a:BCH:cds': '0.10000000', + // 'a:BTC:cds': '0.01699999', + // 'f:BTC:cds': '0.00003902', + // tradingFeeMaker: '0.15', + // tradingFeeTaker: '0.23', + // tradingFeeUserVolumeAmount: '1525951128', + // orderId: '5342275378' } + // -- + // market order (buy): + // { "id": "6281946200", + // "pos": null, + // "time": "2018-05-23T11:55:43.467Z", + // "type": "buy", + // "amount": "0.00000000", + // "lastTx": "6281946210", + // "status": "d", + // "amount2": "20.00", + // "orderId": "6281946200", + // "remains": "0.00000000", + // "symbol1": "ETH", + // "symbol2": "EUR", + // "tfa:EUR": "0.05", + // "tta:EUR": "19.94", + // "a:ETH:cds": "0.03764100", + // "a:EUR:cds": "20.00", + // "f:EUR:cds": "0.05", + // "lastTxTime": "2018-05-23T11:55:43.467Z", + // "tradingFeeTaker": "0.25", + // "tradingFeeUserVolumeAmount": "55998097" } + // -- + // market order (sell): + // { "id": "6282200948", + // "pos": null, + // "time": "2018-05-23T12:42:58.315Z", + // "type": "sell", + // "amount": "-0.05000000", + // "lastTx": "6282200958", + // "status": "d", + // "orderId": "6282200948", + // "remains": "0.00000000", + // "symbol1": "ETH", + // "symbol2": "EUR", + // "tfa:EUR": "0.07", + // "tta:EUR": "26.49", + // "a:ETH:cds": "0.05000000", + // "a:EUR:cds": "26.49", + // "f:EUR:cds": "0.07", + // "lastTxTime": "2018-05-23T12:42:58.315Z", + // "tradingFeeTaker": "0.25", + // "tradingFeeUserVolumeAmount": "56294576" } + const order = response[i]; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const baseId = this.safeString (order, 'symbol1'); + const quoteId = this.safeString (order, 'symbol2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const side = this.safeString (order, 'type'); + const baseAmount = this.safeFloat (order, 'a:' + baseId + ':cds'); + const quoteAmount = this.safeFloat (order, 'a:' + quoteId + ':cds'); + const fee = this.safeFloat (order, 'f:' + quoteId + ':cds'); + const amount = this.safeFloat (order, 'amount'); + const price = this.safeFloat (order, 'price'); + const remaining = this.safeFloat (order, 'remains'); + const filled = amount - remaining; + let orderAmount = undefined; + let cost = undefined; + let average = undefined; + let type = undefined; + if (!price) { + type = 'market'; + orderAmount = baseAmount; + cost = quoteAmount; + average = orderAmount / cost; + } else { + const ta = this.safeFloat (order, 'ta:' + quoteId, 0); + const tta = this.safeFloat (order, 'tta:' + quoteId, 0); + const fa = this.safeFloat (order, 'fa:' + quoteId, 0); + const tfa = this.safeFloat (order, 'tfa:' + quoteId, 0); + if (side === 'sell') { + cost = this.sum (this.sum (ta, tta), this.sum (fa, tfa)); + } else { + cost = this.sum (ta, tta) - this.sum (fa, tfa); + } + type = 'limit'; + orderAmount = amount; + average = cost / filled; + } + const time = this.safeString (order, 'time'); + const lastTxTime = this.safeString (order, 'lastTxTime'); + const timestamp = this.parse8601 (time); + results.push ({ + 'id': this.safeString (order, 'id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastUpdated': this.parse8601 (lastTxTime), + 'status': status, + 'symbol': symbol, + 'side': side, + 'price': price, + 'amount': orderAmount, + 'average': average, + 'type': type, + 'filled': filled, + 'cost': cost, + 'remaining': remaining, + 'fee': { + 'cost': fee, + 'currency': quote, + }, + 'info': order, + }); + } + return results; + } + + parseOrderStatus (status) { + return this.safeString (this.options['order']['status'], status, status); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (amount === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires a amount argument'); + } + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires a price argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + // see: https://cex.io/rest-api#/definitions/CancelReplaceOrderRequest + const request = { + 'pair': market['id'], + 'type': side, + 'amount': amount, + 'price': price, + 'order_id': id, + }; + const response = await this.privatePostCancelReplaceOrderPair (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchDepositAddress (code, params = {}) { + if (code === 'XRP' || code === 'XLM') { + // https://github.com/ccxt/ccxt/pull/2327#issuecomment-375204856 + throw new NotSupported (this.id + ' fetchDepositAddress does not support XRP and XLM addresses yet (awaiting docs from CEX.io)'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostGetAddress (this.extend (request, params)); + const address = this.safeString (response, 'data'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.json (this.extend ({ + 'key': this.apiKey, + 'signature': signature.toUpperCase (), + 'nonce': nonce, + }, query)); + headers = { + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (Array.isArray (response)) { + return response; // public endpoints may return []-arrays + } + if (body === 'true') { + return; + } + if (response === undefined) { + throw new NullResponse (this.id + ' returned ' + this.json (response)); + } + if ('e' in response) { + if ('ok' in response) { + if (response['ok'] === 'ok') { + return; + } + } + } + if ('error' in response) { + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/chilebit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/chilebit.js new file mode 100644 index 000000000..4aeeb9ec3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/chilebit.js @@ -0,0 +1,32 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const foxbit = require ('./foxbit.js'); + +// --------------------------------------------------------------------------- + +module.exports = class chilebit extends foxbit { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'chilebit', + 'name': 'ChileBit', + 'countries': [ 'CL' ], + 'has': { + 'CORS': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27991414-1298f0d8-647f-11e7-9c40-d56409266336.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://chilebit.net', + 'doc': 'https://blinktrade.com/docs', + }, + 'options': { + 'brokerId': '9', // https://blinktrade.com/docs/#brokers + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbase.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbase.js new file mode 100644 index 000000000..61fc650dc --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbase.js @@ -0,0 +1,1200 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, RateLimitExceeded } = require ('./base/errors'); + +// ---------------------------------------------------------------------------- + +module.exports = class coinbase extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinbase', + 'name': 'Coinbase', + 'countries': [ 'US' ], + 'rateLimit': 400, // 10k calls per hour + 'version': 'v2', + 'userAgent': this.userAgents['chrome'], + 'headers': { + 'CB-VERSION': '2018-05-30', + }, + 'has': { + 'CORS': true, + 'cancelOrder': false, + 'createDepositAddress': true, + 'createOrder': false, + 'deposit': false, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': false, + 'fetchOpenOrders': false, + 'fetchOrder': false, + 'fetchOrderBook': false, + 'fetchL2OrderBook': false, + 'fetchLedger': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTime': true, + 'fetchBidsAsks': false, + 'fetchTrades': false, + 'withdraw': false, + 'fetchTransactions': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchMySells': true, + 'fetchMyBuys': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg', + 'api': 'https://api.coinbase.com', + 'www': 'https://www.coinbase.com', + 'doc': 'https://developers.coinbase.com/api/v2', + 'fees': 'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees', + 'referral': 'https://www.coinbase.com/join/58cbe25a355148797479dbd2', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'time', + 'exchange-rates', + 'users/{user_id}', + 'prices/{symbol}/buy', + 'prices/{symbol}/sell', + 'prices/{symbol}/spot', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/{account_id}', + 'accounts/{account_id}/addresses', + 'accounts/{account_id}/addresses/{address_id}', + 'accounts/{account_id}/addresses/{address_id}/transactions', + 'accounts/{account_id}/transactions', + 'accounts/{account_id}/transactions/{transaction_id}', + 'accounts/{account_id}/buys', + 'accounts/{account_id}/buys/{buy_id}', + 'accounts/{account_id}/sells', + 'accounts/{account_id}/sells/{sell_id}', + 'accounts/{account_id}/deposits', + 'accounts/{account_id}/deposits/{deposit_id}', + 'accounts/{account_id}/withdrawals', + 'accounts/{account_id}/withdrawals/{withdrawal_id}', + 'payment-methods', + 'payment-methods/{payment_method_id}', + 'user', + 'user/auth', + ], + 'post': [ + 'accounts', + 'accounts/{account_id}/primary', + 'accounts/{account_id}/addresses', + 'accounts/{account_id}/transactions', + 'accounts/{account_id}/transactions/{transaction_id}/complete', + 'accounts/{account_id}/transactions/{transaction_id}/resend', + 'accounts/{account_id}/buys', + 'accounts/{account_id}/buys/{buy_id}/commit', + 'accounts/{account_id}/sells', + 'accounts/{account_id}/sells/{sell_id}/commit', + 'accounts/{account_id}/deposists', + 'accounts/{account_id}/deposists/{deposit_id}/commit', + 'accounts/{account_id}/withdrawals', + 'accounts/{account_id}/withdrawals/{withdrawal_id}/commit', + ], + 'put': [ + 'accounts/{account_id}', + 'user', + ], + 'delete': [ + 'accounts/{id}', + 'accounts/{account_id}/transactions/{transaction_id}', + ], + }, + }, + 'exceptions': { + 'two_factor_required': AuthenticationError, // 402 When sending money over 2fa limit + 'param_required': ExchangeError, // 400 Missing parameter + 'validation_error': ExchangeError, // 400 Unable to validate POST/PUT + 'invalid_request': ExchangeError, // 400 Invalid request + 'personal_details_required': AuthenticationError, // 400 User’s personal detail required to complete this request + 'identity_verification_required': AuthenticationError, // 400 Identity verification is required to complete this request + 'jumio_verification_required': AuthenticationError, // 400 Document verification is required to complete this request + 'jumio_face_match_verification_required': AuthenticationError, // 400 Document verification including face match is required to complete this request + 'unverified_email': AuthenticationError, // 400 User has not verified their email + 'authentication_error': AuthenticationError, // 401 Invalid auth (generic) + 'invalid_token': AuthenticationError, // 401 Invalid Oauth token + 'revoked_token': AuthenticationError, // 401 Revoked Oauth token + 'expired_token': AuthenticationError, // 401 Expired Oauth token + 'invalid_scope': AuthenticationError, // 403 User hasn’t authenticated necessary scope + 'not_found': ExchangeError, // 404 Resource not found + 'rate_limit_exceeded': RateLimitExceeded, // 429 Rate limit exceeded + 'internal_server_error': ExchangeError, // 500 Internal server error + }, + 'commonCurrencies': { + 'CGLD': 'CELO', + }, + 'options': { + 'fetchCurrencies': { + 'expires': 5000, + }, + 'accounts': [ + 'wallet', + 'fiat', + // 'vault', + ], + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "data": { + // "epoch": 1589295679, + // "iso": "2020-05-12T15:01:19Z" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.safeTimestamp (data, 'epoch'); + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccounts (params); + // + // { + // "id": "XLM", + // "name": "XLM Wallet", + // "primary": false, + // "type": "wallet", + // "currency": { + // "code": "XLM", + // "name": "Stellar Lumens", + // "color": "#000000", + // "sort_index": 127, + // "exponent": 7, + // "type": "crypto", + // "address_regex": "^G[A-Z2-7]{55}$", + // "asset_id": "13b83335-5ede-595b-821e-5bcdfa80560f", + // "destination_tag_name": "XLM Memo ID", + // "destination_tag_regex": "^[ -~]{1,28}$" + // }, + // "balance": { + // "amount": "0.0000000", + // "currency": "XLM" + // }, + // "created_at": null, + // "updated_at": null, + // "resource": "account", + // "resource_path": "/v2/accounts/XLM", + // "allow_deposits": true, + // "allow_withdrawals": true + // } + // + const data = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const currency = this.safeValue (account, 'currency', {}); + const currencyId = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': this.safeString (account, 'id'), + 'type': this.safeString (account, 'type'), + 'code': code, + 'info': account, + }); + } + return result; + } + + async createDepositAddress (code, params = {}) { + let accountId = this.safeString (params, 'account_id'); + params = this.omit (params, 'account_id'); + if (accountId === undefined) { + await this.loadAccounts (); + for (let i = 0; i < this.accounts.length; i++) { + const account = this.accounts[i]; + if (account['code'] === code && account['type'] === 'wallet') { + accountId = account['id']; + break; + } + } + } + if (accountId === undefined) { + throw new ExchangeError (this.id + ' createDepositAddress could not find the account with matching currency code, specify an `account_id` extra param'); + } + const request = { + 'account_id': accountId, + }; + const response = await this.privatePostAccountsAccountIdAddresses (this.extend (request, params)); + // + // { + // "data": { + // "id": "05b1ebbf-9438-5dd4-b297-2ddedc98d0e4", + // "address": "coinbasebase", + // "address_info": { + // "address": "coinbasebase", + // "destination_tag": "287594668" + // }, + // "name": null, + // "created_at": "2019-07-01T14:39:29Z", + // "updated_at": "2019-07-01T14:39:29Z", + // "network": "eosio", + // "uri_scheme": "eosio", + // "resource": "address", + // "resource_path": "/v2/accounts/14cfc769-e852-52f3-b831-711c104d194c/addresses/05b1ebbf-9438-5dd4-b297-2ddedc98d0e4", + // "warnings": [ + // { + // "title": "Only send EOS (EOS) to this address", + // "details": "Sending any other cryptocurrency will result in permanent loss.", + // "image_url": "https://dynamic-assets.coinbase.com/deaca3d47b10ed4a91a872e9618706eec34081127762d88f2476ac8e99ada4b48525a9565cf2206d18c04053f278f693434af4d4629ca084a9d01b7a286a7e26/asset_icons/1f8489bb280fb0a0fd643c1161312ba49655040e9aaaced5f9ad3eeaf868eadc.png" + // }, + // { + // "title": "Both an address and EOS memo are required to receive EOS", + // "details": "If you send funds without an EOS memo or with an incorrect EOS memo, your funds cannot be credited to your account.", + // "image_url": "https://www.coinbase.com/assets/receive-warning-2f3269d83547a7748fb39d6e0c1c393aee26669bfea6b9f12718094a1abff155.png" + // } + // ], + // "warning_title": "Only send EOS (EOS) to this address", + // "warning_details": "Sending any other cryptocurrency will result in permanent loss.", + // "destination_tag": "287594668", + // "deposit_uri": "eosio:coinbasebase?dt=287594668", + // "callback_url": null + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const tag = this.safeString (data, 'destination_tag'); + const address = this.safeString (data, 'address'); + return { + 'currency': code, + 'tag': tag, + 'address': address, + 'info': response, + }; + } + + async fetchMySells (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // they don't have an endpoint for all historical trades + const request = await this.prepareAccountRequest (limit, params); + await this.loadMarkets (); + const query = this.omit (params, [ 'account_id', 'accountId' ]); + const sells = await this.privateGetAccountsAccountIdSells (this.extend (request, query)); + return this.parseTrades (sells['data'], undefined, since, limit); + } + + async fetchMyBuys (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // they don't have an endpoint for all historical trades + const request = await this.prepareAccountRequest (limit, params); + await this.loadMarkets (); + const query = this.omit (params, [ 'account_id', 'accountId' ]); + const buys = await this.privateGetAccountsAccountIdBuys (this.extend (request, query)); + return this.parseTrades (buys['data'], undefined, since, limit); + } + + async fetchTransactionsWithMethod (method, code = undefined, since = undefined, limit = undefined, params = {}) { + const request = await this.prepareAccountRequestWithCurrencyCode (code, limit, params); + await this.loadMarkets (); + const query = this.omit (params, [ 'account_id', 'accountId' ]); + const response = await this[method] (this.extend (request, query)); + return this.parseTransactions (response['data'], undefined, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + // fiat only, for crypto transactions use fetchLedger + return await this.fetchTransactionsWithMethod ('privateGetAccountsAccountIdWithdrawals', code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + // fiat only, for crypto transactions use fetchLedger + return await this.fetchTransactionsWithMethod ('privateGetAccountsAccountIdDeposits', code, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + 'created': 'pending', + 'completed': 'ok', + 'canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, market = undefined) { + // + // fiat deposit + // + // { + // "id": "f34c19f3-b730-5e3d-9f72", + // "status": "completed", + // "payment_method": { + // "id": "a022b31d-f9c7-5043-98f2", + // "resource": "payment_method", + // "resource_path": "/v2/payment-methods/a022b31d-f9c7-5043-98f2" + // }, + // "transaction": { + // "id": "04ed4113-3732-5b0c-af86-b1d2146977d0", + // "resource": "transaction", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86" + // }, + // "user_reference": "2VTYTH", + // "created_at": "2017-02-09T07:01:18Z", + // "updated_at": "2017-02-09T07:01:26Z", + // "resource": "deposit", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72", + // "committed": true, + // "payout_at": "2017-02-12T07:01:17Z", + // "instant": false, + // "fee": { "amount": "0.00", "currency": "EUR" }, + // "amount": { "amount": "114.02", "currency": "EUR" }, + // "subtotal": { "amount": "114.02", "currency": "EUR" }, + // "hold_until": null, + // "hold_days": 0, + // "hold_business_days": 0, + // "next_step": null + // } + // + // fiat_withdrawal + // + // { + // "id": "cfcc3b4a-eeb6-5e8c-8058", + // "status": "completed", + // "payment_method": { + // "id": "8b94cfa4-f7fd-5a12-a76a", + // "resource": "payment_method", + // "resource_path": "/v2/payment-methods/8b94cfa4-f7fd-5a12-a76a" + // }, + // "transaction": { + // "id": "fcc2550b-5104-5f83-a444", + // "resource": "transaction", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/fcc2550b-5104-5f83-a444" + // }, + // "user_reference": "MEUGK", + // "created_at": "2018-07-26T08:55:12Z", + // "updated_at": "2018-07-26T08:58:18Z", + // "resource": "withdrawal", + // "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/cfcc3b4a-eeb6-5e8c-8058", + // "committed": true, + // "payout_at": "2018-07-31T08:55:12Z", + // "instant": false, + // "fee": { "amount": "0.15", "currency": "EUR" }, + // "amount": { "amount": "13130.69", "currency": "EUR" }, + // "subtotal": { "amount": "13130.84", "currency": "EUR" }, + // "idem": "e549dee5-63ed-4e79-8a96", + // "next_step": null + // } + // + const subtotalObject = this.safeValue (transaction, 'subtotal', {}); + const feeObject = this.safeValue (transaction, 'fee', {}); + const id = this.safeString (transaction, 'id'); + const timestamp = this.parse8601 (this.safeValue (transaction, 'created_at')); + const updated = this.parse8601 (this.safeValue (transaction, 'updated_at')); + const type = this.safeString (transaction, 'resource'); + const amount = this.safeFloat (subtotalObject, 'amount'); + const currencyId = this.safeString (subtotalObject, 'currency'); + const currency = this.safeCurrencyCode (currencyId); + const feeCost = this.safeFloat (feeObject, 'amount'); + const feeCurrencyId = this.safeString (feeObject, 'currency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + let status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + if (status === undefined) { + const committed = this.safeValue (transaction, 'committed'); + status = committed ? 'ok' : 'pending'; + } + return { + 'info': transaction, + 'id': id, + 'txid': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': undefined, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': currency, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTrade (trade, market = undefined) { + // + // { + // "id": "67e0eaec-07d7-54c4-a72c-2e92826897df", + // "status": "completed", + // "payment_method": { + // "id": "83562370-3e5c-51db-87da-752af5ab9559", + // "resource": "payment_method", + // "resource_path": "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559" + // }, + // "transaction": { + // "id": "441b9494-b3f0-5b98-b9b0-4d82c21c252a", + // "resource": "transaction", + // "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a" + // }, + // "amount": { "amount": "1.00000000", "currency": "BTC" }, + // "total": { "amount": "10.25", "currency": "USD" }, + // "subtotal": { "amount": "10.10", "currency": "USD" }, + // "created_at": "2015-01-31T20:49:02Z", + // "updated_at": "2015-02-11T16:54:02-08:00", + // "resource": "buy", + // "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/buys/67e0eaec-07d7-54c4-a72c-2e92826897df", + // "committed": true, + // "instant": false, + // "fee": { "amount": "0.15", "currency": "USD" }, + // "payout_at": "2015-02-18T16:54:00-08:00" + // } + // + let symbol = undefined; + const totalObject = this.safeValue (trade, 'total', {}); + const amountObject = this.safeValue (trade, 'amount', {}); + const subtotalObject = this.safeValue (trade, 'subtotal', {}); + const feeObject = this.safeValue (trade, 'fee', {}); + const id = this.safeString (trade, 'id'); + const timestamp = this.parse8601 (this.safeValue (trade, 'created_at')); + if (market === undefined) { + const baseId = this.safeString (amountObject, 'currency'); + const quoteId = this.safeString (totalObject, 'currency'); + if ((baseId !== undefined) && (quoteId !== undefined)) { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + const orderId = undefined; + const side = this.safeString (trade, 'resource'); + const type = undefined; + const cost = this.safeFloat (subtotalObject, 'amount'); + const amount = this.safeFloat (amountObject, 'amount'); + let price = undefined; + if (cost !== undefined) { + if ((amount !== undefined) && (amount > 0)) { + price = cost / amount; + } + } + const feeCost = this.safeFloat (feeObject, 'amount'); + const feeCurrencyId = this.safeString (feeObject, 'currency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + const currencies = this.safeValue (response, 'currencies', {}); + const exchangeRates = this.safeValue (response, 'exchangeRates', {}); + const data = this.safeValue (currencies, 'data', []); + const dataById = this.indexBy (data, 'id'); + const rates = this.safeValue (this.safeValue (exchangeRates, 'data', {}), 'rates', {}); + const baseIds = Object.keys (rates); + const result = []; + for (let i = 0; i < baseIds.length; i++) { + const baseId = baseIds[i]; + const base = this.safeCurrencyCode (baseId); + const type = (baseId in dataById) ? 'fiat' : 'crypto'; + // https://github.com/ccxt/ccxt/issues/6066 + if (type === 'crypto') { + for (let j = 0; j < data.length; j++) { + const quoteCurrency = data[j]; + const quoteId = this.safeString (quoteCurrency, 'id'); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = baseId + '-' + quoteId; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'info': quoteCurrency, + 'precision': { + 'amount': undefined, + 'price': undefined, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (quoteCurrency, 'min_size'), + 'max': undefined, + }, + }, + }); + } + } + } + return result; + } + + async fetchCurrenciesFromCache (params = {}) { + const options = this.safeValue (this.options, 'fetchCurrencies', {}); + const timestamp = this.safeInteger (options, 'timestamp'); + const expires = this.safeInteger (options, 'expires', 1000); + const now = this.milliseconds (); + if ((timestamp === undefined) || ((now - timestamp) > expires)) { + const currencies = await this.publicGetCurrencies (params); + const exchangeRates = await this.publicGetExchangeRates (params); + this.options['fetchCurrencies'] = this.extend (options, { + 'currencies': currencies, + 'exchangeRates': exchangeRates, + 'timestamp': now, + }); + } + return this.safeValue (this.options, 'fetchCurrencies', {}); + } + + async fetchCurrencies (params = {}) { + const response = await this.fetchCurrenciesFromCache (params); + const currencies = this.safeValue (response, 'currencies', {}); + // + // { + // "data":[ + // {"id":"AED","name":"United Arab Emirates Dirham","min_size":"0.01000000"}, + // {"id":"AFN","name":"Afghan Afghani","min_size":"0.01000000"}, + // {"id":"ALL","name":"Albanian Lek","min_size":"0.01000000"}, + // {"id":"AMD","name":"Armenian Dram","min_size":"0.01000000"}, + // {"id":"ANG","name":"Netherlands Antillean Gulden","min_size":"0.01000000"}, + // // ... + // ], + // } + // + const exchangeRates = this.safeValue (response, 'exchangeRates', {}); + // + // { + // "data":{ + // "currency":"USD", + // "rates":{ + // "AED":"3.67", + // "AFN":"78.21", + // "ALL":"110.42", + // "AMD":"474.18", + // "ANG":"1.75", + // // ... + // }, + // } + // } + // + const data = this.safeValue (currencies, 'data', []); + const dataById = this.indexBy (data, 'id'); + const rates = this.safeValue (this.safeValue (exchangeRates, 'data', {}), 'rates', {}); + const keys = Object.keys (rates); + const result = {}; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const type = (key in dataById) ? 'fiat' : 'crypto'; + const currency = this.safeValue (dataById, key, {}); + const id = this.safeString (currency, 'id', key); + const name = this.safeString (currency, 'name'); + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, // the original payload + 'type': type, + 'name': name, + 'active': true, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'min_size'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const timestamp = this.seconds (); + const market = this.market (symbol); + const request = this.extend ({ + 'symbol': market['id'], + }, params); + const buy = await this.publicGetPricesSymbolBuy (request); + const sell = await this.publicGetPricesSymbolSell (request); + const spot = await this.publicGetPricesSymbolSpot (request); + const ask = this.safeFloat (buy['data'], 'amount'); + const bid = this.safeFloat (sell['data'], 'amount'); + const last = this.safeFloat (spot['data'], 'amount'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'bid': bid, + 'ask': ask, + 'last': last, + 'high': undefined, + 'low': undefined, + 'bidVolume': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': { + 'buy': buy, + 'sell': sell, + 'spot': spot, + }, + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + const balances = this.safeValue (response, 'data'); + const accounts = this.safeValue (params, 'type', this.options['accounts']); + const result = { 'info': response }; + for (let b = 0; b < balances.length; b++) { + const balance = balances[b]; + if (this.inArray (balance['type'], accounts)) { + const currencyId = this.safeString (balance['balance'], 'currency'); + const code = this.safeCurrencyCode (currencyId); + const total = this.safeFloat (balance['balance'], 'amount'); + const free = total; + const used = undefined; + if (code in result) { + result[code]['free'] = this.sum (result[code]['free'], total); + result[code]['total'] = this.sum (result[code]['total'], total); + } else { + const account = { + 'free': free, + 'used': used, + 'total': total, + }; + result[code] = account; + } + } + } + return this.parseBalance (result); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = await this.prepareAccountRequestWithCurrencyCode (code, limit, params); + const query = this.omit (params, ['account_id', 'accountId']); + // for pagination use parameter 'starting_after' + // the value for the next page can be obtained from the result of the previous call in the 'pagination' field + // eg: instance.last_json_response.pagination.next_starting_after + const response = await this.privateGetAccountsAccountIdTransactions (this.extend (request, query)); + return this.parseLedger (response['data'], currency, since, limit); + } + + parseLedgerEntryStatus (status) { + const types = { + 'completed': 'ok', + }; + return this.safeString (types, status, status); + } + + parseLedgerEntryType (type) { + const types = { + 'buy': 'trade', + 'sell': 'trade', + 'fiat_deposit': 'transaction', + 'fiat_withdrawal': 'transaction', + 'exchange_deposit': 'transaction', // fiat withdrawal (from coinbase to coinbasepro) + 'exchange_withdrawal': 'transaction', // fiat deposit (to coinbase from coinbasepro) + 'send': 'transaction', // crypto deposit OR withdrawal + 'pro_deposit': 'transaction', // crypto withdrawal (from coinbase to coinbasepro) + 'pro_withdrawal': 'transaction', // crypto deposit (to coinbase from coinbasepro) + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // crypto deposit transaction + // + // { + // id: '34e4816b-4c8c-5323-a01c-35a9fa26e490', + // type: 'send', + // status: 'completed', + // amount: { amount: '28.31976528', currency: 'BCH' }, + // native_amount: { amount: '2799.65', currency: 'GBP' }, + // description: null, + // created_at: '2019-02-28T12:35:20Z', + // updated_at: '2019-02-28T12:43:24Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/34e4816b-4c8c-5323-a01c-35a9fa26e490', + // instant_exchange: false, + // network: { + // status: 'confirmed', + // hash: '56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701', + // transaction_url: 'https://bch.btc.com/56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701' + // }, + // from: { resource: 'bitcoin_cash_network', currency: 'BCH' }, + // details: { title: 'Received Bitcoin Cash', subtitle: 'From Bitcoin Cash address' } + // } + // + // crypto withdrawal transaction + // + // { + // id: '459aad99-2c41-5698-ac71-b6b81a05196c', + // type: 'send', + // status: 'completed', + // amount: { amount: '-0.36775642', currency: 'BTC' }, + // native_amount: { amount: '-1111.65', currency: 'GBP' }, + // description: null, + // created_at: '2019-03-20T08:37:07Z', + // updated_at: '2019-03-20T08:49:33Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/459aad99-2c41-5698-ac71-b6b81a05196c', + // instant_exchange: false, + // network: { + // status: 'confirmed', + // hash: '2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b', + // transaction_url: 'https://blockchain.info/tx/2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b', + // transaction_fee: { amount: '0.00000000', currency: 'BTC' }, + // transaction_amount: { amount: '0.36775642', currency: 'BTC' }, + // confirmations: 15682 + // }, + // to: { + // resource: 'bitcoin_address', + // address: '1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX', + // currency: 'BTC', + // address_info: { address: '1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX' } + // }, + // idem: 'da0a2f14-a2af-4c5a-a37e-d4484caf582bsend', + // application: { + // id: '5756ab6e-836b-553b-8950-5e389451225d', + // resource: 'application', + // resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d' + // }, + // details: { title: 'Sent Bitcoin', subtitle: 'To Bitcoin address' } + // } + // + // withdrawal transaction from coinbase to coinbasepro + // + // { + // id: '5b1b9fb8-5007-5393-b923-02903b973fdc', + // type: 'pro_deposit', + // status: 'completed', + // amount: { amount: '-0.00001111', currency: 'BCH' }, + // native_amount: { amount: '0.00', currency: 'GBP' }, + // description: null, + // created_at: '2019-02-28T13:31:58Z', + // updated_at: '2019-02-28T13:31:58Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/5b1b9fb8-5007-5393-b923-02903b973fdc', + // instant_exchange: false, + // application: { + // id: '5756ab6e-836b-553b-8950-5e389451225d', + // resource: 'application', + // resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d' + // }, + // details: { title: 'Transferred Bitcoin Cash', subtitle: 'To Coinbase Pro' } + // } + // + // withdrawal transaction from coinbase to gdax + // + // { + // id: 'badb7313-a9d3-5c07-abd0-00f8b44199b1', + // type: 'exchange_deposit', + // status: 'completed', + // amount: { amount: '-0.43704149', currency: 'BCH' }, + // native_amount: { amount: '-51.90', currency: 'GBP' }, + // description: null, + // created_at: '2019-03-19T10:30:40Z', + // updated_at: '2019-03-19T10:30:40Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/badb7313-a9d3-5c07-abd0-00f8b44199b1', + // instant_exchange: false, + // details: { title: 'Transferred Bitcoin Cash', subtitle: 'To GDAX' } + // } + // + // deposit transaction from gdax to coinbase + // + // { + // id: '9c4b642c-8688-58bf-8962-13cef64097de', + // type: 'exchange_withdrawal', + // status: 'completed', + // amount: { amount: '0.57729420', currency: 'BTC' }, + // native_amount: { amount: '4418.72', currency: 'GBP' }, + // description: null, + // created_at: '2018-02-17T11:33:33Z', + // updated_at: '2018-02-17T11:33:33Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/9c4b642c-8688-58bf-8962-13cef64097de', + // instant_exchange: false, + // details: { title: 'Transferred Bitcoin', subtitle: 'From GDAX' } + // } + // + // deposit transaction from coinbasepro to coinbase + // + // { + // id: '8d6dd0b9-3416-568a-889d-8f112fae9e81', + // type: 'pro_withdrawal', + // status: 'completed', + // amount: { amount: '0.40555386', currency: 'BTC' }, + // native_amount: { amount: '1140.27', currency: 'GBP' }, + // description: null, + // created_at: '2019-03-04T19:41:58Z', + // updated_at: '2019-03-04T19:41:58Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/8d6dd0b9-3416-568a-889d-8f112fae9e81', + // instant_exchange: false, + // application: { + // id: '5756ab6e-836b-553b-8950-5e389451225d', + // resource: 'application', + // resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d' + // }, + // details: { title: 'Transferred Bitcoin', subtitle: 'From Coinbase Pro' } + // } + // + // sell trade + // + // { + // id: 'a9409207-df64-585b-97ab-a50780d2149e', + // type: 'sell', + // status: 'completed', + // amount: { amount: '-9.09922880', currency: 'BTC' }, + // native_amount: { amount: '-7285.73', currency: 'GBP' }, + // description: null, + // created_at: '2017-03-27T15:38:34Z', + // updated_at: '2017-03-27T15:38:34Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/a9409207-df64-585b-97ab-a50780d2149e', + // instant_exchange: false, + // sell: { + // id: 'e3550b4d-8ae6-5de3-95fe-1fb01ba83051', + // resource: 'sell', + // resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/sells/e3550b4d-8ae6-5de3-95fe-1fb01ba83051' + // }, + // details: { + // title: 'Sold Bitcoin', + // subtitle: 'Using EUR Wallet', + // payment_method_name: 'EUR Wallet' + // } + // } + // + // buy trade + // + // { + // id: '63eeed67-9396-5912-86e9-73c4f10fe147', + // type: 'buy', + // status: 'completed', + // amount: { amount: '2.39605772', currency: 'ETH' }, + // native_amount: { amount: '98.31', currency: 'GBP' }, + // description: null, + // created_at: '2017-03-27T09:07:56Z', + // updated_at: '2017-03-27T09:07:57Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/transactions/63eeed67-9396-5912-86e9-73c4f10fe147', + // instant_exchange: false, + // buy: { + // id: '20b25b36-76c6-5353-aa57-b06a29a39d82', + // resource: 'buy', + // resource_path: '/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/buys/20b25b36-76c6-5353-aa57-b06a29a39d82' + // }, + // details: { + // title: 'Bought Ethereum', + // subtitle: 'Using EUR Wallet', + // payment_method_name: 'EUR Wallet' + // } + // } + // + // fiat deposit transaction + // + // { + // id: '04ed4113-3732-5b0c-af86-b1d2146977d0', + // type: 'fiat_deposit', + // status: 'completed', + // amount: { amount: '114.02', currency: 'EUR' }, + // native_amount: { amount: '97.23', currency: 'GBP' }, + // description: null, + // created_at: '2017-02-09T07:01:21Z', + // updated_at: '2017-02-09T07:01:22Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86-b1d2146977d0', + // instant_exchange: false, + // fiat_deposit: { + // id: 'f34c19f3-b730-5e3d-9f72-96520448677a', + // resource: 'fiat_deposit', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72-96520448677a' + // }, + // details: { + // title: 'Deposited funds', + // subtitle: 'From SEPA Transfer (GB47 BARC 20..., reference CBADVI)', + // payment_method_name: 'SEPA Transfer (GB47 BARC 20..., reference CBADVI)' + // } + // } + // + // fiat withdrawal transaction + // + // { + // id: '957d98e2-f80e-5e2f-a28e-02945aa93079', + // type: 'fiat_withdrawal', + // status: 'completed', + // amount: { amount: '-11000.00', currency: 'EUR' }, + // native_amount: { amount: '-9698.22', currency: 'GBP' }, + // description: null, + // created_at: '2017-12-06T13:19:19Z', + // updated_at: '2017-12-06T13:19:19Z', + // resource: 'transaction', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/957d98e2-f80e-5e2f-a28e-02945aa93079', + // instant_exchange: false, + // fiat_withdrawal: { + // id: 'f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7', + // resource: 'fiat_withdrawal', + // resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7' + // }, + // details: { + // title: 'Withdrew funds', + // subtitle: 'To HSBC BANK PLC (GB74 MIDL...)', + // payment_method_name: 'HSBC BANK PLC (GB74 MIDL...)' + // } + // } + // + const amountInfo = this.safeValue (item, 'amount', {}); + let amount = this.safeFloat (amountInfo, 'amount'); + let direction = undefined; + if (amount < 0) { + direction = 'out'; + amount = -amount; + } else { + direction = 'in'; + } + const currencyId = this.safeString (amountInfo, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + // + // the address and txid do not belong to the unified ledger structure + // + // let address = undefined; + // if (item['to']) { + // address = this.safeString (item['to'], 'address'); + // } + // let txid = undefined; + // + let fee = undefined; + const networkInfo = this.safeValue (item, 'network', {}); + // txid = network['hash']; // txid does not belong to the unified ledger structure + const feeInfo = this.safeValue (networkInfo, 'transaction_fee'); + if (feeInfo !== undefined) { + const feeCurrencyId = this.safeString (feeInfo, 'currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId, currency); + const feeAmount = this.safeFloat (feeInfo, 'amount'); + fee = { + 'cost': feeAmount, + 'currency': feeCurrencyCode, + }; + } + const timestamp = this.parse8601 (this.safeValue (item, 'created_at')); + const id = this.safeString (item, 'id'); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const status = this.parseLedgerEntryStatus (this.safeString (item, 'status')); + const path = this.safeString (item, 'resource_path'); + let accountId = undefined; + if (path !== undefined) { + const parts = path.split ('/'); + const numParts = parts.length; + if (numParts > 3) { + accountId = parts[3]; + } + } + return { + 'info': item, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'direction': direction, + 'account': accountId, + 'referenceId': undefined, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': undefined, + 'after': undefined, + 'status': status, + 'fee': fee, + }; + } + + async findAccountId (code) { + await this.loadMarkets (); + await this.loadAccounts (); + for (let i = 0; i < this.accounts.length; i++) { + const account = this.accounts[i]; + if (account['code'] === code) { + return account['id']; + } + } + return undefined; + } + + prepareAccountRequest (limit = undefined, params = {}) { + const accountId = this.safeString2 (params, 'account_id', 'accountId'); + if (accountId === undefined) { + throw new ArgumentsRequired (this.id + ' method requires an account_id (or accountId) parameter'); + } + const request = { + 'account_id': accountId, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + return request; + } + + async prepareAccountRequestWithCurrencyCode (code = undefined, limit = undefined, params = {}) { + let accountId = this.safeString2 (params, 'account_id', 'accountId'); + if (accountId === undefined) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' method requires an account_id (or accountId) parameter OR a currency code argument'); + } + accountId = await this.findAccountId (code); + if (accountId === undefined) { + throw new ExchangeError (this.id + ' could not find account id for ' + code); + } + } + const request = { + 'account_id': accountId, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + return request; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let fullPath = '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + fullPath += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + fullPath; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let payload = ''; + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + const auth = nonce + method + fullPath + payload; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers = { + 'CB-ACCESS-KEY': this.apiKey, + 'CB-ACCESS-SIGN': signature, + 'CB-ACCESS-TIMESTAMP': nonce, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const feedback = this.id + ' ' + body; + // + // {"error": "invalid_request", "error_description": "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."} + // + // or + // + // { + // "errors": [ + // { + // "id": "not_found", + // "message": "Not found" + // } + // ] + // } + // + let errorCode = this.safeString (response, 'error'); + if (errorCode !== undefined) { + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + const errors = this.safeValue (response, 'errors'); + if (errors !== undefined) { + if (Array.isArray (errors)) { + const numErrors = errors.length; + if (numErrors > 0) { + errorCode = this.safeString (errors[0], 'id'); + if (errorCode !== undefined) { + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + } + } + } + const data = this.safeValue (response, 'data'); + if (data === undefined) { + throw new ExchangeError (this.id + ' failed due to a malformed response ' + this.json (response)); + } + } +}; + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbaseprime.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbaseprime.js new file mode 100644 index 000000000..647e94e11 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbaseprime.js @@ -0,0 +1,31 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const coinbasepro = require ('./coinbasepro.js'); + +// --------------------------------------------------------------------------- + +module.exports = class coinbaseprime extends coinbasepro { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinbaseprime', + 'name': 'Coinbase Prime', + 'pro': true, + 'urls': { + 'test': { + 'public': 'https://api-public.sandbox.prime.coinbase.com', + 'private': 'https://api-public.sandbox.prime.coinbase.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/44539184-29f26e00-a70c-11e8-868f-e907fc236a7c.jpg', + 'api': { + 'public': 'https://api.prime.coinbase.com', + 'private': 'https://api.prime.coinbase.com', + }, + 'www': 'https://prime.coinbase.com', + 'doc': 'https://docs.prime.coinbase.com', + 'fees': 'https://support.prime.coinbase.com/customer/en/portal/articles/2945629-fees?b_id=17475', + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbasepro.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbasepro.js new file mode 100644 index 000000000..f28b951c4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinbasepro.js @@ -0,0 +1,1086 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InsufficientFunds, ArgumentsRequired, ExchangeError, InvalidOrder, InvalidAddress, AuthenticationError, NotSupported, OrderNotFound, OnMaintenance, PermissionDenied, RateLimitExceeded } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class coinbasepro extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinbasepro', + 'name': 'Coinbase Pro', + 'countries': [ 'US' ], + 'rateLimit': 1000, + 'userAgent': this.userAgents['chrome'], + 'pro': true, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createOrder': true, + 'deposit': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': false, // the exchange does not have this method, only createDepositAddress, see https://github.com/ccxt/ccxt/pull/7405 + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTime': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'withdraw': true, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': 60, + '5m': 300, + '15m': 900, + '1h': 3600, + '6h': 21600, + '1d': 86400, + }, + 'urls': { + 'test': { + 'public': 'https://api-public.sandbox.pro.coinbase.com', + 'private': 'https://api-public.sandbox.pro.coinbase.com', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/41764625-63b7ffde-760a-11e8-996d-a6328fa9347a.jpg', + 'api': { + 'public': 'https://api.pro.coinbase.com', + 'private': 'https://api.pro.coinbase.com', + }, + 'www': 'https://pro.coinbase.com/', + 'doc': 'https://docs.pro.coinbase.com', + 'fees': [ + 'https://docs.pro.coinbase.com/#fees', + 'https://support.pro.coinbase.com/customer/en/portal/articles/2945310-fees', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'products', + 'products/{id}', + 'products/{id}/book', + 'products/{id}/candles', + 'products/{id}/stats', + 'products/{id}/ticker', + 'products/{id}/trades', + 'time', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/{id}', + 'accounts/{id}/holds', + 'accounts/{id}/ledger', + 'accounts/{id}/transfers', + 'coinbase-accounts', + 'fills', + 'funding', + 'fees', + 'margin/profile_information', + 'margin/buying_power', + 'margin/withdrawal_power', + 'margin/withdrawal_power_all', + 'margin/exit_plan', + 'margin/liquidation_history', + 'margin/position_refresh_amounts', + 'margin/status', + 'oracle', + 'orders', + 'orders/{id}', + 'orders/client:{client_oid}', + 'otc/orders', + 'payment-methods', + 'position', + 'profiles', + 'profiles/{id}', + 'reports/{report_id}', + 'transfers', + 'transfers/{transfer_id}', + 'users/self/trailing-volume', + 'users/self/exchange-limits', + 'withdrawals/fee-estimate', + ], + 'post': [ + 'conversions', + 'deposits/coinbase-account', + 'deposits/payment-method', + 'coinbase-accounts/{id}/addresses', + 'funding/repay', + 'orders', + 'position/close', + 'profiles/margin-transfer', + 'profiles/transfer', + 'reports', + 'withdrawals/coinbase', + 'withdrawals/coinbase-account', + 'withdrawals/crypto', + 'withdrawals/payment-method', + ], + 'delete': [ + 'orders', + 'orders/client:{client_oid}', + 'orders/{id}', + ], + }, + }, + 'commonCurrencies': { + 'CGLD': 'CELO', + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': true, // complicated tier system per coin + 'percentage': true, + 'maker': 0.5 / 100, // highest fee of all tiers + 'taker': 0.5 / 100, // highest fee of all tiers + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BCH': 0, + 'BTC': 0, + 'LTC': 0, + 'ETH': 0, + 'EUR': 0.15, + 'USD': 25, + }, + 'deposit': { + 'BCH': 0, + 'BTC': 0, + 'LTC': 0, + 'ETH': 0, + 'EUR': 0.15, + 'USD': 10, + }, + }, + }, + 'exceptions': { + 'exact': { + 'Insufficient funds': InsufficientFunds, + 'NotFound': OrderNotFound, + 'Invalid API Key': AuthenticationError, + 'invalid signature': AuthenticationError, + 'Invalid Passphrase': AuthenticationError, + 'Invalid order id': InvalidOrder, + 'Private rate limit exceeded': RateLimitExceeded, + 'Trading pair not available': PermissionDenied, + 'Product not found': InvalidOrder, + }, + 'broad': { + 'Order already done': OrderNotFound, + 'order not found': OrderNotFound, + 'price too small': InvalidOrder, + 'price too precise': InvalidOrder, + 'under maintenance': OnMaintenance, + 'size is too small': InvalidOrder, + 'Cancel only mode': OnMaintenance, // https://github.com/ccxt/ccxt/issues/7690 + }, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // id: 'XTZ', + // name: 'Tezos', + // min_size: '0.000001', + // status: 'online', + // message: '', + // max_precision: '0.000001', + // convertible_to: [], + // details: { + // type: 'crypto', + // symbol: 'Τ', + // network_confirmations: 60, + // sort_order: 53, + // crypto_address_link: 'https://tzstats.com/{{address}}', + // crypto_transaction_link: 'https://tzstats.com/{{txId}}', + // push_payment_methods: [ 'crypto' ], + // group_types: [], + // display_name: '', + // processing_time_seconds: 0, + // min_withdrawal_amount: 1 + // } + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'id'); + const name = this.safeString (currency, 'name'); + const code = this.safeCurrencyCode (id); + const details = this.safeValue (currency, 'details', {}); + const precision = this.safeFloat (currency, 'max_precision'); + const status = this.safeString (currency, 'status'); + const active = (status === 'online'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': this.safeString (details, 'type'), + 'name': name, + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (details, 'min_size'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (details, 'min_withdrawal_amount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetProducts (params); + // + // [ + // { + // "id":"ZEC-BTC", + // "base_currency":"ZEC", + // "quote_currency":"BTC", + // "base_min_size":"0.01000000", + // "base_max_size":"1500.00000000", + // "quote_increment":"0.00000100", + // "base_increment":"0.00010000", + // "display_name":"ZEC/BTC", + // "min_market_funds":"0.001", + // "max_market_funds":"30", + // "margin_enabled":false, + // "post_only":false, + // "limit_only":false, + // "cancel_only":false, + // "trading_disabled":false, + // "status":"online", + // "status_message":"" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const priceLimits = { + 'min': this.safeFloat (market, 'quote_increment'), + 'max': undefined, + }; + const precision = { + 'amount': this.safeFloat (market, 'base_increment'), + 'price': this.safeFloat (market, 'quote_increment'), + }; + const status = this.safeString (market, 'status'); + const active = (status === 'online'); + result.push (this.extend (this.fees['trading'], { + 'id': id, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'base_min_size'), + 'max': this.safeFloat (market, 'base_max_size'), + }, + 'price': priceLimits, + 'cost': { + 'min': this.safeFloat (market, 'min_market_funds'), + 'max': this.safeFloat (market, 'max_market_funds'), + }, + }, + 'active': active, + 'info': market, + })); + } + return result; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccounts (params); + // + // [ + // { + // id: '4aac9c60-cbda-4396-9da4-4aa71e95fba0', + // currency: 'BTC', + // balance: '0.0000000000000000', + // available: '0', + // hold: '0.0000000000000000', + // profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da' + // }, + // { + // id: 'f75fa69a-1ad1-4a80-bd61-ee7faa6135a3', + // currency: 'USDC', + // balance: '0.0000000000000000', + // available: '0', + // hold: '0.0000000000000000', + // profile_id: 'b709263e-f42a-4c7d-949a-a95c83d065da' + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const account = response[i]; + const accountId = this.safeString (account, 'id'); + const currencyId = this.safeString (account, 'currency'); + const code = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': accountId, + 'type': undefined, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'hold'), + 'total': this.safeFloat (balance, 'balance'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + // level 1 - only the best bid and ask + // level 2 - top 50 bids and asks (aggregated) + // level 3 - full order book (non aggregated) + const request = { + 'id': this.marketId (symbol), + 'level': 2, // 1 best bidask, 2 aggregated, 3 full + }; + const response = await this.publicGetProductsIdBook (this.extend (request, params)); + // + // { + // "sequence":1924393896, + // "bids":[ + // ["0.01825","24.34811287",2], + // ["0.01824","72.5463",3], + // ["0.01823","424.54298049",6], + // ], + // "asks":[ + // ["0.01826","171.10414904",4], + // ["0.01827","22.60427028",1], + // ["0.01828","397.46018784",7], + // ] + // } + // + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'sequence'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // publicGetProductsIdTicker + // + // { + // "trade_id":843439, + // "price":"0.997999", + // "size":"80.29769", + // "time":"2020-01-28T02:13:33.012523Z", + // "bid":"0.997094", + // "ask":"0.998", + // "volume":"1903188.03750000" + // } + // + // publicGetProductsIdStats + // + // { + // "open": "34.19000000", + // "high": "95.70000000", + // "low": "7.06000000", + // "volume": "2.41000000" + // } + // + const timestamp = this.parse8601 (this.safeValue (ticker, 'time')); + const bid = this.safeFloat (ticker, 'bid'); + const ask = this.safeFloat (ticker, 'ask'); + const last = this.safeFloat (ticker, 'price'); + const symbol = (market === undefined) ? undefined : market['symbol']; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + // publicGetProductsIdTicker or publicGetProductsIdStats + const method = this.safeString (this.options, 'fetchTickerMethod', 'publicGetProductsIdTicker'); + const response = await this[method] (this.extend (request, params)); + // + // publicGetProductsIdTicker + // + // { + // "trade_id":843439, + // "price":"0.997999", + // "size":"80.29769", + // "time":"2020-01-28T02:13:33.012523Z", + // "bid":"0.997094", + // "ask":"0.998", + // "volume":"1903188.03750000" + // } + // + // publicGetProductsIdStats + // + // { + // "open": "34.19000000", + // "high": "95.70000000", + // "low": "7.06000000", + // "volume": "2.41000000" + // } + // + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // + // { + // type: 'match', + // trade_id: 82047307, + // maker_order_id: '0f358725-2134-435e-be11-753912a326e0', + // taker_order_id: '252b7002-87a3-425c-ac73-f5b9e23f3caf', + // side: 'sell', + // size: '0.00513192', + // price: '9314.78', + // product_id: 'BTC-USD', + // sequence: 12038915443, + // time: '2020-01-31T20:03:41.158814Z' + // } + // + const timestamp = this.parse8601 (this.safeString2 (trade, 'time', 'created_at')); + const marketId = this.safeString (trade, 'product_id'); + const symbol = this.safeSymbol (marketId, market, '-'); + let feeRate = undefined; + let feeCurrency = undefined; + let takerOrMaker = undefined; + if (market !== undefined) { + feeCurrency = market['quote']; + if ('liquidity' in trade) { + takerOrMaker = (trade['liquidity'] === 'T') ? 'taker' : 'maker'; + feeRate = market[takerOrMaker]; + } + } + const feeCost = this.safeFloat2 (trade, 'fill_fees', 'fee'); + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }; + const type = undefined; + const id = this.safeString (trade, 'trade_id'); + let side = (trade['side'] === 'buy') ? 'sell' : 'buy'; + const orderId = this.safeString (trade, 'order_id'); + // Coinbase Pro returns inverted side to fetchMyTrades vs fetchTrades + if (orderId !== undefined) { + side = (trade['side'] === 'buy') ? 'buy' : 'sell'; + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + return { + 'id': id, + 'order': orderId, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'fee': fee, + 'cost': price * amount, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // as of 2018-08-23 + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_id': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetFills (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], // fixes issue #2 + }; + const response = await this.publicGetProductsIdTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591514160, + // 0.02507, + // 0.02507, + // 0.02507, + // 0.02507, + // 0.02816506 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const granularity = this.timeframes[timeframe]; + const request = { + 'id': market['id'], + 'granularity': granularity, + }; + if (since !== undefined) { + request['start'] = this.iso8601 (since); + if (limit === undefined) { + // https://docs.pro.coinbase.com/#get-historic-rates + limit = 300; // max = 300 + } + request['end'] = this.iso8601 (this.sum ((limit - 1) * granularity * 1000, since)); + } + const response = await this.publicGetProductsIdCandles (this.extend (request, params)); + // + // [ + // [1591514160,0.02507,0.02507,0.02507,0.02507,0.02816506], + // [1591514100,0.02507,0.02507,0.02507,0.02507,1.63830323], + // [1591514040,0.02505,0.02507,0.02505,0.02507,0.19918178] + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "iso":"2020-05-12T08:00:51.504Z", + // "epoch":1589270451.504 + // } + // + return this.safeTimestamp (response, 'epoch'); + } + + parseOrderStatus (status) { + const statuses = { + 'pending': 'open', + 'active': 'open', + 'open': 'open', + 'done': 'closed', + 'canceled': 'canceled', + 'canceling': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const marketId = this.safeString (order, 'product_id'); + market = this.safeMarket (marketId, market, '-'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const price = this.safeFloat (order, 'price'); + const filled = this.safeFloat (order, 'filled_size'); + const amount = this.safeFloat (order, 'size', filled); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + const cost = this.safeFloat (order, 'executed_value'); + const feeCost = this.safeFloat (order, 'fill_fees'); + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrencyCode = undefined; + if (market !== undefined) { + feeCurrencyCode = market['quote']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': undefined, + }; + } + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'order_id': id, + }; + const response = await this.privateGetFills (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': 'all', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': 'done', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + // let oid = this.nonce ().toString (); + const request = { + 'product_id': this.marketId (symbol), + 'side': side, + 'size': this.amountToPrecision (symbol, amount), + 'type': type, + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + return await this.privateDeleteOrdersId ({ 'id': id }); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + return await this.privateDeleteOrders (params); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + const cost = amount * price; + const currency = market['quote']; + return { + 'type': takerOrMaker, + 'currency': currency, + 'rate': rate, + 'cost': parseFloat (this.currencyToPrecision (currency, rate * cost)), + }; + } + + async fetchPaymentMethods (params = {}) { + return await this.privateGetPaymentMethods (params); + } + + async deposit (code, amount, address, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + }; + let method = 'privatePostDeposits'; + if ('payment_method_id' in params) { + // deposit from a payment_method, like a bank account + method += 'PaymentMethod'; + } else if ('coinbase_account_id' in params) { + // deposit into Coinbase Pro account from a Coinbase account + method += 'CoinbaseAccount'; + } else { + // deposit methodotherwise we did not receive a supported deposit location + // relevant docs link for the Googlers + // https://docs.pro.coinbase.com/#deposits + throw new NotSupported (this.id + ' deposit() requires one of `coinbase_account_id` or `payment_method_id` extra params'); + } + const response = await this[method] (this.extend (request, params)); + if (!response) { + throw new ExchangeError (this.id + ' deposit() error: ' + this.json (response)); + } + return { + 'info': response, + 'id': response['id'], + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + }; + let method = 'privatePostWithdrawals'; + if ('payment_method_id' in params) { + method += 'PaymentMethod'; + } else if ('coinbase_account_id' in params) { + method += 'CoinbaseAccount'; + } else { + method += 'Crypto'; + request['crypto_address'] = address; + } + const response = await this[method] (this.extend (request, params)); + if (!response) { + throw new ExchangeError (this.id + ' withdraw() error: ' + this.json (response)); + } + return { + 'info': response, + 'id': response['id'], + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let currency = undefined; + let id = this.safeString (params, 'id'); // account id + if (id === undefined) { + if (code !== undefined) { + currency = this.currency (code); + const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency'); + const account = this.safeValue (accountsByCurrencyCode, code); + if (account === undefined) { + throw new ExchangeError (this.id + ' fetchTransactions() could not find account id for ' + code); + } + id = account['id']; + } + } + const request = {}; + if (id !== undefined) { + request['id'] = id; + } + if (limit !== undefined) { + request['limit'] = limit; + } + let response = undefined; + if (id === undefined) { + response = await this.privateGetTransfers (this.extend (request, params)); + for (let i = 0; i < response.length; i++) { + const account_id = this.safeString (response[i], 'account_id'); + const account = this.safeValue (this.accountsById, account_id); + const code = this.safeString (account, 'currency'); + response[i]['currency'] = code; + } + } else { + response = await this.privateGetAccountsIdTransfers (this.extend (request, params)); + for (let i = 0; i < response.length; i++) { + response[i]['currency'] = code; + } + } + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchTransactions (code, since, limit, this.extend (params, { 'type': 'deposit' })); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchTransactions (code, since, limit, this.extend (params, { 'type': 'withdraw' })); + } + + parseTransactionStatus (transaction) { + const canceled = this.safeValue (transaction, 'canceled_at'); + if (canceled) { + return 'canceled'; + } + const processed = this.safeValue (transaction, 'processed_at'); + const completed = this.safeValue (transaction, 'completed_at'); + if (completed) { + return 'ok'; + } else if (processed && !completed) { + return 'failed'; + } else { + return 'pending'; + } + } + + parseTransaction (transaction, currency = undefined) { + const details = this.safeValue (transaction, 'details', {}); + const id = this.safeString (transaction, 'id'); + const txid = this.safeString (details, 'crypto_transaction_hash'); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const updated = this.parse8601 (this.safeString (transaction, 'processed_at')); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (transaction); + const amount = this.safeFloat (transaction, 'amount'); + let type = this.safeString (transaction, 'type'); + let address = this.safeString (details, 'crypto_address'); + const tag = this.safeString (details, 'destination_tag'); + address = this.safeString (transaction, 'crypto_address', address); + let fee = undefined; + if (type === 'withdraw') { + type = 'withdrawal'; + address = this.safeString (details, 'sent_to_address', address); + const feeCost = this.safeFloat (details, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'code': code, + }; + } + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'][api] + request; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let payload = ''; + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + const what = nonce + method + request + payload; + const secret = this.base64ToBinary (this.secret); + const signature = this.hmac (this.encode (what), secret, 'sha256', 'base64'); + headers = { + 'CB-ACCESS-KEY': this.apiKey, + 'CB-ACCESS-SIGN': signature, + 'CB-ACCESS-TIMESTAMP': nonce, + 'CB-ACCESS-PASSPHRASE': this.password, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + let accounts = this.safeValue (this.options, 'coinbaseAccounts'); + if (accounts === undefined) { + accounts = await this.privateGetCoinbaseAccounts (); + this.options['coinbaseAccounts'] = accounts; // cache it + this.options['coinbaseAccountsByCurrencyId'] = this.indexBy (accounts, 'currency'); + } + const currencyId = currency['id']; + const account = this.safeValue (this.options['coinbaseAccountsByCurrencyId'], currencyId); + if (account === undefined) { + // eslint-disable-next-line quotes + throw new InvalidAddress (this.id + " fetchDepositAddress() could not find currency code " + code + " with id = " + currencyId + " in this.options['coinbaseAccountsByCurrencyId']"); + } + const request = { + 'id': account['id'], + }; + const response = await this.privatePostCoinbaseAccountsIdAddresses (this.extend (request, params)); + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'destination_tag'); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 400) || (code === 404)) { + if (body[0] === '{') { + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + throw new ExchangeError (this.id + ' ' + body); + } + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (typeof response !== 'string') { + if ('message' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coincheck.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coincheck.js new file mode 100644 index 000000000..6b2618ad3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coincheck.js @@ -0,0 +1,430 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coincheck extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coincheck', + 'name': 'coincheck', + 'countries': [ 'JP', 'ID' ], + 'rateLimit': 1500, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMyTrades': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182088-1d6d6380-c2ec-11ea-9c64-8ab9f9b289f5.jpg', + 'api': 'https://coincheck.com/api', + 'www': 'https://coincheck.com', + 'doc': 'https://coincheck.com/documents/exchange/api', + 'fees': [ + 'https://coincheck.com/exchange/fee', + 'https://coincheck.com/info/fee', + ], + }, + 'api': { + 'public': { + 'get': [ + 'exchange/orders/rate', + 'order_books', + 'rate/{pair}', + 'ticker', + 'trades', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/balance', + 'accounts/leverage_balance', + 'bank_accounts', + 'deposit_money', + 'exchange/orders/opens', + 'exchange/orders/transactions', + 'exchange/orders/transactions_pagination', + 'exchange/leverage/positions', + 'lending/borrows/matches', + 'send_money', + 'withdraws', + ], + 'post': [ + 'bank_accounts', + 'deposit_money/{id}/fast', + 'exchange/orders', + 'exchange/transfers/to_leverage', + 'exchange/transfers/from_leverage', + 'lending/borrows', + 'lending/borrows/{id}/repay', + 'send_money', + 'withdraws', + ], + 'delete': [ + 'bank_accounts/{id}', + 'exchange/orders/{id}', + 'withdraws/{id}', + ], + }, + }, + 'markets': { + 'BTC/JPY': { 'id': 'btc_jpy', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY', 'baseId': 'btc', 'quoteId': 'jpy' }, // the only real pair + // 'ETH/JPY': { 'id': 'eth_jpy', 'symbol': 'ETH/JPY', 'base': 'ETH', 'quote': 'JPY', 'baseId': 'eth', 'quoteId': 'jpy' }, + // 'ETC/JPY': { 'id': 'etc_jpy', 'symbol': 'ETC/JPY', 'base': 'ETC', 'quote': 'JPY', 'baseId': 'etc', 'quoteId': 'jpy' }, + // 'DAO/JPY': { 'id': 'dao_jpy', 'symbol': 'DAO/JPY', 'base': 'DAO', 'quote': 'JPY', 'baseId': 'dao', 'quoteId': 'jpy' }, + // 'LSK/JPY': { 'id': 'lsk_jpy', 'symbol': 'LSK/JPY', 'base': 'LSK', 'quote': 'JPY', 'baseId': 'lsk', 'quoteId': 'jpy' }, + // 'FCT/JPY': { 'id': 'fct_jpy', 'symbol': 'FCT/JPY', 'base': 'FCT', 'quote': 'JPY', 'baseId': 'fct', 'quoteId': 'jpy' }, + // 'XMR/JPY': { 'id': 'xmr_jpy', 'symbol': 'XMR/JPY', 'base': 'XMR', 'quote': 'JPY', 'baseId': 'xmr', 'quoteId': 'jpy' }, + // 'REP/JPY': { 'id': 'rep_jpy', 'symbol': 'REP/JPY', 'base': 'REP', 'quote': 'JPY', 'baseId': 'rep', 'quoteId': 'jpy' }, + // 'XRP/JPY': { 'id': 'xrp_jpy', 'symbol': 'XRP/JPY', 'base': 'XRP', 'quote': 'JPY', 'baseId': 'xrp', 'quoteId': 'jpy' }, + // 'ZEC/JPY': { 'id': 'zec_jpy', 'symbol': 'ZEC/JPY', 'base': 'ZEC', 'quote': 'JPY', 'baseId': 'zec', 'quoteId': 'jpy' }, + // 'XEM/JPY': { 'id': 'xem_jpy', 'symbol': 'XEM/JPY', 'base': 'XEM', 'quote': 'JPY', 'baseId': 'xem', 'quoteId': 'jpy' }, + // 'LTC/JPY': { 'id': 'ltc_jpy', 'symbol': 'LTC/JPY', 'base': 'LTC', 'quote': 'JPY', 'baseId': 'ltc', 'quoteId': 'jpy' }, + // 'DASH/JPY': { 'id': 'dash_jpy', 'symbol': 'DASH/JPY', 'base': 'DASH', 'quote': 'JPY', 'baseId': 'dash', 'quoteId': 'jpy' }, + // 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc' }, + // 'ETC/BTC': { 'id': 'etc_btc', 'symbol': 'ETC/BTC', 'base': 'ETC', 'quote': 'BTC', 'baseId': 'etc', 'quoteId': 'btc' }, + // 'LSK/BTC': { 'id': 'lsk_btc', 'symbol': 'LSK/BTC', 'base': 'LSK', 'quote': 'BTC', 'baseId': 'lsk', 'quoteId': 'btc' }, + // 'FCT/BTC': { 'id': 'fct_btc', 'symbol': 'FCT/BTC', 'base': 'FCT', 'quote': 'BTC', 'baseId': 'fct', 'quoteId': 'btc' }, + // 'XMR/BTC': { 'id': 'xmr_btc', 'symbol': 'XMR/BTC', 'base': 'XMR', 'quote': 'BTC', 'baseId': 'xmr', 'quoteId': 'btc' }, + // 'REP/BTC': { 'id': 'rep_btc', 'symbol': 'REP/BTC', 'base': 'REP', 'quote': 'BTC', 'baseId': 'rep', 'quoteId': 'btc' }, + // 'XRP/BTC': { 'id': 'xrp_btc', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC', 'baseId': 'xrp', 'quoteId': 'btc' }, + // 'ZEC/BTC': { 'id': 'zec_btc', 'symbol': 'ZEC/BTC', 'base': 'ZEC', 'quote': 'BTC', 'baseId': 'zec', 'quoteId': 'btc' }, + // 'XEM/BTC': { 'id': 'xem_btc', 'symbol': 'XEM/BTC', 'base': 'XEM', 'quote': 'BTC', 'baseId': 'xem', 'quoteId': 'btc' }, + // 'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc' }, + // 'DASH/BTC': { 'id': 'dash_btc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC', 'baseId': 'dash', 'quoteId': 'btc' }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0, + 'taker': 0, + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privateGetAccountsBalance (params); + const result = { 'info': balances }; + const codes = Object.keys (this.currencies); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currencyId = this.currencyId (code); + if (currencyId in balances) { + const account = this.account (); + const reserved = currencyId + '_reserved'; + account['free'] = this.safeFloat (balances, currencyId); + account['used'] = this.safeFloat (balances, reserved); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // Only BTC/JPY is meaningful + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privateGetExchangeOrdersOpens (params); + const rawOrders = this.safeValue (response, 'orders', []); + const parsedOrders = this.parseOrders (rawOrders, market, since, limit); + const result = []; + for (let i = 0; i < parsedOrders.length; i++) { + result.push (this.extend (parsedOrders[i], { 'status': 'open' })); + } + return result; + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrders + // + // { id: 202835, + // order_type: "buy", + // rate: 26890, + // pair: "btc_jpy", + // pending_amount: "0.5527", + // pending_market_buy_amount: null, + // stop_loss_rate: null, + // created_at: "2015-01-10T05:55:38.000Z" } + // + // todo: add formats for fetchOrder, fetchClosedOrders here + // + const id = this.safeString (order, 'id'); + const side = this.safeString (order, 'order_type'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const amount = this.safeFloat (order, 'pending_amount'); + const remaining = this.safeFloat (order, 'pending_amount'); + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if (remaining !== undefined) { + if (amount !== undefined) { + filled = Math.max (amount - remaining, 0); + if (price !== undefined) { + cost = filled * price; + } + } + } + const status = undefined; + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'side': side, + 'type': undefined, + 'timeInForce': undefined, + 'status': status, + 'symbol': symbol, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'fee': undefined, + 'info': order, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + if (symbol !== 'BTC/JPY') { + throw new BadSymbol (this.id + ' fetchOrderBook () supports BTC/JPY only'); + } + await this.loadMarkets (); + const response = await this.publicGetOrderBooks (params); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + if (symbol !== 'BTC/JPY') { + throw new BadSymbol (this.id + ' fetchTicker () supports BTC/JPY only'); + } + await this.loadMarkets (); + const ticker = await this.publicGetTicker (params); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'rate'); + const marketId = this.safeString (trade, 'pair'); + market = this.safeValue (this.markets_by_id, marketId, market); + let symbol = undefined; + let baseId = undefined; + let quoteId = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + baseId = market['baseId']; + quoteId = market['quoteId']; + symbol = market['symbol']; + } else { + const ids = marketId.split ('_'); + baseId = ids[0]; + quoteId = ids[1]; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } + } + let takerOrMaker = undefined; + let amount = undefined; + let cost = undefined; + let side = undefined; + let fee = undefined; + let orderId = undefined; + if ('liquidity' in trade) { + if (this.safeString (trade, 'liquidity') === 'T') { + takerOrMaker = 'taker'; + } else if (this.safeString (trade, 'liquidity') === 'M') { + takerOrMaker = 'maker'; + } + const funds = this.safeValue (trade, 'funds', {}); + amount = this.safeFloat (funds, baseId); + cost = this.safeFloat (funds, quoteId); + fee = { + 'currency': this.safeString (trade, 'fee_currency'), + 'cost': this.safeFloat (trade, 'fee'), + }; + side = this.safeString (trade, 'side'); + orderId = this.safeString (trade, 'order_id'); + } else { + amount = this.safeFloat (trade, 'amount'); + side = this.safeString (trade, 'order_type'); + } + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + return { + 'id': id, + 'info': trade, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': orderId, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.privateGetExchangeOrdersTransactions (this.extend ({}, params)); + const transactions = this.safeValue (response, 'transactions', []); + return this.parseTrades (transactions, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + if (symbol !== 'BTC/JPY') { + throw new BadSymbol (this.id + ' fetchTrades () supports BTC/JPY only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + if (type === 'market') { + const order_type = type + '_' + side; + request['order_type'] = order_type; + const prefix = (side === 'buy') ? (order_type + '_') : ''; + request[prefix + 'amount'] = amount; + } else { + request['order_type'] = side; + request['rate'] = price; + request['amount'] = amount; + } + const response = await this.privatePostExchangeOrders (this.extend (request, params)); + const id = this.safeString (response, 'id'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privateDeleteExchangeOrdersId (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let queryString = ''; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (this.keysort (query)); + } + } else { + if (Object.keys (query).length) { + body = this.urlencode (this.keysort (query)); + queryString = body; + } + } + const auth = nonce + url + queryString; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'ACCESS-KEY': this.apiKey, + 'ACCESS-NONCE': nonce, + 'ACCESS-SIGNATURE': this.hmac (this.encode (auth), this.encode (this.secret)), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (api === 'public') { + return response; + } + if ('success' in response) { + if (response['success']) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinegg.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinegg.js new file mode 100644 index 000000000..0918ef9a0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinegg.js @@ -0,0 +1,515 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, InvalidNonce, InsufficientFunds, InvalidOrder, OrderNotFound, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinegg extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinegg', + 'name': 'CoinEgg', + 'countries': [ 'CN', 'UK' ], + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOpenOrders': 'emulated', + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/36770310-adfa764e-1c5a-11e8-8e09-449daac3d2fb.jpg', + 'api': { + 'web': 'https://trade.coinegg.com/web', + 'rest': 'https://api.coinegg.com/api/v1', + }, + 'www': 'https://www.coinegg.com', + 'doc': 'https://www.coinegg.com/explain.api.html', + 'fees': 'https://www.coinegg.com/fee.html', + 'referral': 'https://www.coinegg.com/user/register?invite=523218', + }, + 'api': { + 'web': { + 'get': [ + 'symbol/ticker?right_coin={quote}', + '{quote}/trends', + '{quote}/{base}/order', + '{quote}/{base}/trades', + '{quote}/{base}/depth.js', + ], + }, + 'public': { + 'get': [ + 'ticker/region/{quote}', + 'depth/region/{quote}', + 'orders/region/{quote}', + ], + }, + 'private': { + 'post': [ + 'balance', + 'trade_add/region/{quote}', + 'trade_cancel/region/{quote}', + 'trade_view/region/{quote}', + 'trade_list/region/{quote}', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': 0.008, + 'BCH': 0.002, + 'LTC': 0.001, + 'ETH': 0.01, + 'ETC': 0.01, + 'NEO': 0, + 'QTUM': '1%', + 'XRP': '1%', + 'DOGE': '1%', + 'LSK': '1%', + 'XAS': '1%', + 'BTS': '1%', + 'GAME': '1%', + 'GOOC': '1%', + 'NXT': '1%', + 'IFC': '1%', + 'DNC': '1%', + 'BLK': '1%', + 'VRC': '1%', + 'XPM': '1%', + 'VTC': '1%', + 'TFC': '1%', + 'PLC': '1%', + 'EAC': '1%', + 'PPC': '1%', + 'FZ': '1%', + 'ZET': '1%', + 'RSS': '1%', + 'PGC': '1%', + 'SKT': '1%', + 'JBC': '1%', + 'RIO': '1%', + 'LKC': '1%', + 'ZCC': '1%', + 'MCC': '1%', + 'QEC': '1%', + 'MET': '1%', + 'YTC': '1%', + 'HLB': '1%', + 'MRYC': '1%', + 'MTC': '1%', + 'KTC': 0, + }, + }, + }, + 'exceptions': { + '103': AuthenticationError, + '104': AuthenticationError, + '105': AuthenticationError, + '106': InvalidNonce, + '200': InsufficientFunds, + '201': InvalidOrder, + '202': InvalidOrder, + '203': OrderNotFound, + '402': DDoSProtection, + }, + 'errorMessages': { + '100': 'Required parameters can not be empty', + '101': 'Illegal parameter', + '102': 'coin does not exist', + '103': 'Key does not exist', + '104': 'Signature does not match', + '105': 'Insufficient permissions', + '106': 'Request expired(nonce error)', + '200': 'Lack of balance', + '201': 'Too small for the number of trading', + '202': 'Price must be in 0 - 1000000', + '203': 'Order does not exist', + '204': 'Pending order amount must be above 0.001 BTC', + '205': 'Restrict pending order prices', + '206': 'Decimal place error', + '401': 'System error', + '402': 'Requests are too frequent', + '403': 'Non-open API', + '404': 'IP restriction does not request the resource', + '405': 'Currency transactions are temporarily closed', + }, + 'options': { + 'quoteIds': [ 'btc', 'eth', 'usc', 'usdt' ], + }, + 'commonCurrencies': { + 'JBC': 'JubaoCoin', + }, + }); + } + + async fetchMarkets (params = {}) { + const quoteIds = this.options['quoteIds']; + const result = []; + for (let b = 0; b < quoteIds.length; b++) { + const quoteId = quoteIds[b]; + const response = await this.webGetSymbolTickerRightCoinQuote ({ + 'quote': quoteId, + }); + const tickers = this.safeValue (response, 'data', []); + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const id = ticker['symbol']; + const baseId = id.split ('_')[0]; + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': ticker, + }); + } + } + return result; + } + + parseTicker (ticker, market = undefined) { + const symbol = market['symbol']; + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'change'); + let open = undefined; + let change = undefined; + let average = undefined; + if (percentage !== undefined) { + const relativeChange = percentage / 100; + open = last / this.sum (1, relativeChange); + change = last - open; + average = this.sum (last, open) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVol'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.publicGetTickerRegionQuote (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.publicGetDepthRegionQuote (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const symbol = market['symbol']; + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = this.costToPrecision (symbol, price * amount); + } + } + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, 'tid'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.publicGetOrdersRegionQuote (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalance (params); + const result = { 'info': response }; + const data = this.safeValue (response, 'data', {}); + const balances = this.omit (data, 'uid'); + const keys = Object.keys (balances); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const [ currencyId, accountType ] = key.split ('_'); + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + result[code] = this.account (); + } + const type = (accountType === 'lock') ? 'used' : 'free'; + result[code][type] = this.safeFloat (balances, key); + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.parse8601 (this.safeString (order, 'datetime')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount_original'); + const remaining = this.safeFloat (order, 'amount_outstanding'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + let status = this.safeString (order, 'status'); + if (status === 'cancelled') { + status = 'canceled'; + } else { + status = remaining ? 'open' : 'closed'; + } + const info = this.safeValue (order, 'info', order); + const type = 'limit'; + const side = this.safeString (order, 'type'); + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': info, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + 'type': side, + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostTradeAddRegionQuote (this.extend (request, params)); + const id = this.safeString (response, 'id'); + const order = this.parseOrder ({ + 'id': id, + 'datetime': this.ymdhms (this.milliseconds ()), + 'amount_original': amount, + 'amount_outstanding': amount, + 'price': price, + 'type': side, + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + return await this.privatePostTradeCancelRegionQuote (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + const response = await this.privatePostTradeViewRegionQuote (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'quote': market['quoteId'], + }; + if (since !== undefined) { + request['since'] = since / 1000; + } + const response = await this.privatePostTradeListRegionQuote (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'open', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let apiType = 'rest'; + if (api === 'web') { + apiType = api; + } + let url = this.urls['api'][apiType] + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'public' || api === 'web') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + query = this.urlencode (this.extend ({ + 'key': this.apiKey, + 'nonce': this.nonce (), + }, query)); + const secret = this.hash (this.encode (this.secret)); + const signature = this.hmac (this.encode (query), this.encode (secret)); + query += '&' + 'signature=' + signature; + if (method === 'GET') { + url += '?' + query; + } else { + headers = { + 'Content-type': 'application/x-www-form-urlencoded', + }; + body = query; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // private endpoints return the following structure: + // {"result":true,"data":{...}} - success + // {"result":false,"code":"103"} - failure + // {"code":0,"msg":"Suceess","data":{"uid":"2716039","btc_balance":"0.00000000","btc_lock":"0.00000000","xrp_balance":"0.00000000","xrp_lock":"0.00000000"}} + const result = this.safeValue (response, 'result'); + if (result === undefined) { + // public endpoint ← this comment left here by the contributor, in fact a missing result does not necessarily mean a public endpoint... + // we should just check the code and don't rely on the result at all here... + return; + } + if (result === true) { + // success + return; + } + const errorCode = this.safeString (response, 'code'); + const errorMessages = this.errorMessages; + const message = this.safeString (errorMessages, errorCode, 'Unknown Error'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (this.id + ' ' + message); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinex.js new file mode 100644 index 000000000..91f4c2052 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinex.js @@ -0,0 +1,948 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InsufficientFunds, OrderNotFound, InvalidOrder, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinex', + 'name': 'CoinEx', + 'version': 'v1', + 'countries': [ 'CN' ], + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '3m': '3min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '12h': '12hour', + '1d': '1day', + '3d': '3day', + '1w': '1week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182089-1e05fa00-c2ec-11ea-8da9-cc73b45abbbc.jpg', + 'api': 'https://api.coinex.com', + 'www': 'https://www.coinex.com', + 'doc': 'https://github.com/coinexcom/coinex_exchange_api/wiki', + 'fees': 'https://www.coinex.com/fees', + 'referral': 'https://www.coinex.com/register?refer_code=yw5fz', + }, + 'api': { + 'public': { + 'get': [ + 'common/currency/rate', + 'common/asset/config', + 'market/info', + 'market/list', + 'market/ticker', + 'market/ticker/all', + 'market/depth', + 'market/deals', + 'market/kline', + ], + }, + 'private': { + 'get': [ + 'balance/coin/deposit', + 'balance/coin/withdraw', + 'balance/info', + 'future/account', + 'future/config', + 'future/limitprice', + 'future/loan/history', + 'future/market', + 'margin/account', + 'margin/config', + 'margin/loan/history', + 'margin/market', + 'order', + 'order/deals', + 'order/finished', + 'order/finished/{id}', + 'order/pending', + 'order/status', + 'order/status/batch', + 'order/user/deals', + ], + 'post': [ + 'balance/coin/withdraw', + 'future/flat', + 'future/loan', + 'future/transfer', + 'margin/flat', + 'margin/loan', + 'margin/transfer', + 'order/batchlimit', + 'order/ioc', + 'order/limit', + 'order/market', + 'sub_account/transfer', + ], + 'delete': [ + 'balance/coin/withdraw', + 'order/pending/batch', + 'order/pending', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.001, + 'taker': 0.001, + }, + 'funding': { + 'withdraw': { + 'BCH': 0.0, + 'BTC': 0.001, + 'LTC': 0.001, + 'ETH': 0.001, + 'ZEC': 0.0001, + 'DASH': 0.0001, + }, + }, + }, + 'limits': { + 'amount': { + 'min': 0.001, + 'max': undefined, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarketInfo (params); + // + // { + // "code": 0, + // "data": { + // "WAVESBTC": { + // "name": "WAVESBTC", + // "min_amount": "1", + // "maker_fee_rate": "0.001", + // "taker_fee_rate": "0.001", + // "pricing_name": "BTC", + // "pricing_decimal": 8, + // "trading_name": "WAVES", + // "trading_decimal": 8 + // } + // } + // } + // + const markets = this.safeValue (response, 'data', {}); + const result = []; + const keys = Object.keys (markets); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const market = markets[key]; + const id = this.safeString (market, 'name'); + const tradingName = this.safeString (market, 'trading_name'); + const baseId = tradingName; + const quoteId = this.safeString (market, 'pricing_name'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + if (tradingName === id) { + symbol = id; + } + const precision = { + 'amount': this.safeInteger (market, 'trading_decimal'), + 'price': this.safeInteger (market, 'pricing_decimal'), + }; + const active = undefined; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'taker': this.safeFloat (market, 'taker_fee_rate'), + 'maker': this.safeFloat (market, 'maker_fee_rate'), + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + }, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeInteger (ticker, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + ticker = this.safeValue (ticker, 'ticker', {}); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat2 (ticker, 'vol', 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketTicker (this.extend (request, params)); + return this.parseTicker (response['data'], market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketTickerAll (params); + const data = this.safeValue (response, 'data'); + const timestamp = this.safeInteger (data, 'date'); + const tickers = this.safeValue (data, 'ticker'); + const marketIds = Object.keys (tickers); + const result = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + const ticker = this.parseTicker ({ + 'date': timestamp, + 'ticker': tickers[marketId], + }, market); + ticker['symbol'] = symbol; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = 20, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 20; // default + } + const request = { + 'market': this.marketId (symbol), + 'merge': '0.0000000001', + 'limit': limit.toString (), + }; + const response = await this.publicGetMarketDepth (this.extend (request, params)); + return this.parseOrderBook (response['data']); + } + + parseTrade (trade, market = undefined) { + // this method parses both public and private trades + let timestamp = this.safeTimestamp (trade, 'create_time'); + if (timestamp === undefined) { + timestamp = this.safeInteger (trade, 'date_ms'); + } + const tradeId = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'order_id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const marketId = this.safeString (trade, 'market'); + const symbol = this.safeSymbol (marketId, market); + let cost = this.safeFloat (trade, 'deal_money'); + if (!cost) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_asset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const takerOrMaker = this.safeString (trade, 'role'); + const side = this.safeString (trade, 'type'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': tradeId, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetMarketDeals (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591484400, + // "0.02505349", + // "0.02506988", + // "0.02507000", + // "0.02505304", + // "343.19716223", + // "8.6021323866383196", + // "ETHBTC" + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'type': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketKline (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // [1591484400, "0.02505349", "0.02506988", "0.02507000", "0.02505304", "343.19716223", "8.6021323866383196", "ETHBTC"], + // [1591484700, "0.02506990", "0.02508109", "0.02508109", "0.02506979", "91.59841581", "2.2972047780447000", "ETHBTC"], + // [1591485000, "0.02508106", "0.02507996", "0.02508106", "0.02507500", "65.15307697", "1.6340597822306000", "ETHBTC"], + // ], + // "message": "OK" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalanceInfo (params); + // + // { + // "code": 0, + // "data": { + // "BCH": { # BCH account + // "available": "13.60109", # Available BCH + // "frozen": "0.00000" # Frozen BCH + // }, + // "BTC": { # BTC account + // "available": "32590.16", # Available BTC + // "frozen": "7000.00" # Frozen BTC + // }, + // "ETH": { # ETH account + // "available": "5.06000", # Available ETH + // "frozen": "0.00000" # Frozen ETH + // } + // }, + // "message": "Ok" + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'frozen'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'not_deal': 'open', + 'part_deal': 'open', + 'done': 'closed', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // "amount": "0.1", + // "asset_fee": "0.22736197736197736197", + // "avg_price": "196.85000000000000000000", + // "create_time": 1537270135, + // "deal_amount": "0.1", + // "deal_fee": "0", + // "deal_money": "19.685", + // "fee_asset": "CET", + // "fee_discount": "0.5", + // "id": 1788259447, + // "left": "0", + // "maker_fee_rate": "0", + // "market": "ETHUSDT", + // "order_type": "limit", + // "price": "170.00000000", + // "status": "done", + // "taker_fee_rate": "0.0005", + // "type": "sell", + // } + // + const timestamp = this.safeTimestamp (order, 'create_time'); + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'deal_money'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'deal_amount'); + const average = this.safeFloat (order, 'avg_price'); + let symbol = undefined; + const marketId = this.safeString (order, 'market'); + market = this.safeMarket (marketId, market); + const feeCurrencyId = this.safeString (order, 'fee_asset'); + let feeCurrency = this.safeCurrencyCode (feeCurrencyId); + if (market !== undefined) { + symbol = market['symbol']; + if (feeCurrency === undefined) { + feeCurrency = market['quote']; + } + } + const remaining = this.safeFloat (order, 'left'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const type = this.safeString (order, 'order_type'); + const side = this.safeString (order, 'type'); + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': { + 'currency': feeCurrency, + 'cost': this.safeFloat (order, 'deal_fee'), + }, + 'info': order, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostOrder' + this.capitalize (type); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'type': side, + }; + amount = parseFloat (amount); + // for market buy it requires the amount of quote currency to spend + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + price = parseFloat (price); + request['amount'] = this.costToPrecision (symbol, amount * price); + } + } else { + request['amount'] = this.costToPrecision (symbol, amount); + } + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if ((type === 'limit') || (type === 'ioc')) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'market': market['id'], + }; + const response = await this.privateDeleteOrderPending (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'market': market['id'], + }; + const response = await this.privateGetOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "amount": "0.1", + // "asset_fee": "0.22736197736197736197", + // "avg_price": "196.85000000000000000000", + // "create_time": 1537270135, + // "deal_amount": "0.1", + // "deal_fee": "0", + // "deal_money": "19.685", + // "fee_asset": "CET", + // "fee_discount": "0.5", + // "id": 1788259447, + // "left": "0", + // "maker_fee_rate": "0", + // "market": "ETHUSDT", + // "order_type": "limit", + // "price": "170.00000000", + // "status": "done", + // "taker_fee_rate": "0.0005", + // "type": "sell", + // }, + // "message": "Ok" + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const request = { + 'page': 1, + 'limit': limit, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const method = 'privateGetOrder' + this.capitalize (status); + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const orders = this.safeValue (data, 'data', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('pending', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('finished', symbol, since, limit, params); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const request = { + 'page': 1, + 'limit': limit, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.privateGetOrderUserDeals (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const trades = this.safeValue (data, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag) { + address = address + ':' + tag; + } + const request = { + 'coin_type': currency['id'], + 'coin_address': address, // must be authorized, inter-user transfer by a registered mobile phone number or an email address is supported + 'actual_amount': parseFloat (amount), // the actual amount without fees, https://www.coinex.com/fees + 'transfer_method': 'onchain', // onchain, local + }; + const response = await this.privatePostBalanceCoinWithdraw (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "actual_amount": "1.00000000", + // "amount": "1.00000000", + // "coin_address": "1KAv3pazbTk2JnQ5xTo6fpKK7p1it2RzD4", + // "coin_type": "BCH", + // "coin_withdraw_id": 206, + // "confirmations": 0, + // "create_time": 1524228297, + // "status": "audit", + // "tx_fee": "0", + // "tx_id": "" + // }, + // "message": "Ok" + // } + // + const transaction = this.safeValue (response, 'data', {}); + return this.parseTransaction (transaction, currency); + } + + parseTransactionStatus (status) { + const statuses = { + 'audit': 'pending', + 'pass': 'pending', + 'processing': 'pending', + 'confirming': 'pending', + 'not_pass': 'failed', + 'cancel': 'canceled', + 'finish': 'ok', + 'fail': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "actual_amount": "120.00000000", + // "actual_amount_display": "120", + // "add_explorer": "XXX", + // "amount": "120.00000000", + // "amount_display": "120", + // "coin_address": "XXXXXXXX", + // "coin_address_display": "XXXXXXXX", + // "coin_deposit_id": 1866, + // "coin_type": "USDT", + // "confirmations": 0, + // "create_time": 1539595701, + // "explorer": "", + // "remark": "", + // "status": "finish", + // "status_display": "finish", + // "transfer_method": "local", + // "tx_id": "", + // "tx_id_display": "XXXXXXXXXX" + // } + // + // fetchWithdrawals + // + // { + // "actual_amount": "0.10000000", + // "amount": "0.10000000", + // "coin_address": "15sr1VdyXQ6sVLqeJUJ1uPzLpmQtgUeBSB", + // "coin_type": "BCH", + // "coin_withdraw_id": 203, + // "confirmations": 11, + // "create_time": 1515806440, + // "status": "finish", + // "tx_fee": "0", + // "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6" + // } + // + const id = this.safeString2 (transaction, 'coin_withdraw_id', 'coin_deposit_id'); + const address = this.safeString (transaction, 'coin_address'); + let tag = this.safeString (transaction, 'remark'); // set but unused + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + let txid = this.safeValue (transaction, 'tx_id'); + if (txid !== undefined) { + if (txid.length < 1) { + txid = undefined; + } + } + const currencyId = this.safeString (transaction, 'coin_type'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeTimestamp (transaction, 'create_time'); + const type = ('coin_withdraw_id' in transaction) ? 'withdraw' : 'deposit'; + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + let feeCost = this.safeFloat (transaction, 'tx_fee'); + if (type === 'deposit') { + feeCost = 0; + } + const fee = { + 'cost': feeCost, + 'currency': code, + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin_type': currency['id'], + }; + if (limit !== undefined) { + request['Limit'] = limit; + } + const response = await this.privateGetBalanceCoinWithdraw (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "has_next": true, + // "curr_page": 1, + // "count": 10, + // "data": [ + // { + // "coin_withdraw_id": 203, + // "create_time": 1513933541, + // "actual_amount": "0.00100000", + // "actual_amount_display": "***", + // "amount": "0.00100000", + // "amount_display": "******", + // "coin_address": "1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF", + // "app_coin_address_display": "**********", + // "coin_address_display": "****************", + // "add_explorer": "https://explorer.viawallet.com/btc/address/1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF", + // "coin_type": "BTC", + // "confirmations": 6, + // "explorer": "https://explorer.viawallet.com/btc/tx/1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF", + // "fee": "0", + // "remark": "", + // "smart_contract_name": "BTC", + // "status": "finish", + // "status_display": "finish", + // "transfer_method": "onchain", + // "tx_fee": "0", + // "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6" + // }, /* many more data points */ + // ], + // "total": ***, + // "total_page":*** + // }, + // "message": "Success" + // } + // + let data = this.safeValue (response, 'data'); + if (!Array.isArray (data)) { + data = this.safeValue (data, 'data', []); + } + return this.parseTransactions (data, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin_type': currency['id'], + }; + if (limit !== undefined) { + request['Limit'] = limit; + } + const response = await this.privateGetBalanceCoinDeposit (this.extend (request, params)); + // { + // "code": 0, + // "data": [ + // { + // "actual_amount": "4.65397682", + // "actual_amount_display": "4.65397682", + // "add_explorer": "https://etherscan.io/address/0x361XXXXXX", + // "amount": "4.65397682", + // "amount_display": "4.65397682", + // "coin_address": "0x36dabcdXXXXXX", + // "coin_address_display": "0x361X*****XXXXX", + // "coin_deposit_id": 966191, + // "coin_type": "ETH", + // "confirmations": 30, + // "create_time": 1531661445, + // "explorer": "https://etherscan.io/tx/0x361XXXXXX", + // "remark": "", + // "status": "finish", + // "status_display": "finish", + // "transfer_method": "onchain", + // "tx_id": "0x361XXXXXX", + // "tx_id_display": "0x361XXXXXX" + // } + // ], + // "message": "Ok" + // } + // + let data = this.safeValue (response, 'data'); + if (!Array.isArray (data)) { + data = this.safeValue (data, 'data', []); + } + return this.parseTransactions (data, currency, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + path = this.implodeParams (path, params); + let url = this.urls['api'] + '/' + this.version + '/' + path; + let query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + query = this.extend ({ + 'access_id': this.apiKey, + 'tonce': nonce.toString (), + }, query); + query = this.keysort (query); + const urlencoded = this.urlencode (query); + const signature = this.hash (this.encode (urlencoded + '&secret_key=' + this.secret)); + headers = { + 'Authorization': signature.toUpperCase (), + 'Content-Type': 'application/json', + }; + if ((method === 'GET') || (method === 'DELETE')) { + url += '?' + urlencoded; + } else { + body = this.json (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const code = this.safeString (response, 'code'); + const data = this.safeValue (response, 'data'); + const message = this.safeString (response, 'message'); + if ((code !== '0') || (data === undefined) || ((message !== 'Ok') && !data)) { + const responseCodes = { + '24': AuthenticationError, + '25': AuthenticationError, + '107': InsufficientFunds, + '600': OrderNotFound, + '601': InvalidOrder, + '602': InvalidOrder, + '606': InvalidOrder, + }; + const ErrorClass = this.safeValue (responseCodes, code, ExchangeError); + throw new ErrorClass (response['message']); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinfalcon.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinfalcon.js new file mode 100644 index 000000000..563a4c987 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinfalcon.js @@ -0,0 +1,452 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, RateLimitExceeded, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinfalcon extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinfalcon', + 'name': 'CoinFalcon', + 'countries': [ 'GB' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/41822275-ed982188-77f5-11e8-92bb-496bcd14ca52.jpg', + 'api': 'https://coinfalcon.com', + 'www': 'https://coinfalcon.com', + 'doc': 'https://docs.coinfalcon.com', + 'fees': 'https://coinfalcon.com/fees', + 'referral': 'https://coinfalcon.com/?ref=CFJSVGTUPASB', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'markets/{market}/orders', + 'markets/{market}/trades', + ], + }, + 'private': { + 'get': [ + 'user/accounts', + 'user/orders', + 'user/orders/{id}', + 'user/trades', + ], + 'post': [ + 'user/orders', + ], + 'delete': [ + 'user/orders/{id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'maker': 0.0, + 'taker': 0.002, // tiered fee starts at 0.2% + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const markets = this.safeValue (response, 'data'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const [ baseId, quoteId ] = market['name'].split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'size_precision'), + 'price': this.safeInteger (market, 'price_precision'), + }; + result.push ({ + 'id': market['name'], + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const marketId = this.safeString (ticker, 'name'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.milliseconds (); + const last = parseFloat (ticker['last_price']); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'change_in_24h'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const tickers = await this.fetchTickers (params); + return tickers[symbol]; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarkets (params); + const tickers = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'level': '3', + }; + const response = await this.publicGetMarketsMarketOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrderBook (data, undefined, 'bids', 'asks', 'price', 'size'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + const symbol = market['symbol']; + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const tradeId = this.safeString (trade, 'id'); + const side = this.safeString (trade, 'side'); + const orderId = this.safeString (trade, 'order_id'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = this.safeString (trade, 'fee_currency_code'); + fee = { + 'cost': feeCost, + 'currency': this.safeCurrencyCode (feeCurrencyCode), + }; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': tradeId, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (since !== undefined) { + request['start_time'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetUserTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (since !== undefined) { + request['since'] = this.iso8601 (since); + } + const response = await this.publicGetMarketsMarketTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUserAccounts (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available_balance'), + 'used': this.safeFloat (balance, 'hold_balance'), + 'total': this.safeFloat (balance, 'balance'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'fulfilled': 'closed', + 'canceled': 'canceled', + 'pending': 'open', + 'open': 'open', + 'partially_filled': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id":"8bdd79f4-8414-40a2-90c3-e9f4d6d1eef4" + // "market":"IOT-BTC" + // "price":"0.0000003" + // "size":"4.0" + // "size_filled":"3.0" + // "fee":"0.0075" + // "fee_currency_code":"iot" + // "funds":"0.0" + // "status":"canceled" + // "order_type":"buy" + // "post_only":false + // "operation_type":"market_order" + // "created_at":"2018-01-12T21:14:06.747828Z" + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'size_filled'); + let remaining = undefined; + let cost = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + if (price !== undefined) { + cost = filled * price; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let type = this.safeString (order, 'operation_type'); + if (type !== undefined) { + type = type.split ('_'); + type = type[0]; + } + const side = this.safeString (order, 'order_type'); + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'lastTradeTimestamp': undefined, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // price/size must be string + const request = { + 'market': market['id'], + 'size': this.amountToPrecision (symbol, amount), + 'order_type': side, + }; + if (type === 'limit') { + price = this.priceToPrecision (symbol, price); + request['price'] = price.toString (); + } + request['operation_type'] = type + '_order'; + const response = await this.privatePostUserOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateDeleteUserOrdersId (this.extend (request, params)); + const market = this.market (symbol); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetUserOrdersId (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['since_time'] = this.iso8601 (since); + } + // TODO: test status=all if it works for closed orders too + const response = await this.privateGetUserOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + const orders = this.filterByArray (data, 'status', [ 'pending', 'open', 'partially_filled' ], false); + return this.parseOrders (orders, market, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/api/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + } + const seconds = this.seconds ().toString (); + let payload = [ seconds, method, request ].join ('|'); + if (body) { + payload += '|' + body; + } + const signature = this.hmac (this.encode (payload), this.encode (this.secret)); + headers = { + 'CF-API-KEY': this.apiKey, + 'CF-API-TIMESTAMP': seconds, + 'CF-API-SIGNATURE': signature, + 'Content-Type': 'application/json', + }; + } + const url = this.urls['api'] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code < 400) { + return; + } + const ErrorClass = this.safeValue ({ + '401': AuthenticationError, + '429': RateLimitExceeded, + }, code, ExchangeError); + throw new ErrorClass (body); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinfloor.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinfloor.js new file mode 100644 index 000000000..5d59bdf98 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinfloor.js @@ -0,0 +1,550 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { InsufficientFunds, ExchangeError, InvalidNonce, InvalidOrder, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinfloor extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinfloor', + 'name': 'coinfloor', + 'rateLimit': 1000, + 'countries': [ 'UK' ], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchLedger': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153925-ef265e80-c2c0-11ea-91b5-020c804b90e0.jpg', + 'api': 'https://webapi.coinfloor.co.uk/bist', + 'www': 'https://www.coinfloor.co.uk', + 'doc': [ + 'https://github.com/coinfloor/api', + 'https://www.coinfloor.co.uk/api', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + 'password': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + '{id}/ticker/', + '{id}/order_book/', + '{id}/transactions/', + ], + }, + 'private': { + 'post': [ + '{id}/balance/', + '{id}/user_transactions/', + '{id}/open_orders/', + '{symbol}/cancel_order/', + '{id}/buy/', + '{id}/sell/', + '{id}/buy_market/', + '{id}/sell_market/', + '{id}/estimate_sell_market/', + '{id}/estimate_buy_market/', + ], + }, + }, + 'markets': { + 'BTC/GBP': { 'id': 'XBT/GBP', 'symbol': 'BTC/GBP', 'base': 'BTC', 'quote': 'GBP', 'baseId': 'XBT', 'quoteId': 'GBP', 'precision': { 'price': 0, 'amount': 4 }}, + 'BTC/EUR': { 'id': 'XBT/EUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'XBT', 'quoteId': 'EUR', 'precision': { 'price': 0, 'amount': 4 }}, + }, + 'exceptions': { + 'exact': { + 'You have insufficient funds.': InsufficientFunds, + 'Tonce is out of sequence.': InvalidNonce, + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + let market = undefined; + let query = params; + const symbol = this.safeString (params, 'symbol'); + if (symbol !== undefined) { + market = this.market (params['symbol']); + query = this.omit (params, 'symbol'); + } + const marketId = this.safeString (params, 'id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + if (market === undefined) { + throw new ArgumentsRequired (this.id + ' fetchBalance requires a symbol param'); + } + const request = { + 'id': market['id'], + }; + const response = await this.privatePostIdBalance (this.extend (request, query)); + const result = { + 'info': response, + }; + // base/quote used for keys e.g. "xbt_reserved" + const base = market['base']; + const quote = market['quote']; + const baseIdLower = this.safeStringLower (market, 'baseId'); + const quoteIdLower = this.safeStringLower (market, 'quoteId'); + result[base] = { + 'free': this.safeFloat (response, baseIdLower + '_available'), + 'used': this.safeFloat (response, baseIdLower + '_reserved'), + 'total': this.safeFloat (response, baseIdLower + '_balance'), + }; + result[quote] = { + 'free': this.safeFloat (response, quoteIdLower + '_available'), + 'used': this.safeFloat (response, quoteIdLower + '_reserved'), + 'total': this.safeFloat (response, quoteIdLower + '_balance'), + }; + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.publicGetIdOrderBook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTicker (ticker, market = undefined) { + // rewrite to get the timestamp from HTTP headers + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetIdTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetIdTransactions (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + // code is actually a market symbol in this situation, not a currency code + await this.loadMarkets (); + let market = undefined; + if (code !== undefined) { + market = this.market (code); + if (market === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions requires a code argument (a market symbol)'); + } + } + const request = { + 'id': market['id'], + 'limit': limit, + }; + const response = await this.privatePostIdUserTransactions (this.extend (request, params)); + return this.parseLedger (response, undefined, since, undefined); + } + + parseLedgerEntryStatus (status) { + const types = { + 'completed': 'ok', + }; + return this.safeString (types, status, status); + } + + parseLedgerEntryType (type) { + const types = { + '0': 'transaction', // deposit + '1': 'transaction', // withdrawal + '2': 'trade', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // trade + // + // { + // "datetime": "2017-07-25 06:41:24", + // "id": 1500964884381265, + // "type": 2, + // "xbt": "0.1000", + // "xbt_eur": "2322.00", + // "eur": "-232.20", + // "fee": "0.00", + // "order_id": 84696745 + // } + // + // transaction (withdrawal) + // + // { + // "datetime": "2017-07-25 13:19:46", + // "id": 97669, + // "type": 1, + // "xbt": "-3.0000", + // "xbt_eur": null, + // "eur": "0", + // "fee": "0.0000", + // "order_id": null + // } + // + // transaction (deposit) + // + // { + // "datetime": "2017-07-27 16:44:55", + // "id": 98277, + // "type": 0, + // "xbt": "0", + // "xbt_eur": null, + // "eur": "4970.04", + // "fee": "0.00", + // "order_id": null + // } + // + const keys = Object.keys (item); + let baseId = undefined; + let quoteId = undefined; + let baseAmount = undefined; + let quoteAmount = undefined; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key.indexOf ('_') > 0) { + const parts = key.split ('_'); + const numParts = parts.length; + if (numParts === 2) { + const tmpBaseAmount = this.safeFloat (item, parts[0]); + const tmpQuoteAmount = this.safeFloat (item, parts[1]); + if (tmpBaseAmount !== undefined && tmpQuoteAmount !== undefined) { + baseId = parts[0]; + quoteId = parts[1]; + baseAmount = tmpBaseAmount; + quoteAmount = tmpQuoteAmount; + } + } + } + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const referenceId = this.safeString (item, 'id'); + const timestamp = this.parse8601 (this.safeString (item, 'datetime')); + let fee = undefined; + const feeCost = this.safeFloat (item, 'fee'); + const result = { + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'amount': undefined, + 'direction': undefined, + 'currency': undefined, + 'type': type, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'before': undefined, + 'after': undefined, + 'status': 'ok', + 'fee': fee, + 'info': item, + }; + if (type === 'trade') { + // + // it's a trade so let's make multiple entries, we have several options: + // + // if fee is always in quote currency (the exchange uses this) + // https://github.com/coinfloor/API/blob/master/IMPL-GUIDE.md#how-fees-affect-trade-quantities + // + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': quote, + }; + } + return [ + this.extend (result, { 'currency': base, 'amount': Math.abs (baseAmount), 'direction': (baseAmount > 0) ? 'in' : 'out' }), + this.extend (result, { 'currency': quote, 'amount': Math.abs (quoteAmount), 'direction': (quoteAmount > 0) ? 'in' : 'out', 'fee': fee }), + ]; + // + // if fee is base or quote depending on buy/sell side + // + // const baseFee = (baseAmount > 0) ? { 'currency': base, 'cost': feeCost } : undefined; + // const quoteFee = (quoteAmount > 0) ? { 'currency': quote, 'cost': feeCost } : undefined; + // return [ + // this.extend (result, { 'currency': base, 'amount': baseAmount, 'direction': (baseAmount > 0) ? 'in' : 'out', 'fee': baseFee }), + // this.extend (result, { 'currency': quote, 'amount': quoteAmount, 'direction': (quoteAmount > 0) ? 'in' : 'out', 'fee': quoteFee }), + // ]; + // + // fee as the 3rd item + // + // return [ + // this.extend (result, { 'currency': base, 'amount': baseAmount, 'direction': (baseAmount > 0) ? 'in' : 'out' }), + // this.extend (result, { 'currency': quote, 'amount': quoteAmount, 'direction': (quoteAmount > 0) ? 'in' : 'out' }), + // this.extend (result, { 'currency': feeCurrency, 'amount': feeCost, 'direction': 'out', 'type': 'fee' }), + // ]; + // + } else { + // + // it's a regular transaction (deposit or withdrawal) + // + const amount = (baseAmount === 0) ? quoteAmount : baseAmount; + const code = (baseAmount === 0) ? quote : base; + const direction = (amount > 0) ? 'in' : 'out'; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return this.extend (result, { + 'currency': code, + 'amount': Math.abs (amount), + 'direction': direction, + 'fee': fee, + }); + } + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + let method = 'privatePostId' + this.capitalize (side); + if (type === 'market') { + request['quantity'] = amount; + method += 'Market'; + } else { + request['price'] = price; + request['amount'] = amount; + } + // + // { + // "id":31950584, + // "datetime":"2020-05-21 08:38:18", + // "type":1, + // "price":"9100", + // "amount":"0.0026" + // } + // + const response = await this[method] (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (response, 'datetime')); + return { + 'id': this.safeString (response, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'type': type, + 'price': this.safeFloat (response, 'price'), + 'remaining': this.safeFloat (response, 'amount'), + 'info': response, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'id': id, + }; + const response = await this.privatePostSymbolCancelOrder (request); + if (response === 'false') { + // unfortunately the exchange does not give much info in the response + throw new InvalidOrder (this.id + ' cancel was rejected'); + } + return response; + } + + parseOrder (order, market = undefined) { + const timestamp = this.parse8601 (this.safeString (order, 'datetime')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let side = undefined; + const status = this.safeString (order, 'status'); + if (order['type'] === 0) { + side = 'buy'; + } else if (order['type'] === 1) { + side = 'sell'; + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (order, 'id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': undefined, + 'filled': undefined, + 'remaining': amount, + 'cost': cost, + 'fee': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol param'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.privatePostIdOpenOrders (this.extend (request, params)); + // { + // "amount": "1.0000", + // "datetime": "2019-07-12 13:28:16", + // "id": 233123443, + // "price": "1000.00", + // "type": 0 + // } + return this.parseOrders (response, market, since, limit, { 'status': 'open' }); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code < 400) { + return; + } + if (response === undefined) { + return; + } + const message = this.safeString (response, 'error_msg'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (feedback); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + // curl -k -u '[User ID]/[API key]:[Passphrase]' https://webapi.coinfloor.co.uk:8090/bist/XBT/GBP/balance/ + let url = this.urls['api'] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, query)); + const auth = this.uid + '/' + this.apiKey + ':' + this.password; + const signature = this.decode (this.stringToBase64 (auth)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coingi.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coingi.js new file mode 100644 index 000000000..650d80885 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coingi.js @@ -0,0 +1,348 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coingi extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coingi', + 'name': 'Coingi', + 'rateLimit': 1000, + 'countries': [ 'PA', 'BG', 'CN', 'US' ], // Panama, Bulgaria, China, US + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'referral': 'https://www.coingi.com/?r=XTPPMC', + 'logo': 'https://user-images.githubusercontent.com/1294454/28619707-5c9232a8-7212-11e7-86d6-98fe5d15cc6e.jpg', + 'api': { + 'www': 'https://coingi.com', + 'current': 'https://api.coingi.com', + 'user': 'https://api.coingi.com', + }, + 'www': 'https://coingi.com', + 'doc': 'https://coingi.docs.apiary.io', + }, + 'api': { + 'www': { + 'get': [ + '', + ], + }, + 'current': { + 'get': [ + 'order-book/{pair}/{askCount}/{bidCount}/{depth}', + 'transactions/{pair}/{maxCount}', + '24hour-rolling-aggregation', + ], + }, + 'user': { + 'post': [ + 'balance', + 'add-order', + 'cancel-order', + 'orders', + 'transactions', + 'create-crypto-withdrawal', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.2 / 100, + 'maker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.001, + 'LTC': 0.01, + 'DOGE': 2, + 'PPC': 0.02, + 'VTC': 0.2, + 'NMC': 2, + 'DASH': 0.002, + 'USD': 10, + 'EUR': 10, + }, + 'deposit': { + 'BTC': 0, + 'LTC': 0, + 'DOGE': 0, + 'PPC': 0, + 'VTC': 0, + 'NMC': 0, + 'DASH': 0, + 'USD': 5, + 'EUR': 1, + }, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.wwwGet (params); + const parts = response.split ('do=currencyPairSelector-selectCurrencyPair" class="active">'); + const currencyParts = parts[1].split ('
'); + const result = []; + for (let i = 1; i < currencyParts.length; i++) { + const currencyPart = currencyParts[i]; + const idParts = currencyPart.split ('
'); + let id = idParts[0]; + id = id.replace ('/', '-'); + id = id.toLowerCase (); + const [ baseId, quoteId ] = id.split ('-'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': id, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const lowercaseCurrencies = []; + const currencies = Object.keys (this.currencies); + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + lowercaseCurrencies.push (currency.toLowerCase ()); + } + const request = { + 'currencies': lowercaseCurrencies.join (','), + }; + const response = await this.userPostBalance (this.extend (request, params)); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance['currency'], 'name'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + const blocked = this.safeFloat (balance, 'blocked'); + const inOrders = this.safeFloat (balance, 'inOrders'); + const withdrawing = this.safeFloat (balance, 'withdrawing'); + account['used'] = this.sum (blocked, inOrders, withdrawing); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = 512, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'depth': 32, // maximum number of depth range steps 1-32 + 'askCount': limit, // maximum returned number of asks 1-512 + 'bidCount': limit, // maximum returned number of bids 1-512 + }; + const orderbook = await this.currentGetOrderBookPairAskCountBidCountDepth (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'baseAmount'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': undefined, + 'last': undefined, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume'), + 'quoteVolume': this.safeFloat (ticker, 'counterVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.currentGet24hourRollingAggregation (params); + const result = {}; + for (let t = 0; t < response.length; t++) { + const ticker = response[t]; + const base = ticker['currencyPair']['base'].toUpperCase (); + const quote = ticker['currencyPair']['counter'].toUpperCase (); + const symbol = base + '/' + quote; + let market = undefined; + if (symbol in this.markets) { + market = this.markets[symbol]; + } + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const tickers = await this.fetchTickers (undefined, params); + if (symbol in tickers) { + return tickers[symbol]; + } + throw new ExchangeError (this.id + ' return did not contain ' + symbol); + } + + parseTrade (trade, market = undefined) { + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const timestamp = this.safeInteger (trade, 'timestamp'); + const id = this.safeString (trade, 'id'); + const marketId = this.safeString (trade, 'currencyPair'); + const symbol = this.safeSymbol (marketId, market); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, // type + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'maxCount': 128, + }; + const response = await this.currentGetTransactionsPairMaxCount (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + 'volume': amount, + 'price': price, + 'orderType': (side === 'buy') ? 0 : 1, + }; + const response = await this.userPostAddOrder (this.extend (request, params)); + return { + 'info': response, + 'id': response['result'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + return await this.userPostCancelOrder (this.extend (request, params)); + } + + sign (path, api = 'current', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api !== 'www') { + url += '/' + api + '/' + this.implodeParams (path, params); + } + const query = this.omit (params, this.extractParams (path)); + if (api === 'current') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'user') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = this.extend ({ + 'token': this.apiKey, + 'nonce': nonce, + }, query); + const auth = nonce.toString () + '$' + this.apiKey; + request['signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.json (request); + headers = { + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'current', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if (typeof response !== 'string') { + if ('errors' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinmarketcap.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinmarketcap.js new file mode 100644 index 000000000..3d983cc88 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinmarketcap.js @@ -0,0 +1,345 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinmarketcap extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinmarketcap', + 'name': 'CoinMarketCap', + 'rateLimit': 10000, + 'version': 'v1', + 'countries': [ 'US' ], + 'has': { + 'cancelOrder': false, + 'CORS': true, + 'createLimitOrder': false, + 'createMarketOrder': false, + 'createOrder': false, + 'editOrder': false, + 'privateAPI': false, + 'fetchBalance': false, + 'fetchCurrencies': true, + 'fetchL2OrderBook': false, + 'fetchMarkets': true, + 'fetchOHLCV': false, + 'fetchOrderBook': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg', + 'api': { + 'public': 'https://api.coinmarketcap.com', + 'files': 'https://files.coinmarketcap.com', + 'charts': 'https://graph.coinmarketcap.com', + }, + 'www': 'https://coinmarketcap.com', + 'doc': 'https://coinmarketcap.com/api', + }, + 'requiredCredentials': { + 'apiKey': false, + 'secret': false, + }, + 'api': { + 'files': { + 'get': [ + 'generated/stats/global.json', + ], + }, + 'graphs': { + 'get': [ + 'currencies/{name}/', + ], + }, + 'public': { + 'get': [ + 'ticker/', + 'ticker/{id}/', + 'global/', + ], + }, + }, + 'currencyCodes': [ + 'AUD', + 'BRL', + 'CAD', + 'CHF', + 'CNY', + 'EUR', + 'GBP', + 'HKD', + 'IDR', + 'INR', + 'JPY', + 'KRW', + 'MXN', + 'RUB', + 'USD', + 'BTC', + 'ETH', + 'LTC', + ], + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + throw new ExchangeError ('Fetching order books is not supported by the API of ' + this.id); + } + + currencyCode (base, name) { + const currencies = { + 'ACChain': 'ACChain', + 'AdCoin': 'AdCoin', + 'BatCoin': 'BatCoin', + 'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE) + 'Bitgem': 'Bitgem', + 'BlazeCoin': 'BlazeCoin', + 'BlockCAT': 'BlockCAT', + 'Blocktrade Token': 'Blocktrade Token', + 'BOX Token': 'BOX Token', // conflict with BOX (ContentBox) + 'Catcoin': 'Catcoin', + 'CanYaCoin': 'CanYaCoin', // conflict with CAN (Content and AD Network) + 'CryptoBossCoin': 'CryptoBossCoin', // conflict with CBC (CashBet Coin) + 'Comet': 'Comet', // conflict with CMT (CyberMiles) + 'CPChain': 'CPChain', + 'CrowdCoin': 'CrowdCoin', // conflict with CRC CryCash + 'Cryptaur': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081 + 'Cubits': 'Cubits', // conflict with QBT (Qbao) + 'DAO.Casino': 'DAO.Casino', // conflict with BET (BetaCoin) + 'DefiBox': 'DefiBox', // conflict with BOX (ContentBox) + 'E-Dinar Coin': 'E-Dinar Coin', // conflict with EDR Endor Protocol and EDRCoin + 'EDRcoin': 'EDRcoin', // conflict with EDR Endor Protocol and E-Dinar Coin + 'ENTCash': 'ENTCash', // conflict with ENT (Eternity) + 'FairCoin': 'FairCoin', // conflict with FAIR (FairGame) https://github.com/ccxt/ccxt/pull/5865 + 'Fabric Token': 'Fabric Token', + // 'GET Protocol': 'GET Protocol', + 'GHOSTPRISM': 'GHOSTPRISM', // conflict with GHOST + 'Global Tour Coin': 'Global Tour Coin', // conflict with GTC (Game.com) + 'GuccioneCoin': 'GuccioneCoin', // conflict with GCC (Global Cryptocurrency) + 'HarmonyCoin': 'HarmonyCoin', // conflict with HMC (Hi Mutual Society) + 'Harvest Masternode Coin': 'Harvest Masternode Coin', // conflict with HC (HyperCash) + 'HOT Token': 'HOT Token', + 'Hydro Protocol': 'Hydro Protocol', // conflict with HOT (Holo) + 'Huncoin': 'Huncoin', // conflict with HNC (Helleniccoin) + 'iCoin': 'iCoin', + 'Infinity Economics': 'Infinity Economics', // conflict with XIN (Mixin) + 'IQ.cash': 'IQ.cash', // conflict with IQ (Everipedia) + 'KingN Coin': 'KingN Coin', // conflict with KNC (Kyber Network) + 'LiteBitcoin': 'LiteBitcoin', // conflict with LBTC (LightningBitcoin) + 'Maggie': 'Maggie', + 'Menlo One': 'Menlo One', // conflict with Harmony (ONE) + 'Mobilian Coin': 'Mobilian Coin', // conflict with Membrana (MBN) + 'Monarch': 'Monarch', // conflict with MyToken (MT) + 'MTC Mesh Network': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025 + 'IOTA': 'IOTA', // a special case, most exchanges list it as IOTA, therefore we change just the Coinmarketcap instead of changing them all + 'NetCoin': 'NetCoin', + 'PCHAIN': 'PCHAIN', // conflict with PAI (Project Pai) + 'Penta': 'Penta', // conflict with PNT (pNetwork) + 'Plair': 'Plair', // conflict with PLA (PLANET) + 'PlayChip': 'PlayChip', // conflict with PLA (PLANET) + 'Polcoin': 'Polcoin', + 'PutinCoin': 'PutinCoin', // conflict with PUT (Profile Utility Token) + 'Rcoin': 'Rcoin', // conflict with RCN (Ripio Credit Network) + // https://github.com/ccxt/ccxt/issues/6081 + // https://github.com/ccxt/ccxt/issues/3365 + // https://github.com/ccxt/ccxt/issues/2873 + 'TerraCredit': 'TerraCredit', // conflict with CREDIT (PROXI) + 'Themis': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol) + 'UNI COIN': 'UNI COIN', // conflict with UNI (Uniswap) + 'UNICORN Token': 'UNICORN Token', // conflict with UNI (Uniswap) + 'Universe': 'Universe', // conflict with UNI (Uniswap) + }; + return this.safeValue (currencies, name, base); + } + + async fetchMarkets (params = {}) { + const request = { + 'limit': 0, + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const currencies = this.currencyCodes; + for (let j = 0; j < currencies.length; j++) { + const quote = currencies[j]; + const quoteId = quote.toLowerCase (); + const baseId = market['id']; + const base = this.currencyCode (market['symbol'], market['name']); + const symbol = base + '/' + quote; + const id = baseId + '/' + quoteId; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + } + return result; + } + + async fetchGlobal (currency = 'USD') { + await this.loadMarkets (); + const request = {}; + if (currency) { + request['convert'] = currency; + } + return await this.publicGetGlobal (request); + } + + parseTicker (ticker, market = undefined) { + let timestamp = this.safeTimestamp (ticker, 'last_updated'); + if (timestamp === undefined) { + timestamp = this.milliseconds (); + } + const change = this.safeFloat (ticker, 'percent_change_24h'); + let last = undefined; + let symbol = undefined; + let volume = undefined; + if (market !== undefined) { + symbol = market['symbol']; + const priceKey = 'price_' + market['quoteId']; + last = this.safeFloat (ticker, priceKey); + const volumeKey = '24h_volume_' + market['quoteId']; + volume = this.safeFloat (ticker, volumeKey); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': change, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': volume, + 'info': ticker, + }; + } + + async fetchTickers (currency = 'USD', params = {}) { + await this.loadMarkets (); + const request = { + 'limit': 10000, + }; + if (currency) { + request['convert'] = currency; + } + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + for (let t = 0; t < response.length; t++) { + const ticker = response[t]; + const currencyId = currency.toLowerCase (); + const id = ticker['id'] + '/' + currencyId; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } + result[symbol] = this.parseTicker (ticker, market); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'convert': market['quote'], + 'id': market['baseId'], + }; + const response = await this.publicGetTickerId (this.extend (request, params)); + const ticker = response[0]; + return this.parseTicker (ticker, market); + } + + async fetchCurrencies (params = {}) { + const request = { + 'limit': 0, + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const name = this.safeString (currency, 'name'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const precision = 8; // default precision, todo: fix "magic constants" + const code = this.currencyCode (id, name); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': true, + 'fee': undefined, // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('error' in response) { + if (response['error']) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinmate.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinmate.js new file mode 100644 index 000000000..844bc0011 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinmate.js @@ -0,0 +1,759 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InvalidOrder, OrderNotFound, RateLimitExceeded, InsufficientFunds } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinmate extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinmate', + 'name': 'CoinMate', + 'countries': [ 'GB', 'CZ', 'EU' ], // UK, Czech Republic + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460806-1c9f3f00-c616-11ea-8c46-a77018a8f3f4.jpg', + 'api': 'https://coinmate.io/api', + 'www': 'https://coinmate.io', + 'fees': 'https://coinmate.io/fees', + 'doc': [ + 'https://coinmate.docs.apiary.io', + 'https://coinmate.io/developers', + ], + 'referral': 'https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'orderBook', + 'ticker', + 'transactions', + 'tradingPairs', + ], + }, + 'private': { + 'post': [ + 'balances', + 'bitcoinCashWithdrawal', + 'bitcoinCashDepositAddresses', + 'bitcoinDepositAddresses', + 'bitcoinWithdrawal', + 'bitcoinWithdrawalFees', + 'buyInstant', + 'buyLimit', + 'cancelOrder', + 'cancelOrderWithInfo', + 'createVoucher', + 'dashDepositAddresses', + 'dashWithdrawal', + 'ethereumWithdrawal', + 'ethereumDepositAddresses', + 'litecoinWithdrawal', + 'litecoinDepositAddresses', + 'openOrders', + 'order', + 'orderHistory', + 'orderById', + 'pusherAuth', + 'redeemVoucher', + 'replaceByBuyLimit', + 'replaceByBuyInstant', + 'replaceBySellLimit', + 'replaceBySellInstant', + 'rippleDepositAddresses', + 'rippleWithdrawal', + 'sellInstant', + 'sellLimit', + 'transactionHistory', + 'traderFees', + 'tradeHistory', + 'transfer', + 'transferHistory', + 'unconfirmedBitcoinDeposits', + 'unconfirmedBitcoinCashDeposits', + 'unconfirmedDashDeposits', + 'unconfirmedEthereumDeposits', + 'unconfirmedLitecoinDeposits', + 'unconfirmedRippleDeposits', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.12 / 100, + 'taker': 0.25 / 100, + 'tiers': { + 'taker': [ + [0, 0.25 / 100], + [10000, 0.23 / 100], + [100000, 0.21 / 100], + [250000, 0.20 / 100], + [500000, 0.15 / 100], + [1000000, 0.13 / 100], + [3000000, 0.10 / 100], + [15000000, 0.05 / 100], + ], + 'maker': [ + [0, 0.12 / 100], + [10000, 0.11 / 100], + [1000000, 0.10 / 100], + [250000, 0.08 / 100], + [500000, 0.05 / 100], + [1000000, 0.03 / 100], + [3000000, 0.02 / 100], + [15000000, 0], + ], + }, + }, + 'promotional': { + 'trading': { + 'maker': 0.05 / 100, + 'taker': 0.15 / 100, + 'tiers': { + 'taker': [ + [0, 0.15 / 100], + [10000, 0.14 / 100], + [100000, 0.13 / 100], + [250000, 0.12 / 100], + [500000, 0.11 / 100], + [1000000, 0.1 / 100], + [3000000, 0.08 / 100], + [15000000, 0.05 / 100], + ], + 'maker': [ + [0, 0.05 / 100], + [10000, 0.04 / 100], + [1000000, 0.03 / 100], + [250000, 0.02 / 100], + [500000, 0], + [1000000, 0], + [3000000, 0], + [15000000, 0], + ], + }, + }, + }, + }, + 'options': { + 'promotionalMarkets': ['ETH/EUR', 'ETH/CZK', 'ETH/BTC', 'XRP/EUR', 'XRP/CZK', 'XRP/BTC', 'DASH/EUR', 'DASH/CZK', 'DASH/BTC', 'BCH/EUR', 'BCH/CZK', 'BCH/BTC'], + }, + 'exceptions': { + 'exact': { + 'No order with given ID': OrderNotFound, + }, + 'broad': { + 'Not enough account balance available': InsufficientFunds, + 'Incorrect order ID': InvalidOrder, + 'Minimum Order Size ': InvalidOrder, + 'TOO MANY REQUESTS': RateLimitExceeded, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTradingPairs (params); + // + // { + // "error":false, + // "errorMessage":null, + // "data": [ + // { + // "name":"BTC_EUR", + // "firstCurrency":"BTC", + // "secondCurrency":"EUR", + // "priceDecimals":2, + // "lotDecimals":8, + // "minAmount":0.0002, + // "tradesWebSocketChannelId":"trades-BTC_EUR", + // "orderBookWebSocketChannelId":"order_book-BTC_EUR", + // "tradeStatisticsWebSocketChannelId":"statistics-BTC_EUR" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data'); + const result = []; + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'firstCurrency'); + const quoteId = this.safeString (market, 'secondCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const promotionalMarkets = this.safeValue (this.options, 'promotionalMarkets', []); + let fees = this.safeValue (this.fees, 'trading'); + if (this.inArray (symbol, promotionalMarkets)) { + const promotionalFees = this.safeValue (this.fees, 'promotional', {}); + fees = this.safeValue (promotionalFees, 'trading', fees); + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'maker': fees['maker'], + 'taker': fees['taker'], + 'info': market, + 'precision': { + 'price': this.safeInteger (market, 'priceDecimals'), + 'amount': this.safeInteger (market, 'lotDecimals'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalances (params); + const balances = this.safeValue (response, 'data'); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (balances, currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'reserved'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + 'groupByPriceLimit': 'False', + }; + const response = await this.publicGetOrderBook (this.extend (request, params)); + const orderbook = response['data']; + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'data'); + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'vwap': undefined, + 'askVolume': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'amount'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'limit': 1000, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timestampFrom'] = since; + } + if (code !== undefined) { + request['currency'] = this.currencyId (code); + } + const response = await this.privatePostTransferHistory (this.extend (request, params)); + const items = response['data']; + return this.parseTransactions (items, undefined, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + // any other types ? + 'COMPLETED': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (item, currency = undefined) { + // + // deposits + // + // { + // transactionId: 1862815, + // timestamp: 1516803982388, + // amountCurrency: 'LTC', + // amount: 1, + // fee: 0, + // walletType: 'LTC', + // transferType: 'DEPOSIT', + // transferStatus: 'COMPLETED', + // txid: + // 'ccb9255dfa874e6c28f1a64179769164025329d65e5201849c2400abd6bce245', + // destination: 'LQrtSKA6LnhcwRrEuiborQJnjFF56xqsFn', + // destinationTag: null + // } + // + // withdrawals + // + // { + // transactionId: 2140966, + // timestamp: 1519314282976, + // amountCurrency: 'EUR', + // amount: 8421.7228, + // fee: 16.8772, + // walletType: 'BANK_WIRE', + // transferType: 'WITHDRAWAL', + // transferStatus: 'COMPLETED', + // txid: null, + // destination: null, + // destinationTag: null + // } + // + const timestamp = this.safeInteger (item, 'timestamp'); + const amount = this.safeFloat (item, 'amount'); + const fee = this.safeFloat (item, 'fee'); + const txid = this.safeString (item, 'txid'); + const address = this.safeString (item, 'destination'); + const tag = this.safeString (item, 'destinationTag'); + const currencyId = this.safeString (item, 'amountCurrency'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeStringLower (item, 'transferType'); + const status = this.parseTransactionStatus (this.safeString (item, 'transferStatus')); + const id = this.safeString (item, 'transactionId'); + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'currency': code, + 'amount': amount, + 'type': type, + 'txid': txid, + 'address': address, + 'tag': tag, + 'status': status, + 'fee': { + 'cost': fee, + 'currency': code, + }, + 'info': item, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 1000; + } + const request = { + 'limit': limit, + }; + if (symbol !== undefined) { + const market = this.market (symbol); + request['currencyPair'] = market['id']; + } + if (since !== undefined) { + request['timestampFrom'] = since; + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const items = response['data']; + return this.parseTrades (items, undefined, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchMyTrades (private) + // + // { + // transactionId: 2671819, + // createdTimestamp: 1529649127605, + // currencyPair: 'LTC_BTC', + // type: 'BUY', + // orderType: 'LIMIT', + // orderId: 101810227, + // amount: 0.01, + // price: 0.01406, + // fee: 0, + // feeType: 'MAKER' + // } + // + // fetchTrades (public) + // + // { + // "timestamp":1561598833416, + // "transactionId":"4156303", + // "price":10950.41, + // "amount":0.004, + // "currencyPair":"BTC_EUR", + // "tradeType":"BUY" + // } + // + const marketId = this.safeString (trade, 'currencyPair'); + market = this.safeMarket (marketId, market, '_'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + const side = this.safeStringLower2 (trade, 'type', 'tradeType'); + const type = this.safeStringLower (trade, 'orderType'); + const orderId = this.safeString (trade, 'orderId'); + const id = this.safeString (trade, 'transactionId'); + const timestamp = this.safeInteger2 (trade, 'timestamp', 'createdTimestamp'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + let takerOrMaker = this.safeString (trade, 'feeType'); + takerOrMaker = (takerOrMaker === 'MAKER') ? 'maker' : 'taker'; + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'type': type, + 'side': side, + 'order': orderId, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'minutesIntoHistory': 10, + }; + const response = await this.publicGetTransactions (this.extend (request, params)); + // + // { + // "error":false, + // "errorMessage":null, + // "data":[ + // { + // "timestamp":1561598833416, + // "transactionId":"4156303", + // "price":10950.41, + // "amount":0.004, + // "currencyPair":"BTC_EUR", + // "tradeType":"BUY" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.privatePostOpenOrders (this.extend ({}, params)); + const extension = { 'status': 'open' }; + return this.parseOrders (response['data'], undefined, since, limit, extension); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + // offset param that appears in other parts of the API doesn't appear to be supported here + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostOrderHistory (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'FILLED': 'closed', + 'CANCELLED': 'canceled', + 'PARTIALLY_FILLED': 'open', + 'OPEN': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrderType (type) { + const types = { + 'LIMIT': 'limit', + 'MARKET': 'market', + }; + return this.safeString (types, type, type); + } + + parseOrder (order, market = undefined) { + // + // limit sell + // + // { + // id: 781246605, + // timestamp: 1584480015133, + // trailingUpdatedTimestamp: null, + // type: 'SELL', + // currencyPair: 'ETH_BTC', + // price: 0.0345, + // amount: 0.01, + // stopPrice: null, + // originalStopPrice: null, + // marketPriceAtLastUpdate: null, + // marketPriceAtOrderCreation: null, + // orderTradeType: 'LIMIT', + // hidden: false, + // trailing: false, + // clientOrderId: null + // } + // + // limit buy + // + // { + // id: 67527001, + // timestamp: 1517931722613, + // trailingUpdatedTimestamp: null, + // type: 'BUY', + // price: 5897.24, + // remainingAmount: 0.002367, + // originalAmount: 0.1, + // stopPrice: null, + // originalStopPrice: null, + // marketPriceAtLastUpdate: null, + // marketPriceAtOrderCreation: null, + // status: 'CANCELLED', + // orderTradeType: 'LIMIT', + // hidden: false, + // avgPrice: null, + // trailing: false, + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.safeInteger (order, 'timestamp'); + const side = this.safeStringLower (order, 'type'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'originalAmount'); + let remaining = this.safeFloat (order, 'remainingAmount'); + if (remaining === undefined) { + remaining = this.safeFloat (order, 'amount'); + } + let status = this.parseOrderStatus (this.safeString (order, 'status')); + const type = this.parseOrderType (this.safeString (order, 'orderTradeType')); + let filled = undefined; + let cost = undefined; + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (amount - remaining, 0); + if (remaining === 0) { + status = 'closed'; + } + if (price !== undefined) { + cost = filled * price; + } + } + const average = this.safeFloat (order, 'avgPrice'); + const marketId = this.safeString (order, 'currencyPair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'trades': undefined, + 'info': order, + 'fee': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let method = 'privatePost' + this.capitalize (side); + const request = { + 'currencyPair': this.marketId (symbol), + }; + if (type === 'market') { + if (side === 'buy') { + request['total'] = this.amountToPrecision (symbol, amount); // amount in fiat + } else { + request['amount'] = this.amountToPrecision (symbol, amount); // amount in fiat + } + method += 'Instant'; + } else { + request['amount'] = this.amountToPrecision (symbol, amount); // amount in crypto + request['price'] = this.priceToPrecision (symbol, price); + method += this.capitalize (type); + } + const response = await this[method] (this.extend (request, params)); + const id = this.safeString (response, 'data'); + return { + 'info': response, + 'id': id, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + let market = undefined; + if (symbol) { + market = this.market (symbol); + } + const response = await this.privatePostOrderById (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + // {"error":false,"errorMessage":null,"data":{"success":true,"remainingAmount":0.01}} + const request = { 'orderId': id }; + const response = await this.privatePostCancelOrderWithInfo (this.extend (request, params)); + return { + 'info': response, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const auth = nonce + this.uid + this.apiKey; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.urlencode (this.extend ({ + 'clientId': this.uid, + 'nonce': nonce, + 'publicKey': this.apiKey, + 'signature': signature.toUpperCase (), + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response !== undefined) { + if ('error' in response) { + // {"error":true,"errorMessage":"Minimum Order Size 0.01 ETH","data":null} + if (response['error']) { + const message = this.safeString (response, 'errorMessage'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + } + if (code > 400) { + if (body) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], body, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); // unknown message + } + throw new ExchangeError (this.id + ' ' + body); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinone.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinone.js new file mode 100644 index 000000000..eb14695fa --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinone.js @@ -0,0 +1,681 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, BadRequest, ExchangeError, ArgumentsRequired, OrderNotFound, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinone extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinone', + 'name': 'CoinOne', + 'countries': [ 'KR' ], // Korea + // 'enableRateLimit': false, + 'rateLimit': 667, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + // https://github.com/ccxt/ccxt/pull/7067 + // the endpoint that should return closed orders actually returns trades + 'fetchClosedOrders': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/38003300-adc12fba-323f-11e8-8525-725f53c4a659.jpg', + 'api': 'https://api.coinone.co.kr', + 'www': 'https://coinone.co.kr', + 'doc': 'https://doc.coinone.co.kr', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'api': { + 'public': { + 'get': [ + 'orderbook/', + 'trades/', + 'ticker/', + ], + }, + 'private': { + 'post': [ + 'account/btc_deposit_address/', + 'account/balance/', + 'account/daily_balance/', + 'account/user_info/', + 'account/virtual_account/', + 'order/cancel_all/', + 'order/cancel/', + 'order/limit_buy/', + 'order/limit_sell/', + 'order/complete_orders/', + 'order/limit_orders/', + 'order/order_info/', + 'transaction/auth_number/', + 'transaction/history/', + 'transaction/krw/history/', + 'transaction/btc/', + 'transaction/coin/', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.002, + }, + }, + 'precision': { + 'price': 4, + 'amount': 4, + 'cost': 8, + }, + 'exceptions': { + '405': OnMaintenance, // {"errorCode":"405","status":"maintenance","result":"error"} + '104': OrderNotFound, // {"errorCode":"104","errorMsg":"Order id is not exist","result":"error"} + '108': BadSymbol, // {"errorCode":"108","errorMsg":"Unknown CryptoCurrency","result":"error"} + '107': BadRequest, // {"errorCode":"107","errorMsg":"Parameter error","result":"error"} + }, + }); + } + + async fetchMarkets (params = {}) { + const request = { + 'currency': 'all', + }; + const response = await this.publicGetTicker (request); + const result = []; + const quoteId = 'krw'; + const quote = this.safeCurrencyCode (quoteId); + const baseIds = Object.keys (response); + for (let i = 0; i < baseIds.length; i++) { + const baseId = baseIds[i]; + const ticker = this.safeValue (response, baseId, {}); + const currency = this.safeValue (ticker, 'currency'); + if (currency === undefined) { + continue; + } + const base = this.safeCurrencyCode (baseId); + result.push ({ + 'id': baseId, + 'symbol': base + '/' + quote, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostAccountBalance (params); + const result = { 'info': response }; + const balances = this.omit (response, [ + 'errorCode', + 'result', + 'normalWallets', + ]); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'avail'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'format': 'json', + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp, 'bid', 'ask', 'price', 'qty'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': 'all', + 'format': 'json', + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + const ids = Object.keys (response); + const timestamp = this.safeTimestamp (response, 'timestamp'); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + const ticker = response[id]; + result[symbol] = this.parseTicker (ticker, market); + result[symbol]['timestamp'] = timestamp; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'format': 'json', + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'timestamp'); + const first = this.safeFloat (ticker, 'first'); + const last = this.safeFloat (ticker, 'last'); + let average = undefined; + if (first !== undefined && last !== undefined) { + average = this.sum (first, last) / 2; + } + const previousClose = this.safeFloat (ticker, 'yesterday_last'); + let change = undefined; + let percentage = undefined; + if (last !== undefined && previousClose !== undefined) { + change = last - previousClose; + if (previousClose !== 0) { + percentage = change / previousClose * 100; + } + } + const symbol = (market !== undefined) ? market['symbol'] : undefined; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': first, + 'close': last, + 'last': last, + 'previousClose': previousClose, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "timestamp": "1416893212", + // "price": "420000.0", + // "qty": "0.1", + // "is_ask": "1" + // } + // + // fetchMyTrades (private) + // + // { + // "timestamp": "1416561032", + // "price": "419000.0", + // "type": "bid", + // "qty": "0.001", + // "feeRate": "-0.0015", + // "fee": "-0.0000015", + // "orderId": "E84A1AC2-8088-4FA0-B093-A3BCDB9B3C85" + // } + // + const timestamp = this.safeTimestamp (trade, 'timestamp'); + const symbol = (market !== undefined) ? market['symbol'] : undefined; + const is_ask = this.safeString (trade, 'is_ask'); + let side = this.safeString (trade, 'type'); + if (is_ask !== undefined) { + if (is_ask === '1') { + side = 'sell'; + } else if (is_ask === '0') { + side = 'buy'; + } + } else { + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'qty'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const orderId = this.safeString (trade, 'orderId'); + let feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + let feeRate = this.safeFloat (trade, 'feeRate'); + feeRate = Math.abs (feeRate); + let feeCurrencyCode = undefined; + if (market !== undefined) { + feeCurrencyCode = (side === 'sell') ? market['quote'] : market['base']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + } + return { + 'id': this.safeString (trade, 'id'), + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'format': 'json', + }; + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "timestamp": "1416895635", + // "currency": "btc", + // "completeOrders": [ + // { + // "timestamp": "1416893212", + // "price": "420000.0", + // "qty": "0.1", + // "is_ask": "1" + // } + // ] + // } + // + const completeOrders = this.safeValue (response, 'completeOrders', []); + return this.parseTrades (completeOrders, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const request = { + 'price': price, + 'currency': this.marketId (symbol), + 'qty': amount, + }; + const method = 'privatePostOrder' + this.capitalize (type) + this.capitalize (side); + const response = await this[method] (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "orderId": "8a82c561-40b4-4cb3-9bc0-9ac9ffc1d63b" + // } + // + return this.parseOrder (response); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'currency': market['id'], + }; + const response = await this.privatePostOrderOrderInfo (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "status": "live", + // "info": { + // "orderId": "32FF744B-D501-423A-8BA1-05BB6BE7814A", + // "currency": "BTC", + // "type": "bid", + // "price": "2922000.0", + // "qty": "115.4950", + // "remainQty": "45.4950", + // "feeRate": "0.0003", + // "fee": "0", + // "timestamp": "1499340941" + // } + // } + // + const info = this.safeValue (response, 'info', {}); + info['status'] = this.safeString (info, 'status'); + return this.parseOrder (info, market); + } + + parseOrderStatus (status) { + const statuses = { + 'live': 'open', + 'partially_filled': 'open', + 'filled': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "result": "success", + // "errorCode": "0", + // "orderId": "8a82c561-40b4-4cb3-9bc0-9ac9ffc1d63b" + // } + // + // fetchOrder + // + // { + // "status": "live", // injected in fetchOrder + // "orderId": "32FF744B-D501-423A-8BA1-05BB6BE7814A", + // "currency": "BTC", + // "type": "bid", + // "price": "2922000.0", + // "qty": "115.4950", + // "remainQty": "45.4950", + // "feeRate": "0.0003", + // "fee": "0", + // "timestamp": "1499340941" + // } + // + // fetchOpenOrders + // + // { + // "index": "0", + // "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489", + // "timestamp": "1449037367", + // "price": "444000.0", + // "qty": "0.3456", + // "type": "ask", + // "feeRate": "-0.0015" + // } + // + const id = this.safeString (order, 'orderId'); + const price = this.safeFloat (order, 'price'); + const timestamp = this.safeTimestamp (order, 'timestamp'); + let side = this.safeString (order, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const remaining = this.safeFloat (order, 'remainQty'); + let filled = undefined; + const amount = this.safeFloat (order, 'qty'); + let status = this.safeString (order, 'status'); + // https://github.com/ccxt/ccxt/pull/7067 + if (status === 'live') { + if ((remaining !== undefined) && (amount !== undefined)) { + if (remaining < amount) { + status = 'canceled'; + } + } + } + if ((remaining !== undefined) && (amount !== undefined)) { + filled = Math.max (amount - remaining); + } + status = this.parseOrderStatus (status); + let cost = undefined; + if ((price !== undefined) && (filled !== undefined)) { + cost = price * filled; + } + let symbol = undefined; + let base = undefined; + let quote = undefined; + const marketId = this.safeStringLower (order, 'currency'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + base = this.safeCurrencyCode (marketId); + quote = 'KRW'; + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = (side === 'sell') ? quote : base; + fee = { + 'cost': feeCost, + 'rate': this.safeFloat (order, 'feeRate'), + 'currency': feeCurrencyCode, + }; + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': amount, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // The returned amount might not be same as the ordered amount. If an order is partially filled, the returned amount means the remaining amount. + // For the same reason, the returned amount and remaining are always same, and the returned filled and cost are always zero. + if (symbol === undefined) { + throw new ExchangeError (this.id + ' allows fetching closed orders with a specific symbol'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.privatePostOrderLimitOrders (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0", + // "limitOrders": [ + // { + // "index": "0", + // "orderId": "68665943-1eb5-4e4b-9d76-845fc54f5489", + // "timestamp": "1449037367", + // "price": "444000.0", + // "qty": "0.3456", + // "type": "ask", + // "feeRate": "-0.0015" + // } + // ] + // } + // + const limitOrders = this.safeValue (response, 'limitOrders', []); + return this.parseOrders (limitOrders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.privatePostOrderCompleteOrders (this.extend (request, params)); + // + // despite the name of the endpoint it returns trades which may have a duplicate orderId + // https://github.com/ccxt/ccxt/pull/7067 + // + // { + // "result": "success", + // "errorCode": "0", + // "completeOrders": [ + // { + // "timestamp": "1416561032", + // "price": "419000.0", + // "type": "bid", + // "qty": "0.001", + // "feeRate": "-0.0015", + // "fee": "-0.0000015", + // "orderId": "E84A1AC2-8088-4FA0-B093-A3BCDB9B3C85" + // } + // ] + // } + // + const completeOrders = this.safeValue (response, 'completeOrders', []); + return this.parseTrades (completeOrders, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + // eslint-disable-next-line quotes + throw new ArgumentsRequired (this.id + " cancelOrder requires a symbol argument. To cancel the order, pass a symbol argument and {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument of cancelOrder."); + } + const price = this.safeFloat (params, 'price'); + const qty = this.safeFloat (params, 'qty'); + const isAsk = this.safeInteger (params, 'is_ask'); + if ((price === undefined) || (qty === undefined) || (isAsk === undefined)) { + // eslint-disable-next-line quotes + throw new ArgumentsRequired (this.id + " cancelOrder requires {'price': 12345, 'qty': 1.2345, 'is_ask': 0} in the params argument."); + } + await this.loadMarkets (); + const request = { + 'order_id': id, + 'price': price, + 'qty': qty, + 'is_ask': isAsk, + 'currency': this.marketId (symbol), + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + // + // { + // "result": "success", + // "errorCode": "0" + // } + // + return response; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + '/'; + if (api === 'public') { + url += request; + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + url += this.version + '/' + request; + const nonce = this.nonce ().toString (); + const json = this.json (this.extend ({ + 'access_token': this.apiKey, + 'nonce': nonce, + }, params)); + const payload = this.stringToBase64 (json); + body = this.decode (payload); + const secret = this.secret.toUpperCase (); + const signature = this.hmac (payload, this.encode (secret), 'sha512'); + headers = { + 'Content-Type': 'application/json', + 'X-COINONE-PAYLOAD': payload, + 'X-COINONE-SIGNATURE': signature, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if ('result' in response) { + const result = response['result']; + if (result !== 'success') { + // + // { "errorCode": "405", "status": "maintenance", "result": "error"} + // + const errorCode = this.safeString (response, 'errorCode'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); + } + } else { + throw new ExchangeError (this.id + ' ' + body); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinspot.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinspot.js new file mode 100644 index 000000000..2da1a5ee7 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/coinspot.js @@ -0,0 +1,235 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class coinspot extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'coinspot', + 'name': 'CoinSpot', + 'countries': [ 'AU' ], // Australia + 'rateLimit': 1000, + 'has': { + 'cancelOrder': false, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28208429-3cacdf9a-6896-11e7-854e-4c79a772a30f.jpg', + 'api': { + 'public': 'https://www.coinspot.com.au/pubapi', + 'private': 'https://www.coinspot.com.au/api', + }, + 'www': 'https://www.coinspot.com.au', + 'doc': 'https://www.coinspot.com.au/api', + 'referral': 'https://www.coinspot.com.au/register?code=PJURCU', + }, + 'api': { + 'public': { + 'get': [ + 'latest', + ], + }, + 'private': { + 'post': [ + 'orders', + 'orders/history', + 'my/coin/deposit', + 'my/coin/send', + 'quote/buy', + 'quote/sell', + 'my/balances', + 'my/orders', + 'my/buy', + 'my/sell', + 'my/buy/cancel', + 'my/sell/cancel', + 'ro/my/balances', + 'ro/my/balances/{cointype}', + 'ro/my/deposits', + 'ro/my/withdrawals', + 'ro/my/transactions', + 'ro/my/transactions/{cointype}', + 'ro/my/transactions/open', + 'ro/my/transactions/{cointype}/open', + 'ro/my/sendreceive', + 'ro/my/affiliatepayments', + 'ro/my/referralpayments', + ], + }, + }, + 'markets': { + 'BTC/AUD': { 'id': 'btc', 'symbol': 'BTC/AUD', 'base': 'BTC', 'quote': 'AUD', 'baseId': 'btc', 'quoteId': 'aud' }, + 'LTC/AUD': { 'id': 'ltc', 'symbol': 'LTC/AUD', 'base': 'LTC', 'quote': 'AUD', 'baseId': 'ltc', 'quoteId': 'aud' }, + 'DOGE/AUD': { 'id': 'doge', 'symbol': 'DOGE/AUD', 'base': 'DOGE', 'quote': 'AUD', 'baseId': 'doge', 'quoteId': 'aud' }, + }, + 'commonCurrencies': { + 'DRK': 'DASH', + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostMyBalances (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'balance', {}); + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balances, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cointype': market['id'], + }; + const orderbook = await this.privatePostOrders (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'buyorders', 'sellorders', 'rate', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetLatest (params); + let id = this.marketId (symbol); + id = id.toLowerCase (); + const ticker = response['prices'][id]; + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'cointype': market['id'], + }; + const response = await this.privatePostOrdersHistory (this.extend (request, params)); + // + // { + // "status":"ok", + // "orders":[ + // {"amount":0.00102091,"rate":21549.09999991,"total":21.99969168,"coin":"BTC","solddate":1604890646143,"market":"BTC/AUD"}, + // ], + // } + // + const trades = this.safeValue (response, 'orders', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "amount":0.00102091, + // "rate":21549.09999991, + // "total":21.99969168, + // "coin":"BTC", + // "solddate":1604890646143, + // "market":"BTC/AUD" + // } + // + const price = this.safeFloat (trade, 'rate'); + const amount = this.safeFloat (trade, 'amount'); + let cost = this.safeFloat (trade, 'total'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const timestamp = this.safeInteger (trade, 'solddate'); + const marketId = this.safeString (trade, 'market'); + const symbol = this.safeSymbol (marketId, market, '/'); + return { + 'info': trade, + 'id': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': undefined, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostMy' + this.capitalize (side); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'cointype': this.marketId (symbol), + 'amount': amount, + 'rate': price, + }; + return await this[method] (this.extend (request, params)); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + throw new NotSupported (this.id + ' cancelOrder () is not fully implemented yet'); + // let method = 'privatePostMyBuy'; + // return await this[method] ({ 'id': id }); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (!this.apiKey) { + throw new AuthenticationError (this.id + ' requires apiKey for all requests'); + } + const url = this.urls['api'][api] + '/' + path; + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.json (this.extend ({ 'nonce': nonce }, params)); + headers = { + 'Content-Type': 'application/json', + 'key': this.apiKey, + 'sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/crex24.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/crex24.js new file mode 100644 index 000000000..5ea3e826c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/crex24.js @@ -0,0 +1,1308 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadRequest, InvalidNonce, RequestTimeout, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, AuthenticationError, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class crex24 extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'crex24', + 'name': 'CREX24', + 'countries': [ 'EE' ], // Estonia + 'rateLimit': 500, + 'version': 'v2', + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': false, // actually, true, but will be implemented later + 'fetchTradingFees': false, // actually, true, but will be implemented later + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '1d': '1d', + '1w': '1w', + '1M': '1mo', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/47813922-6f12cc00-dd5d-11e8-97c6-70f957712d47.jpg', + 'api': 'https://api.crex24.com', + 'www': 'https://crex24.com', + 'referral': 'https://crex24.com/?refid=slxsjsjtil8xexl9hksr', + 'doc': 'https://docs.crex24.com/trade-api/v2', + 'fees': 'https://crex24.com/fees', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'instruments', + 'tickers', + 'recentTrades', + 'orderBook', + 'ohlcv', + ], + }, + 'trading': { + 'get': [ + 'orderStatus', + 'orderTrades', + 'activeOrders', + 'orderHistory', + 'tradeHistory', + 'tradeFee', + // this is in trading API according to their docs, but most likely a typo in their docs + 'moneyTransferStatus', + ], + 'post': [ + 'placeOrder', + 'modifyOrder', + 'cancelOrdersById', + 'cancelOrdersByInstrument', + 'cancelAllOrders', + ], + }, + 'account': { + 'get': [ + 'balance', + 'depositAddress', + 'moneyTransfers', + // this is in trading API according to their docs, but most likely a typo in their docs + 'moneyTransferStatus', + 'previewWithdrawal', + ], + 'post': [ + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.001, + 'maker': -0.0001, + }, + // should be deleted, these are outdated and inaccurate + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'commonCurrencies': { + 'BCC': 'BCH', + 'BIT': 'BitMoney', + 'BULL': 'BuySell', + 'CREDIT': 'TerraCredit', + 'GHOST': 'GHOSTPRISM', + 'IQ': 'IQ.Cash', + 'PUT': 'PutinCoin', + 'UNI': 'Universe', + 'YOYO': 'YOYOW', + }, + // exchange-specific options + 'options': { + 'fetchOrdersMethod': 'tradingGetOrderHistory', // or 'tradingGetActiveOrders' + 'fetchClosedOrdersMethod': 'tradingGetOrderHistory', // or 'tradingGetActiveOrders' + 'fetchTickersMethod': 'publicGetTicker24hr', + 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel + 'hasAlreadyAuthenticatedSuccessfully': false, + 'warnOnFetchOpenOrdersWithoutSymbol': true, + 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision + 'newOrderRespType': 'RESULT', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + }, + 'exceptions': { + 'exact': { + "Parameter 'filter' contains invalid value.": BadRequest, // eslint-disable-quotes + "Mandatory parameter 'instrument' is missing.": BadRequest, // eslint-disable-quotes + "The value of parameter 'till' must be greater than or equal to the value of parameter 'from'.": BadRequest, // eslint-disable-quotes + 'Failed to verify request signature.': AuthenticationError, // eslint-disable-quotes + "Nonce error. Make sure that the value passed in the 'X-CREX24-API-NONCE' header is greater in each consecutive request than in the previous one for the corresponding API-Key provided in 'X-CREX24-API-KEY' header.": InvalidNonce, + 'Market orders are not supported by the instrument currently.': InvalidOrder, + "Parameter 'instrument' contains invalid value.": BadSymbol, + }, + 'broad': { + 'API Key': AuthenticationError, // "API Key '9edc48de-d5b0-4248-8e7e-f59ffcd1c7f1' doesn't exist." + 'Insufficient funds': InsufficientFunds, // "Insufficient funds: new order requires 10 ETH which is more than the available balance." + 'has been delisted.': BadSymbol, // {"errorDescription":"Instrument '$PAC-BTC' has been delisted."} + 'Mandatory parameter': BadRequest, // {"errorDescription":"Mandatory parameter 'feeCurrency' is missing."} + }, + }, + }); + } + + nonce () { + return this.milliseconds (); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInstruments (params); + // + // [ { symbol: "$PAC-BTC", + // baseCurrency: "$PAC", + // quoteCurrency: "BTC", + // feeCurrency: "BTC", + // tickSize: 1e-8, + // minPrice: 1e-8, + // minVolume: 1, + // supportedOrderTypes: ["limit"], + // state: "active" }, + // { symbol: "ZZC-USD", + // baseCurrency: "ZZC", + // quoteCurrency: "USD", + // feeCurrency: "USD", + // tickSize: 0.0001, + // minPrice: 0.0001, + // minVolume: 1, + // supportedOrderTypes: ["limit"], + // state: "active" } ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const tickSize = this.safeValue (market, 'tickSize'); + const minPrice = this.safeValue (market, 'minPrice'); + const minAmount = this.safeFloat (market, 'minVolume'); + const precision = { + 'amount': this.precisionFromString (this.numberToString (minAmount)), + 'price': this.precisionFromString (this.numberToString (tickSize)), + }; + const active = (market['state'] === 'active'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': minPrice, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ { symbol: "$PAC", + // name: "PACCoin", + // isFiat: false, + // depositsAllowed: true, + // depositConfirmationCount: 8, + // minDeposit: 0, + // withdrawalsAllowed: true, + // withdrawalPrecision: 8, + // minWithdrawal: 4, + // maxWithdrawal: 1000000000, + // flatWithdrawalFee: 2, + // isDelisted: false }, + // { symbol: "ZZC", + // name: "Zozo", + // isFiat: false, + // depositsAllowed: false, + // depositConfirmationCount: 8, + // minDeposit: 0, + // withdrawalsAllowed: false, + // withdrawalPrecision: 8, + // minWithdrawal: 0.2, + // maxWithdrawal: 1000000000, + // flatWithdrawalFee: 0.1, + // isDelisted: false } ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (currency, 'withdrawalPrecision'); + const address = this.safeValue (currency, 'BaseAddress'); + const active = (currency['depositsAllowed'] && currency['withdrawalsAllowed'] && !currency['isDelisted']); + const type = currency['isFiat'] ? 'fiat' : 'crypto'; + result[code] = { + 'id': id, + 'code': code, + 'address': address, + 'info': currency, + 'type': type, + 'name': this.safeString (currency, 'name'), + 'active': active, + 'fee': this.safeFloat (currency, 'flatWithdrawalFee'), // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'deposit': { + 'min': this.safeFloat (currency, 'minDeposit'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawal'), + 'max': this.safeFloat (currency, 'maxWithdrawal'), + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': 'ETH', // comma-separated list of currency ids + // 'nonZeroOnly': 'false', // true by default + }; + const response = await this.accountGetBalance (this.extend (request, params)); + // + // [ + // { + // "currency": "ETH", + // "available": 0.0, + // "reserved": 0.0 + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = maximum = 100 + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { buyLevels: [ { price: 0.03099, volume: 0.00610063 }, + // { price: 0.03097, volume: 1.33455158 }, + // { price: 0.03096, volume: 0.0830889 }, + // { price: 0.03095, volume: 0.0820356 }, + // { price: 0.03093, volume: 0.5499419 }, + // { price: 0.03092, volume: 0.23317494 }, + // { price: 0.03091, volume: 0.62105322 }, + // { price: 0.00620041, volume: 0.003 } ], + // sellLevels: [ { price: 0.03117, volume: 5.47492315 }, + // { price: 0.03118, volume: 1.97744139 }, + // { price: 0.03119, volume: 0.012 }, + // { price: 0.03121, volume: 0.741242 }, + // { price: 0.03122, volume: 0.96178089 }, + // { price: 0.03123, volume: 0.152326 }, + // { price: 0.03124, volume: 2.63462933 }, + // { price: 0.069, volume: 0.004 } ] } + // + return this.parseOrderBook (response, undefined, 'buyLevels', 'sellLevels', 'price', 'volume'); + } + + parseTicker (ticker, market = undefined) { + // + // { instrument: "ZZC-USD", + // last: 0.065, + // percentChange: 0, + // low: 0.065, + // high: 0.065, + // baseVolume: 0, + // quoteVolume: 0, + // volumeInBtc: 0, + // volumeInUsd: 0, + // ask: 0.5, + // bid: 0.0007, + // timestamp: "2018-10-31T09:21:25Z" } ] + // + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const marketId = this.safeString (ticker, 'instrument'); + const symbol = this.safeSymbol (marketId, market, '-'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'percentChange'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'baseVolume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ { instrument: "$PAC-BTC", + // last: 3.3e-7, + // percentChange: 3.125, + // low: 2.7e-7, + // high: 3.3e-7, + // baseVolume: 191700.79823187, + // quoteVolume: 0.0587930939346704, + // volumeInBtc: 0.0587930939346704, + // volumeInUsd: 376.2006339435353, + // ask: 3.3e-7, + // bid: 3.1e-7, + // timestamp: "2018-10-31T09:21:25Z" } ] + // + const numTickers = response.length; + if (numTickers < 1) { + throw new ExchangeError (this.id + ' fetchTicker could not load quotes for symbol ' + symbol); + } + return this.parseTicker (response[0], market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const ids = this.marketIds (symbols); + request['instrument'] = ids.join (','); + } + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ { instrument: "$PAC-BTC", + // last: 3.3e-7, + // percentChange: 3.125, + // low: 2.7e-7, + // high: 3.3e-7, + // baseVolume: 191700.79823187, + // quoteVolume: 0.0587930939346704, + // volumeInBtc: 0.0587930939346704, + // volumeInUsd: 376.2006339435353, + // ask: 3.3e-7, + // bid: 3.1e-7, + // timestamp: "2018-10-31T09:21:25Z" }, + // { instrument: "ZZC-USD", + // last: 0.065, + // percentChange: 0, + // low: 0.065, + // high: 0.065, + // baseVolume: 0, + // quoteVolume: 0, + // volumeInBtc: 0, + // volumeInUsd: 0, + // ask: 0.5, + // bid: 0.0007, + // timestamp: "2018-10-31T09:21:25Z" } ] + // + return this.parseTickers (response, symbols); + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { price: 0.03105, + // volume: 0.11, + // side: "sell", + // timestamp: "2018-10-31T04:19:35Z" } ] + // + // private fetchMyTrades + // + // { + // "id": 3005866, + // "orderId": 468533093, + // "timestamp": "2018-06-02T16:26:27Z", + // "instrument": "BCH-ETH", + // "side": "buy", + // "price": 1.78882, + // "volume": 0.027, + // "fee": 0.0000483, + // "feeCurrency": "ETH" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const id = this.safeString (trade, 'id'); + const side = this.safeString (trade, 'side'); + const orderId = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'instrument'); + const symbol = this.safeSymbol (marketId, market, '-'); + let fee = undefined; + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + const feeCode = this.safeCurrencyCode (feeCurrencyId); + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCode, + }; + } + const takerOrMaker = undefined; + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'cost': cost, + 'amount': amount, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // min 1, max 1000, default 100 + } + const response = await this.publicGetRecentTrades (this.extend (request, params)); + // + // [ { price: 0.03117, + // volume: 0.02597403, + // side: "buy", + // timestamp: "2018-10-31T09:37:46Z" }, + // { price: 0.03105, + // volume: 0.11, + // side: "sell", + // timestamp: "2018-10-31T04:19:35Z" } ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // timestamp: '2019-09-21T10:36:00Z', + // open: 0.02152, + // high: 0.02156, + // low: 0.02152, + // close: 0.02156, + // volume: 0.01741259 + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'granularity': this.timeframes[timeframe], + 'instrument': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // Accepted values: 1 - 1000. If the parameter is not specified, the number of results is limited to 100 + } + const response = await this.publicGetOhlcv (this.extend (request, params)); + // + // [ + // { + // "timestamp": "2020-06-06T17:36:00Z", + // "open": 0.025, + // "high": 0.025, + // "low": 0.02499, + // "close": 0.02499, + // "volume": 0.00643127 + // } + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'submitting': 'open', // A newly created limit order has a status "submitting" until it has been processed. + // This status changes during the lifetime of an order and can have different values depending on the value of the parameter Time In Force. + 'unfilledActive': 'open', // order is active, no trades have been made + 'partiallyFilledActive': 'open', // part of the order has been filled, the other part is active + 'filled': 'closed', // order has been filled entirely + 'partiallyFilledCancelled': 'canceled', // part of the order has been filled, the other part has been cancelled either by the trader or by the system (see the value of cancellationReason of an Order for more details on the reason of cancellation) + 'unfilledCancelled': 'canceled', // order has been cancelled, no trades have taken place (see the value of cancellationReason of an Order for more details on the reason of cancellation) + }; + return (status in statuses) ? statuses[status] : status; + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": 469594855, + // "timestamp": "2018-06-08T16:59:44Z", + // "instrument": "BTS-BTC", + // "side": "buy", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 4.0, + // "price": 0.000025, + // "stopPrice": null, + // "remainingVolume": 4.0, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'instrument'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + const remaining = this.safeFloat (order, 'remainingVolume'); + let filled = undefined; + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'lastUpdate')); + let cost = undefined; + if (remaining !== undefined) { + if (amount !== undefined) { + filled = amount - remaining; + if (this.options['parseOrderToPrecision']) { + filled = parseFloat (this.amountToPrecision (symbol, filled)); + } + filled = Math.max (filled, 0.0); + if (price !== undefined) { + cost = price * filled; + } + } + } + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + const side = this.safeString (order, 'side'); + const fee = undefined; + const trades = undefined; + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + if (this.options['parseOrderToPrecision']) { + cost = parseFloat (this.costToPrecision (symbol, cost)); + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument': market['id'], + 'volume': this.amountToPrecision (symbol, amount), + // The value must comply with the list of order types supported by the instrument (see the value of parameter supportedOrderTypes of the Instrument) + // If the parameter is not specified, the default value "limit" is used + // More about order types in the corresponding section of documentation + 'type': type, // 'limit', 'market', 'stopLimit', in fact as of 2018-10-31, only 'limit' orders are supported for all markets + 'side': side, // 'buy' or 'sell' + // "GTC" - Good-Til-Cancelled + // "IOC" - Immediate-Or-Cancel (currently not supported by the exchange API, reserved for future use) + // "FOK" - Fill-Or-Kill (currently not supported by the exchange API, reserved for future use) + // 'timeInForce': 'GTC', // IOC', 'FOK' + // 'strictValidation': false, // false - prices will be rounded to meet the requirement, true - execution of the method will be aborted and an error message will be returned + }; + let priceIsRequired = false; + let stopPriceIsRequired = false; + if (type === 'limit') { + priceIsRequired = true; + } else if (type === 'stopLimit') { + priceIsRequired = true; + stopPriceIsRequired = true; + } + if (priceIsRequired) { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a price argument for a ' + type + ' order'); + } + request['price'] = this.priceToPrecision (symbol, price); + } + if (stopPriceIsRequired) { + const stopPrice = this.safeFloat (params, 'stopPrice'); + if (stopPrice === undefined) { + throw new InvalidOrder (this.id + ' createOrder method requires a stopPrice extra param for a ' + type + ' order'); + } else { + request['stopPrice'] = this.priceToPrecision (symbol, stopPrice); + } + params = this.omit (params, 'stopPrice'); + } + const response = await this.tradingPostPlaceOrder (this.extend (request, params)); + // + // { + // "id": 469594855, + // "timestamp": "2018-06-08T16:59:44Z", + // "instrument": "BTS-BTC", + // "side": "buy", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 4.0, + // "price": 0.000025, + // "stopPrice": null, + // "remainingVolume": 4.0, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // + return this.parseOrder (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.tradingGetOrderStatus (this.extend (request, params)); + // + // [ + // { + // "id": 466747915, + // "timestamp": "2018-05-26T06:43:49Z", + // "instrument": "UNI-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 5700.0, + // "price": 0.000005, + // "stopPrice": null, + // "remainingVolume": 1.948051948052, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // ] + // + const numOrders = response.length; + if (numOrders < 1) { + throw new OrderNotFound (this.id + ' fetchOrder could not fetch order id ' + id); + } + return this.parseOrder (response[0]); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['from'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (symbol !== undefined) { + const market = this.market (symbol); + request['instrument'] = market['id']; + } + const method = this.safeString (this.options, 'fetchOrdersMethod', 'tradingGetOrderHistory'); + const response = await this[method] (this.extend (request, params)); + // + // [ + // { + // "id": 468535711, + // "timestamp": "2018-06-02T16:42:40Z", + // "instrument": "BTC-EUR", + // "side": "sell", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 0.00770733, + // "price": 6724.9, + // "stopPrice": null, + // "remainingVolume": 0.00770733, + // "lastUpdate": "2018-06-02T16:42:40Z", + // "parentOrderId": null, + // "childOrderId": null + // } + // ] + // + return this.parseOrders (response); + } + + async fetchOrdersByIds (ids = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': ids.join (','), + }; + const response = await this.tradingGetOrderStatus (this.extend (request, params)); + // + // [ + // { + // "id": 466747915, + // "timestamp": "2018-05-26T06:43:49Z", + // "instrument": "UNI-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 5700.0, + // "price": 0.000005, + // "stopPrice": null, + // "remainingVolume": 1.948051948052, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // } + // ] + // + return this.parseOrders (response, undefined, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument'] = market['id']; + } + const response = await this.tradingGetActiveOrders (this.extend (request, params)); + // + // [ + // { + // "id": 466747915, + // "timestamp": "2018-05-26T06:43:49Z", + // "instrument": "UNI-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 5700.0, + // "price": 0.000005, + // "stopPrice": null, + // "remainingVolume": 1.948051948052, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // { + // "id": 466748077, + // "timestamp": "2018-05-26T06:45:29Z", + // "instrument": "PRJ-BTC", + // "side": "sell", + // "type": "limit", + // "status": "partiallyFilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 10000.0, + // "price": 0.0000007, + // "stopPrice": null, + // "remainingVolume": 9975.0, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // ... + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['limit'] = limit; // min 1, max 1000, default 100 + } + const method = this.safeString (this.options, 'fetchClosedOrdersMethod', 'tradingGetOrderHistory'); + const response = await this[method] (this.extend (request, params)); + // [ + // { + // "id": 468535711, + // "timestamp": "2018-06-02T16:42:40Z", + // "instrument": "BTC-EUR", + // "side": "sell", + // "type": "limit", + // "status": "submitting", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 0.00770733, + // "price": 6724.9, + // "stopPrice": null, + // "remainingVolume": 0.00770733, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // { + // "id": 468535707, + // "timestamp": "2018-06-02T16:42:37Z", + // "instrument": "BTG-BTC", + // "side": "buy", + // "type": "limit", + // "status": "unfilledActive", + // "cancellationReason": null, + // "timeInForce": "GTC", + // "volume": 0.0173737, + // "price": 0.00589027, + // "stopPrice": null, + // "remainingVolume": 0.0173737, + // "lastUpdate": null, + // "parentOrderId": null, + // "childOrderId": null + // }, + // ... + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'ids': [ + parseInt (id), + ], + }; + const response = await this.tradingPostCancelOrdersById (this.extend (request, params)); + // + // [ + // 465448358, + // 468364313 + // ] + // + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const response = await this.tradingPostCancelAllOrders (params); + // + // [ + // 465448358, + // 468364313 + // ] + // + return response; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['instrument'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['limit'] = limit; // min 1, max 1000, default 100 + } + const response = await this.tradingGetTradeHistory (this.extend (request, params)); + // + // [ + // { + // "id": 3005866, + // "orderId": 468533093, + // "timestamp": "2018-06-02T16:26:27Z", + // "instrument": "BCH-ETH", + // "side": "buy", + // "price": 1.78882, + // "volume": 0.027, + // "fee": 0.0000483, + // "feeCurrency": "ETH" + // }, + // { + // "id": 3005812, + // "orderId": 468515771, + // "timestamp": "2018-06-02T16:16:05Z", + // "instrument": "ETC-BTC", + // "side": "sell", + // "price": 0.00210958, + // "volume": 0.05994006, + // "fee": -0.000000063224, + // "feeCurrency": "BTC" + // }, + // ... + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['from'] = this.ymd (since, 'T'); + } + const response = await this.accountGetMoneyTransfers (this.extend (request, params)); + // + // [ + // { + // "id": 756446, + // "type": "deposit", + // "currency": "ETH", + // "address": "0x451d5a1b7519aa75164f440df78c74aac96023fe", + // "paymentId": null, + // "amount": 0.142, + // "fee": null, + // "txId": "0x2b49098749840a9482c4894be94f94864b498a1306b6874687a5640cc9871918", + // "createdAt": "2018-06-02T19:30:28Z", + // "processedAt": "2018-06-02T21:10:41Z", + // "confirmationsRequired": 12, + // "confirmationCount": 12, + // "status": "success", + // "errorDescription": null + // }, + // { + // "id": 754618, + // "type": "deposit", + // "currency": "BTC", + // "address": "1IgNfmERVcier4IhfGEfutkLfu4AcmeiUC", + // "paymentId": null, + // "amount": 0.09, + // "fee": null, + // "txId": "6876541687a9187e987c9187654f7198b9718af974641687b19a87987f91874f", + // "createdAt": "2018-06-02T16:19:44Z", + // "processedAt": "2018-06-02T16:20:50Z", + // "confirmationsRequired": 1, + // "confirmationCount": 1, + // "status": "success", + // "errorDescription": null + // }, + // ... + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'deposit', + }; + return this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'withdrawal', + }; + return this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', // transfer is in progress + 'success': 'ok', // completed successfully + 'failed': 'failed', // aborted at some point (money will be credited back to the account of origin) + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": 756446, + // "type": "deposit", + // "currency": "ETH", + // "address": "0x451d5a1b7519aa75164f440df78c74aac96023fe", + // "paymentId": null, + // "amount": 0.142, + // "fee": null, + // "txId": "0x2b49098749840a9482c4894be94f94864b498a1306b6874687a5640cc9871918", + // "createdAt": "2018-06-02T19:30:28Z", + // "processedAt": "2018-06-02T21:10:41Z", + // "confirmationsRequired": 12, + // "confirmationCount": 12, + // "status": "success", + // "errorDescription": null, + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'paymentId'); + const txid = this.safeValue (transaction, 'txId'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const type = this.safeString (transaction, 'type'); + const timestamp = this.parse8601 (this.safeString (transaction, 'createdAt')); + const updated = this.parse8601 (this.safeString (transaction, 'processedAt')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const feeCost = this.safeFloat (transaction, 'fee'); + const fee = { + 'cost': feeCost, + 'currency': code, + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.accountGetDepositAddress (this.extend (request, params)); + // + // { + // "currency": "BTS", + // "address": "crex24", + // "paymentId": "0fg4da4186741579" + // } + // + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'paymentId'); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'address': address, + 'amount': parseFloat (this.currencyToPrecision (code, amount)), + // sets whether the specified amount includes fee, can have either of the two values + // true - balance will be decreased by amount, whereas [amount - fee] will be transferred to the specified address + // false - amount will be deposited to the specified address, whereas the balance will be decreased by [amount + fee] + // 'includeFee': false, // the default value is false + 'feeCurrency': currency['id'], // https://github.com/ccxt/ccxt/issues/7544 + }; + if (tag !== undefined) { + request['paymentId'] = tag; + } + const response = await this.accountPostWithdraw (this.extend (request, params)); + return this.parseTransaction (response); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + this.version + '/' + api + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + request; + if ((api === 'trading') || (api === 'account')) { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const secret = this.base64ToBinary (this.secret); + let auth = request + nonce; + headers = { + 'X-CREX24-API-KEY': this.apiKey, + 'X-CREX24-API-NONCE': nonce, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + body = this.json (params); + auth += body; + } + headers['X-CREX24-API-SIGN'] = this.hmac (this.encode (auth), secret, 'sha512', 'base64'); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!this.isJsonEncodedObject (body)) { + return; // fallback to default error handler + } + if ((code >= 200) && (code < 300)) { + return; // no error + } + const message = this.safeString (response, 'errorDescription'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + if (code === 400) { + throw new BadRequest (feedback); + } else if (code === 401) { + throw new AuthenticationError (feedback); + } else if (code === 403) { + throw new AuthenticationError (feedback); + } else if (code === 429) { + throw new DDoSProtection (feedback); + } else if (code === 500) { + throw new ExchangeError (feedback); + } else if (code === 503) { + throw new ExchangeNotAvailable (feedback); + } else if (code === 504) { + throw new RequestTimeout (feedback); + } + throw new ExchangeError (feedback); // unknown message + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/currencycom.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/currencycom.js new file mode 100644 index 000000000..3914c1bfc --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/currencycom.js @@ -0,0 +1,1119 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError, BadRequest } = require ('./base/errors'); +const { ROUND, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class currencycom extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'currencycom', + 'name': 'Currency.com', + 'countries': [ 'BY' ], // Belarus + 'rateLimit': 500, + 'certified': true, + 'pro': true, + 'version': 'v1', + // new metainfo interface + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTradingFees': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '1d': '1d', + '1w': '1w', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg', + 'api': { + 'public': 'https://api-adapter.backend.currency.com/api', + 'private': 'https://api-adapter.backend.currency.com/api', + }, + 'www': 'https://www.currency.com', + 'referral': 'https://currency.com/trading/signup?c=362jaimv&pid=referral', + 'doc': [ + 'https://currency.com/api', + ], + 'fees': 'https://currency.com/fees-charges', + }, + 'api': { + 'public': { + 'get': [ + 'time', + 'exchangeInfo', + 'depth', + 'aggTrades', + 'klines', + 'ticker/24hr', + ], + }, + 'private': { + 'get': [ + 'leverageSettings', + 'openOrders', + 'tradingPositions', + 'account', + 'myTrades', + ], + 'post': [ + 'order', + 'updateTradingPosition', + 'updateTradingOrder', + 'closeTradingPosition', + ], + 'delete': [ + 'order', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.002, + }, + }, + 'precisionMode': TICK_SIZE, + // exchange-specific options + 'options': { + 'defaultTimeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel, 'FOK' = Fill Or Kill + 'warnOnFetchOpenOrdersWithoutSymbol': true, + 'recvWindow': 5 * 1000, // 5 sec, default + 'timeDifference': 0, // the difference between system clock and Binance clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + 'parseOrderToPrecision': false, // force amounts and costs in parseOrder to precision + 'newOrderRespType': { + 'market': 'FULL', // 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills + 'limit': 'RESULT', // we change it from 'ACK' by default to 'RESULT' + 'stop': 'RESULT', + }, + }, + 'exceptions': { + 'broad': { + 'FIELD_VALIDATION_ERROR Cancel is available only for LIMIT order': InvalidOrder, + 'API key does not exist': AuthenticationError, + 'Order would trigger immediately.': InvalidOrder, + 'Account has insufficient balance for requested action.': InsufficientFunds, + 'Rest API trading is not enabled.': ExchangeNotAvailable, + }, + 'exact': { + '-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."} + '-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL + '-1021': InvalidNonce, // 'your time is ahead of server' + '-1022': AuthenticationError, // {"code":-1022,"msg":"Signature for this request is not valid."} + '-1100': InvalidOrder, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price' + '-1104': ExchangeError, // Not all sent parameters were read, read 8 parameters but was sent 9 + '-1025': AuthenticationError, // {"code":-1025,"msg":"Invalid API-key, IP, or permissions for action"} + '-1128': BadRequest, // {"code":-1128,"msg":"Combination of optional parameters invalid."} + '-2010': ExchangeError, // generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc... + '-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER' + '-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist' + '-2014': AuthenticationError, // { "code":-2014, "msg": "API-key format invalid." } + '-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action." + }, + }, + 'commonCurrencies': { + 'IQ': 'iQIYI', + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "serverTime": 1590998366609 + // } + // + return this.safeInteger (response, 'serverTime'); + } + + async loadTimeDifference (params = {}) { + const response = await this.publicGetTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = parseInt (after - response['serverTime']); + return this.options['timeDifference']; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeInfo (params); + // + // { + // "timezone":"UTC", + // "serverTime":1603252990096, + // "rateLimits":[ + // {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200}, + // {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":1,"limit":10}, + // {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":864000}, + // ], + // "exchangeFilters":[], + // "symbols":[ + // { + // "symbol":"EVK", + // "name":"Evonik", + // "status":"BREAK", + // "baseAsset":"EVK", + // "baseAssetPrecision":3, + // "quoteAsset":"EUR", + // "quoteAssetId":"EUR", + // "quotePrecision":3, + // "orderTypes":["LIMIT","MARKET"], + // "filters":[ + // {"filterType":"LOT_SIZE","minQty":"1","maxQty":"27000","stepSize":"1"}, + // {"filterType":"MIN_NOTIONAL","minNotional":"23"} + // ], + // "marketType":"SPOT", + // "country":"DE", + // "sector":"Basic Materials", + // "industry":"Diversified Chemicals", + // "tradingHours":"UTC; Mon 07:02 - 15:30; Tue 07:02 - 15:30; Wed 07:02 - 15:30; Thu 07:02 - 15:30; Fri 07:02 - 15:30", + // "tickSize":0.005, + // "tickValue":0.11125, + // "exchangeFee":0.05 + // }, + // { + // "symbol":"BTC/USD_LEVERAGE", + // "name":"Bitcoin / USD", + // "status":"TRADING", + // "baseAsset":"BTC", + // "baseAssetPrecision":3, + // "quoteAsset":"USD", + // "quoteAssetId":"USD_LEVERAGE", + // "quotePrecision":3, + // "orderTypes":["LIMIT","MARKET","STOP"], + // "filters":[ + // {"filterType":"LOT_SIZE","minQty":"0.001","maxQty":"100","stepSize":"0.001"}, + // {"filterType":"MIN_NOTIONAL","minNotional":"13"} + // ], + // "marketType":"LEVERAGE", + // "longRate":-0.01, + // "shortRate":0.01, + // "swapChargeInterval":480, + // "country":"", + // "sector":"", + // "industry":"", + // "tradingHours":"UTC; Mon - 21:00, 21:05 -; Tue - 21:00, 21:05 -; Wed - 21:00, 21:05 -; Thu - 21:00, 21:05 -; Fri - 21:00, 22:01 -; Sat - 21:00, 21:05 -; Sun - 20:00, 21:05 -", + // "tickSize":0.05, + // "tickValue":610.20875, + // "makerFee":-0.025, + // "takerFee":0.075 + // }, + // ] + // } + // + if (this.options['adjustForTimeDifference']) { + await this.loadTimeDifference (); + } + const markets = this.safeValue (response, 'symbols'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + if (id.indexOf ('/') >= 0) { + symbol = id; + } + const filters = this.safeValue (market, 'filters', []); + const filtersByType = this.indexBy (filters, 'filterType'); + const precision = { + 'amount': 1 / Math.pow (1, this.safeInteger (market, 'baseAssetPrecision')), + 'price': this.safeFloat (market, 'tickSize'), + }; + const status = this.safeString (market, 'status'); + const active = (status === 'TRADING'); + let type = this.safeStringLower (market, 'marketType'); + if (type === 'leverage') { + type = 'margin'; + } + const spot = (type === 'spot'); + const margin = (type === 'margin'); + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': type, + 'spot': spot, + 'margin': margin, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': -1 * Math.log10 (precision['amount']), + 'max': undefined, + }, + }, + }; + const exchangeFee = this.safeFloat2 (market, 'exchangeFee', 'tradingFee'); + const makerFee = this.safeFloat (market, 'makerFee', exchangeFee); + const takerFee = this.safeFloat (market, 'takerFee', exchangeFee); + if (makerFee !== undefined) { + entry['maker'] = makerFee / 100; + } + if (takerFee !== undefined) { + entry['taker'] = takerFee / 100; + } + if ('PRICE_FILTER' in filtersByType) { + const filter = this.safeValue (filtersByType, 'PRICE_FILTER', {}); + entry['precision']['price'] = this.safeFloat (filter, 'tickSize'); + // PRICE_FILTER reports zero values for maxPrice + // since they updated filter types in November 2018 + // https://github.com/ccxt/ccxt/issues/4286 + // therefore limits['price']['max'] doesn't have any meaningful value except undefined + entry['limits']['price'] = { + 'min': this.safeFloat (filter, 'minPrice'), + 'max': undefined, + }; + const maxPrice = this.safeFloat (filter, 'maxPrice'); + if ((maxPrice !== undefined) && (maxPrice > 0)) { + entry['limits']['price']['max'] = maxPrice; + } + } + if ('LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'LOT_SIZE', {}); + entry['precision']['amount'] = this.safeFloat (filter, 'stepSize'); + entry['limits']['amount'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MARKET_LOT_SIZE' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MARKET_LOT_SIZE', {}); + entry['limits']['market'] = { + 'min': this.safeFloat (filter, 'minQty'), + 'max': this.safeFloat (filter, 'maxQty'), + }; + } + if ('MIN_NOTIONAL' in filtersByType) { + const filter = this.safeValue (filtersByType, 'MIN_NOTIONAL', {}); + entry['limits']['cost']['min'] = this.safeFloat (filter, 'minNotional'); + } + result.push (entry); + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccount (params); + // + // { + // "makerCommission":0.20, + // "takerCommission":0.20, + // "buyerCommission":0.20, + // "sellerCommission":0.20, + // "canTrade":true, + // "canWithdraw":true, + // "canDeposit":true, + // "updateTime":1591056268, + // "balances":[ + // { + // "accountId":5470306579272968, + // "collateralCurrency":true, + // "asset":"ETH", + // "free":0.0, + // "locked":0.0, + // "default":false, + // }, + // ] + // } + // + const accounts = this.safeValue (response, 'balances', []); + const result = []; + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + const accountId = this.safeInteger (account, 'accountId'); + const currencyId = this.safeString (account, 'asset'); + const currencyCode = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': accountId, + 'type': undefined, + 'currency': currencyCode, + 'info': response, + }); + } + return result; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + return { + 'info': response, + 'maker': this.safeFloat (response, 'makerCommission'), + 'taker': this.safeFloat (response, 'takerCommission'), + }; + } + + parseBalanceResponse (response) { + // + // { + // "makerCommission":0.20, + // "takerCommission":0.20, + // "buyerCommission":0.20, + // "sellerCommission":0.20, + // "canTrade":true, + // "canWithdraw":true, + // "canDeposit":true, + // "updateTime":1591056268, + // "balances":[ + // { + // "accountId":5470306579272968, + // "collateralCurrency":true, + // "asset":"ETH", + // "free":0.0, + // "locked":0.0, + // "default":false, + // }, + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'balances', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + // + // { + // "makerCommission":0.20, + // "takerCommission":0.20, + // "buyerCommission":0.20, + // "sellerCommission":0.20, + // "canTrade":true, + // "canWithdraw":true, + // "canDeposit":true, + // "updateTime":1591056268, + // "balances":[ + // { + // "accountId":5470306579272968, + // "collateralCurrency":true, + // "asset":"ETH", + // "free":0.0, + // "locked":0.0, + // "default":false, + // }, + // ] + // } + // + return this.parseBalanceResponse (response); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 1000, valid limits 5, 10, 20, 50, 100, 500, 1000, 5000 + } + const response = await this.publicGetDepth (this.extend (request, params)); + // + // { + // "lastUpdateId":1590999849037, + // "asks":[ + // [0.02495,60.0000], + // [0.02496,120.0000], + // [0.02497,240.0000], + // ], + // "bids":[ + // [0.02487,60.0000], + // [0.02486,120.0000], + // [0.02485,240.0000], + // ] + // } + // + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId'); + return orderbook; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "symbol":"ETH/BTC", + // "priceChange":"0.00030", + // "priceChangePercent":"1.21", + // "weightedAvgPrice":"0.02481", + // "prevClosePrice":"0.02447", + // "lastPrice":"0.02477", + // "lastQty":"60.0", + // "bidPrice":"0.02477", + // "askPrice":"0.02484", + // "openPrice":"0.02447", + // "highPrice":"0.02524", + // "lowPrice":"0.02438", + // "volume":"11.97", + // "quoteVolume":"0.298053", + // "openTime":1590969600000, + // "closeTime":1591000072693 + // } + // + // fetchTickers + // + // { + // "symbol":"EVK", + // "highPrice":"22.57", + // "lowPrice":"22.16", + // "volume":"1", + // "quoteVolume":"22.2", + // "openTime":1590699364000, + // "closeTime":1590785764000 + // } + // + const timestamp = this.safeInteger (ticker, 'closeTime'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'lastPrice'); + const open = this.safeFloat (ticker, 'openPrice'); + let average = undefined; + if ((open !== undefined) && (last !== undefined)) { + average = this.sum (open, last) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat (ticker, 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat (ticker, 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': this.safeFloat (ticker, 'weightedAvgPrice'), + 'open': open, + 'close': last, + 'last': last, + 'previousClose': this.safeFloat (ticker, 'prevClosePrice'), // previous day close + 'change': this.safeFloat (ticker, 'priceChange'), + 'percentage': this.safeFloat (ticker, 'priceChangePercent'), + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker24hr (this.extend (request, params)); + // + // { + // "symbol":"ETH/BTC", + // "priceChange":"0.00030", + // "priceChangePercent":"1.21", + // "weightedAvgPrice":"0.02481", + // "prevClosePrice":"0.02447", + // "lastPrice":"0.02477", + // "lastQty":"60.0", + // "bidPrice":"0.02477", + // "askPrice":"0.02484", + // "openPrice":"0.02447", + // "highPrice":"0.02524", + // "lowPrice":"0.02438", + // "volume":"11.97", + // "quoteVolume":"0.298053", + // "openTime":1590969600000, + // "closeTime":1591000072693 + // } + // + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker24hr (params); + // + // [ + // { + // "symbol":"EVK", + // "highPrice":"22.57", + // "lowPrice":"22.16", + // "volume":"1", + // "quoteVolume":"22.2", + // "openTime":1590699364000, + // "closeTime":1590785764000 + // } + // ] + // + return this.parseTickers (response, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1590971040000, + // "0.02454", + // "0.02456", + // "0.02452", + // "0.02456", + // 249 + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.publicGetKlines (this.extend (request, params)); + // + // [ + // [1590971040000,"0.02454","0.02456","0.02452","0.02456",249], + // [1590971100000,"0.02455","0.02457","0.02452","0.02456",300], + // [1590971160000,"0.02455","0.02456","0.02453","0.02454",286], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public aggregate trades) + // + // { + // "a":1658318071, + // "p":"0.02476", + // "q":"0.0", + // "T":1591001423382, + // "m":false + // } + // + // createOrder fills (private) + // + // { + // "price": "9807.05", + // "qty": "0.01", + // "commission": "0", + // "commissionAsset": "dUSD" + // } + // + // fetchMyTrades + // + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true + // } + // + const timestamp = this.safeInteger2 (trade, 'T', 'time'); + const price = this.safeFloat2 (trade, 'p', 'price'); + const amount = this.safeFloat2 (trade, 'q', 'qty'); + const id = this.safeString2 (trade, 'a', 'id'); + let side = undefined; + const orderId = this.safeString (trade, 'orderId'); + if ('m' in trade) { + side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally + } else if ('isBuyerMaker' in trade) { + side = trade['isBuyerMaker'] ? 'sell' : 'buy'; + } else { + if ('isBuyer' in trade) { + side = (trade['isBuyer']) ? 'buy' : 'sell'; // this is a true side + } + } + let fee = undefined; + if ('commission' in trade) { + fee = { + 'cost': this.safeFloat (trade, 'commission'), + 'currency': this.safeCurrencyCode (this.safeString (trade, 'commissionAsset')), + }; + } + let takerOrMaker = undefined; + if ('isMaker' in trade) { + takerOrMaker = trade['isMaker'] ? 'maker' : 'taker'; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': price * amount, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'limit': 500, // default 500, max 1000 + }; + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.publicGetAggTrades (this.extend (request, params)); + // + // [ + // { + // "a":1658318071, + // "p":"0.02476", + // "q":"0.0", + // "T":1591001423382, + // "m":false + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'PARTIALLY_FILLED': 'open', + 'FILLED': 'closed', + 'CANCELED': 'canceled', + 'PENDING_CANCEL': 'canceling', // currently unused + 'REJECTED': 'rejected', + 'EXPIRED': 'expired', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "symbol": "BTC/USD", + // "orderId": "00000000-0000-0000-0000-0000000c0263", + // "clientOrderId": "00000000-0000-0000-0000-0000000c0263", + // "transactTime": 1589878206426, + // "price": "9825.66210000", + // "origQty": "0.01", + // "executedQty": "0.01", + // "status": "FILLED", + // "timeInForce": "FOK", + // "type": "MARKET", + // "side": "BUY", + // "fills": [ + // { + // "price": "9807.05", + // "qty": "0.01", + // "commission": "0", + // "commissionAsset": "dUSD" + // } + // ] + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '/'); + let timestamp = undefined; + if ('time' in order) { + timestamp = this.safeInteger (order, 'time'); + } else if ('transactTime' in order) { + timestamp = this.safeInteger (order, 'transactTime'); + } + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'origQty'); + const filled = this.safeFloat (order, 'executedQty'); + let remaining = undefined; + let cost = this.safeFloat (order, 'cummulativeQuoteQty'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + if (this.options['parseOrderToPrecision']) { + remaining = parseFloat (this.amountToPrecision (symbol, remaining)); + } + remaining = Math.max (remaining, 0.0); + } + if (price !== undefined) { + if (cost === undefined) { + cost = price * filled; + } + } + } + const id = this.safeString (order, 'orderId'); + const type = this.safeStringLower (order, 'type'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + const side = this.safeStringLower (order, 'side'); + let fee = undefined; + let trades = undefined; + const fills = this.safeValue (order, 'fills'); + if (fills !== undefined) { + trades = this.parseTrades (fills, market); + const numTrades = trades.length; + if (numTrades > 0) { + cost = trades[0]['cost']; + fee = { + 'cost': trades[0]['fee']['cost'], + 'currency': trades[0]['fee']['currency'], + }; + for (let i = 1; i < trades.length; i++) { + cost = this.sum (cost, trades[i]['cost']); + fee['cost'] = this.sum (fee['cost'], trades[i]['fee']['cost']); + } + } + } + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + if (this.options['parseOrderToPrecision']) { + cost = parseFloat (this.costToPrecision (symbol, cost)); + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'info': order, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let accountId = undefined; + if (market['margin']) { + accountId = this.safeInteger (params, 'accountId'); + if (accountId === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires an accountId parameter for ' + market['type'] + ' market ' + symbol); + } + } + const uppercaseType = type.toUpperCase (); + const newOrderRespType = this.safeValue (this.options['newOrderRespType'], type, 'RESULT'); + const request = { + 'symbol': market['id'], + 'quantity': this.amountToPrecision (symbol, amount), + 'type': uppercaseType, + 'side': side.toUpperCase (), + 'newOrderRespType': newOrderRespType, // 'RESULT' for full order or 'FULL' for order with fills + // 'leverage': 1, + // 'accountId': 5470306579272968, // required for leverage markets + // 'takeProfit': '123.45', + // 'stopLoss': '54.321' + // 'guaranteedStopLoss': '54.321', + }; + if (uppercaseType === 'LIMIT') { + request['price'] = this.priceToPrecision (symbol, price); + request['timeInForce'] = this.options['defaultTimeInForce']; // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel, 'FOK' = Fill Or Kill + } else if (uppercaseType === 'STOP') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrder (this.extend (request, params)); + // + // { + // "symbol": "BTC/USD", + // "orderId": "00000000-0000-0000-0000-0000000c0263", + // "clientOrderId": "00000000-0000-0000-0000-0000000c0263", + // "transactTime": 1589878206426, + // "price": "9825.66210000", + // "origQty": "0.01", + // "executedQty": "0.01", + // "status": "FILLED", + // "timeInForce": "FOK", + // "type": "MARKET", + // "side": "BUY", + // "fills": [ + // { + // "price": "9807.05", + // "qty": "0.01", + // "commission": "0", + // "commissionAsset": "dUSD" + // } + // ] + // } + // + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) { + const symbols = this.symbols; + const numSymbols = symbols.length; + const fetchOpenOrdersRateLimit = parseInt (numSymbols / 2); + throw new ExchangeError (this.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.'); + } + const response = await this.privateGetOpenOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const origClientOrderId = this.safeValue (params, 'origClientOrderId'); + const request = { + 'symbol': market['id'], + // 'orderId': parseInt (id), + // 'origClientOrderId': id, + }; + if (origClientOrderId === undefined) { + request['orderId'] = id; + } else { + request['origClientOrderId'] = origClientOrderId; + } + const response = await this.privateDeleteOrder (this.extend (request, params)); + // + // { + // "symbol":"ETH/USD", + // "orderId":"00000000-0000-0000-0000-00000024383b", + // "clientOrderId":"00000000-0000-0000-0000-00000024383b", + // "price":"150", + // "origQty":"0.1", + // "executedQty":"0.0", + // "status":"CANCELED", + // "timeInForce":"GTC", + // "type":"LIMIT", + // "side":"BUY" + // } + // + return this.parseOrder (response, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetMyTrades (this.extend (request, params)); + // + // [ + // { + // "symbol": "BNBBTC", + // "id": 28457, + // "orderId": 100234, + // "price": "4.00000100", + // "qty": "12.00000000", + // "commission": "10.10000000", + // "commissionAsset": "BNB", + // "time": 1499865549590, + // "isBuyer": true, + // "isMaker": false, + // "isBestMatch": true + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + path; + if (path === 'historicalTrades') { + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + } + if (api === 'private') { + this.checkRequiredCredentials (); + let query = this.urlencode (this.extend ({ + 'timestamp': this.nonce (), + 'recvWindow': this.options['recvWindow'], + }, params)); + const signature = this.hmac (this.encode (query), this.encode (this.secret)); + query += '&' + 'signature=' + signature; + headers = { + 'X-MBX-APIKEY': this.apiKey, + }; + if ((method === 'GET') || (method === 'DELETE')) { + url += '?' + query; + } else { + body = query; + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + } else { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((httpCode === 418) || (httpCode === 429)) { + throw new DDoSProtection (this.id + ' ' + httpCode.toString () + ' ' + reason + ' ' + body); + } + // error response in a form: { "code": -1013, "msg": "Invalid quantity." } + // following block cointains legacy checks against message patterns in "msg" property + // will switch "code" checks eventually, when we know all of them + if (httpCode >= 400) { + if (body.indexOf ('Price * QTY is zero or less') >= 0) { + throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body); + } + if (body.indexOf ('LOT_SIZE') >= 0) { + throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size ' + body); + } + if (body.indexOf ('PRICE_FILTER') >= 0) { + throw new InvalidOrder (this.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use this.priceToPrecision (symbol, amount) ' + body); + } + } + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"code":-1128,"msg":"Combination of optional parameters invalid."} + // + const errorCode = this.safeString (response, 'code'); + if ((errorCode !== undefined) && (errorCode !== '0')) { + const feedback = this.id + ' ' + this.json (response); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + const message = this.safeString (response, 'msg'); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/deribit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/deribit.js new file mode 100644 index 000000000..9f88f869b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/deribit.js @@ -0,0 +1,1785 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, DDoSProtection, NotSupported, ExchangeNotAvailable, InsufficientFunds, BadRequest, InvalidAddress, OnMaintenance } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class deribit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'deribit', + 'name': 'Deribit', + 'countries': [ 'NL' ], // Netherlands + 'version': 'v2', + 'userAgent': undefined, + 'rateLimit': 500, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '3m': '3', + '5m': '5', + '10m': '10', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '3h': '180', + '6h': '360', + '12h': '720', + '1d': '1D', + }, + 'urls': { + 'test': 'https://test.deribit.com', + 'logo': 'https://user-images.githubusercontent.com/1294454/41933112-9e2dd65a-798b-11e8-8440-5bab2959fcb8.jpg', + 'api': 'https://www.deribit.com', + 'www': 'https://www.deribit.com', + 'doc': [ + 'https://docs.deribit.com/v2', + 'https://github.com/deribit', + ], + 'fees': 'https://www.deribit.com/pages/information/fees', + 'referral': 'https://www.deribit.com/reg-1189.4038', + }, + 'api': { + 'public': { + 'get': [ + // Authentication + 'auth', + 'exchange_token', + 'fork_token', + // Session management + 'set_heartbeat', + 'disable_heartbeat', + // Supporting + 'get_time', + 'hello', + 'test', + // Subscription management + 'subscribe', + 'unsubscribe', + // Account management + 'get_announcements', + // Market data + 'get_book_summary_by_currency', + 'get_book_summary_by_instrument', + 'get_contract_size', + 'get_currencies', + 'get_funding_chart_data', + 'get_funding_rate_history', + 'get_funding_rate_value', + 'get_historical_volatility', + 'get_index', + 'get_instruments', + 'get_last_settlements_by_currency', + 'get_last_settlements_by_instrument', + 'get_last_trades_by_currency', + 'get_last_trades_by_currency_and_time', + 'get_last_trades_by_instrument', + 'get_last_trades_by_instrument_and_time', + 'get_order_book', + 'get_trade_volumes', + 'get_tradingview_chart_data', + 'ticker', + ], + }, + 'private': { + 'get': [ + // Authentication + 'logout', + // Session management + 'enable_cancel_on_disconnect', + 'disable_cancel_on_disconnect', + 'get_cancel_on_disconnect', + // Subscription management + 'subscribe', + 'unsubscribe', + // Account management + 'change_api_key_name', + 'change_scope_in_api_key', + 'change_subaccount_name', + 'create_api_key', + 'create_subaccount', + 'disable_api_key', + 'disable_tfa_for_subaccount', + 'enable_api_key', + 'get_account_summary', + 'get_email_language', + 'get_new_announcements', + 'get_position', + 'get_positions', + 'get_subaccounts', + 'list_api_keys', + 'remove_api_key', + 'reset_api_key', + 'set_announcement_as_read', + 'set_api_key_as_default', + 'set_email_for_subaccount', + 'set_email_language', + 'set_password_for_subaccount', + 'toggle_notifications_from_subaccount', + 'toggle_subaccount_login', + // Block Trade + 'execute_block_trade', + 'get_block_trade', + 'get_last_block_trades_by_currency', + 'invalidate_block_trade_signature', + 'verify_block_trade', + // Trading + 'buy', + 'sell', + 'edit', + 'cancel', + 'cancel_all', + 'cancel_all_by_currency', + 'cancel_all_by_instrument', + 'cancel_by_label', + 'close_position', + 'get_margins', + 'get_open_orders_by_currency', + 'get_open_orders_by_instrument', + 'get_order_history_by_currency', + 'get_order_history_by_instrument', + 'get_order_margin_by_ids', + 'get_order_state', + 'get_stop_order_history', + 'get_user_trades_by_currency', + 'get_user_trades_by_currency_and_time', + 'get_user_trades_by_instrument', + 'get_user_trades_by_instrument_and_time', + 'get_user_trades_by_order', + 'get_settlement_history_by_instrument', + 'get_settlement_history_by_currency', + // Wallet + 'cancel_transfer_by_id', + 'cancel_withdrawal', + 'create_deposit_address', + 'get_current_deposit_address', + 'get_deposits', + 'get_transfers', + 'get_withdrawals', + 'submit_transfer_to_subaccount', + 'submit_transfer_to_user', + 'withdraw', + ], + }, + }, + 'exceptions': { + // 0 or absent Success, No error. + '9999': PermissionDenied, // 'api_not_enabled' User didn't enable API for the Account. + '10000': AuthenticationError, // 'authorization_required' Authorization issue, invalid or absent signature etc. + '10001': ExchangeError, // 'error' Some general failure, no public information available. + '10002': InvalidOrder, // 'qty_too_low' Order quantity is too low. + '10003': InvalidOrder, // 'order_overlap' Rejection, order overlap is found and self-trading is not enabled. + '10004': OrderNotFound, // 'order_not_found' Attempt to operate with order that can't be found by specified id. + '10005': InvalidOrder, // 'price_too_low ' Price is too low, defines current limit for the operation. + '10006': InvalidOrder, // 'price_too_low4idx ' Price is too low for current index, defines current bottom limit for the operation. + '10007': InvalidOrder, // 'price_too_high ' Price is too high, defines current up limit for the operation. + '10008': InvalidOrder, // 'price_too_high4idx ' Price is too high for current index, defines current up limit for the operation. + '10009': InsufficientFunds, // 'not_enough_funds' Account has not enough funds for the operation. + '10010': OrderNotFound, // 'already_closed' Attempt of doing something with closed order. + '10011': InvalidOrder, // 'price_not_allowed' This price is not allowed for some reason. + '10012': InvalidOrder, // 'book_closed' Operation for instrument which order book had been closed. + '10013': PermissionDenied, // 'pme_max_total_open_orders ' Total limit of open orders has been exceeded, it is applicable for PME users. + '10014': PermissionDenied, // 'pme_max_future_open_orders ' Limit of count of futures' open orders has been exceeded, it is applicable for PME users. + '10015': PermissionDenied, // 'pme_max_option_open_orders ' Limit of count of options' open orders has been exceeded, it is applicable for PME users. + '10016': PermissionDenied, // 'pme_max_future_open_orders_size ' Limit of size for futures has been exceeded, it is applicable for PME users. + '10017': PermissionDenied, // 'pme_max_option_open_orders_size ' Limit of size for options has been exceeded, it is applicable for PME users. + '10018': PermissionDenied, // 'non_pme_max_future_position_size ' Limit of size for futures has been exceeded, it is applicable for non-PME users. + '10019': PermissionDenied, // 'locked_by_admin' Trading is temporary locked by admin. + '10020': ExchangeError, // 'invalid_or_unsupported_instrument' Instrument name is not valid. + '10021': InvalidOrder, // 'invalid_amount' Amount is not valid. + '10022': InvalidOrder, // 'invalid_quantity' quantity was not recognized as a valid number (for API v1). + '10023': InvalidOrder, // 'invalid_price' price was not recognized as a valid number. + '10024': InvalidOrder, // 'invalid_max_show' max_show parameter was not recognized as a valid number. + '10025': InvalidOrder, // 'invalid_order_id' Order id is missing or its format was not recognized as valid. + '10026': InvalidOrder, // 'price_precision_exceeded' Extra precision of the price is not supported. + '10027': InvalidOrder, // 'non_integer_contract_amount' Futures contract amount was not recognized as integer. + '10028': DDoSProtection, // 'too_many_requests' Allowed request rate has been exceeded. + '10029': OrderNotFound, // 'not_owner_of_order' Attempt to operate with not own order. + '10030': ExchangeError, // 'must_be_websocket_request' REST request where Websocket is expected. + '10031': ExchangeError, // 'invalid_args_for_instrument' Some of arguments are not recognized as valid. + '10032': InvalidOrder, // 'whole_cost_too_low' Total cost is too low. + '10033': NotSupported, // 'not_implemented' Method is not implemented yet. + '10034': InvalidOrder, // 'stop_price_too_high' Stop price is too high. + '10035': InvalidOrder, // 'stop_price_too_low' Stop price is too low. + '10036': InvalidOrder, // 'invalid_max_show_amount' Max Show Amount is not valid. + '10040': ExchangeNotAvailable, // 'retry' Request can't be processed right now and should be retried. + '10041': OnMaintenance, // 'settlement_in_progress' Settlement is in progress. Every day at settlement time for several seconds, the system calculates user profits and updates balances. That time trading is paused for several seconds till the calculation is completed. + '10043': InvalidOrder, // 'price_wrong_tick' Price has to be rounded to a certain tick size. + '10044': InvalidOrder, // 'stop_price_wrong_tick' Stop Price has to be rounded to a certain tick size. + '10045': InvalidOrder, // 'can_not_cancel_liquidation_order' Liquidation order can't be canceled. + '10046': InvalidOrder, // 'can_not_edit_liquidation_order' Liquidation order can't be edited. + '10047': DDoSProtection, // 'matching_engine_queue_full' Reached limit of pending Matching Engine requests for user. + '10048': ExchangeError, // 'not_on_this_server' The requested operation is not available on this server. + '11008': InvalidOrder, // 'already_filled' This request is not allowed in regards to the filled order. + '11029': BadRequest, // 'invalid_arguments' Some invalid input has been detected. + '11030': ExchangeError, // 'other_reject ' Some rejects which are not considered as very often, more info may be specified in . + '11031': ExchangeError, // 'other_error ' Some errors which are not considered as very often, more info may be specified in . + '11035': DDoSProtection, // 'no_more_stops ' Allowed amount of stop orders has been exceeded. + '11036': InvalidOrder, // 'invalid_stoppx_for_index_or_last' Invalid StopPx (too high or too low) as to current index or market. + '11037': BadRequest, // 'outdated_instrument_for_IV_order' Instrument already not available for trading. + '11038': InvalidOrder, // 'no_adv_for_futures' Advanced orders are not available for futures. + '11039': InvalidOrder, // 'no_adv_postonly' Advanced post-only orders are not supported yet. + '11041': InvalidOrder, // 'not_adv_order' Advanced order properties can't be set if the order is not advanced. + '11042': PermissionDenied, // 'permission_denied' Permission for the operation has been denied. + '11043': BadRequest, // 'bad_argument' Bad argument has been passed. + '11044': InvalidOrder, // 'not_open_order' Attempt to do open order operations with the not open order. + '11045': BadRequest, // 'invalid_event' Event name has not been recognized. + '11046': BadRequest, // 'outdated_instrument' At several minutes to instrument expiration, corresponding advanced implied volatility orders are not allowed. + '11047': BadRequest, // 'unsupported_arg_combination' The specified combination of arguments is not supported. + '11048': ExchangeError, // 'wrong_max_show_for_option' Wrong Max Show for options. + '11049': BadRequest, // 'bad_arguments' Several bad arguments have been passed. + '11050': BadRequest, // 'bad_request' Request has not been parsed properly. + '11051': OnMaintenance, // 'system_maintenance' System is under maintenance. + '11052': ExchangeError, // 'subscribe_error_unsubscribed' Subscription error. However, subscription may fail without this error, please check list of subscribed channels returned, as some channels can be not subscribed due to wrong input or lack of permissions. + '11053': ExchangeError, // 'transfer_not_found' Specified transfer is not found. + '11090': InvalidAddress, // 'invalid_addr' Invalid address. + '11091': InvalidAddress, // 'invalid_transfer_address' Invalid addres for the transfer. + '11092': InvalidAddress, // 'address_already_exist' The address already exists. + '11093': DDoSProtection, // 'max_addr_count_exceeded' Limit of allowed addresses has been reached. + '11094': ExchangeError, // 'internal_server_error' Some unhandled error on server. Please report to admin. The details of the request will help to locate the problem. + '11095': ExchangeError, // 'disabled_deposit_address_creation' Deposit address creation has been disabled by admin. + '11096': ExchangeError, // 'address_belongs_to_user' Withdrawal instead of transfer. + '12000': AuthenticationError, // 'bad_tfa' Wrong TFA code + '12001': DDoSProtection, // 'too_many_subaccounts' Limit of subbacounts is reached. + '12002': ExchangeError, // 'wrong_subaccount_name' The input is not allowed as name of subaccount. + '12998': AuthenticationError, // 'tfa_over_limit' The number of failed TFA attempts is limited. + '12003': AuthenticationError, // 'login_over_limit' The number of failed login attempts is limited. + '12004': AuthenticationError, // 'registration_over_limit' The number of registration requests is limited. + '12005': AuthenticationError, // 'country_is_banned' The country is banned (possibly via IP check). + '12100': ExchangeError, // 'transfer_not_allowed' Transfer is not allowed. Possible wrong direction or other mistake. + '12999': AuthenticationError, // 'tfa_used' TFA code is correct but it is already used. Please, use next code. + '13000': AuthenticationError, // 'invalid_login' Login name is invalid (not allowed or it contains wrong characters). + '13001': AuthenticationError, // 'account_not_activated' Account must be activated. + '13002': PermissionDenied, // 'account_blocked' Account is blocked by admin. + '13003': AuthenticationError, // 'tfa_required' This action requires TFA authentication. + '13004': AuthenticationError, // 'invalid_credentials' Invalid credentials has been used. + '13005': AuthenticationError, // 'pwd_match_error' Password confirmation error. + '13006': AuthenticationError, // 'security_error' Invalid Security Code. + '13007': AuthenticationError, // 'user_not_found' User's security code has been changed or wrong. + '13008': ExchangeError, // 'request_failed' Request failed because of invalid input or internal failure. + '13009': AuthenticationError, // 'unauthorized' Wrong or expired authorization token or bad signature. For example, please check scope of the token, 'connection' scope can't be reused for other connections. + '13010': BadRequest, // 'value_required' Invalid input, missing value. + '13011': BadRequest, // 'value_too_short' Input is too short. + '13012': PermissionDenied, // 'unavailable_in_subaccount' Subaccount restrictions. + '13013': BadRequest, // 'invalid_phone_number' Unsupported or invalid phone number. + '13014': BadRequest, // 'cannot_send_sms' SMS sending failed -- phone number is wrong. + '13015': BadRequest, // 'invalid_sms_code' Invalid SMS code. + '13016': BadRequest, // 'invalid_input' Invalid input. + '13017': ExchangeError, // 'subscription_failed' Subscription hailed, invalid subscription parameters. + '13018': ExchangeError, // 'invalid_content_type' Invalid content type of the request. + '13019': ExchangeError, // 'orderbook_closed' Closed, expired order book. + '13020': ExchangeError, // 'not_found' Instrument is not found, invalid instrument name. + '13021': PermissionDenied, // 'forbidden' Not enough permissions to execute the request, forbidden. + '13025': ExchangeError, // 'method_switched_off_by_admin' API method temporarily switched off by administrator. + '-32602': BadRequest, // 'Invalid params' see JSON-RPC spec. + '-32601': BadRequest, // 'Method not found' see JSON-RPC spec. + '-32700': BadRequest, // 'Parse error' see JSON-RPC spec. + '-32000': BadRequest, // 'Missing params' see JSON-RPC spec. + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'code': 'BTC', + 'fetchBalance': { + 'code': 'BTC', + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetGetTime (params); + // + // { + // jsonrpc: '2.0', + // result: 1583922446019, + // usIn: 1583922446019955, + // usOut: 1583922446019956, + // usDiff: 1, + // testnet: false + // } + // + return this.safeInteger (response, 'result'); + } + + codeFromOptions (methodName) { + const defaultCode = this.safeValue (this.options, 'code', 'BTC'); + const options = this.safeValue (this.options, methodName, {}); + return this.safeValue (options, 'code', defaultCode); + } + + async fetchStatus (params = {}) { + const request = { + // 'expected_result': false, // true will trigger an error for testing purposes + }; + await this.publicGetTest (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { version: '1.2.26' }, + // usIn: 1583922623964485, + // usOut: 1583922623964487, + // usDiff: 2, + // testnet: false + // } + // + this.status = this.extend (this.status, { + 'status': 'ok', + 'updated': this.milliseconds (), + }); + return this.status; + } + + async fetchMarkets (params = {}) { + const currenciesResponse = await this.publicGetGetCurrencies (params); + // + // { + // jsonrpc: '2.0', + // result: [ + // { + // withdrawal_priorities: [ + // { value: 0.15, name: 'very_low' }, + // { value: 1.5, name: 'very_high' }, + // ], + // withdrawal_fee: 0.0005, + // min_withdrawal_fee: 0.0005, + // min_confirmations: 1, + // fee_precision: 4, + // currency_long: 'Bitcoin', + // currency: 'BTC', + // coin_type: 'BITCOIN' + // } + // ], + // usIn: 1583761588590479, + // usOut: 1583761588590544, + // usDiff: 65, + // testnet: false + // } + // + const currenciesResult = this.safeValue (currenciesResponse, 'result', []); + const result = []; + for (let i = 0; i < currenciesResult.length; i++) { + const currencyId = this.safeString (currenciesResult[i], 'currency'); + const request = { + 'currency': currencyId, + }; + const instrumentsResponse = await this.publicGetGetInstruments (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: [ + // { + // tick_size: 0.0005, + // taker_commission: 0.0004, + // strike: 300, + // settlement_period: 'week', + // quote_currency: 'USD', + // option_type: 'call', + // min_trade_amount: 1, + // maker_commission: 0.0004, + // kind: 'option', + // is_active: true, + // instrument_name: 'ETH-13MAR20-300-C', + // expiration_timestamp: 1584086400000, + // creation_timestamp: 1582790403000, + // contract_size: 1, + // base_currency: 'ETH' + // }, + // ], + // usIn: 1583761889500586, + // usOut: 1583761889505066, + // usDiff: 4480, + // testnet: false + // } + // + const instrumentsResult = this.safeValue (instrumentsResponse, 'result', []); + for (let k = 0; k < instrumentsResult.length; k++) { + const market = instrumentsResult[k]; + const id = this.safeString (market, 'instrument_name'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const type = this.safeString (market, 'kind'); + const future = (type === 'future'); + const option = (type === 'option'); + const active = this.safeValue (market, 'is_active'); + const minTradeAmount = this.safeFloat (market, 'min_trade_amount'); + const tickSize = this.safeFloat (market, 'tick_size'); + const precision = { + 'amount': minTradeAmount, + 'price': tickSize, + }; + result.push ({ + 'id': id, + 'symbol': id, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'taker': this.safeFloat (market, 'taker_commission'), + 'maker': this.safeFloat (market, 'maker_commission'), + 'limits': { + 'amount': { + 'min': minTradeAmount, + 'max': undefined, + }, + 'price': { + 'min': tickSize, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'type': type, + 'spot': false, + 'future': future, + 'option': option, + 'info': market, + }); + } + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const code = this.codeFromOptions ('fetchBalance'); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetGetAccountSummary (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // total_pl: 0, + // session_upl: 0, + // session_rpl: 0, + // session_funding: 0, + // portfolio_margining_enabled: false, + // options_vega: 0, + // options_theta: 0, + // options_session_upl: 0, + // options_session_rpl: 0, + // options_pl: 0, + // options_gamma: 0, + // options_delta: 0, + // margin_balance: 0.00062359, + // maintenance_margin: 0, + // limits: { + // non_matching_engine_burst: 300, + // non_matching_engine: 200, + // matching_engine_burst: 20, + // matching_engine: 2 + // }, + // initial_margin: 0, + // futures_session_upl: 0, + // futures_session_rpl: 0, + // futures_pl: 0, + // equity: 0.00062359, + // deposit_address: '13tUtNsJSZa1F5GeCmwBywVrymHpZispzw', + // delta_total: 0, + // currency: 'BTC', + // balance: 0.00062359, + // available_withdrawal_funds: 0.00062359, + // available_funds: 0.00062359 + // }, + // usIn: 1583775838115975, + // usOut: 1583775838116520, + // usDiff: 545, + // testnet: false + // } + // + const result = { + 'info': response, + }; + const balance = this.safeValue (response, 'result', {}); + const currencyId = this.safeString (balance, 'currency'); + const currencyCode = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableFunds'); + account['used'] = this.safeFloat (balance, 'maintenanceMargin'); + account['total'] = this.safeFloat (balance, 'equity'); + result[currencyCode] = account; + return this.parseBalance (result); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetCreateDepositAddress (this.extend (request, params)); + // + // { + // 'jsonrpc': '2.0', + // 'id': 7538, + // 'result': { + // 'address': '2N8udZGBc1hLRCFsU9kGwMPpmYUwMFTuCwB', + // 'creation_timestamp': 1550575165170, + // 'currency': 'BTC', + // 'type': 'deposit' + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const address = this.safeString (result, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetGetCurrentDepositAddress (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // type: 'deposit', + // status: 'ready', + // requires_confirmation: true, + // currency: 'BTC', + // creation_timestamp: 1514694684651, + // address: '13tUtNsJSZa1F5GeCmwBywVrymHpZispzw' + // }, + // usIn: 1583785137274288, + // usOut: 1583785137274454, + // usDiff: 166, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', {}); + const address = this.safeString (result, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker /public/ticker + // + // { + // timestamp: 1583778859480, + // stats: { volume: 60627.57263769, low: 7631.5, high: 8311.5 }, + // state: 'open', + // settlement_price: 7903.21, + // open_interest: 111543850, + // min_price: 7634, + // max_price: 7866.51, + // mark_price: 7750.02, + // last_price: 7750.5, + // instrument_name: 'BTC-PERPETUAL', + // index_price: 7748.01, + // funding_8h: 0.0000026, + // current_funding: 0, + // best_bid_price: 7750, + // best_bid_amount: 19470, + // best_ask_price: 7750.5, + // best_ask_amount: 343280 + // } + // + // fetchTicker /public/get_book_summary_by_instrument + // fetchTickers /public/get_book_summary_by_currency + // + // { + // volume: 124.1, + // underlying_price: 7856.445926872601, + // underlying_index: 'SYN.BTC-10MAR20', + // quote_currency: 'USD', + // open_interest: 121.8, + // mid_price: 0.01975, + // mark_price: 0.01984559, + // low: 0.0095, + // last: 0.0205, + // interest_rate: 0, + // instrument_name: 'BTC-10MAR20-7750-C', + // high: 0.0295, + // estimated_delivery_price: 7856.29, + // creation_timestamp: 1583783678366, + // bid_price: 0.0185, + // base_currency: 'BTC', + // ask_price: 0.021 + // }, + // + const timestamp = this.safeInteger2 (ticker, 'timestamp', 'creation_timestamp'); + const marketId = this.safeString (ticker, 'instrument_name'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat2 (ticker, 'last_price', 'last'); + const stats = this.safeValue (ticker, 'stats', ticker); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (stats, 'high', 'max_price'), + 'low': this.safeFloat2 (stats, 'low', 'min_price'), + 'bid': this.safeFloat2 (ticker, 'best_bid_price', 'bid_price'), + 'bidVolume': this.safeFloat (ticker, 'best_bid_amount'), + 'ask': this.safeFloat2 (ticker, 'best_ask_price', 'ask_price'), + 'askVolume': this.safeFloat (ticker, 'best_ask_amount'), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (stats, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // timestamp: 1583778859480, + // stats: { volume: 60627.57263769, low: 7631.5, high: 8311.5 }, + // state: 'open', + // settlement_price: 7903.21, + // open_interest: 111543850, + // min_price: 7634, + // max_price: 7866.51, + // mark_price: 7750.02, + // last_price: 7750.5, + // instrument_name: 'BTC-PERPETUAL', + // index_price: 7748.01, + // funding_8h: 0.0000026, + // current_funding: 0, + // best_bid_price: 7750, + // best_bid_amount: 19470, + // best_ask_price: 7750.5, + // best_ask_amount: 343280 + // }, + // usIn: 1583778859483941, + // usOut: 1583778859484075, + // usDiff: 134, + // testnet: false + // } + // + const result = this.safeValue (response, 'result'); + return this.parseTicker (result, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const code = this.codeFromOptions ('fetchTickers'); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.publicGetGetBookSummaryByCurrency (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: [ + // { + // volume: 124.1, + // underlying_price: 7856.445926872601, + // underlying_index: 'SYN.BTC-10MAR20', + // quote_currency: 'USD', + // open_interest: 121.8, + // mid_price: 0.01975, + // mark_price: 0.01984559, + // low: 0.0095, + // last: 0.0205, + // interest_rate: 0, + // instrument_name: 'BTC-10MAR20-7750-C', + // high: 0.0295, + // estimated_delivery_price: 7856.29, + // creation_timestamp: 1583783678366, + // bid_price: 0.0185, + // base_currency: 'BTC', + // ask_price: 0.021 + // }, + // ], + // usIn: 1583783678361966, + // usOut: 1583783678372069, + // usDiff: 10103, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', []); + const tickers = {}; + for (let i = 0; i < result.length; i++) { + const ticker = this.parseTicker (result[i]); + const symbol = ticker['symbol']; + tickers[symbol] = ticker; + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + 'resolution': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + const now = this.milliseconds (); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + request['start_timestamp'] = now - (limit - 1) * duration * 1000; + request['end_timestamp'] = now; + } + } else { + request['start_timestamp'] = since; + if (limit === undefined) { + request['end_timestamp'] = now; + } else { + request['end_timestamp'] = this.sum (since, limit * duration * 1000); + } + } + const response = await this.publicGetGetTradingviewChartData (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // volume: [ 3.6680847969999992, 22.682721123, 3.011587939, 0 ], + // ticks: [ 1583916960000, 1583917020000, 1583917080000, 1583917140000 ], + // status: 'ok', + // open: [ 7834, 7839, 7833.5, 7833 ], + // low: [ 7834, 7833.5, 7832.5, 7833 ], + // high: [ 7839.5, 7839, 7833.5, 7833 ], + // cost: [ 28740, 177740, 23590, 0 ], + // close: [ 7839.5, 7833.5, 7833, 7833 ] + // }, + // usIn: 1583917166709801, + // usOut: 1583917166710175, + // usDiff: 374, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', {}); + const ohlcvs = this.convertTradingViewToOHLCV (result, 'ticks', 'open', 'high', 'low', 'close', 'volume', true); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // 'trade_seq': 39201926, + // 'trade_id':' 64135724', + // 'timestamp': 1583174775400, + // 'tick_direction': 1, + // 'price': 8865.0, + // 'instrument_name': 'BTC-PERPETUAL', + // 'index_price': 8863.31, + // 'direction': 'buy', + // 'amount': 10.0 + // } + // + // fetchMyTrades, fetchOrderTrades (private) + // + // { + // "trade_seq": 3, + // "trade_id": "ETH-34066", + // "timestamp": 1550219814585, + // "tick_direction": 1, + // "state": "open", + // "self_trade": false, + // "reduce_only": false, + // "price": 0.04, + // "post_only": false, + // "order_type": "limit", + // "order_id": "ETH-334607", + // "matching_id": null, + // "liquidity": "M", + // "iv": 56.83, + // "instrument_name": "ETH-22FEB19-120-C", + // "index_price": 121.37, + // "fee_currency": "ETH", + // "fee": 0.0011, + // "direction": "buy", + // "amount": 11 + // } + // + const id = this.safeString (trade, 'trade_id'); + const marketId = this.safeString (trade, 'instrument_name'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (trade, 'timestamp'); + const side = this.safeString (trade, 'direction'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const liquidity = this.safeString (trade, 'liquidity'); + let takerOrMaker = undefined; + if (liquidity !== undefined) { + // M = maker, T = taker, MT = both + takerOrMaker = (liquidity === 'M') ? 'maker' : 'taker'; + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': this.safeString (trade, 'order_id'), + 'type': this.safeString (trade, 'order_type'), + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + 'include_old': true, + }; + const method = (since === undefined) ? 'publicGetGetLastTradesByInstrument' : 'publicGetGetLastTradesByInstrumentAndTime'; + if (since !== undefined) { + request['start_timestamp'] = since; + } + if (limit !== undefined) { + request['count'] = limit; // default 10 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // 'jsonrpc': '2.0', + // 'result': { + // 'trades': [ + // { + // 'trade_seq': 39201926, + // 'trade_id':' 64135724', + // 'timestamp': 1583174775400, + // 'tick_direction': 1, + // 'price': 8865.0, + // 'instrument_name': 'BTC-PERPETUAL', + // 'index_price': 8863.31, + // 'direction': 'buy', + // 'amount': 10.0 + // }, + // ], + // 'has_more': true, + // }, + // 'usIn': 1583779594843931, + // 'usOut': 1583779594844446, + // 'usDiff': 515, + // 'testnet': false + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetGetOrderBook (this.extend (request, params)); + // + // { + // jsonrpc: '2.0', + // result: { + // timestamp: 1583781354740, + // stats: { volume: 61249.66735634, low: 7631.5, high: 8311.5 }, + // state: 'open', + // settlement_price: 7903.21, + // open_interest: 111536690, + // min_price: 7695.13, + // max_price: 7929.49, + // mark_price: 7813.06, + // last_price: 7814.5, + // instrument_name: 'BTC-PERPETUAL', + // index_price: 7810.12, + // funding_8h: 0.0000031, + // current_funding: 0, + // change_id: 17538025952, + // bids: [ + // [7814, 351820], + // [7813.5, 207490], + // [7813, 32160], + // ], + // best_bid_price: 7814, + // best_bid_amount: 351820, + // best_ask_price: 7814.5, + // best_ask_amount: 11880, + // asks: [ + // [7814.5, 11880], + // [7815, 18100], + // [7815.5, 2640], + // ], + // }, + // usIn: 1583781354745804, + // usOut: 1583781354745932, + // usDiff: 128, + // testnet: false + // } + // + const result = this.safeValue (response, 'result', {}); + const timestamp = this.safeInteger (result, 'timestamp'); + const nonce = this.safeInteger (result, 'change_id'); + const orderbook = this.parseOrderBook (result, timestamp); + orderbook['nonce'] = nonce; + return orderbook; + } + + parseOrderStatus (status) { + const statuses = { + 'open': 'open', + 'cancelled': 'canceled', + 'filled': 'closed', + 'rejected': 'rejected', + // 'untriggered': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'good_til_cancelled': 'GTC', + 'fill_or_kill': 'FOK', + 'immediate_or_cancel': 'IOC', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "time_in_force": "good_til_cancelled", + // "reduce_only": false, + // "profit_loss": 0, + // "price": "market_price", + // "post_only": false, + // "order_type": "market", + // "order_state": "filled", + // "order_id": "ETH-349249", + // "max_show": 40, + // "last_update_timestamp": 1550657341322, + // "label": "market0000234", + // "is_liquidation": false, + // "instrument_name": "ETH-PERPETUAL", + // "filled_amount": 40, + // "direction": "buy", + // "creation_timestamp": 1550657341322, + // "commission": 0.000139, + // "average_price": 143.81, + // "api": true, + // "amount": 40, + // "trades": [], // injected by createOrder + // } + // + const timestamp = this.safeInteger (order, 'creation_timestamp'); + const lastUpdate = this.safeInteger (order, 'last_update_timestamp'); + const id = this.safeString (order, 'order_id'); + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'average_price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled_amount'); + let lastTradeTimestamp = undefined; + if (filled !== undefined) { + if (filled > 0) { + lastTradeTimestamp = lastUpdate; + } + } + let remaining = undefined; + let cost = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (price !== undefined) { + cost = price * filled; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'order_state')); + const marketId = this.safeString (order, 'instrument_name'); + market = this.safeMarket (marketId, market); + const side = this.safeStringLower (order, 'direction'); + let feeCost = this.safeFloat (order, 'commission'); + let fee = undefined; + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + fee = { + 'cost': feeCost, + 'currency': market['base'], + }; + } + const type = this.safeString (order, 'order_type'); + // injected in createOrder + let trades = this.safeValue (order, 'trades'); + if (trades !== undefined) { + trades = this.parseTrades (trades, market); + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'time_in_force')); + const stopPrice = undefined; + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetGetOrderState (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 4316, + // "result": { + // "time_in_force": "good_til_cancelled", + // "reduce_only": false, + // "profit_loss": 0.051134, + // "price": 118.94, + // "post_only": false, + // "order_type": "limit", + // "order_state": "filled", + // "order_id": "ETH-331562", + // "max_show": 37, + // "last_update_timestamp": 1550219810944, + // "label": "", + // "is_liquidation": false, + // "instrument_name": "ETH-PERPETUAL", + // "filled_amount": 37, + // "direction": "sell", + // "creation_timestamp": 1550219749176, + // "commission": 0.000031, + // "average_price": 118.94, + // "api": false, + // "amount": 37 + // } + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + // for perpetual and futures the amount is in USD + // for options it is in corresponding cryptocurrency contracts, e.g., BTC or ETH + 'amount': this.amountToPrecision (symbol, amount), + 'type': type, // limit, stop_limit, market, stop_market, default is limit + // 'label': 'string', // user-defined label for the order (maximum 64 characters) + // 'price': this.priceToPrecision (symbol, 123.45), // only for limit and stop_limit orders + // 'time_in_force' : 'good_til_cancelled', // fill_or_kill, immediate_or_cancel + // 'max_show': 123.45, // max amount within an order to be shown to other customers, 0 for invisible order + // 'post_only': false, // if the new price would cause the order to be filled immediately (as taker), the price will be changed to be just below the spread. + // 'reject_post_only': false, // if true the order is put to order book unmodified or request is rejected + // 'reduce_only': false, // if true, the order is intended to only reduce a current position + // 'stop_price': false, // stop price, required for stop_limit orders + // 'trigger': 'index_price', // mark_price, last_price, required for stop_limit orders + // 'advanced': 'usd', // 'implv', advanced option order type, options only + }; + let priceIsRequired = false; + let stopPriceIsRequired = false; + if (type === 'limit') { + priceIsRequired = true; + } else if (type === 'stop_limit') { + priceIsRequired = true; + stopPriceIsRequired = true; + } + if (priceIsRequired) { + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } else { + throw new ArgumentsRequired (this.id + ' createOrder requires a price argument for a ' + type + ' order'); + } + } + if (stopPriceIsRequired) { + const stopPrice = this.safeFloat2 (params, 'stop_price', 'stopPrice'); + if (stopPrice === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a stop_price or stopPrice param for a ' + type + ' order'); + } else { + request['stop_price'] = this.priceToPrecision (symbol, stopPrice); + } + params = this.omit (params, [ 'stop_price', 'stopPrice' ]); + } + const method = 'privateGet' + this.capitalize (side); + const response = await this[method] (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 5275, + // "result": { + // "trades": [ + // { + // "trade_seq": 14151, + // "trade_id": "ETH-37435", + // "timestamp": 1550657341322, + // "tick_direction": 2, + // "state": "closed", + // "self_trade": false, + // "price": 143.81, + // "order_type": "market", + // "order_id": "ETH-349249", + // "matching_id": null, + // "liquidity": "T", + // "label": "market0000234", + // "instrument_name": "ETH-PERPETUAL", + // "index_price": 143.73, + // "fee_currency": "ETH", + // "fee": 0.000139, + // "direction": "buy", + // "amount": 40 + // } + // ], + // "order": { + // "time_in_force": "good_til_cancelled", + // "reduce_only": false, + // "profit_loss": 0, + // "price": "market_price", + // "post_only": false, + // "order_type": "market", + // "order_state": "filled", + // "order_id": "ETH-349249", + // "max_show": 40, + // "last_update_timestamp": 1550657341322, + // "label": "market0000234", + // "is_liquidation": false, + // "instrument_name": "ETH-PERPETUAL", + // "filled_amount": 40, + // "direction": "buy", + // "creation_timestamp": 1550657341322, + // "commission": 0.000139, + // "average_price": 143.81, + // "api": true, + // "amount": 40 + // } + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const order = this.safeValue (result, 'order'); + const trades = this.safeValue (result, 'trades', []); + order['trades'] = trades; + return this.parseOrder (order, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (amount === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires an amount argument'); + } + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires a price argument'); + } + await this.loadMarkets (); + const request = { + 'order_id': id, + // for perpetual and futures the amount is in USD + // for options it is in corresponding cryptocurrency contracts, e.g., BTC or ETH + 'amount': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), // required + // 'post_only': false, // if the new price would cause the order to be filled immediately (as taker), the price will be changed to be just below the spread. + // 'reject_post_only': false, // if true the order is put to order book unmodified or request is rejected + // 'reduce_only': false, // if true, the order is intended to only reduce a current position + // 'stop_price': false, // stop price, required for stop_limit orders + // 'advanced': 'usd', // 'implv', advanced option order type, options only + }; + const response = await this.privateGetEdit (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + const order = this.safeValue (result, 'order'); + const trades = this.safeValue (result, 'trades', []); + order['trades'] = trades; + return this.parseOrder (order); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetCancel (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = undefined; + if (symbol === undefined) { + method = 'privateGetCancelAll'; + } else { + method = 'privateGetCancelAllByInstrument'; + const market = this.market (symbol); + request['instrument_name'] = market['id']; + } + const response = await this[method] (this.extend (request, params)); + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + let method = undefined; + if (symbol === undefined) { + const code = this.codeFromOptions ('fetchOpenOrders'); + const currency = this.currency (code); + request['currency'] = currency['id']; + method = 'privateGetGetOpenOrdersByCurrency'; + } else { + market = this.market (symbol); + request['instrument_name'] = market['id']; + method = 'privateGetGetOpenOrdersByInstrument'; + } + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + let method = undefined; + if (symbol === undefined) { + const code = this.codeFromOptions ('fetchClosedOrders'); + const currency = this.currency (code); + request['currency'] = currency['id']; + method = 'privateGetGetOrderHistoryByCurrency'; + } else { + market = this.market (symbol); + request['instrument_name'] = market['id']; + method = 'privateGetGetOrderHistoryByInstrument'; + } + const response = await this[method] (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetGetUserTradesByOrder (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 9367, + // "result": { + // "trades": [ + // { + // "trade_seq": 3, + // "trade_id": "ETH-34066", + // "timestamp": 1550219814585, + // "tick_direction": 1, + // "state": "open", + // "self_trade": false, + // "reduce_only": false, + // "price": 0.04, + // "post_only": false, + // "order_type": "limit", + // "order_id": "ETH-334607", + // "matching_id": null, + // "liquidity": "M", + // "iv": 56.83, + // "instrument_name": "ETH-22FEB19-120-C", + // "index_price": 121.37, + // "fee_currency": "ETH", + // "fee": 0.0011, + // "direction": "buy", + // "amount": 11 + // }, + // ], + // "has_more": true + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, undefined, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'include_old': true, + }; + let market = undefined; + let method = undefined; + if (symbol === undefined) { + const code = this.codeFromOptions ('fetchMyTrades'); + const currency = this.currency (code); + request['currency'] = currency['id']; + if (since === undefined) { + method = 'privateGetGetUserTradesByCurrency'; + } else { + method = 'privateGetGetUserTradesByCurrencyAndTime'; + request['start_timestamp'] = since; + } + } else { + market = this.market (symbol); + request['instrument_name'] = market['id']; + if (since === undefined) { + method = 'privateGetGetUserTradesByInstrument'; + } else { + method = 'privateGetGetUserTradesByInstrumentAndTime'; + request['start_timestamp'] = since; + } + } + if (limit !== undefined) { + request['count'] = limit; // default 10 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 9367, + // "result": { + // "trades": [ + // { + // "trade_seq": 3, + // "trade_id": "ETH-34066", + // "timestamp": 1550219814585, + // "tick_direction": 1, + // "state": "open", + // "self_trade": false, + // "reduce_only": false, + // "price": 0.04, + // "post_only": false, + // "order_type": "limit", + // "order_id": "ETH-334607", + // "matching_id": null, + // "liquidity": "M", + // "iv": 56.83, + // "instrument_name": "ETH-22FEB19-120-C", + // "index_price": 121.37, + // "fee_currency": "ETH", + // "fee": 0.0011, + // "direction": "buy", + // "amount": 11 + // }, + // ], + // "has_more": true + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetDeposits (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 5611, + // "result": { + // "count": 1, + // "data": [ + // { + // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", + // "amount": 5, + // "currency": "BTC", + // "received_timestamp": 1549295017670, + // "state": "completed", + // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", + // "updated_timestamp": 1549295130159 + // } + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privateGetGetWithdrawals (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 2745, + // "result": { + // "count": 1, + // "data": [ + // { + // "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", + // "amount": 0.5, + // "confirmed_timestamp": null, + // "created_timestamp": 1550571443070, + // "currency": "BTC", + // "fee": 0.0001, + // "id": 1, + // "priority": 0.15, + // "state": "unconfirmed", + // "transaction_id": null, + // "updated_timestamp": 1550571443070 + // } + // ] + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const data = this.safeValue (result, 'data', []); + return this.parseTransactions (data, currency, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + 'completed': 'ok', + 'unconfirmed': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchWithdrawals + // + // { + // "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", + // "amount": 0.5, + // "confirmed_timestamp": null, + // "created_timestamp": 1550571443070, + // "currency": "BTC", + // "fee": 0.0001, + // "id": 1, + // "priority": 0.15, + // "state": "unconfirmed", + // "transaction_id": null, + // "updated_timestamp": 1550571443070 + // } + // + // fetchDeposits + // + // { + // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", + // "amount": 5, + // "currency": "BTC", + // "received_timestamp": 1549295017670, + // "state": "completed", + // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", + // "updated_timestamp": 1549295130159 + // } + // + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeInteger2 (transaction, 'created_timestamp', 'received_timestamp'); + const updated = this.safeInteger (transaction, 'updated_timestamp'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const address = this.safeString (transaction, 'address'); + const feeCost = this.safeFloat (transaction, 'fee'); + let type = 'deposit'; + let fee = undefined; + if (feeCost !== undefined) { + type = 'withdrawal'; + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'transaction_id'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': undefined, + 'tagTo': undefined, + 'tagFrom': undefined, + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'instrument_name': market['id'], + }; + const response = await this.privateGetPosition (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 404, + // "result": { + // "average_price": 0, + // "delta": 0, + // "direction": "buy", + // "estimated_liquidation_price": 0, + // "floating_profit_loss": 0, + // "index_price": 3555.86, + // "initial_margin": 0, + // "instrument_name": "BTC-PERPETUAL", + // "leverage": 100, + // "kind": "future", + // "maintenance_margin": 0, + // "mark_price": 3556.62, + // "open_orders_margin": 0.000165889, + // "realized_profit_loss": 0, + // "settlement_price": 3555.44, + // "size": 0, + // "size_currency": 0, + // "total_profit_loss": 0 + // } + // } + // + // todo unify parsePosition/parsePositions + const result = this.safeValue (response, 'result'); + return result; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const code = this.codeFromOptions ('fetchPositions'); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetPositions (this.extend (request, params)); + // + // { + // "jsonrpc": "2.0", + // "id": 2236, + // "result": [ + // { + // "average_price": 7440.18, + // "delta": 0.006687487, + // "direction": "buy", + // "estimated_liquidation_price": 1.74, + // "floating_profit_loss": 0, + // "index_price": 7466.79, + // "initial_margin": 0.000197283, + // "instrument_name": "BTC-PERPETUAL", + // "kind": "future", + // "leverage": 34, + // "maintenance_margin": 0.000143783, + // "mark_price": 7476.65, + // "open_orders_margin": 0.000197288, + // "realized_funding": -1e-8, + // "realized_profit_loss": -9e-9, + // "settlement_price": 7476.65, + // "size": 50, + // "size_currency": 0.006687487, + // "total_profit_loss": 0.000032781 + // } + // ] + // } + // + // todo unify parsePosition/parsePositions + const result = this.safeValue (response, 'result', []); + return result; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'address': address, // must be in the address book + 'amount': amount, + // 'priority': 'high', // low, mid, high, very_high, extreme_high, insane + // 'tfa': '123456', // if enabled + }; + if (this.twofa !== undefined) { + request['tfa'] = this.oath (); + } + const response = await this.privateGetWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/' + 'api/' + this.version + '/' + api + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + request += '?' + this.urlencode (params); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const timestamp = this.milliseconds ().toString (); + const requestBody = ''; + if (Object.keys (params).length) { + request += '?' + this.urlencode (params); + } + const requestData = method + "\n" + request + "\n" + requestBody + "\n"; // eslint-disable-line quotes + const auth = timestamp + "\n" + nonce + "\n" + requestData; // eslint-disable-line quotes + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + headers = { + 'Authorization': 'deri-hmac-sha256 id=' + this.apiKey + ',ts=' + timestamp + ',sig=' + signature + ',' + 'nonce=' + nonce, + }; + } + const url = this.urls['api'] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + // + // { + // jsonrpc: '2.0', + // error: { + // message: 'Invalid params', + // data: { reason: 'invalid currency', param: 'currency' }, + // code: -32602 + // }, + // testnet: false, + // usIn: 1583763842150374, + // usOut: 1583763842150410, + // usDiff: 36 + // } + // + const error = this.safeValue (response, 'error'); + if (error !== undefined) { + const errorCode = this.safeString (error, 'code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/digifinex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/digifinex.js new file mode 100644 index 000000000..3ddd01f35 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/digifinex.js @@ -0,0 +1,1497 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AccountSuspended, BadRequest, BadResponse, NetworkError, DDoSProtection, AuthenticationError, PermissionDenied, ArgumentsRequired, ExchangeError, InsufficientFunds, InvalidOrder, InvalidNonce, OrderNotFound, InvalidAddress, RateLimitExceeded, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class digifinex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'digifinex', + 'name': 'DigiFinex', + 'countries': [ 'SG' ], + 'version': 'v3', + 'rateLimit': 900, // 300 for posts + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '4h': '240', + '12h': '720', + '1d': '1D', + '1w': '1W', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443315-01283a00-c5fe-11ea-8628-c2a0feaf07ac.jpg', + 'api': 'https://openapi.digifinex.com', + 'www': 'https://www.digifinex.com', + 'doc': [ + 'https://docs.digifinex.com', + ], + 'fees': 'https://digifinex.zendesk.com/hc/en-us/articles/360000328422-Fee-Structure-on-DigiFinex', + 'referral': 'https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp', + }, + 'api': { + 'v2': { + 'get': [ + 'ticker', + ], + }, + 'public': { + 'get': [ + '{market}/symbols', + 'kline', + 'margin/currencies', + 'margin/symbols', + 'markets', + 'order_book', + 'ping', + 'spot/symbols', + 'time', + 'trades', + 'trades/symbols', + 'ticker', + 'currencies', // todo add fetchCurrencies + ], + }, + 'private': { + 'get': [ + '{market}/financelog', + '{market}/mytrades', + '{market}/order', + '{market}​/order​/detail', // todo add fetchOrder + '{market}/order/current', + '{market}/order/history', + 'margin/assets', + 'margin/financelog', + 'margin/mytrades', + 'margin/order', + 'margin/order/current', + 'margin/order/history', + 'margin/positions', + 'otc/financelog', + 'spot/assets', + 'spot/financelog', + 'spot/mytrades', + 'spot/order', + 'spot/order/current', + 'spot/order/history', + 'deposit/address', // todo add fetchDepositAddress + 'deposit/history', // todo add fetchDeposits + 'withdraw/history', // todo add fetchWithdrawals + ], + 'post': [ + '{market}/order/cancel', + '{market}/order/new', + '{market}​/order​/batch_new', + 'margin/order/cancel', + 'margin/order/new', + 'margin/position/close', + 'spot/order/cancel', + 'spot/order/new', + 'transfer', + 'withdraw/new', // todo add withdraw() + 'withdraw/cancel', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.002, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + '10001': [ BadRequest, "Wrong request method, please check it's a GET ot POST request" ], + '10002': [ AuthenticationError, 'Invalid ApiKey' ], + '10003': [ AuthenticationError, "Sign doesn't match" ], + '10004': [ BadRequest, 'Illegal request parameters' ], + '10005': [ DDoSProtection, 'Request frequency exceeds the limit' ], + '10006': [ PermissionDenied, 'Unauthorized to execute this request' ], + '10007': [ PermissionDenied, 'IP address Unauthorized' ], + '10008': [ InvalidNonce, 'Timestamp for this request is invalid, timestamp must within 1 minute' ], + '10009': [ NetworkError, 'Unexist endpoint, please check endpoint URL' ], + '10011': [ AccountSuspended, 'ApiKey expired. Please go to client side to re-create an ApiKey' ], + '20001': [ PermissionDenied, 'Trade is not open for this trading pair' ], + '20002': [ PermissionDenied, 'Trade of this trading pair is suspended' ], + '20003': [ InvalidOrder, 'Invalid price or amount' ], + '20007': [ InvalidOrder, 'Price precision error' ], + '20008': [ InvalidOrder, 'Amount precision error' ], + '20009': [ InvalidOrder, 'Amount is less than the minimum requirement' ], + '20010': [ InvalidOrder, 'Cash Amount is less than the minimum requirement' ], + '20011': [ InsufficientFunds, 'Insufficient balance' ], + '20012': [ BadRequest, 'Invalid trade type, valid value: buy/sell)' ], + '20013': [ InvalidOrder, 'No order info found' ], + '20014': [ BadRequest, 'Invalid date, Valid format: 2018-07-25)' ], + '20015': [ BadRequest, 'Date exceeds the limit' ], + '20018': [ PermissionDenied, 'Your trading rights have been banned by the system' ], + '20019': [ BadRequest, 'Wrong trading pair symbol. Correct format:"usdt_btc". Quote asset is in the front' ], + '20020': [ DDoSProtection, "You have violated the API operation trading rules and temporarily forbid trading. At present, we have certain restrictions on the user's transaction rate and withdrawal rate." ], + '50000': [ ExchangeError, 'Exception error' ], + '20021': [ BadRequest, 'Invalid currency' ], + '20022': [ BadRequest, 'The ending timestamp must be larger than the starting timestamp' ], + '20023': [ BadRequest, 'Invalid transfer type' ], + '20024': [ BadRequest, 'Invalid amount' ], + '20025': [ BadRequest, 'This currency is not transferable at the moment' ], + '20026': [ InsufficientFunds, 'Transfer amount exceed your balance' ], + '20027': [ PermissionDenied, 'Abnormal account status' ], + '20028': [ PermissionDenied, 'Blacklist for transfer' ], + '20029': [ PermissionDenied, 'Transfer amount exceed your daily limit' ], + '20030': [ BadRequest, 'You have no position on this trading pair' ], + '20032': [ PermissionDenied, 'Withdrawal limited' ], + '20033': [ BadRequest, 'Wrong Withdrawal ID' ], + '20034': [ PermissionDenied, 'Withdrawal service of this crypto has been closed' ], + '20035': [ PermissionDenied, 'Withdrawal limit' ], + '20036': [ ExchangeError, 'Withdrawal cancellation failed' ], + '20037': [ InvalidAddress, 'The withdrawal address, Tag or chain type is not included in the withdrawal management list' ], + '20038': [ InvalidAddress, 'The withdrawal address is not on the white list' ], + '20039': [ ExchangeError, "Can't be canceled in current status" ], + '20040': [ RateLimitExceeded, 'Withdraw too frequently; limitation: 3 times a minute, 100 times a day' ], + '20041': [ PermissionDenied, 'Beyond the daily withdrawal limit' ], + '20042': [ BadSymbol, 'Current trading pair does not support API trading' ], + }, + 'broad': { + }, + }, + 'options': { + 'defaultType': 'spot', + 'types': [ 'spot', 'margin', 'otc' ], + }, + 'commonCurrencies': { + 'BHT': 'Black House Test', + 'MBN': 'Mobilian Coin', + 'TEL': 'TEL666', + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "data":[ + // { + // "deposit_status":1, + // "min_deposit_amount":10, + // "withdraw_fee_rate":0, + // "min_withdraw_amount":10, + // "min_withdraw_fee":5, + // "currency":"USDT", + // "withdraw_status":0, + // "chain":"OMNI" + // }, + // { + // "deposit_status":1, + // "min_deposit_amount":10, + // "withdraw_fee_rate":0, + // "min_withdraw_amount":10, + // "min_withdraw_fee":3, + // "currency":"USDT", + // "withdraw_status":1, + // "chain":"ERC20" + // }, + // { + // "deposit_status":0, + // "min_deposit_amount":0, + // "withdraw_fee_rate":0, + // "min_withdraw_amount":0, + // "min_withdraw_fee":0, + // "currency":"DGF13", + // "withdraw_status":0, + // "chain":"" + // }, + // ], + // "code":200 + // } + // + const data = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < data.length; i++) { + const currency = data[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const depositStatus = this.safeValue (currency, 'deposit_status', 1); + const withdrawStatus = this.safeValue (currency, 'withdraw_status', 1); + const active = depositStatus && withdrawStatus; + const fee = this.safeFloat (currency, 'withdraw_fee_rate'); + if (code in result) { + if (Array.isArray (result[code]['info'])) { + result[code]['info'].push (currency); + } else { + result[code]['info'] = [ result[code]['info'], currency ]; + } + } else { + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': undefined, + 'active': active, + 'fee': fee, + 'precision': 8, // todo fix hardcoded value + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'min_withdraw_amount'), + 'max': undefined, + }, + }, + }; + } + } + return result; + } + + async fetchMarkets (params = {}) { + const options = this.safeValue (this.options, 'fetchMarkets', {}); + const method = this.safeString (options, 'method', 'fetch_markets_v2'); + return await this[method] (params); + } + + async fetchMarketsV2 (params = {}) { + const response = await this.publicGetTradesSymbols (params); + // + // { + // "symbol_list":[ + // { + // "order_types":["LIMIT","MARKET"], + // "quote_asset":"USDT", + // "minimum_value":2, + // "amount_precision":4, + // "status":"TRADING", + // "minimum_amount":0.0001, + // "symbol":"BTC_USDT", + // "is_allow":1, + // "zone":"MAIN", + // "base_asset":"BTC", + // "price_precision":2 + // } + // ], + // "code":0 + // } + // + const markets = this.safeValue (response, 'symbol_list', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'base_asset'); + const quoteId = this.safeString (market, 'quote_asset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amount_precision'), + 'price': this.safeInteger (market, 'price_precision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minimum_amount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minimum_value'), + 'max': undefined, + }, + }; + // + // The status is documented in the exchange API docs as follows: + // TRADING, HALT (delisted), BREAK (trading paused) + // https://docs.digifinex.vip/en-ww/v3/#/public/spot/symbols + // However, all spot markets actually have status === 'HALT' + // despite that they appear to be active on the exchange website. + // Apparently, we can't trust this status. + // const status = this.safeString (market, 'status'); + // const active = (status === 'TRADING'); + // + const isAllowed = this.safeValue (market, 'is_allow', 1); + const active = isAllowed ? true : false; + const type = 'spot'; + const spot = (type === 'spot'); + const margin = (type === 'margin'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'type': type, + 'spot': spot, + 'margin': margin, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchMarketsV1 (params = {}) { + const response = await this.publicGetMarkets (params); + // + // { + // "data": [ + // { + // "volume_precision":4, + // "price_precision":2, + // "market":"btc_usdt", + // "min_amount":2, + // "min_volume":0.0001 + // }, + // ], + // "date":1564507456, + // "code":0 + // } + // + const markets = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'market'); + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'volume_precision'), + 'price': this.safeInteger (market, 'price_precision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'min_volume'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': undefined, + }, + }; + const active = undefined; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const method = 'privateGet' + this.capitalize (type) + 'Assets'; + const response = await this[method] (params); + // + // { + // "code": 0, + // "list": [ + // { + // "currency": "BTC", + // "free": 4723846.89208129, + // "total": 0 + // } + // ] + // } + const balances = this.safeValue (response, 'list', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['used'] = this.safeFloat (balance, 'frozen'); + account['free'] = this.safeFloat (balance, 'free'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 150 + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { + // "bids": [ + // [9605.77,0.0016], + // [9605.46,0.0003], + // [9602.04,0.0127], + // ], + // "asks": [ + // [9627.22,0.025803], + // [9627.12,0.168543], + // [9626.52,0.0011529], + // ], + // "date":1564509499, + // "code":0 + // } + // + const timestamp = this.safeTimestamp (response, 'date'); + return this.parseOrderBook (response, timestamp); + } + + async fetchTickers (symbols = undefined, params = {}) { + const apiKey = this.safeValue (params, 'apiKey', this.apiKey); + if (!apiKey) { + throw new ArgumentsRequired (this.id + ' fetchTicker is a private v2 endpoint that requires an `exchange.apiKey` credential or an `apiKey` extra parameter'); + } + await this.loadMarkets (); + const request = { + 'apiKey': apiKey, + }; + const response = await this.v2GetTicker (this.extend (request, params)); + // + // { + // "ticker":{ + // "btc_eth":{ + // "last":0.021957, + // "base_vol":2249.3521732227, + // "change":-0.6, + // "vol":102443.5111, + // "sell":0.021978, + // "low":0.021791, + // "buy":0.021946, + // "high":0.022266 + // } + // }, + // "date":1564518452, + // "code":0 + // } + // + const result = {}; + const tickers = this.safeValue (response, 'ticker', {}); + const date = this.safeInteger (response, 'date'); + const reversedMarketIds = Object.keys (tickers); + for (let i = 0; i < reversedMarketIds.length; i++) { + const reversedMarketId = reversedMarketIds[i]; + const ticker = this.extend ({ + 'date': date, + }, tickers[reversedMarketId]); + const [ quoteId, baseId ] = reversedMarketId.split ('_'); + const marketId = baseId.toUpperCase () + '_' + quoteId.toUpperCase (); + const market = this.safeMarket (marketId, undefined, '_'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const apiKey = this.safeValue (params, 'apiKey', this.apiKey); + if (!apiKey) { + throw new ArgumentsRequired (this.id + ' fetchTicker is a private v2 endpoint that requires an `exchange.apiKey` credential or an `apiKey` extra parameter'); + } + await this.loadMarkets (); + const market = this.market (symbol); + // reversed base/quote in v2 + const marketId = market['quoteId'].toLowerCase () + '_' + market['baseId'].toLowerCase (); + const request = { + 'symbol': marketId, + 'apiKey': apiKey, + }; + const response = await this.v2GetTicker (this.extend (request, params)); + // + // { + // "ticker":{ + // "btc_eth":{ + // "last":0.021957, + // "base_vol":2249.3521732227, + // "change":-0.6, + // "vol":102443.5111, + // "sell":0.021978, + // "low":0.021791, + // "buy":0.021946, + // "high":0.022266 + // } + // }, + // "date":1564518452, + // "code":0 + // } + // + const date = this.safeInteger (response, 'date'); + const ticker = this.safeValue (response, 'ticker', {}); + let result = this.safeValue (ticker, marketId, {}); + result = this.extend ({ 'date': date }, result); + return this.parseTicker (result, market); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "last":0.021957, + // "base_vol":2249.3521732227, + // "change":-0.6, + // "vol":102443.5111, + // "sell":0.021978, + // "low":0.021791, + // "buy":0.021946, + // "high":0.022266, + // "date"1564518452, // injected from fetchTicker/fetchTickers + // } + // + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (ticker, 'date'); + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'change'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'base_vol'), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "date":1564520003, + // "id":1596149203, + // "amount":0.7073, + // "type":"buy", + // "price":0.02193, + // } + // + // fetchMyTrades (private) + // + // { + // "symbol": "BTC_USDT", + // "order_id": "6707cbdcda0edfaa7f4ab509e4cbf966", + // "id": 28457, + // "price": 0.1, + // "amount": 0, + // "fee": 0.096, + // "fee_currency": "USDT", + // "timestamp": 1499865549, + // "side": "buy", + // "is_maker": true + // } + // + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'order_id'); + const timestamp = this.safeTimestamp2 (trade, 'date', 'timestamp'); + const side = this.safeString2 (trade, 'type', 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const takerOrMaker = this.safeValue (trade, 'is_maker'); + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': orderId, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "server_time": 1589873762, + // "code": 0 + // } + // + return this.safeTimestamp (response, 'server_time'); + } + + async fetchStatus (params = {}) { + await this.publicGetPing (params); + // + // { + // "msg": "pong", + // "code": 0 + // } + // + this.status = this.extend (this.status, { + 'status': 'ok', + 'updated': this.milliseconds (), + }); + return this.status; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 500 + } + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "data":[ + // { + // "date":1564520003, + // "id":1596149203, + // "amount":0.7073, + // "type":"buy", + // "price":0.02193, + // }, + // { + // "date":1564520002, + // "id":1596149165, + // "amount":0.3232, + // "type":"sell", + // "price":0.021927, + // }, + // ], + // "code": 0, + // "date": 1564520003, + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1556712900, + // 2205.899, + // 0.029967, + // 0.02997, + // 0.029871, + // 0.029927 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 5), // open + this.safeFloat (ohlcv, 3), // high + this.safeFloat (ohlcv, 4), // low + this.safeFloat (ohlcv, 2), // close + this.safeFloat (ohlcv, 1), // volume + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + // 'start_time': 1564520003, // starting timestamp, 200 candles before end_time by default + // 'end_time': 1564520003, // ending timestamp, current timestamp by default + }; + if (since !== undefined) { + const startTime = parseInt (since / 1000); + request['start_time'] = startTime; + if (limit !== undefined) { + const duration = this.parseTimeframe (timeframe); + request['end_time'] = this.sum (startTime, limit * duration); + } + } else if (limit !== undefined) { + const endTime = this.seconds (); + const duration = this.parseTimeframe (timeframe); + request['startTime'] = this.sum (endTime, -limit * duration); + } + const response = await this.publicGetKline (this.extend (request, params)); + // + // { + // "code":0, + // "data":[ + // [1556712900,2205.899,0.029967,0.02997,0.029871,0.029927], + // [1556713800,1912.9174,0.029992,0.030014,0.029955,0.02996], + // [1556714700,1556.4795,0.029974,0.030019,0.029969,0.02999], + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const request = { + 'market': orderType, + 'symbol': market['id'], + 'amount': this.amountToPrecision (symbol, amount), + // 'post_only': 0, // 0 by default, if set to 1 the order will be canceled if it can be executed immediately, making sure there will be no market taking + }; + let suffix = ''; + if (type === 'market') { + suffix = '_market'; + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + request['type'] = side + suffix; + const response = await this.privatePostMarketOrderNew (this.extend (request, params)); + // + // { + // "code": 0, + // "order_id": "198361cecdc65f9c8c9bb2fa68faec40" + // } + // + const result = this.parseOrder (response, market); + return this.extend (result, { + 'symbol': symbol, + 'side': side, + 'type': type, + 'amount': amount, + 'price': price, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const request = { + 'market': orderType, + 'order_id': id, + }; + const response = await this.privatePostMarketOrderCancel (this.extend (request, params)); + // + // { + // "code": 0, + // "success": [ + // "198361cecdc65f9c8c9bb2fa68faec40", + // "3fb0d98e51c18954f10d439a9cf57de0" + // ], + // "error": [ + // "78a7104e3c65cc0c5a212a53e76d0205" + // ] + // } + // + const canceledOrders = this.safeValue (response, 'success', []); + const numCanceledOrders = canceledOrders.length; + if (numCanceledOrders !== 1) { + throw new OrderNotFound (this.id + ' cancelOrder ' + id + ' not found'); + } + return response; + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const request = { + 'market': orderType, + 'order_id': ids.join (','), + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "success": [ + // "198361cecdc65f9c8c9bb2fa68faec40", + // "3fb0d98e51c18954f10d439a9cf57de0" + // ], + // "error": [ + // "78a7104e3c65cc0c5a212a53e76d0205" + // ] + // } + // + const canceledOrders = this.safeValue (response, 'success', []); + const numCanceledOrders = canceledOrders.length; + if (numCanceledOrders < 1) { + throw new OrderNotFound (this.id + ' cancelOrders error'); + } + return response; + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'open', // partially filled + '2': 'closed', + '3': 'canceled', + '4': 'canceled', // partially filled and canceled + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "code": 0, + // "order_id": "198361cecdc65f9c8c9bb2fa68faec40" + // } + // + // fetchOrder, fetchOpenOrders, fetchOrders + // + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // + const id = this.safeString (order, 'order_id'); + const timestamp = this.safeTimestamp (order, 'created_date'); + const lastTradeTimestamp = this.safeTimestamp (order, 'finished_date'); + let side = this.safeString (order, 'type'); + let type = undefined; + if (side !== undefined) { + const parts = side.split ('_'); + const numParts = parts.length; + if (numParts > 1) { + side = parts[0]; + type = parts[1]; + } else { + type = 'limit'; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'executed_amount'); + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'avg_price'); + let remaining = undefined; + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = filled * average; + } + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'average': average, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + const request = { + 'market': orderType, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetMarketOrderCurrent (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + const request = { + 'market': orderType, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); // default 3 days from now, max 30 days + } + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 100 + } + const response = await this.privateGetMarketOrderHistory (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'market': orderType, + 'order_id': id, + }; + const response = await this.privateGetMarketOrder (this.extend (request, params)); + // + // { + // "code": 0, + // "data": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "dd3164b333a4afa9d5730bb87f6db8b3", + // "created_date": 1562303547, + // "finished_date": 0, + // "price": 0.1, + // "amount": 1, + // "cash_amount": 1, + // "executed_amount": 0, + // "avg_price": 0, + // "status": 1, + // "type": "buy", + // "kind": "margin" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const order = this.safeValue (data, 0); + if (order === undefined) { + throw new OrderNotFound (this.id + ' fetchOrder() order ' + id + ' not found'); + } + return this.parseOrder (order, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + let market = undefined; + const request = { + 'market': orderType, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); // default 3 days from now, max 30 days + } + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 100 + } + const response = await this.privateGetMarketMytrades (this.extend (request, params)); + // + // { + // "code": 0, + // "list": [ + // { + // "symbol": "BTC_USDT", + // "order_id": "6707cbdcda0edfaa7f4ab509e4cbf966", + // "id": 28457, + // "price": 0.1, + // "amount": 0, + // "fee": 0.096, + // "fee_currency": "USDT", + // "timestamp": 1499865549, + // "side": "buy", + // "is_maker": true + // } + // ] + // } + // + const data = this.safeValue (response, 'list', []); + return this.parseTrades (data, market, since, limit); + } + + parseLedgerEntryType (type) { + const types = {}; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "currency_mark": "BTC", + // "type": 100234, + // "num": 28457, + // "balance": 0.1, + // "time": 1546272000 + // } + // + const id = this.safeString (item, 'num'); + const account = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const code = this.safeCurrencyCode (this.safeString (item, 'currency_mark'), currency); + const timestamp = this.safeTimestamp (item, 'time'); + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': undefined, + 'account': account, + 'referenceId': undefined, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': undefined, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + const defaultType = this.safeString (this.options, 'defaultType', 'spot'); + const orderType = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + await this.loadMarkets (); + const request = { + 'market': orderType, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency_mark'] = currency['id']; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + } + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 1000 + } + const response = await this.privateGetMarketFinancelog (this.extend (request, params)); + // + // { + // "code": 0, + // "data": { + // "total": 521, + // "finance": [ + // { + // "currency_mark": "BTC", + // "type": 100234, + // "num": 28457, + // "balance": 0.1, + // "time": 1546272000 + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const items = this.safeValue (data, 'finance', []); + return this.parseLedger (items, currency, since, limit); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // "addressTag":"", + // "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373", + // "currency":"USDT", + // "chain":"ERC20" + // } + // + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'addressTag'); + const currencyId = this.safeStringUpper (depositAddress, 'currency'); + const code = this.safeCurrencCode (currencyId); + return { + 'info': depositAddress, + 'code': code, + 'address': address, + 'tag': tag, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + // + // { + // "data":[ + // { + // "addressTag":"", + // "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373", + // "currency":"USDT", + // "chain":"ERC20" + // } + // ], + // "code":200 + // } + // + const data = this.safeValue (response, 'data', []); + const addresses = this.parseDepositAddresses (data); + const address = this.safeValue (addresses, code); + if (address === undefined) { + throw new InvalidAddress (this.id + ' fetchDepositAddress did not return an address for ' + code + ' - create the deposit address in the user settings on the exchange website first.'); + } + return address; + } + + async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + // 'currency': currency['id'], + // 'from': 'fromId', // When direct is' prev ', from is 1, returning from old to new ascending, when direct is' next ', from is the ID of the most recent record, returned from the old descending order + // 'size': 100, // default 100, max 500 + // 'direct': 'prev', // "prev" ascending, "next" descending + }; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = Math.min (500, limit); + } + const method = (type === 'deposit') ? 'privateGetDepositHistory' : 'privateGetWithdrawHistory'; + const response = await this[method] (this.extend (request, params)); + // + // { + // "code": 200, + // "data": [ + // { + // "id": 1171, + // "currency": "xrp", + // "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "chain": "", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "memo": "100040", + // "fee": 0, + // "state": "safe", + // "created_date": "2020-04-20 11:23:00", + // "finished_date": "2020-04-20 13:23:00" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit, { 'type': type }); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposit', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdrawal', code, since, limit, params); + } + + parseTransactionStatus (status) { + const statuses = { + '0': 'pending', // Email Sent + '1': 'canceled', // Cancelled (different from 1 = ok in deposits) + '2': 'pending', // Awaiting Approval + '3': 'failed', // Rejected + '4': 'pending', // Processing + '5': 'failed', // Failure + '6': 'ok', // Completed + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchWithdrawals + // + // { + // "id": 1171, + // "currency": "xrp", + // "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b", + // "chain": "", + // "amount": 7.457467, + // "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm", + // "memo": "100040", + // "fee": 0, + // "state": "safe", + // "created_date": "2020-04-20 11:23:00", + // "finished_date": "2020-04-20 13:23:00" + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString (transaction, 'memo'); // set but unused + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const txid = this.safeString (transaction, 'hash'); + const currencyId = this.safeStringUpper (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_date')); + const updated = this.parse8601 (this.safeString (transaction, 'finished_date')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const amount = this.safeFloat (transaction, 'amount'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { 'currency': code, 'cost': feeCost }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': undefined, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const version = (api === 'v2') ? api : this.version; + let url = this.urls['api'] + '/' + version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const urlencoded = this.urlencode (this.keysort (query)); + if (api === 'private') { + const nonce = this.nonce ().toString (); + const auth = urlencoded; + // the signature is not time-limited :\ + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + if (method === 'GET') { + if (urlencoded) { + url += '?' + urlencoded; + } + } else if (method === 'POST') { + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + if (urlencoded) { + body = urlencoded; + } + } + headers = { + 'ACCESS-KEY': this.apiKey, + 'ACCESS-SIGN': signature, + 'ACCESS-TIMESTAMP': nonce, + }; + } else { + if (urlencoded) { + url += '?' + urlencoded; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + if (!response) { + return; // fall back to default error handler + } + const code = this.safeString (response, 'code'); + if ((code === '0') || (code === '200')) { + return; // no error + } + const feedback = this.id + ' ' + responseBody; + if (code === undefined) { + throw new BadResponse (feedback); + } + const unknownError = [ ExchangeError, feedback ]; + const [ ExceptionClass, message ] = this.safeValue (this.exceptions['exact'], code, unknownError); + throw new ExceptionClass (message); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/dsx.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/dsx.js new file mode 100644 index 000000000..cff6ab255 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/dsx.js @@ -0,0 +1,1273 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InvalidNonce, BadRequest, InsufficientFunds, PermissionDenied, DDoSProtection, InvalidOrder, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class dsx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'dsx', + 'name': 'DSX', + 'countries': [ 'UK' ], + 'rateLimit': 1500, + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTransactions': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/76909626-cb2bb100-68bc-11ea-99e0-28ba54f04792.jpg', + 'api': { + 'public': 'https://dsxglobal.com/mapi', // market data + 'private': 'https://dsxglobal.com/tapi', // trading + 'dwapi': 'https://dsxglobal.com/dwapi', // deposit/withdraw + }, + 'www': 'https://dsxglobal.com', + 'doc': [ + 'https://dsxglobal.com/developers/publicApi', + ], + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.15 / 100, + 'taker': 0.25 / 100, + }, + }, + 'timeframes': { + '1m': 'm', + '1h': 'h', + '1d': 'd', + }, + 'api': { + // market data (public) + 'public': { + 'get': [ + 'barsFromMoment/{pair}/{period}/{start}', + 'depth/{pair}', + 'info', + 'lastBars/{pair}/{period}/{amount}', // period is 'm', 'h' or 'd' + 'periodBars/{pair}/{period}/{start}/{end}', + 'ticker/{pair}', + 'trades/{pair}', + ], + }, + // trading (private) + 'private': { + 'post': [ + 'info/account', + 'history/transactions', + 'history/trades', + 'history/orders', + 'orders', + 'order/cancel', + // 'order/cancel/all', + 'order/status', + 'order/new', + 'volume', + 'fees', // trading fee schedule + ], + }, + // deposit / withdraw (private) + 'dwapi': { + 'post': [ + 'deposit/cryptoaddress', + 'withdraw/crypto', + 'withdraw/fiat', + 'withdraw/submit', + // 'withdraw/cancel', + 'transaction/status', // see 'history/transactions' in private tapi above + ], + }, + }, + 'exceptions': { + 'exact': { + 'Sign is invalid': AuthenticationError, // {"success":0,"error":"Sign is invalid"} + 'Order was rejected. Incorrect price.': InvalidOrder, // {"success":0,"error":"Order was rejected. Incorrect price."} + "Order was rejected. You don't have enough money.": InsufficientFunds, // {"success":0,"error":"Order was rejected. You don't have enough money."} + 'This method is blocked for your pair of keys': PermissionDenied, // {"success":0,"error":"This method is blocked for your pair of keys"} + }, + 'broad': { + 'INVALID_PARAMETER': BadRequest, + 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} + 'invalid api key': AuthenticationError, + 'invalid sign': AuthenticationError, + 'api key dont have trade permission': AuthenticationError, + 'invalid parameter': InvalidOrder, + 'invalid order': InvalidOrder, + 'Requests too often': DDoSProtection, + 'not available': ExchangeNotAvailable, + 'data unavailable': ExchangeNotAvailable, + 'external service unavailable': ExchangeNotAvailable, + 'nonce is invalid': InvalidNonce, // {"success":0,"error":"Parameter: nonce is invalid"} + 'Incorrect volume': InvalidOrder, // {"success": 0,"error":"Order was rejected. Incorrect volume."} + }, + }, + 'options': { + 'fetchTickersMaxLength': 250, + }, + 'commonCurrencies': { + 'DSH': 'DASH', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInfo (params); + // + // { + // "server_time": 1522057909, + // "pairs": { + // "ethusd": { + // "decimal_places": 5, + // "min_price": 100, + // "max_price": 1500, + // "min_amount": 0.01, + // "hidden": 0, + // "fee": 0, + // "amount_decimal_places": 4, + // "quoted_currency": "USD", + // "base_currency": "ETH" + // } + // } + // } + // + const markets = this.safeValue (response, 'pairs'); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quoted_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'decimal_places'), + 'price': this.safeInteger (market, 'decimal_places'), + }; + const amountLimits = { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }; + const priceLimits = { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }; + const costLimits = { + 'min': this.safeFloat (market, 'min_total'), + }; + const limits = { + 'amount': amountLimits, + 'price': priceLimits, + 'cost': costLimits, + }; + const hidden = this.safeInteger (market, 'hidden'); + const active = (hidden === 0); + // see parseMarket below + // https://github.com/ccxt/ccxt/pull/5786 + const otherId = base.toLowerCase () + quote.toLowerCase (); + result.push ({ + 'id': id, + 'otherId': otherId, // https://github.com/ccxt/ccxt/pull/5786 + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostInfoAccount (); + // + // { + // "success" : 1, + // "return" : { + // "funds" : { + // "BTC" : { + // "total" : 0, + // "available" : 0 + // }, + // "USD" : { + // "total" : 0, + // "available" : 0 + // }, + // "USDT" : { + // "total" : 0, + // "available" : 0 + // } + // }, + // "rights" : { + // "info" : 1, + // "trade" : 1 + // }, + // "transactionCount" : 0, + // "openOrders" : 0, + // "serverTime" : 1537451465 + // } + // } + // + const balances = this.safeValue (response, 'return'); + const result = { 'info': response }; + const funds = this.safeValue (balances, 'funds'); + const currencyIds = Object.keys (funds); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (funds, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTicker (ticker, market = undefined) { + // + // { high: 0.03492, + // low: 0.03245, + // avg: 29.46133, + // vol: 500.8661, + // vol_cur: 17.000797104, + // last: 0.03364, + // buy: 0.03362, + // sell: 0.03381, + // updated: 1537521993, + // pair: "ethbtc" } + // + const timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + const marketId = this.safeString (ticker, 'pair'); + market = this.parseMarket (marketId); + if (market !== undefined) { + symbol = market['symbol']; + } + // dsx average is inverted, liqui average is not + let average = this.safeFloat (ticker, 'avg'); + if (average !== undefined) { + if (average > 0) { + average = 1 / average; + } + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'vol_cur'), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "amount" : 0.0128, + // "price" : 6483.99000, + // "timestamp" : 1540334614, + // "tid" : 35684364, + // "type" : "ask" + // } + // + // fetchMyTrades (private) + // + // { + // "number": "36635882", // <-- this is present if the trade has come from the '/order/status' call + // "id": "36635882", // <-- this may have been artifically added by the parseTrades method + // "pair": "btcusd", + // "type": "buy", + // "volume": 0.0595, + // "rate": 9750, + // "orderId": 77149299, + // "timestamp": 1519612317, + // "commission": 0.00020825, + // "commissionCurrency": "btc" + // } + // + const timestamp = this.safeTimestamp (trade, 'timestamp'); + let side = this.safeString (trade, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const price = this.safeFloat2 (trade, 'rate', 'price'); + const id = this.safeString2 (trade, 'number', 'id'); + const orderId = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'pair'); + market = this.parseMarket (marketId); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const amount = this.safeFloat2 (trade, 'amount', 'volume'); + const type = 'limit'; // all trades are still limit trades + let takerOrMaker = undefined; + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const isYourOrder = this.safeValue (trade, 'is_your_order'); + if (isYourOrder !== undefined) { + takerOrMaker = 'taker'; + if (isYourOrder) { + takerOrMaker = 'maker'; + } + if (fee === undefined) { + fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker); + } + } + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + parseTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { + let result = []; + if (Array.isArray (trades)) { + for (let i = 0; i < trades.length; i++) { + result.push (this.parseTrade (trades[i], market)); + } + } else { + const ids = Object.keys (trades); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const trade = this.parseTrade (trades[id], market); + result.push (this.extend (trade, { 'id': id }, params)); + } + } + result = this.sortBy (result, 'timestamp'); + const symbol = (market !== undefined) ? market['symbol'] : undefined; + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const market_id_in_reponse = (market['id'] in response); + if (!market_id_in_reponse) { + throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); + } + const orderbook = response[market['id']]; + return this.parseOrderBook (orderbook); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join ('-'); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const result = {}; + ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = id; + if (id in this.markets_by_id) { + const market = this.markets_by_id[id]; + symbol = market['symbol']; + } + result[symbol] = this.parseOrderBook (response[id]); + } + return result; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = this.ids; + if (symbols === undefined) { + const numIds = ids.length; + ids = ids.join ('-'); + const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); + // max URL length is 2048 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + const tickers = await this.publicGetTickerPair (this.extend (request, params)); + // + // { + // "bchbtc" : { + // "high" : 0.02989, + // "low" : 0.02736, + // "avg" : 33.90585, + // "vol" : 0.65982205, + // "vol_cur" : 0.0194604180960, + // "last" : 0.03000, + // "buy" : 0.02980, + // "sell" : 0.03001, + // "updated" : 1568104614, + // "pair" : "bchbtc" + // }, + // "ethbtc" : { + // "high" : 0.01772, + // "low" : 0.01742, + // "avg" : 56.89082, + // "vol" : 229.247115044, + // "vol_cur" : 4.02959737298943, + // "last" : 0.01769, + // "buy" : 0.01768, + // "sell" : 0.01776, + // "updated" : 1568104614, + // "pair" : "ethbtc" + // } + // } + // + const result = {}; + const keys = Object.keys (tickers); + for (let k = 0; k < keys.length; k++) { + const id = keys[k]; + const ticker = tickers[id]; + let symbol = id; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return tickers[symbol]; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTradesPair (this.extend (request, params)); + if (Array.isArray (response)) { + const numElements = response.length; + if (numElements === 0) { + return []; + } + } + return this.parseTrades (response[market['id']], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "high" : 0.01955, + // "open" : 0.01955, + // "low" : 0.01955, + // "close" : 0.01955, + // "amount" : 2.5, + // "timestamp" : 1565155740000 + // } + // + return [ + this.safeInteger (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'amount'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'period': this.timeframes[timeframe], + }; + let method = 'publicGetLastBarsPairPeriodAmount'; + if (since === undefined) { + if (limit === undefined) { + limit = 100; // required, max 2000 + } + request['amount'] = limit; + } else { + method = 'publicGetPeriodBarsPairPeriodStartEnd'; + // in their docs they expect milliseconds + // but it returns empty arrays with milliseconds + // however, it does work properly with seconds + request['start'] = parseInt (since / 1000); + if (limit === undefined) { + request['end'] = this.seconds (); + } else { + const duration = this.parseTimeframe (timeframe) * 1000; + const end = this.sum (since, duration * limit); + request['end'] = parseInt (end / 1000); + } + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "ethbtc": [ + // { + // "high" : 0.01955, + // "open" : 0.01955, + // "low" : 0.01955, + // "close" : 0.01955, + // "amount" : 2.5, + // "timestamp" : 1565155740000 + // }, + // { + // "high" : 0.01967, + // "open" : 0.01967, + // "low" : 0.01967, + // "close" : 0.01967, + // "amount" : 0, + // "timestamp" : 1565155680000 + // } + // ] + // } + // + const candles = this.safeValue (response, market['id'], []); + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (type === 'market' && price === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder requires a price argument even for market orders, that is the worst price that you agree to fill your order for'); + } + const request = { + 'pair': market['id'], + 'type': side, + 'volume': this.amountToPrecision (symbol, amount), + 'rate': this.priceToPrecision (symbol, price), + 'orderType': type, + }; + price = parseFloat (price); + amount = parseFloat (amount); + const response = await this.privatePostOrderNew (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "received": 0, + // "remains": 10, + // "funds": { + // "BTC": { + // "total": 100, + // "available": 95 + // }, + // "USD": { + // "total": 10000, + // "available": 9995 + // }, + // "EUR": { + // "total": 1000, + // "available": 995 + // }, + // "LTC": { + // "total": 1000, + // "available": 995 + // } + // }, + // "orderId": 0, // https://github.com/ccxt/ccxt/issues/3677 + // } + // } + // + let status = 'open'; + let filled = 0.0; + let remaining = amount; + const responseReturn = this.safeValue (response, 'return'); + let id = this.safeString2 (responseReturn, 'orderId', 'order_id'); + if (id === '0') { + id = this.safeString (responseReturn, 'initOrderId', 'init_order_id'); + status = 'closed'; + } + filled = this.safeFloat (responseReturn, 'received', 0.0); + remaining = this.safeFloat (responseReturn, 'remains', amount); + const timestamp = this.milliseconds (); + return { + 'info': response, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * filled, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + // 'trades': this.parseTrades (order['trades'], market), + }; + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', // Active + '1': 'closed', // Filled + '2': 'canceled', // Killed + '3': 'canceling', // Killing + '7': 'canceled', // Rejected + }; + return this.safeString (statuses, status, status); + } + + parseMarket (id) { + if (id in this.markets_by_id) { + return this.markets_by_id[id]; + } else { + // the following is a fix for + // https://github.com/ccxt/ccxt/pull/5786 + // https://github.com/ccxt/ccxt/issues/5770 + let markets_by_other_id = this.safeValue (this.options, 'markets_by_other_id'); + if (markets_by_other_id === undefined) { + this.options['markets_by_other_id'] = this.indexBy (this.markets, 'otherId'); + markets_by_other_id = this.options['markets_by_other_id']; + } + if (id in markets_by_other_id) { + return markets_by_other_id[id]; + } + } + return undefined; + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // "number": 36635882, + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit", + // "deals": [ + // { + // "pair": "btcusd", + // "type": "buy", + // "amount": 1, + // "rate": 1000.0, + // "orderId": 1, + // "timestamp": 1496672724, + // "commission": 0.001, + // "commissionCurrency": "btc" + // } + // ] + // } + // + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'timestampCreated'); + const marketId = this.safeString (order, 'pair'); + market = this.parseMarket (marketId); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const remaining = this.safeFloat (order, 'remainingVolume'); + const amount = this.safeFloat (order, 'volume'); + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + cost = price * filled; + } + } + const orderType = this.safeString (order, 'orderType'); + const side = this.safeString (order, 'type'); + let fee = undefined; + const deals = this.safeValue (order, 'deals', []); + const numDeals = deals.length; + let trades = undefined; + let lastTradeTimestamp = undefined; + if (numDeals > 0) { + trades = this.parseTrades (deals); + let feeCost = undefined; + let feeCurrency = undefined; + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + if (feeCost === undefined) { + feeCost = 0; + } + feeCost = this.sum (feeCost, trade['fee']['cost']); + feeCurrency = trade['fee']['currency']; + lastTradeTimestamp = trade['timestamp']; + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': parseInt (id), + }; + const response = await this.privatePostOrderStatus (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit", + // "deals": [ + // { + // "pair": "btcusd", + // "type": "buy", + // "amount": 1, + // "rate": 1000.0, + // "orderId": 1, + // "timestamp": 1496672724, + // "commission": 0.001, + // "commissionCurrency": "btc" + // } + // ] + // } + // } + // + return this.parseOrder (this.extend ({ + 'id': id, + }, response['return'])); + } + + parseOrdersById (orders, symbol = undefined, since = undefined, limit = undefined) { + const ids = Object.keys (orders); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const order = this.parseOrder (this.extend ({ + 'id': id.toString (), + }, orders[id])); + result.push (order); + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'count': 10, // Decimal, The maximum number of orders to return + // 'fromId': 123, // Decimal, ID of the first order of the selection + // 'endId': 321, // Decimal, ID of the last order of the selection + // 'order': 'ASC', // String, Order in which orders shown. Possible values are "ASC" — from first to last, "DESC" — from last to first. + }; + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "0": { + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit" + // } + // } + // } + // + return this.parseOrdersById (this.safeValue (response, 'return', {}), symbol, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'count': 10, // Decimal, The maximum number of orders to return + // 'fromId': 123, // Decimal, ID of the first order of the selection + // 'endId': 321, // Decimal, ID of the last order of the selection + // 'order': 'ASC', // String, Order in which orders shown. Possible values are "ASC" — from first to last, "DESC" — from last to first. + }; + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privatePostHistoryOrders (this.extend (request, params)); + // + // { + // "success": 1, + // "return": { + // "0": { + // "pair": "btcusd", + // "type": "buy", + // "remainingVolume": 10, + // "volume": 10, + // "rate": 1000.0, + // "timestampCreated": 1496670, + // "status": 0, + // "orderType": "limit" + // } + // } + // } + // + return this.parseOrdersById (this.safeValue (response, 'return', {}), symbol, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + return response; + } + + parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { + const result = []; + const ids = Object.keys (orders); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const order = this.extend ({ 'id': id }, orders[id]); + result.push (this.extend (this.parseOrder (order, market), params)); + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + return this.filterBy (orders, 'status', 'closed'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + // some derived classes use camelcase notation for request fields + const request = { + // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) + // 'count': 1000, // the number of trades for display numerical, default = 1000 + // 'from_id': trade ID, from which the display starts numerical 0 + // 'end_id': trade ID on which the display ends numerical ∞ + // 'order': 'ASC', // sorting, default = DESC (test result: liqui ignores this field, most recent trade always goes last) + // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) + // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) + // 'pair': 'eth_btc', // default = all markets + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['count'] = parseInt (limit); + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privatePostHistoryTrades (this.extend (request, params)); + let trades = []; + if ('return' in response) { + trades = response['return']; + } + return this.parseTrades (trades, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['count'] = limit; + } + const response = await this.privatePostHistoryTransactions (this.extend (request, params)); + // + // { + // "success": 1, + // "return": [ + // { + // "id": 1, + // "timestamp": 11, + // "type": "Withdraw", + // "amount": 1, + // "currency": "btc", + // "confirmationsCount": 6, + // "address": "address", + // "status": 2, + // "commission": 0.0001 + // } + // ] + // } + // + const transactions = this.safeValue (response, 'return', []); + return this.parseTransactions (transactions, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + '1': 'failed', + '2': 'ok', + '3': 'pending', + '4': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": 1, + // "timestamp": 11, // 11 in their docs ( + // "type": "Withdraw", + // "amount": 1, + // "currency": "btc", + // "confirmationsCount": 6, + // "address": "address", + // "status": 2, + // "commission": 0.0001 + // } + // + const timestamp = this.safeTimestamp (transaction, 'timestamp'); + let type = this.safeString (transaction, 'type'); + if (type !== undefined) { + if (type === 'Incoming') { + type = 'deposit'; + } else if (type === 'Withdraw') { + type = 'withdrawal'; + } + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + return { + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'txid'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'address'), + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'fee': { + 'currency': code, + 'cost': this.safeFloat (transaction, 'commission'), + 'rate': undefined, + }, + 'info': transaction, + }; + } + + async createDepositAddress (code, params = {}) { + const request = { + 'new': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + return response; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.dwapiPostDepositCryptoaddress (this.extend (request, params)); + const result = this.safeValue (response, 'return', {}); + const address = this.safeString (result, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, // not documented in DSX API + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const commission = this.safeValue (params, 'commission'); + if (commission === undefined) { + throw new ArgumentsRequired (this.id + ' withdraw() requires a `commission` (withdrawal fee) parameter (string)'); + } + params = this.omit (params, commission); + const request = { + 'currency': currency['id'], + 'amount': parseFloat (amount), + 'address': address, + 'commission': commission, + }; + if (tag !== undefined) { + request['address'] += ':' + tag; + } + const response = await this.dwapiPostWithdrawCrypto (this.extend (request, params)); + // + // [ + // { + // "success": 1, + // "return": { + // "transactionId": 2863073 + // } + // } + // ] + // + const data = this.safeValue (response, 'return', {}); + const id = this.safeString (data, 'transactionId'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.omit (params, this.extractParams (path)); + if (api === 'private' || api === 'dwapi') { + url += '/' + this.version + '/' + this.implodeParams (path, params); + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + }, query)); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512', 'base64'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } else if (api === 'public') { + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + url += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + if (Object.keys (query).length) { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // 1 - Liqui only returns the integer 'success' key from their private API + // + // { "success": 1, ... } httpCode === 200 + // { "success": 0, ... } httpCode === 200 + // + // 2 - However, exchanges derived from Liqui, can return non-integers + // + // It can be a numeric string + // { "sucesss": "1", ... } + // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) + // + // Or just a string + // { "success": "true", ... } + // { "success": "false", ... }, httpCode >= 200 + // + // Or a boolean + // { "success": true, ... } + // { "success": false, ... }, httpCode >= 200 + // + // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types + // + // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui + // + // To cover points 1, 2, 3 and 4 combined this handler should work like this: + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/eterbase.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/eterbase.js new file mode 100644 index 000000000..56d975d44 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/eterbase.js @@ -0,0 +1,1141 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, InvalidOrder, ExchangeError, BadRequest, BadSymbol } = require ('./base/errors'); +const { TRUNCATE, SIGNIFICANT_DIGITS } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class eterbase extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'eterbase', + 'name': 'Eterbase', + 'countries': [ 'SK' ], // Slovakia + 'rateLimit': 500, + 'version': 'v1', + 'certified': true, + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelOrder': true, + 'createOrder': true, + 'deposit': false, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '1h': '60', + '4h': '240', + '1d': '1440', + '1w': '10080', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg', + 'api': 'https://api.eterbase.exchange', + 'www': 'https://www.eterbase.com', + 'doc': 'https://developers.eterbase.exchange', + 'fees': 'https://www.eterbase.com/exchange/fees', + 'referral': 'https://eterbase.exchange/invite/1wjjh4Pe', + }, + 'api': { + 'markets': { + 'get': [ + '{id}/order-book', + ], + }, + 'public': { + 'get': [ + 'ping', + 'assets', + 'markets', + 'tickers', + 'tickers/{id}/ticker', + 'markets/{id}/trades', + 'markets/{id}/ohlcv', + 'wstoken', + ], + }, + 'private': { + 'get': [ + 'accounts/{id}/balances', + 'accounts/{id}/orders', + 'accounts/{id}/fills', + 'orders/{id}/fills', + 'orders/{id}', + ], + 'post': [ + 'orders', + 'accounts/{id}/withdrawals', + ], + 'delete': [ + 'orders/{id}', + ], + }, + 'feed': { + 'get': [ + 'feed', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.35 / 100, + 'maker': 0.35 / 100, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'precisionMode': SIGNIFICANT_DIGITS, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + }, + 'exceptions': { + 'exact': { + 'Invalid cost': InvalidOrder, // {"message":"Invalid cost","_links":{"self":{"href":"/orders","templated":false}}} + 'Invalid order ID': InvalidOrder, // {"message":"Invalid order ID","_links":{"self":{"href":"/orders/4a151805-d594-4a96-9d64-e3984f2441f7","templated":false}}} + 'Invalid market !': BadSymbol, // {"message":"Invalid market !","_links":{"self":{"href":"/markets/300/order-book","templated":false}}} + }, + 'broad': { + 'Failed to convert argument': BadRequest, + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { "pong": 1556354416582 } + // + return this.safeInteger (response, 'pong'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "id":33, + // "symbol":"ETHUSDT", + // "base":"ETH", + // "quote":"USDT", + // "priceSigDigs":5, + // "qtySigDigs":8, + // "costSigDigs":8, + // "verificationLevelUser":1, + // "verificationLevelCorporate":11, + // "group":"USD", + // "tradingRules":[ + // {"attribute":"Qty","condition":"Min","value":0.006}, + // {"attribute":"Qty","condition":"Max","value":1000}, + // {"attribute":"Cost","condition":"Min","value":1}, + // {"attribute":"Cost","condition":"Max","value":210000} + // ], + // "allowedOrderTypes":[1,2,3,4], + // "state":"Trading" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = this.parseMarket (response[i]); + result.push (market); + } + return result; + } + + parseMarket (market) { + // + // { + // "id":33, + // "symbol":"ETHUSDT", + // "base":"ETH", + // "quote":"USDT", + // "priceSigDigs":5, + // "qtySigDigs":8, + // "costSigDigs":8, + // "verificationLevelUser":1, + // "verificationLevelCorporate":11, + // "group":"USD", + // "tradingRules":[ + // {"attribute":"Qty","condition":"Min","value":0.006}, + // {"attribute":"Qty","condition":"Max","value":1000}, + // {"attribute":"Cost","condition":"Min","value":1}, + // {"attribute":"Cost","condition":"Max","value":210000} + // ], + // "allowedOrderTypes":[1,2,3,4], + // "state":"Trading" + // } + // + const id = this.safeString (market, 'id'); + // const numericId = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const state = this.safeString (market, 'state'); + const active = (state === 'Trading'); + const precision = { + 'price': this.safeInteger (market, 'priceSigDigs'), + 'amount': this.safeInteger (market, 'qtySigDigs'), + 'cost': this.safeInteger (market, 'costSigDigs'), + }; + const rules = this.safeValue (market, 'tradingRules', []); + let minAmount = undefined; + let maxAmount = undefined; + let minCost = undefined; + let maxCost = undefined; + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + const attribute = this.safeString (rule, 'attribute'); + const condition = this.safeString (rule, 'condition'); + const value = this.safeFloat (rule, 'value'); + if ((attribute === 'Qty') && (condition === 'Min')) { + minAmount = value; + } else if ((attribute === 'Qty') && (condition === 'Max')) { + maxAmount = value; + } else if ((attribute === 'Cost') && (condition === 'Min')) { + minCost = value; + } else if ((attribute === 'Cost') && (condition === 'Max')) { + maxCost = value; + } + } + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': maxCost, + }, + }, + }; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetAssets (params); + // + // [ + // { + // "id":"LINK", + // "name":"ChainLink Token", + // "precisionDisplay":8, + // "precisionMax":18, + // "precisionBasis":1000000000000000000, + // "precisionStep":1, + // "verificationLevelMin":"null", + // "cmcId":"LINK", + // "txnUrl":"https://etherscan.io/tx/{txnId}", + // "state":"Active", + // "type":"Crypto", + // "isReference":false, + // "withdrawalMin":"0", + // "withdrawalMax":"50587", + // "withdrawalFee":"0.55", + // "depositEnabled":true, + // "withdrawalEnabled":true, + // "description":"", + // "coingeckoUrl":"https://www.coingecko.com/en/coins/chainlink", + // "coinmarketcapUrl":"https://coinmarketcap.com/currencies/chainlink", + // "eterbaseUrl":"https://www.eterbase.com/system-status/LINK", + // "explorerUrl":"https://etherscan.io/token/0x514910771af9ca656af840dff83e8264ecf986ca", + // "withdrawalMemoAllowed":false, + // "countries":[], + // "networks":[] + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'id'); + const precision = this.safeInteger (currency, 'precisionDisplay'); + const code = this.safeCurrencyCode (id); + const depositEnabled = this.safeValue (currency, 'depositEnabled'); + const withdrawalEnabled = this.safeValue (currency, 'withdrawalEnabled'); + const state = this.safeString (currency, 'state'); + const active = depositEnabled && withdrawalEnabled && (state === 'Active'); + const type = this.safeStringLower (currency, 'type'); + const name = this.safeString (currency, 'name'); + result[code] = { + 'id': id, + 'info': currency, + 'code': code, + 'type': type, + 'name': name, + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawalFee'), + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'withdrawalMin'), + 'max': this.safeFloat (currency, 'withdrawalMax'), + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "time":1588778516608, + // "marketId":250, + // "symbol": "ETHUSDT", + // "price":0.0, + // "change":0.0, + // "volumeBase":0.0, + // "volume":0.0, + // "low":0.0, + // "high":0.0, + // } + // + const marketId = this.safeString (ticker, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (ticker, 'time'); + const last = this.safeFloat (ticker, 'price'); + const baseVolume = this.safeFloat (ticker, 'volumeBase'); + const quoteVolume = this.safeFloat (ticker, 'volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + const percentage = this.safeFloat (ticker, 'change'); + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': undefined, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetTickersIdTicker (this.extend (request, params)); + // + // { + // "time":1588778516608, + // "marketId":250, + // "price":0.0, + // "change":0.0, + // "volumeBase":0.0, + // "volume":0.0, + // "low":0.0, + // "high":0.0, + // } + // + return this.parseTicker (response, market); + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'quote': 'USDT', // identifier of a quote asset to filter the markets + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ + // { + // "time":1588831771698, + // "marketId":33, + // "price":204.54, + // "change":-1.03, + // "volumeBase":544.9801776699998, + // "volume":111550.433735, + // "low":200.33, + // "high":209.51 + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + parseTrade (trade, market) { + // + // fetchTrades (public) + // + // { + // "id":251199246, + // "side":2, + // "price":0.022044, + // "executedAt":1588830682664, + // "qty":0.13545846, + // "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c", + // "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3" + // } + // + // fetchMyTrades, fetchOrderTrades (private) + // + // { + // "id": 123, + // "marketId": 123, + // "side": 1, + // "qty": "1.23456", + // "price": "1.23456", + // "cost": "1.23456", + // "fee": "1.23456", + // "feeAsset": "XBASE", + // "liquidity": 1, + // "orderId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "tradeId": 123, + // "filledAt": 1556355722341 + // } + // + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'qty'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeAsset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + let cost = this.safeFloat (trade, 'qty'); + if ((cost === undefined) && (price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const timestamp = this.safeInteger2 (trade, 'executedAt', 'filledAt'); + const tradeSide = this.safeString (trade, 'side'); + const side = (tradeSide === '1') ? 'buy' : 'sell'; + const liquidity = this.safeString (trade, 'liquidity'); + let takerOrMaker = undefined; + if (liquidity !== undefined) { + takerOrMaker = (liquidity === '1') ? 'maker' : 'taker'; + } + const orderId = this.safeString (trade, 'orderId'); + const id = this.safeString (trade, 'id'); + const marketId = this.safeString (trade, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + return { + 'info': trade, + 'id': id, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + // 'offset': 0 // the number of records to skip + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketsIdTrades (this.extend (request, params)); + // + // [ + // { + // "id":251199246, + // "side":2, + // "price":0.022044, + // "executedAt":1588830682664, + // "qty":0.13545846, + // "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c", + // "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.marketsGetIdOrderBook (this.extend (request, params)); + // + // { + // "type":"ob_snapshot", + // "marketId":3, + // "timestamp":1588836429847, + // "bids":[ + // [0.021694,8.8793688,1], // price, amount, count + // [0.01937,7.1340473,1], + // [0.020774,3.314881,1], + // ], + // "asks":[ + // [0.02305,8.8793688,1], + // [0.028022,3.314881,1], + // [0.022598,3.314881,1], + // ] + // } + // + const timestamp = this.safeInteger (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":1588807500000, + // "open":0.022077, + // "high":0.022077, + // "low":0.022051, + // "close":0.022051, + // "volume":10.532025119999997 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + const request = { + // 'id': market['id'], + 'interval': this.timeframes[timeframe], + // 'start': 1588830682664, // milliseconds + // 'end': 1588830682664, // milliseconds + }; + const duration = this.parseTimeframe (timeframe); + const now = this.milliseconds (); + if (since !== undefined) { + request['start'] = since; + if (limit === undefined) { + request['end'] = now; + } else { + request['end'] = this.sum (since, duration * limit * 1000); + } + } else if (limit !== undefined) { + request['start'] = now - duration * limit * 1000; + request['end'] = now; + } else { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument, or a limit argument, or both'); + } + await this.loadMarkets (); + const market = this.market (symbol); + request['id'] = market['id']; + const response = await this.publicGetMarketsIdOhlcv (this.extend (request, params)); + // + // [ + // {"time":1588807500000,"open":0.022077,"high":0.022077,"low":0.022051,"close":0.022051,"volume":10.532025119999997}, + // {"time":1588807800000,"open":0.022051,"high":0.022051,"low":0.022044,"close":0.022044,"volume":0.655987}, + // {"time":1588808400000,"open":0.022044,"high":0.022044,"low":0.022044,"close":0.022044,"volume":3.9615545499999993}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.uid, + }; + const response = await this.privateGetAccountsIdBalances (this.extend (request, params)); + // + // [ + // { + // "assetId":"USDT", + // "available":"25", + // "balance":"25", + // "reserved":"0", + // "balanceBtc":"0.0", + // "balanceRef":"0.0", + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'assetId'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': this.safeFloat (balance, 'reserved'), + 'total': this.safeFloat (balance, 'balance'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + // + // { + // "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "marketId": 123, + // "type": 1, + // "side": 1, + // "qty": "1.23456", + // "cost": "1.23456", + // "remainingQty": "1.23456", + // "remainingCost": "1.23456", + // "limitPrice": "1.23456", + // "stopPrice": "1.23456", + // "postOnly": false, + // "timeInForce": "GTC", + // "state": 1, + // "closeReason": "FILLED", + // "placedAt": 1556355722341, + // "closedAt": 1556355722341 + // } + // + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + '1': undefined, // pending + '2': 'open', // open + '3': 'open', // partially filled + '4': 'closed', // closed + 'FILLED': 'closed', + 'USER_REQUESTED_CANCEL': 'canceled', + 'ADMINISTRATIVE_CANCEL': 'canceled', + 'NOT_ENOUGH_LIQUIDITY': 'canceled', + 'EXPIRED': 'expired', + 'ONE_CANCELS_OTHER': 'canceled', + }; + return this.safeString (statuses, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // { + // "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "marketId": 123, + // "type": 1, + // "side": 1, + // "qty": "1.23456", + // "cost": "1.23456", + // "remainingQty": "1.23456", + // "remainingCost": "1.23456", + // "limitPrice": "1.23456", + // "stopPrice": "1.23456", + // "postOnly": false, + // "timeInForce": "GTC", + // "state": 1, + // "closeReason": "FILLED", + // "placedAt": 1556355722341, + // "closedAt": 1556355722341 + // } + // + // createOrder + // + // market buy + // + // { + // "id":"ff81127c-8fd5-4846-b683-110639dcd322", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":1, + // "side":1, + // "cost":"25", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589510846735 + // } + // + // market sell, limit buy, limit sell + // + // { + // "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":2, + // "side":1, + // "qty":"1000", + // "limitPrice":"100", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589403938682, + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.safeInteger (order, 'placedAt'); + const marketId = this.safeInteger (order, 'marketId'); + const symbol = this.safeSymbol (marketId, market); + let status = this.parseOrderStatus (this.safeString (order, 'state')); + if (status === 'closed') { + status = this.parseOrderStatus (this.safeString (order, 'closeReason')); + } + const orderSide = this.safeString (order, 'side'); + const side = (orderSide === '1') ? 'buy' : 'sell'; + const orderType = this.safeString (order, 'type'); + let type = undefined; + if (orderType === '1') { + type = 'market'; + } else if (orderType === '2') { + type = 'limit'; + } else if (orderType === '3') { + type = 'stopmarket'; + } else { + type = 'stoplimit'; + } + let price = this.safeFloat (order, 'limitPrice'); + const amount = this.safeFloat (order, 'qty'); + let remaining = this.safeFloat (order, 'remainingQty'); + let filled = undefined; + const remainingCost = this.safeFloat (order, 'remainingCost'); + if ((remainingCost !== undefined) && (remainingCost === 0.0)) { + remaining = 0; + } + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + const cost = this.safeFloat (order, 'cost'); + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + let average = undefined; + if (cost !== undefined) { + if (filled) { + average = cost / filled; + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'timeInForce': timeInForce, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const now = this.milliseconds (); + const ninetyDays = 90 * 24 * 60 * 60 * 1000; // 90 days timerange max + const request = { + 'id': this.uid, + 'state': state, + // 'side': Integer, // 1 = buy, 2 = sell + // 'offset': 0, // the number of records to skip + }; + if (since === undefined) { + request['from'] = now - ninetyDays; + request['to'] = now; + } else { + request['from'] = since; + request['to'] = this.sum (since, ninetyDays); + } + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50 + } + const response = await this.privateGetAccountsIdOrders (this.extend (request, params)); + // + // [ + // { + // "id": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "accountId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "marketId": 123, + // "type": 1, + // "side": 1, + // "qty": "1.23456", + // "cost": "1.23456", + // "remainingQty": "1.23456", + // "remainingCost": "1.23456", + // "limitPrice": "1.23456", + // "stopPrice": "1.23456", + // "postOnly": false, + // "timeInForce": "GTC", + // "state": 1, + // "closeReason": "FILLED", + // "placedAt": 1556355722341, + // "closedAt": 1556355722341 + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('INACTIVE', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('ACTIVE', symbol, since, limit, params); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const now = this.milliseconds (); + const ninetyDays = 90 * 24 * 60 * 60 * 1000; // 90 days timerange max + const request = { + 'id': this.uid, + // 'side': Integer, // 1 = buy, 2 = sell + // 'offset': 0, // the number of records to skip + }; + if (since === undefined) { + request['from'] = now - ninetyDays; + request['to'] = now; + } else { + request['from'] = since; + request['to'] = this.sum (since, ninetyDays); + } + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['marketId'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 200 + } + const response = await this.privateGetAccountsIdFills (this.extend (request, params)); + // + // [ + // { + // "id": 123, + // "marketId": 123, + // "side": 1, + // "qty": "1.23456", + // "price": "1.23456", + // "cost": "1.23456", + // "fee": "1.23456", + // "feeAsset": "XBASE", + // "liquidity": 1, + // "orderId": "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792", + // "tradeId": 123, + // "filledAt": 1556355722341 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const trades = await this.privateGetOrdersIdFills (this.extend (request, params)); + return this.parseTrades (trades); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + if (uppercaseType === 'MARKET') { + type = 1; + } else if (uppercaseType === 'LIMIT') { + type = 2; + } else if (uppercaseType === 'STOPMARKET') { + type = 3; + } else if (uppercaseType === 'STOPLIMIT') { + type = 4; + } + const uppercaseSide = side.toUpperCase (); + side = uppercaseSide === 'BUY' ? 1 : 2; + const request = { + 'accountId': this.uid, + 'marketId': market['id'], + 'type': type, + 'side': side, + // 'postOnly': false, + // 'timeInForce': 'GTC', + }; + const clientOrderId = this.safeValue2 (params, 'refId', 'clientOrderId'); + let query = params; + if (clientOrderId !== undefined) { + request['refId'] = clientOrderId; + query = this.omit (params, [ 'refId', 'clientOrderId' ]); + } + if ((uppercaseType === 'MARKET') && (uppercaseSide === 'BUY')) { + // for market buy it requires the amount of quote currency to spend + let cost = this.safeFloat (params, 'cost'); + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (cost === undefined) { + if (price !== undefined) { + cost = amount * price; + } else { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } + } + } else { + cost = (cost === undefined) ? amount : cost; + } + const precision = market['precision']['price']; + request['cost'] = this.decimalToPrecision (cost, TRUNCATE, precision, this.precisionMode); + } else { + request['qty'] = this.amountToPrecision (symbol, amount); + } + if (uppercaseType === 'LIMIT') { + request['limitPrice'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, query)); + // + // market buy + // + // { + // "id":"ff81127c-8fd5-4846-b683-110639dcd322", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":1, + // "side":1, + // "cost":"25", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589510846735 + // } + // + // market sell, limit buy, limit sell + // + // { + // "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1", + // "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2", + // "marketId":33, + // "type":2, + // "side":1, + // "qty":"1000", + // "limitPrice":"100", + // "postOnly":false, + // "timeInForce":"GTC", + // "state":1, + // "placedAt":1589403938682, + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': id, + }; + return await this.privateDeleteOrdersId (this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'id': this.uid, + 'accountId': this.uid, + 'assetId': currency['id'], + 'amount': amount, + // 'cryptoAddress': address, + // 'accountNumber': 'IBAN', // IBAN account number + // 'networkId': 'XBASE', // underlying network + }; + if (address !== undefined) { + request['cryptoAddress'] = address; + if (tag !== undefined) { + request['memo'] = tag; + } + } + const response = await this.privatePostAccountsIdWithdrawals (this.extend (request, params)); + // + // { + // "id": "98b62dde-a87f-45f0-8db8-80ae2d312fa6" + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, httpHeaders = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let request = '/'; + if (api === 'public') { + request += 'api/' + this.version; + } else if (api === 'private') { + request += 'api/' + this.version; + } else if (api === 'markets') { + request += 'api/' + api; + } + request += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + request += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + request; + if (api === 'private') { + this.checkRequiredCredentials (); + let payload = ''; + if (method !== 'GET') { + if (Object.keys (query).length) { + body = this.json (query); + payload = body; + } + } + // construct signature + const hasBody = (method === 'POST') || (method === 'PUT') || (method === 'PATCH'); + // const date = 'Mon, 30 Sep 2019 13:57:23 GMT'; + const date = this.rfc2616 (this.milliseconds ()); + let headersCSV = 'date' + ' ' + 'request-line'; + let message = 'date' + ':' + ' ' + date + "\n" + method + ' ' + request + ' HTTP/1.1'; // eslint-disable-line quotes + let digest = ''; + if (hasBody) { + digest = 'SHA-256=' + this.hash (payload, 'sha256', 'base64'); + message += "\ndigest" + ':' + ' ' + digest; // eslint-disable-line quotes + headersCSV += ' ' + 'digest'; + } + const signature = this.hmac (this.encode (message), this.encode (this.secret), 'sha256', 'base64'); + const authorizationHeader = 'hmac username="' + this.apiKey + '",algorithm="hmac-sha256",headers="' + headersCSV + '",' + 'signature="' + signature + '"'; + httpHeaders = { + 'Date': date, + 'Authorization': authorizationHeader, + 'Content-Type': 'application/json', + }; + if (hasBody) { + httpHeaders['Digest'] = digest; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': httpHeaders }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"message":"Invalid cost","_links":{"self":{"href":"/orders","templated":false}}} + // + const message = this.safeString (response, 'message'); + if (message !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/exmo.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/exmo.js new file mode 100644 index 000000000..52c2ba65e --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/exmo.js @@ -0,0 +1,1591 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, ExchangeError, OrderNotFound, AuthenticationError, InsufficientFunds, InvalidOrder, InvalidNonce, NotSupported, OnMaintenance, RateLimitExceeded, BadRequest, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class exmo extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'exmo', + 'name': 'EXMO', + 'countries': [ 'ES', 'RU' ], // Spain, Russia + 'rateLimit': 350, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second + 'version': 'v1.1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': 'emulated', + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '45m': '45', + '1h': '60', + '2h': '120', + '3h': '180', + '4h': '240', + '1d': 'D', + '1w': 'W', + '1M': 'M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg', + 'api': { + 'public': 'https://api.exmo.com', + 'private': 'https://api.exmo.com', + 'web': 'https://exmo.me', + }, + 'www': 'https://exmo.me', + 'referral': 'https://exmo.me/?ref=131685', + 'doc': [ + 'https://exmo.me/en/api_doc?ref=131685', + 'https://github.com/exmo-dev/exmo_api_lib/tree/master/nodejs', + ], + 'fees': 'https://exmo.com/en/docs/fees', + }, + 'api': { + 'web': { + 'get': [ + 'ctrl/feesAndLimits', + 'en/docs/fees', + ], + }, + 'public': { + 'get': [ + 'currency', + 'currency/list/extended', + 'order_book', + 'pair_settings', + 'ticker', + 'trades', + 'candles_history', + 'required_amount', + 'payments/providers/crypto/list', + ], + }, + 'private': { + 'post': [ + 'user_info', + 'order_create', + 'order_cancel', + 'stop_market_order_create', + 'stop_market_order_cancel', + 'user_open_orders', + 'user_trades', + 'user_cancelled_orders', + 'order_trades', + 'deposit_address', + 'withdraw_crypt', + 'withdraw_get_txid', + 'excode_create', + 'excode_load', + 'code_check', + 'wallet_history', + 'wallet_operations', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, // fixed funding fees for crypto, see fetchFundingFees below + }, + }, + 'options': { + 'useWebapiForFetchingFees': false, // TODO: figure why Exmo bans us when we try to fetch() their web urls + 'feesAndLimits': { + 'success': 1, + 'ctlr': 'feesAndLimits', + 'error': '', + 'data': { + 'limits': [ + { 'pair': 'BTC/USD', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/RUB', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '2000000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/EUR', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/GBP', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '30000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/UAH', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '15000000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/PLN', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '20000000', 'min_a': '50', 'max_a': '2000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/TRY', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1', 'max_p': '800000', 'min_a': '40', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/KZT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '1000', 'max_p': '12000000', 'min_a': '1000', 'max_a': '100000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTC/USDT', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.01', 'max_p': '30000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'ETH/BTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/USD', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/RUB', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/USD', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/RUB', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ADA/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ADA/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ADA/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '50', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/USDT', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'ALGO/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ALGO/EUR', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ATOM/EUR', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/USD', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '30000', 'min_a': '0.0001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/RUB', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '2000000', 'min_a': '0.0001', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/EUR', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/UAH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.1', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/BTC', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '5', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/ETH', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.0000001', 'max_p': '200', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BCH/USDT', 'min_q': '0.003', 'max_q': '5000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'BTG/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTG/BTC', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTG/ETH', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTT/RUB', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTT/UAH', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'BTT/BTC', 'min_q': '1', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'CRON/BTC', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'CRON/ETH', 'min_q': '1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'CRON/USDT', 'min_q': '1', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'CRON/EXM', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '0.5', 'max_a': '30000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DAI/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '200000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DASH/USDT', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.01', 'max_p': '5000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'DCR/RUB', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '3000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DCR/UAH', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.25', 'max_a': '1000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DCR/BTC', 'min_q': '0.01', 'max_q': '50000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DOGE/USD', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'DOGE/BTC', 'min_q': '100', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EOS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EOS/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EOS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETC/USD', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETC/RUB', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '0.01', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETC/BTC', 'min_q': '0.2', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '0.5', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/EUR', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/GBP', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/UAH', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '1000000', 'min_a': '90', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/PLN', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '2000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/TRY', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.1', 'max_p': '80000', 'min_a': '10', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/KZT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '4', 'max_p': '40000000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETH/USDT', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'ETH/LTC', 'min_q': '0.001', 'max_q': '5000', 'min_p': '0.00000001', 'max_p': '100000', 'min_a': '0.05', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETZ/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETZ/ETH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ETZ/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '1000', 'taker': '0', 'maker': '0' }, + { 'pair': 'EXM/USDT', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0', 'maker': '0' }, + { 'pair': 'EXM/ETH', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GAS/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GAS/BTC', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GNT/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GNT/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GUSD/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GUSD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'GUSD/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'HP/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'HB/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LSK/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.1', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LSK/RUB', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LSK/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.0015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/USD', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/RUB', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '150', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/EUR', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/UAH', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.01', 'max_p': '300000', 'min_a': '5', 'max_a': '18000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'LTC/BTC', 'min_q': '0.05', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MKR/BTC', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.0001', 'max_p': '100', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MKR/DAI', 'min_q': '0.0001', 'max_q': '1000', 'min_p': '0.5', 'max_p': '500000', 'min_a': '0.005', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MNC/USD', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MNC/BTC', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'MNC/ETH', 'min_q': '10', 'max_q': '500000000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'NEO/USD', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.01', 'max_p': '50000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'NEO/RUB', 'min_q': '0.01', 'max_q': '100000', 'min_p': '0.001', 'max_p': '1500000', 'min_a': '50', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'NEO/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'OMG/USD', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'OMG/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'OMG/ETH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/EXM', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/BTC', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/RUB', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '250000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONG/UAH', 'min_q': '1', 'max_q': '1000000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '50', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/EXM', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '100', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ONT/UAH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '100000', 'min_a': '200', 'max_a': '250000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '600000', 'min_a': '10', 'max_a': '600000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.000001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/EOS', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0000001', 'max_p': '5000', 'min_a': '0.01', 'max_a': '20000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'PTI/USDT', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '10000', 'min_a': '0.01', 'max_a': '100000', 'taker': '0', 'maker': '0' }, + { 'pair': 'QTUM/USD', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '10000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'QTUM/BTC', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'QTUM/ETH', 'min_q': '0.1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ROOBEE/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/USD', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/RUB', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.0001', 'max_p': '100000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/EUR', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.000001', 'max_p': '1000', 'min_a': '1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'SMART/BTC', 'min_q': '10', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/USD', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/EUR', 'min_q': '0.01', 'max_q': '50000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/RUB', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/UAH', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.000001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'TRX/BTC', 'min_q': '1', 'max_q': '50000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/BTC', 'min_q': '1', 'max_q': '500000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/ETH', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDC/USDT', 'min_q': '1', 'max_q': '500000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '3', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'USDT/USD', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/EUR', 'min_q': '0.01', 'max_q': '500000', 'min_p': '0.1', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/GBP', 'min_q': '1', 'max_q': '500000', 'min_p': '0.5', 'max_p': '10', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/UAH', 'min_q': '0.01', 'max_q': '500000', 'min_p': '1', 'max_p': '3000', 'min_a': '2', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USDT/KZT', 'min_q': '1', 'max_q': '500000', 'min_p': '200', 'max_p': '4000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'VLX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/USD', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.001', 'max_p': '3500', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/RUB', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.01', 'max_p': '10000', 'min_a': '1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/BTC', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.000001', 'max_p': '1', 'min_a': '0.0001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'WAVES/ETH', 'min_q': '0.5', 'max_q': '500000', 'min_p': '0.00001', 'max_p': '30', 'min_a': '0.0035', 'max_a': '3500', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/USD', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/EUR', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/UAH', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.0001', 'max_p': '30000', 'min_a': '10', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XEM/BTC', 'min_q': '10', 'max_q': '10000000', 'min_p': '0.0000001', 'max_p': '1', 'min_a': '0.00015', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/USD', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/RUB', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/TRY', 'min_q': '0.01', 'max_q': '5000000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XLM/BTC', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '600000', 'min_a': '10', 'max_a': '16000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/UAH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '300000', 'min_a': '5', 'max_a': '16000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.0001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XMR/ETH', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00000001', 'max_p': '100', 'min_a': '0.001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/EUR', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/GBP', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/TRY', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '6000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/UAH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.01', 'max_a': '15000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XRP/USDT', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.001', 'max_p': '1000', 'min_a': '0.001', 'max_a': '500000', 'taker': '0', 'maker': '0' }, + { 'pair': 'XRP/ETH', 'min_q': '1', 'max_q': '5000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.00001', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/USD', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '100000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/RUB', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00001', 'max_p': '100000', 'min_a': '0.5', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/BTC', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.00001', 'max_a': '10', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'XTZ/ETH', 'min_q': '0.1', 'max_q': '100000', 'min_p': '0.0000001', 'max_p': '10', 'min_a': '0.0001', 'max_a': '1000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/USD', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/RUB', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '100000', 'min_a': '0.1', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/EUR', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.001', 'max_p': '5000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZEC/BTC', 'min_q': '0.01', 'max_q': '10000', 'min_p': '0.00001', 'max_p': '10', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZRX/USD', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00001', 'max_p': '1000', 'min_a': '0.1', 'max_a': '500000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZRX/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZRX/ETH', 'min_q': '0.01', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '10', 'min_a': '0.01', 'max_a': '5000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'ZAG/BTC', 'min_q': '1', 'max_q': '10000000', 'min_p': '0.00000001', 'max_p': '0.1', 'min_a': '0.00001', 'max_a': '100', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'USD/RUB', 'min_q': '1', 'max_q': '500000', 'min_p': '0.01', 'max_p': '1000', 'min_a': '10', 'max_a': '50000000', 'taker': '0.4', 'maker': '0.4' }, + { 'pair': 'EXM/BTC', 'min_q': '1', 'max_q': '100000000', 'min_p': '0.00000001', 'max_p': '1', 'min_a': '0.0000001', 'max_a': '1', 'taker': '0.4', 'maker': '0.4' }, + ], + 'fees': [ + { + 'group': 'crypto', + 'title': 'Cryptocurrency', + 'items': [ + { 'prov': 'EXM', 'dep': '0%', 'wd': '1 EXM' }, + { 'prov': 'BTC', 'dep': '0%', 'wd': '0.0004 BTC' }, + { 'prov': 'LTC', 'dep': '0%', 'wd': '0.01 LTC' }, + { 'prov': 'DOGE', 'dep': '0%', 'wd': '1 Doge' }, + { 'prov': 'DASH', 'dep': '0%', 'wd': '0.002 DASH' }, + { 'prov': 'ETH', 'dep': '0%', 'wd': '0.003 ETH' }, + { 'prov': 'WAVES', 'dep': '0%', 'wd': '0.001 WAVES' }, + { 'prov': 'ZEC', 'dep': '0%', 'wd': '0.001 ZEC' }, + { 'prov': 'USDT', 'dep': '0%', 'wd': '' }, + { 'prov': 'XMR', 'dep': '0%', 'wd': '0.001 XMR' }, + { 'prov': 'XRP', 'dep': '0%', 'wd': '0.02 XRP' }, + { 'prov': 'ETC', 'dep': '0%', 'wd': '0.01 ETC' }, + { 'prov': 'BCH', 'dep': '0%', 'wd': '0.001 BCH' }, + { 'prov': 'BTG', 'dep': '0%', 'wd': '0.001 BTG' }, + { 'prov': 'EOS', 'dep': '0%', 'wd': '0.05 EOS' }, + { 'prov': 'XLM', 'dep': '0%', 'wd': '0.01 XLM' }, + { 'prov': 'OMG', 'dep': '0.1 OMG', 'wd': '0.5 OMG' }, + { 'prov': 'TRX', 'dep': '0%', 'wd': '1 TRX' }, + { 'prov': 'ADA', 'dep': '0%', 'wd': '1 ADA' }, + { 'prov': 'NEO', 'dep': '0%', 'wd': '0%' }, + { 'prov': 'GAS', 'dep': '0%', 'wd': '0%' }, + { 'prov': 'ZRX', 'dep': '0%', 'wd': '1 ZRX' }, + { 'prov': 'GNT', 'dep': '0%', 'wd': '1 GNT' }, + { 'prov': 'GUSD', 'dep': '0%', 'wd': '0.5 GUSD' }, + { 'prov': 'LSK', 'dep': '0%', 'wd': '0.1 LSK' }, + { 'prov': 'XEM', 'dep': '0%', 'wd': '5 XEM' }, + { 'prov': 'SMART', 'dep': '0%', 'wd': '0.5 SMART' }, + { 'prov': 'QTUM', 'dep': '0%', 'wd': '0.01 QTUM' }, + { 'prov': 'HB', 'dep': '0%', 'wd': '10 HB' }, + { 'prov': 'DAI', 'dep': '0%', 'wd': '1 DAI' }, + { 'prov': 'MKR', 'dep': '0%', 'wd': '0.005 MKR' }, + { 'prov': 'MNC', 'dep': '0%', 'wd': '15 MNC' }, + { 'prov': 'PTI', 'dep': '-', 'wd': '10 PTI' }, + { 'prov': 'ETZ', 'dep': '0%', 'wd': '1 ETZ' }, + { 'prov': 'USDC', 'dep': '0%', 'wd': '0.5 USDC' }, + { 'prov': 'ROOBEE', 'dep': '0%', 'wd': '200 ROOBEE' }, + { 'prov': 'DCR', 'dep': '0%', 'wd': '0.01 DCR' }, + { 'prov': 'ZAG', 'dep': '0%', 'wd': '0%' }, + { 'prov': 'BTT', 'dep': '0 BTT', 'wd': '100 BTT' }, + { 'prov': 'VLX', 'dep': '0%', 'wd': '1 VLX' }, + { 'prov': 'CRON', 'dep': '0%', 'wd': '5 CRON' }, + { 'prov': 'ONT', 'dep': '0%', 'wd': '1 ONT' }, + { 'prov': 'ONG', 'dep': '0%', 'wd': '5 ONG' }, + { 'prov': 'ALGO', 'dep': '0%', 'wd': '0.01 ALGO' }, + { 'prov': 'ATOM', 'dep': '0%', 'wd': '0.05 ATOM' }, + ], + }, + { + 'group': 'usd', + 'title': 'USD', + 'items': [ + { 'prov': 'Payeer', 'dep': '3.95%', 'wd': '-' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'AdvCash', 'dep': '0%', 'wd': '2.49%' }, + { 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5% + 0.5 USD', 'wd': '-' }, + { 'prov': 'Visa', 'dep': '3.45%', 'wd': '-' }, + { 'prov': 'Frick Bank', 'dep': '0 USD', 'wd': '-' }, + ], + }, + { + 'group': 'eur', + 'title': 'EUR', + 'items': [ + { 'prov': 'Visa/MasterCard', 'dep': '4.5% + 0.5 EUR', 'wd': '-' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Visa', 'dep': '2.95%', 'wd': '-' }, + { 'prov': 'Frick Internal Transfer', 'dep': '0 EUR', 'wd': '-' }, + { 'prov': 'SEPA Frick Bank', 'dep': '0 EUR', 'wd': '1 EUR' }, + { 'prov': 'WIRE Frick Bank', 'dep': '0%', 'wd': '20 EUR' }, + { 'prov': 'SEPA Weg Ag', 'dep': '-', 'wd': '1 EUR' }, + ], + }, + { + 'group': 'gbp', + 'title': 'GBP', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'WIRE Frick Bank', 'dep': '10 GBP', 'wd': '-' }, + ], + }, + { + 'group': 'rub', + 'title': 'RUB', + 'items': [ + { 'prov': 'Payeer', 'dep': '2.49%', 'wd': '3.49%' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Qiwi', 'dep': '1.49%', 'wd': '2.49%' }, + { 'prov': 'Yandex Money', 'dep': '1.49%', 'wd': '1.95 %' }, + { 'prov': 'AdvCash', 'dep': '0.99%', 'wd': '0.99%' }, + { 'prov': 'Visa/MasterCard', 'dep': '2.99%', 'wd': '3.99% + 60 RUB' }, + ], + }, + { + 'group': 'pln', + 'title': 'PLN', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + ], + }, + { + 'group': 'try', + 'title': 'TRY', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Visa', 'dep': '3.05%', 'wd': '-' }, + { 'prov': 'Visa/MasterCard (Simplex)', 'dep': '4.5% + 2 TRY', 'wd': '-' }, + { 'prov': 'AdvCash', 'dep': '0%', 'wd': '-' }, + ], + }, + { + 'group': 'uah', + 'title': 'UAH', + 'items': [ + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'Terminal', 'dep': '2.6%', 'wd': '-' }, + { 'prov': 'Visa/MasterCard EasyTransfer', 'dep': '-', 'wd': '2.99%' }, + { 'prov': 'Visa/MasterCard', 'dep': '1% + 5 UAH', 'wd': '-' }, + ], + }, + { + 'group': 'kzt', + 'title': 'KZT', + 'items': [ + { 'prov': 'Visa/MasterCard', 'dep': '3.5%', 'wd': '2.99% + 450 KZT' }, + { 'prov': 'EX-CODE', 'dep': '', 'wd': '0.2%' }, + { 'prov': 'AdvCash', 'dep': '0%', 'wd': '-' }, + ], + }, + ], + }, + }, + }, + 'exceptions': { + 'exact': { + '40005': AuthenticationError, // Authorization error, incorrect signature + '40009': InvalidNonce, // + '40015': ExchangeError, // API function do not exist + '40016': OnMaintenance, // {"result":false,"error":"Error 40016: Maintenance work in progress"} + '40017': AuthenticationError, // Wrong API Key + '40032': PermissionDenied, // {"result":false,"error":"Error 40032: Access is denied for this API key"} + '40034': RateLimitExceeded, // {"result":false,"error":"Error 40034: Access is denied, rate limit is exceeded"} + '50052': InsufficientFunds, + '50054': InsufficientFunds, + '50304': OrderNotFound, // "Order was not found '123456789'" (fetching order trades for an order that does not have trades yet) + '50173': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) + '50277': InvalidOrder, + '50319': InvalidOrder, // Price by order is less than permissible minimum for this pair + '50321': InvalidOrder, // Price by order is more than permissible maximum for this pair + }, + 'broad': { + 'range period is too long': BadRequest, + 'invalid syntax': BadRequest, + 'API rate limit exceeded': RateLimitExceeded, // {"result":false,"error":"API rate limit exceeded for 99.33.55.224. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400} + }, + }, + 'orders': {}, // orders cache / emulation + }); + } + + async fetchTradingFees (params = {}) { + if (this.options['useWebapiForFetchingFees']) { + const response = await this.webGetEnDocsFees (params); + let parts = response.split (''); + let numParts = parts.length; + if (numParts !== 2) { + throw new NotSupported (this.id + ' fetchTradingFees format has changed'); + } + const rest = parts[1]; + parts = rest.split (''); + numParts = parts.length; + if (numParts < 2) { + throw new NotSupported (this.id + ' fetchTradingFees format has changed'); + } + const fee = parseFloat (parts[0].replace ('%', '')) * 0.01; + const taker = fee; + const maker = fee; + return { + // 'info': response, + 'maker': maker, + 'taker': taker, + }; + } else { + return { + 'maker': this.fees['trading']['maker'], + 'taker': this.fees['trading']['taker'], + }; + } + } + + parseFixedFloatValue (input) { + if ((input === undefined) || (input === '-')) { + return undefined; + } + if (input === '') { + return 0; + } + const isPercentage = (input.indexOf ('%') >= 0); + const parts = input.split (' '); + const value = parts[0].replace ('%', ''); + const result = parseFloat (value); + if ((result > 0) && isPercentage) { + throw new ExchangeError (this.id + ' parseFixedFloatValue detected an unsupported non-zero percentage-based fee ' + input); + } + return result; + } + + async fetchFundingFees (params = {}) { + let response = undefined; + if (this.options['useWebapiForFetchingFees']) { + response = await this.webGetCtrlFeesAndLimits (params); + } else { + response = this.options['feesAndLimits']; + } + // the code below assumes all non-zero crypto fees are fixed (for now) + const withdraw = {}; + const deposit = {}; + const groups = this.safeValue (response['data'], 'fees'); + const groupsByGroup = this.indexBy (groups, 'group'); + const items = groupsByGroup['crypto']['items']; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const code = this.safeCurrencyCode (this.safeString (item, 'prov')); + const withdrawalFee = this.safeString (item, 'wd'); + const depositFee = this.safeString (item, 'dep'); + if (withdrawalFee !== undefined) { + withdraw[code] = this.parseFixedFloatValue (withdrawalFee); + } + if (depositFee !== undefined) { + deposit[code] = this.parseFixedFloatValue (depositFee); + } + } + // sets fiat fees to undefined + const fiatGroups = this.toArray (this.omit (groupsByGroup, 'crypto')); + for (let i = 0; i < fiatGroups.length; i++) { + const code = this.safeCurrencyCode (this.safeString (fiatGroups[i], 'title')); + withdraw[code] = undefined; + deposit[code] = undefined; + } + const result = { + 'info': response, + 'withdraw': withdraw, + 'deposit': deposit, + }; + // cache them for later use + this.options['fundingFees'] = result; + return result; + } + + async fetchCurrencies (params = {}) { + const fees = await this.fetchFundingFees (params); + // todo redesign the 'fee' property in currencies + const ids = Object.keys (fees['withdraw']); + const limitsByMarketId = this.indexBy (fees['info']['data']['limits'], 'pair'); + const marketIds = Object.keys (limitsByMarketId); + const minAmounts = {}; + const minPrices = {}; + const minCosts = {}; + const maxAmounts = {}; + const maxPrices = {}; + const maxCosts = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const limit = limitsByMarketId[marketId]; + const [ baseId, quoteId ] = marketId.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const maxAmount = this.safeFloat (limit, 'max_q'); + const maxPrice = this.safeFloat (limit, 'max_p'); + const maxCost = this.safeFloat (limit, 'max_a'); + const minAmount = this.safeFloat (limit, 'min_q'); + const minPrice = this.safeFloat (limit, 'min_p'); + const minCost = this.safeFloat (limit, 'min_a'); + minAmounts[base] = Math.min (this.safeFloat (minAmounts, base, minAmount), minAmount); + maxAmounts[base] = Math.max (this.safeFloat (maxAmounts, base, maxAmount), maxAmount); + minPrices[quote] = Math.min (this.safeFloat (minPrices, quote, minPrice), minPrice); + minCosts[quote] = Math.min (this.safeFloat (minCosts, quote, minCost), minCost); + maxPrices[quote] = Math.max (this.safeFloat (maxPrices, quote, maxPrice), maxPrice); + maxCosts[quote] = Math.max (this.safeFloat (maxCosts, quote, maxCost), maxCost); + } + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + const fee = this.safeValue (fees['withdraw'], code); + const active = true; + result[code] = { + 'id': id, + 'code': code, + 'name': code, + 'active': active, + 'fee': fee, + 'precision': 8, + 'limits': { + 'amount': { + 'min': this.safeFloat (minAmounts, code), + 'max': this.safeFloat (maxAmounts, code), + }, + 'price': { + 'min': this.safeFloat (minPrices, code), + 'max': this.safeFloat (maxPrices, code), + }, + 'cost': { + 'min': this.safeFloat (minCosts, code), + 'max': this.safeFloat (maxCosts, code), + }, + }, + 'info': id, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPairSettings (params); + // + // { + // "BTC_USD":{ + // "min_quantity":"0.0001", + // "max_quantity":"1000", + // "min_price":"1", + // "max_price":"30000", + // "max_amount":"500000", + // "min_amount":"1", + // "price_precision":8, + // "commission_taker_percent":"0.4", + // "commission_maker_percent":"0.4" + // }, + // } + // + const keys = Object.keys (response); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = response[id]; + const symbol = id.replace ('_', '/'); + const [ baseId, quoteId ] = symbol.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const taker = this.safeFloat (market, 'commission_taker_percent'); + const maker = this.safeFloat (market, 'commission_maker_percent'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'taker': taker / 100, + 'maker': maker / 100, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_quantity'), + 'max': this.safeFloat (market, 'max_quantity'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }, + }, + 'precision': { + 'amount': 8, + 'price': this.safeInteger (market, 'price_precision'), + }, + 'info': market, + }); + } + return result; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + }; + const options = this.safeValue (this.options, 'fetchOHLCV'); + const maxLimit = this.safeInteger (options, 'maxLimit', 3000); + const duration = this.parseTimeframe (timeframe); + const now = this.milliseconds (); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); + } else { + if (limit > maxLimit) { + throw new BadRequest (this.id + ' fetchOHLCV will serve ' + maxLimit.toString () + ' candles at most'); + } + request['from'] = parseInt (now / 1000) - limit * duration - 1; + request['to'] = parseInt (now / 1000); + } + } else { + request['from'] = parseInt (since / 1000) - 1; + if (limit === undefined) { + request['to'] = parseInt (now / 1000); + } else { + if (limit > maxLimit) { + throw new BadRequest (this.id + ' fetchOHLCV will serve ' + maxLimit.toString () + ' candles at most'); + } + const to = this.sum (since, limit * duration * 1000); + request['to'] = parseInt (to / 1000); + } + } + const response = await this.publicGetCandlesHistory (this.extend (request, params)); + // + // { + // "candles":[ + // {"t":1584057600000,"o":0.02235144,"c":0.02400233,"h":0.025171,"l":0.02221,"v":5988.34031761}, + // {"t":1584144000000,"o":0.0240373,"c":0.02367413,"h":0.024399,"l":0.0235,"v":2027.82522329}, + // {"t":1584230400000,"o":0.02363458,"c":0.02319242,"h":0.0237948,"l":0.02223196,"v":1707.96944997}, + // ] + // } + // + const candles = this.safeValue (response, 'candles', []); + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "t":1584057600000, + // "o":0.02235144, + // "c":0.02400233, + // "h":0.025171, + // "l":0.02221, + // "v":5988.34031761 + // } + // + return [ + this.safeInteger (ohlcv, 't'), + this.safeFloat (ohlcv, 'o'), + this.safeFloat (ohlcv, 'h'), + this.safeFloat (ohlcv, 'l'), + this.safeFloat (ohlcv, 'c'), + this.safeFloat (ohlcv, 'v'), + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostUserInfo (params); + const result = { 'info': response }; + const free = this.safeValue (response, 'balances', {}); + const used = this.safeValue (response, 'reserved', {}); + const codes = Object.keys (free); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + const currencyId = this.currencyId (code); + const account = this.account (); + if (currencyId in free) { + account['free'] = this.safeFloat (free, currencyId); + } + if (currencyId in used) { + account['used'] = this.safeFloat (used, currencyId); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + const result = this.safeValue (response, market['id']); + return this.parseOrderBook (result, undefined, 'bid', 'ask'); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join (','); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join (','); + } + const request = { + 'pair': ids, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + const result = {}; + const marketIds = Object.keys (response); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let symbol = marketId; + if (marketId in this.markets_by_id) { + const market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } + result[symbol] = this.parseOrderBook (response[marketId], undefined, 'bid', 'ask'); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last_trade'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy_price'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell_price'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'vol_curr'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const result = {}; + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.markets_by_id[id]; + const symbol = market['symbol']; + const ticker = response[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const market = this.market (symbol); + return this.parseTicker (response[market['id']], market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "trade_id":165087520, + // "date":1587470005, + // "type":"buy", + // "quantity":"1.004", + // "price":"0.02491461", + // "amount":"0.02501426" + // }, + // + // fetchMyTrades, fetchOrderTrades + // + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100, + // "exec_type": "taker", + // "commission_amount": "0.02", + // "commission_currency": "BTC", + // "commission_percent": "0.2" + // } + // + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + const id = this.safeString (trade, 'trade_id'); + const orderId = this.safeString (trade, 'order_id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const cost = this.safeFloat (trade, 'amount'); + const side = this.safeString (trade, 'type'); + const type = undefined; + const marketId = this.safeString (trade, 'pair'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ baseId, quoteId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const takerOrMaker = this.safeString (trade, 'exec_type'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission_amount'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commission_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + let feeRate = this.safeFloat (trade, 'commission_percent'); + if (feeRate !== undefined) { + feeRate /= 1000; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "ETH_BTC":[ + // { + // "trade_id":165087520, + // "date":1587470005, + // "type":"buy", + // "quantity":"1.004", + // "price":"0.02491461", + // "amount":"0.02501426" + // }, + // { + // "trade_id":165087369, + // "date":1587469938, + // "type":"buy", + // "quantity":"0.94", + // "price":"0.02492348", + // "amount":"0.02342807" + // } + // ] + // } + // + const data = this.safeValue (response, market['id'], []); + return this.parseTrades (data, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // a symbol is required but it can be a single string, or a non-empty array + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument (a single symbol or an array)'); + } + await this.loadMarkets (); + let pair = undefined; + let market = undefined; + if (Array.isArray (symbol)) { + const numSymbols = symbol.length; + if (numSymbols < 1) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a non-empty symbol array'); + } + const marketIds = this.marketIds (symbol); + pair = marketIds.join (','); + } else { + market = this.market (symbol); + pair = market['id']; + } + const request = { + 'pair': pair, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostUserTrades (this.extend (request, params)); + let result = []; + const marketIds = Object.keys (response); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let symbol = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + const [ baseId, quoteId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const items = response[marketId]; + const trades = this.parseTrades (items, market, since, limit, { + 'symbol': symbol, + }); + result = this.arrayConcat (result, trades); + } + return this.filterBySinceLimit (result, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const prefix = (type === 'market') ? (type + '_') : ''; + const market = this.market (symbol); + if ((type === 'market') && (price === undefined)) { + price = 0; + } + const request = { + 'pair': market['id'], + 'quantity': this.amountToPrecision (symbol, amount), + 'type': prefix + side, + 'price': this.priceToPrecision (symbol, price), + }; + const response = await this.privatePostOrderCreate (this.extend (request, params)); + const id = this.safeString (response, 'order_id'); + const timestamp = this.milliseconds (); + amount = parseFloat (amount); + price = parseFloat (price); + const status = 'open'; + return { + 'id': id, + 'info': response, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * amount, + 'amount': amount, + 'remaining': amount, + 'filled': 0.0, + 'fee': undefined, + 'trades': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'order_id': id }; + return await this.privatePostOrderCancel (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id.toString (), + }; + const response = await this.privatePostOrderTrades (this.extend (request, params)); + // + // { + // "type": "buy", + // "in_currency": "BTC", + // "in_amount": "1", + // "out_currency": "USD", + // "out_amount": "100", + // "trades": [ + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100 + // } + // ] + // } + // + const order = this.parseOrder (response); + return this.extend (order, { + 'id': id.toString (), + }); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'order_id': id.toString (), + }; + const response = await this.privatePostOrderTrades (this.extend (request, params)); + // + // { + // "type": "buy", + // "in_currency": "BTC", + // "in_amount": "1", + // "out_currency": "USD", + // "out_amount": "100", + // "trades": [ + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100, + // "exec_type": "taker", + // "commission_amount": "0.02", + // "commission_currency": "BTC", + // "commission_percent": "0.2" + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades'); + return this.parseTrades (trades, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostUserOpenOrders (params); + const marketIds = Object.keys (response); + let orders = []; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let market = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } + const parsedOrders = this.parseOrders (response[marketId], market); + orders = this.arrayConcat (orders, parsedOrders); + } + return this.filterBySymbolSinceLimit (orders, symbol, since, limit); + } + + parseOrder (order, market = undefined) { + // + // fetchOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "order_id": "14", + // "created": "1435517311", + // "type": "buy", + // "pair": "BTC_USD", + // "price": "100", + // "quantity": "1", + // "amount": "100" + // } + // + // fetchOrder + // + // { + // "type": "buy", + // "in_currency": "BTC", + // "in_amount": "1", + // "out_currency": "USD", + // "out_amount": "100", + // "trades": [ + // { + // "trade_id": 3, + // "date": 1435488248, + // "type": "buy", + // "pair": "BTC_USD", + // "order_id": 12345, + // "quantity": 1, + // "price": 100, + // "amount": 100 + // } + // ] + // } + // + let id = this.safeString (order, 'order_id'); + let timestamp = this.safeTimestamp (order, 'created'); + let symbol = undefined; + const side = this.safeString (order, 'type'); + if (market === undefined) { + let marketId = undefined; + if ('pair' in order) { + marketId = order['pair']; + } else if (('in_currency' in order) && ('out_currency' in order)) { + if (side === 'buy') { + marketId = order['in_currency'] + '_' + order['out_currency']; + } else { + marketId = order['out_currency'] + '_' + order['in_currency']; + } + } + if ((marketId !== undefined) && (marketId in this.markets_by_id)) { + market = this.markets_by_id[marketId]; + } + } + let amount = this.safeFloat (order, 'quantity'); + if (amount === undefined) { + const amountField = (side === 'buy') ? 'in_amount' : 'out_amount'; + amount = this.safeFloat (order, amountField); + } + let price = this.safeFloat (order, 'price'); + let cost = this.safeFloat (order, 'amount'); + let filled = 0.0; + const trades = []; + const transactions = this.safeValue (order, 'trades', []); + let feeCost = undefined; + let lastTradeTimestamp = undefined; + let average = undefined; + const numTransactions = transactions.length; + if (numTransactions > 0) { + feeCost = 0; + for (let i = 0; i < numTransactions; i++) { + const trade = this.parseTrade (transactions[i], market); + if (id === undefined) { + id = trade['order']; + } + if (timestamp === undefined) { + timestamp = trade['timestamp']; + } + if (timestamp > trade['timestamp']) { + timestamp = trade['timestamp']; + } + filled = this.sum (filled, trade['amount']); + feeCost = this.sum (feeCost, trade['fee']['cost']); + trades.push (trade); + } + lastTradeTimestamp = trades[numTransactions - 1]['timestamp']; + } + let status = this.safeString (order, 'status'); // in case we need to redefine it for canceled orders + let remaining = undefined; + if (amount !== undefined) { + remaining = amount - filled; + if (filled >= amount) { + status = 'closed'; + } else { + status = 'open'; + } + } + if (market === undefined) { + market = this.getMarketFromTrades (trades); + } + let feeCurrency = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } else { + if (filled > 0) { + if (average === undefined) { + average = cost / filled; + } + if (price === undefined) { + price = cost / filled; + } + } + } + const fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': average, + 'trades': trades, + 'fee': fee, + 'info': order, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostDepositAddress (params); + const depositAddress = this.safeString (response, code); + let address = undefined; + let tag = undefined; + if (depositAddress) { + const addressAndTag = depositAddress.split (','); + address = addressAndTag[0]; + const numParts = addressAndTag.length; + if (numParts > 1) { + tag = addressAndTag[1]; + } + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + getMarketFromTrades (trades) { + const tradesBySymbol = this.indexBy (trades, 'pair'); + const symbols = Object.keys (tradesBySymbol); + const numSymbols = symbols.length; + if (numSymbols === 1) { + return this.markets[symbols[0]]; + } + return undefined; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + let key = 'quote'; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, cost)), + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'amount': amount, + 'currency': currency['id'], + 'address': address, + }; + if (tag !== undefined) { + request['invoice'] = tag; + } + const response = await this.privatePostWithdrawCrypt (this.extend (request, params)); + return { + 'info': response, + 'id': response['task_id'], + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'transferred': 'ok', + 'paid': 'ok', + 'pending': 'pending', + 'processing': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchTransactions + // + // { + // "dt": 1461841192, + // "type": "deposit", + // "curr": "RUB", + // "status": "processing", + // "provider": "Qiwi (LA) [12345]", + // "amount": "1", + // "account": "", + // "txid": "ec46f784ad976fd7f7539089d1a129fe46...", + // } + // + const timestamp = this.safeTimestamp (transaction, 'dt'); + let amount = this.safeFloat (transaction, 'amount'); + if (amount !== undefined) { + amount = Math.abs (amount); + } + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const txid = this.safeString (transaction, 'txid'); + const type = this.safeString (transaction, 'type'); + const currencyId = this.safeString (transaction, 'curr'); + const code = this.safeCurrencyCode (currencyId, currency); + let address = this.safeString (transaction, 'account'); + if (address !== undefined) { + const parts = address.split (':'); + const numParts = parts.length; + if (numParts === 2) { + address = parts[1].replace (' ', ''); + } + } + let fee = undefined; + // fixed funding fees only (for now) + if (!this.fees['funding']['percentage']) { + const key = (type === 'withdrawal') ? 'withdraw' : 'deposit'; + let feeCost = this.safeFloat (this.options['fundingFees'][key], code); + // users don't pay for cashbacks, no fees for that + const provider = this.safeString (transaction, 'provider'); + if (provider === 'cashback') { + feeCost = 0; + } + if (feeCost !== undefined) { + // withdrawal amount includes the fee + if (type === 'withdrawal') { + amount = amount - feeCost; + } + fee = { + 'cost': feeCost, + 'currency': code, + 'rate': undefined, + }; + } + } + return { + 'info': transaction, + 'id': undefined, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, // refix it properly + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['date'] = parseInt (since / 1000); + } + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privatePostWalletHistory (this.extend (request, params)); + // + // { + // "result": true, + // "error": "", + // "begin": "1493942400", + // "end": "1494028800", + // "history": [ + // { + // "dt": 1461841192, + // "type": "deposit", + // "curr": "RUB", + // "status": "processing", + // "provider": "Qiwi (LA) [12345]", + // "amount": "1", + // "account": "", + // "txid": "ec46f784ad976fd7f7539089d1a129fe46...", + // }, + // { + // "dt": 1463414785, + // "type": "withdrawal", + // "curr": "USD", + // "status": "paid", + // "provider": "EXCODE", + // "amount": "-1", + // "account": "EX-CODE_19371_USDda...", + // "txid": "", + // }, + // ], + // } + // + return this.parseTransactions (response['history'], currency, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + if (api !== 'web') { + url += this.version + '/'; + } + url += path; + if ((api === 'public') || (api === 'web')) { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + nonce () { + return this.milliseconds (); + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (('result' in response) || ('errmsg' in response)) { + // + // {"result":false,"error":"Error 50052: Insufficient funds"} + // {"s":"error","errmsg":"strconv.ParseInt: parsing \"\": invalid syntax"} + // + let success = this.safeValue (response, 'result', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + let code = undefined; + const message = this.safeString2 (response, 'error', 'errmsg'); + const errorParts = message.split (':'); + const numParts = errorParts.length; + if (numParts > 1) { + const errorSubParts = errorParts[0].split (' '); + const numSubParts = errorSubParts.length; + code = (numSubParts > 1) ? errorSubParts[1] : errorSubParts[0]; + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/exx.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/exx.js new file mode 100644 index 000000000..925d4a2f2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/exx.js @@ -0,0 +1,463 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ExchangeNotAvailable, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class exx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'exx', + 'name': 'EXX', + 'countries': [ 'CN' ], + 'rateLimit': 1000 / 10, + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg', + 'api': { + 'public': 'https://api.exx.com/data/v1', + 'private': 'https://trade.exx.com/api', + }, + 'www': 'https://www.exx.com/', + 'doc': 'https://www.exx.com/help/restApi', + 'fees': 'https://www.exx.com/help/rate', + 'referral': 'https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'tickers', + 'ticker', + 'depth', + 'trades', + ], + }, + 'private': { + 'get': [ + 'order', + 'cancel', + 'getOrder', + 'getOpenOrders', + 'getBalance', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BCC': 0.0003, + 'BCD': 0.0, + 'BOT': 10.0, + 'BTC': 0.001, + 'BTG': 0.0, + 'BTM': 25.0, + 'BTS': 3.0, + 'EOS': 1.0, + 'ETC': 0.01, + 'ETH': 0.01, + 'ETP': 0.012, + 'HPY': 0.0, + 'HSR': 0.001, + 'INK': 20.0, + 'LTC': 0.005, + 'MCO': 0.6, + 'MONA': 0.01, + 'QASH': 5.0, + 'QCASH': 5.0, + 'QTUM': 0.01, + 'USDT': 5.0, + }, + }, + }, + 'commonCurrencies': { + 'DOS': 'DEMOS', + 'TV': 'TIV', // Ti-Value + }, + 'exceptions': { + '103': AuthenticationError, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const ids = Object.keys (response); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = response[id]; + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = market['isOpen'] === true; + const precision = { + 'amount': parseInt (market['amountScale']), + 'price': parseInt (market['priceScale']), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const symbol = market['symbol']; + const timestamp = this.safeInteger (ticker, 'date'); + ticker = ticker['ticker']; + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'riseRate'), + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const result = {}; + const timestamp = this.milliseconds (); + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (!(id in this.marketsById)) { + continue; + } + const market = this.marketsById[id]; + const symbol = market['symbol']; + const ticker = { + 'date': timestamp, + 'ticker': response[id], + }; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': this.marketId (symbol), + }; + const response = await this.publicGetDepth (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, 'tid'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetGetBalance (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'funds'); + const currencies = Object.keys (balances); + for (let i = 0; i < currencies.length; i++) { + const currencyId = currencies[i]; + const balance = balances[currencyId]; + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'balance'), + 'used': this.safeFloat (balance, 'freeze'), + 'total': this.safeFloat (balance, 'total'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + // + // { + // "fees": 0, + // "total_amount": 1, + // "trade_amount": 0, + // "price": 30, + // "currency": “eth_hsr", + // "id": "13878", + // "trade_money": 0, + // "type": "buy", + // "trade_date": 1509728897772, + // "status": 0 + // } + // + const symbol = market['symbol']; + const timestamp = parseInt (order['trade_date']); + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'trade_money'); + const amount = this.safeFloat (order, 'total_amount'); + const filled = this.safeFloat (order, 'trade_amount', 0.0); + const remaining = parseFloat (this.amountToPrecision (symbol, amount - filled)); + let status = this.safeInteger (order, 'status'); + if (status === 1) { + status = 'canceled'; + } else if (status === 2) { + status = 'closed'; + } else { + status = 'open'; + } + let fee = undefined; + if ('fees' in order) { + fee = { + 'cost': this.safeFloat (order, 'fees'), + 'currency': market['quote'], + }; + } + return { + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': order['type'], + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'type': side, + 'price': price, + 'amount': amount, + }; + const response = await this.privateGetOrder (this.extend (request, params)); + const id = this.safeString (response, 'id'); + const order = this.parseOrder ({ + 'id': id, + 'trade_date': this.milliseconds (), + 'total_amount': amount, + 'price': price, + 'type': side, + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'currency': market['id'], + }; + const response = await this.privateGetCancel (this.extend (request, params)); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'currency': market['id'], + }; + const response = await this.privateGetGetOrder (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.privateGetGetOpenOrders (this.extend (request, params)); + if (!Array.isArray (response)) { + return []; + } + return this.parseOrders (response, market, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const query = this.urlencode (this.keysort (this.extend ({ + 'accesskey': this.apiKey, + 'nonce': this.nonce (), + }, params))); + const signed = this.hmac (this.encode (query), this.encode (this.secret), 'sha512'); + url += '?' + query + '&signature=' + signed; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"result":false,"message":"服务端忙碌"} + // ... and other formats + // + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + if (code === '100') { + return; + } + if (code !== undefined) { + this.throwExactlyMatchedException (this.exceptions, code, feedback); + if (code === '308') { + // this is returned by the exchange when there are no open orders + // {"code":308,"message":"Not Found Transaction Record"} + return; + } else { + throw new ExchangeError (feedback); + } + } + const result = this.safeValue (response, 'result'); + if (result !== undefined) { + if (!result) { + if (message === '服务端忙碌') { + throw new ExchangeNotAvailable (feedback); + } else { + throw new ExchangeError (feedback); + } + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/fcoin.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/fcoin.js new file mode 100644 index 000000000..1bdb5920d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/fcoin.js @@ -0,0 +1,742 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeError, ExchangeNotAvailable, ArgumentsRequired, InsufficientFunds, InvalidOrder, RateLimitExceeded, InvalidNonce, AuthenticationError, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class fcoin extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'fcoin', + 'name': 'FCoin', + 'countries': [ 'CN' ], + 'rateLimit': 2000, + 'userAgent': this.userAgents['chrome39'], + 'version': 'v2', + 'accounts': undefined, + 'accountsById': undefined, + 'hostname': 'fcoin.com', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': false, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingLimits': false, + 'withdraw': false, + }, + 'timeframes': { + '1m': 'M1', + '3m': 'M3', + '5m': 'M5', + '15m': 'M15', + '30m': 'M30', + '1h': 'H1', + '1d': 'D1', + '1w': 'W1', + '1M': 'MN', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/42244210-c8c42e1e-7f1c-11e8-8710-a5fb63b165c4.jpg', + 'api': { + 'public': 'https://api.{hostname}', + 'private': 'https://api.{hostname}', + 'market': 'https://api.{hostname}', + 'openapi': 'https://www.{hostname}', + }, + 'www': 'https://www.fcoin.com', + 'referral': 'https://www.fcoin.com/i/Z5P7V', + 'doc': 'https://developer.fcoin.com', + 'fees': 'https://fcoinjp.zendesk.com/hc/en-us/articles/360018727371', + }, + 'api': { + 'openapi': { + 'get': [ + 'symbols', + ], + }, + 'market': { + 'get': [ + 'ticker/{symbol}', + 'depth/{level}/{symbol}', + 'trades/{symbol}', + 'candles/{timeframe}/{symbol}', + ], + }, + 'public': { + 'get': [ + 'symbols', + 'currencies', + 'server-time', + ], + }, + 'private': { + 'get': [ + 'accounts/balance', + 'assets/accounts/balance', + 'broker/otc/suborders', + 'broker/otc/suborders/{id}', + 'broker/otc/suborders/{id}/payments', + 'broker/otc/users', + 'broker/otc/users/me/balances', + 'broker/otc/users/me/balance', + 'broker/leveraged_accounts/account', + 'broker/leveraged_accounts', + 'orders', + 'orders/{order_id}', + 'orders/{order_id}/match-results', // check order result + ], + 'post': [ + 'assets/accounts/assets-to-spot', + 'accounts/spot-to-assets', + 'broker/otc/assets/transfer/in', + 'broker/otc/assets/transfer/out', + 'broker/otc/suborders', + 'broker/otc/suborders/{id}/pay_confirm', + 'broker/otc/suborders/{id}/cancel', + 'broker/leveraged/assets/transfer/in', + 'broker/leveraged/assets/transfer/out', + 'orders', + 'orders/{order_id}/submit-cancel', // cancel order + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': -0.0002, + 'taker': 0.0003, + }, + }, + 'limits': { + 'amount': { 'min': 0.01, 'max': 100000 }, + }, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarketsMethod': 'fetch_markets_from_open_api', // or 'fetch_markets_from_api' + 'limits': { + 'BTM/USDT': { 'amount': { 'min': 0.1, 'max': 10000000 }}, + 'ETC/USDT': { 'amount': { 'min': 0.001, 'max': 400000 }}, + 'ETH/USDT': { 'amount': { 'min': 0.001, 'max': 10000 }}, + 'LTC/USDT': { 'amount': { 'min': 0.001, 'max': 40000 }}, + 'BCH/USDT': { 'amount': { 'min': 0.001, 'max': 5000 }}, + 'BTC/USDT': { 'amount': { 'min': 0.001, 'max': 1000 }}, + 'ICX/ETH': { 'amount': { 'min': 0.01, 'max': 3000000 }}, + 'OMG/ETH': { 'amount': { 'min': 0.01, 'max': 500000 }}, + 'FT/USDT': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'ZIL/ETH': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'ZIP/ETH': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'FT/BTC': { 'amount': { 'min': 1, 'max': 10000000 }}, + 'FT/ETH': { 'amount': { 'min': 1, 'max': 10000000 }}, + }, + }, + 'exceptions': { + '400': NotSupported, // Bad Request + '401': AuthenticationError, + '405': NotSupported, + '429': RateLimitExceeded, // Too Many Requests, exceed api request limit + '1002': ExchangeNotAvailable, // System busy + '1016': InsufficientFunds, + '2136': AuthenticationError, // The API key is expired + '3008': InvalidOrder, + '6004': InvalidNonce, + '6005': AuthenticationError, // Illegal API Signature + '40003': BadSymbol, + }, + 'commonCurrencies': { + 'DAG': 'DAGX', + 'PAI': 'PCHAIN', + 'MT': 'Mariana Token', + }, + }); + } + + async fetchMarkets (params = {}) { + const method = this.safeString (this.options, 'fetchMarketsMethod', 'fetch_markets_from_open_api'); + return await this[method] (params); + } + + async fetchMarketsFromOpenAPI (params = {}) { + // https://github.com/ccxt/ccxt/issues/5648 + const response = await this.openapiGetSymbols (params); + // + // { + // "status":"ok", + // "data":{ + // "categories":[ "fone::coinforce", ... ], + // "symbols":{ + // "mdaeth":{ + // "price_decimal":8, + // "amount_decimal":2, + // "base_currency":"mda", + // "quote_currency":"eth", + // "symbol":"mdaeth", + // "category":"fone::bitangel", + // "leveraged_multiple":null, + // "tradeable":false, + // "market_order_enabled":false, + // "limit_amount_min":"1", + // "limit_amount_max":"10000000", + // "main_tag":"", + // "daily_open_at":"", + // "daily_close_at":"" + // }, + // } + // "category_ref":{ + // "fone::coinforce":[ "btcusdt", ... ], + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const markets = this.safeValue (data, 'symbols', {}); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const market = markets[key]; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'price_decimal'), + 'amount': this.safeInteger (market, 'amount_decimal'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'limit_amount_min'), + 'max': this.safeFloat (market, 'limit_amount_max'), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + const active = this.safeValue (market, 'tradeable', false); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchMarketsFromAPI (params = {}) { + const response = await this.publicGetSymbols (params); + // + // { + // "status":0, + // "data":[ + // { + // "name":"dapusdt", + // "base_currency":"dap", + // "quote_currency":"usdt", + // "price_decimal":6, + // "amount_decimal":2, + // "tradable":true + // }, + // ] + // } + // + const result = []; + const markets = this.safeValue (response, 'data'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quote_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': market['price_decimal'], + 'amount': market['amount_decimal'], + }; + let limits = { + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + }; + const active = this.safeValue (market, 'tradable', false); + if (symbol in this.options['limits']) { + limits = this.extend (this.options['limits'][symbol], limits); + } + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountsBalance (params); + const result = { 'info': response }; + const balances = this.safeValue (response, 'data'); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'frozen'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseBidsAsks (orders, priceKey = 0, amountKey = 1) { + const result = []; + const length = orders.length; + const halfLength = parseInt (length / 2); + // += 2 in the for loop below won't transpile + for (let i = 0; i < halfLength; i++) { + const index = i * 2; + const priceField = this.sum (index, priceKey); + const amountField = this.sum (index, amountKey); + result.push ([ + this.safeFloat (orders, priceField), + this.safeFloat (orders, amountField), + ]); + } + return result; + } + + async fetchOrderBook (symbol = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit !== undefined) { + if ((limit === 20) || (limit === 150)) { + limit = 'L' + limit.toString (); + } else { + throw new ExchangeError (this.id + ' fetchOrderBook supports limit of 20 or 150. Other values are not accepted'); + } + } else { + limit = 'L20'; + } + const request = { + 'symbol': this.marketId (symbol), + 'level': limit, // L20, L150 + }; + const response = await this.marketGetDepthLevelSymbol (this.extend (request, params)); + const orderbook = this.safeValue (response, 'data'); + return this.parseOrderBook (orderbook, orderbook['ts'], 'bids', 'asks', 0, 1); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const ticker = await this.marketGetTickerSymbol (this.extend (request, params)); + return this.parseTicker (ticker['data'], market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = undefined; + let symbol = undefined; + if (market === undefined) { + const tickerType = this.safeString (ticker, 'type'); + if (tickerType !== undefined) { + const parts = tickerType.split ('.'); + const id = parts[1]; + symbol = this.safeSymbol (id, market); + } + } + const values = ticker['ticker']; + const last = this.safeFloat (values, 0); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (values, 7), + 'low': this.safeFloat (values, 8), + 'bid': this.safeFloat (values, 2), + 'bidVolume': this.safeFloat (values, 3), + 'ask': this.safeFloat (values, 4), + 'askVolume': this.safeFloat (values, 5), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (values, 9), + 'quoteVolume': this.safeFloat (values, 10), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (trade, 'ts'); + const side = this.safeStringLower (trade, 'side'); + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const fee = undefined; + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetServerTime (params); + // + // { + // "status": 0, + // "data": 1523430502977 + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchTrades (symbol, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'limit': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.marketGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + 'side': side, + 'type': type, + }; + // for market buy it requires the amount of quote currency to spend + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + request['amount'] = this.costToPrecision (symbol, amount * price); + } + } else { + request['amount'] = this.costToPrecision (symbol, amount); + } + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if ((type === 'limit') || (type === 'ioc') || (type === 'fok')) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + return { + 'info': response, + 'id': response['data'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privatePostOrdersOrderIdSubmitCancel (this.extend (request, params)); + const order = this.parseOrder (response); + return this.extend (order, { + 'id': id, + 'status': 'canceled', + }); + } + + parseOrderStatus (status) { + const statuses = { + 'submitted': 'open', + 'canceled': 'canceled', + 'partial_filled': 'open', + 'partial_canceled': 'canceled', + 'filled': 'closed', + 'pending_cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": "string", + // "symbol": "string", + // "type": "limit", + // "side": "buy", + // "price": "string", + // "amount": "string", + // "state": "submitted", + // "executed_value": "string", + // "fill_fees": "string", + // "filled_amount": "string", + // "created_at": 0, + // "source": "web" + // } + // + const id = this.safeString (order, 'id'); + const side = this.safeString (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const orderType = this.safeString (order, 'type'); + const timestamp = this.safeInteger (order, 'created_at'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'filled_amount'); + let remaining = undefined; + let price = this.safeFloat (order, 'price'); + let cost = this.safeFloat (order, 'executed_value'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } else if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + let feeCurrency = undefined; + let feeCost = undefined; + const feeRebate = this.safeFloat (order, 'fees_income'); + if ((feeRebate !== undefined) && (feeRebate > 0)) { + if (market !== undefined) { + feeCurrency = (side === 'buy') ? market['quote'] : market['base']; + } + feeCost = -feeRebate; + } else { + feeCost = this.safeFloat (order, 'fill_fees'); + if (market !== undefined) { + feeCurrency = (side === 'buy') ? market['base'] : market['quote']; + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'average': undefined, + 'status': status, + 'fee': { + 'cost': feeCost, + 'currency': feeCurrency, + }, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetOrdersOrderId (this.extend (request, params)); + return this.parseOrder (response['data']); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'states': 'submitted,partial_filled' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'states': 'partial_canceled,filled' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'states': 'submitted,partial_filled,partial_canceled,filled,canceled', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + return this.parseOrders (response['data'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeTimestamp (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'base_vol'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 20; // default is 20 + } + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + const sinceInSeconds = parseInt (since / 1000); + const timerange = limit * this.parseTimeframe (timeframe); + request['before'] = this.sum (sinceInSeconds, timerange) - 1; + } + const response = await this.marketGetCandlesTimeframeSymbol (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/'; + const openAPI = (api === 'openapi'); + const privateAPI = (api === 'private'); + request += openAPI ? (api + '/') : ''; + request += this.version + '/'; + request += (privateAPI || openAPI) ? '' : (api + '/'); + request += this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }); + url += request; + if (privateAPI) { + this.checkRequiredCredentials (); + const timestamp = this.nonce ().toString (); + query = this.keysort (query); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.rawencode (query); + } + } + // HTTP_METHOD + HTTP_REQUEST_URI + TIMESTAMP + POST_BODY + let auth = method + url + timestamp; + if (method === 'POST') { + if (Object.keys (query).length) { + body = this.json (query); + auth += this.urlencode (query); + } + } + const payload = this.stringToBase64 (auth); + let signature = this.hmac (payload, this.encode (this.secret), 'sha1', 'binary'); + signature = this.decode (this.stringToBase64 (signature)); + headers = { + 'FC-ACCESS-KEY': this.apiKey, + 'FC-ACCESS-SIGNATURE': signature, + 'FC-ACCESS-TIMESTAMP': timestamp, + 'Content-Type': 'application/json', + }; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const status = this.safeString (response, 'status'); + if (status !== '0' && status !== 'ok') { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, status, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/fcoinjp.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/fcoinjp.js new file mode 100644 index 000000000..9ecf75d43 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/fcoinjp.js @@ -0,0 +1,24 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const fcoin = require ('./fcoin.js'); + +// --------------------------------------------------------------------------- + +module.exports = class fcoinjp extends fcoin { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'fcoinjp', + 'name': 'FCoinJP', + 'countries': [ 'JP' ], + 'hostname': 'fcoinjp.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/54219174-08b66b00-4500-11e9-862d-f522d0fe08c6.jpg', + 'fees': 'https://fcoinjp.zendesk.com/hc/en-us/articles/360018727371', + 'www': 'https://www.fcoinjp.com', + 'referral': undefined, + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/flowbtc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/flowbtc.js new file mode 100644 index 000000000..d38ebfea9 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/flowbtc.js @@ -0,0 +1,284 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class flowbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'flowbtc', + 'name': 'flowBTC', + 'countries': [ 'BR' ], // Brazil + 'version': 'v1', + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443317-01c0d080-c5fe-11ea-95c2-9ebe1a8fafd9.jpg', + 'api': 'https://publicapi.flowbtc.com.br', + 'www': 'https://www.flowbtc.com.br', + 'doc': 'https://www.flowbtc.com.br/api.html', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'post': [ + 'GetTicker', + 'GetTrades', + 'GetTradesByDate', + 'GetOrderBook', + 'GetProductPairs', + 'GetProducts', + ], + }, + 'private': { + 'post': [ + 'CreateAccount', + 'GetUserInfo', + 'SetUserInfo', + 'GetAccountInfo', + 'GetAccountTrades', + 'GetDepositAddresses', + 'Withdraw', + 'CreateOrder', + 'ModifyOrder', + 'CancelOrder', + 'CancelAllOrders', + 'GetAccountOpenOrders', + 'GetOrderFee', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0025, + 'taker': 0.005, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicPostGetProductPairs (params); + const markets = this.safeValue (response, 'productPairs'); + const result = {}; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'product1Label'); + const quoteId = this.safeString (market, 'product2Label'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const precision = { + 'amount': this.safeInteger (market, 'product1DecimalPlaces'), + 'price': this.safeInteger (market, 'product2DecimalPlaces'), + }; + const symbol = base + '/' + quote; + result[symbol] = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + 'active': undefined, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetAccountInfo (params); + const balances = this.safeValue (response, 'currencies'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = balance['name']; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['total'] = this.safeFloat (balance, 'hold'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'productPair': market['id'], + }; + const response = await this.publicPostGetOrderBook (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'px', 'qty'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'productPair': market['id'], + }; + const ticker = await this.publicPostGetTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume24hr'), + 'quoteVolume': this.safeFloat (ticker, 'volume24hrProduct2'), + 'info': ticker, + }; + } + + parseTrade (trade, market) { + const timestamp = this.safeTimestamp (trade, 'unixtime'); + const side = (trade['incomingOrderSide'] === 0) ? 'buy' : 'sell'; + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'px'); + const amount = this.safeFloat (trade, 'qty'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'id': id, + 'order': undefined, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': undefined, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'ins': market['id'], + 'startIndex': -1, + }; + const response = await this.publicPostGetTrades (this.extend (request, params)); + return this.parseTrades (response['trades'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const orderType = (type === 'market') ? 1 : 0; + const request = { + 'ins': this.marketId (symbol), + 'side': side, + 'orderType': orderType, + 'qty': amount, + 'px': this.priceToPrecision (symbol, price), + }; + const response = await this.privatePostCreateOrder (this.extend (request, params)); + return { + 'info': response, + 'id': response['serverOrderId'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + if ('ins' in params) { + const request = { + 'serverOrderId': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + throw new ExchangeError (this.id + ' requires `ins` symbol parameter for cancelling an order'); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + body = this.json (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const auth = nonce.toString () + this.uid + this.apiKey; + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + body = this.json (this.extend ({ + 'apiKey': this.apiKey, + 'apiNonce': nonce, + 'apiSig': signature.toUpperCase (), + }, params)); + headers = { + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('isAccepted' in response) { + if (response['isAccepted']) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/foxbit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/foxbit.js new file mode 100644 index 000000000..5830969a3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/foxbit.js @@ -0,0 +1,263 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class foxbit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'foxbit', + 'name': 'FoxBit', + 'countries': [ 'BR' ], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'rateLimit': 1000, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87443320-01c0d080-c5fe-11ea-92e2-4ef56d32b026.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://foxbit.com.br/exchange', + 'doc': 'https://foxbit.com.br/api/', + }, + 'comment': 'Blinktrade API', + 'api': { + 'public': { + 'get': [ + '{currency}/ticker', // ?crypto_currency=BTC + '{currency}/orderbook', // ?crypto_currency=BTC + '{currency}/trades', // ?crypto_currency=BTC&since=&limit= + ], + }, + 'private': { + 'post': [ + 'D', // order + 'F', // cancel order + 'U2', // balance + 'U4', // my orders + 'U6', // withdraw + 'U18', // deposit + 'U24', // confirm withdrawal + 'U26', // list withdrawals + 'U30', // list deposits + 'U34', // ledger + 'U70', // cancel withdrawal + ], + }, + }, + 'markets': { + 'BTC/VEF': { 'id': 'BTCVEF', 'symbol': 'BTC/VEF', 'base': 'BTC', 'quote': 'VEF', 'brokerId': 1, 'broker': 'SurBitcoin' }, + 'BTC/VND': { 'id': 'BTCVND', 'symbol': 'BTC/VND', 'base': 'BTC', 'quote': 'VND', 'brokerId': 3, 'broker': 'VBTC' }, + 'BTC/BRL': { 'id': 'BTCBRL', 'symbol': 'BTC/BRL', 'base': 'BTC', 'quote': 'BRL', 'brokerId': 4, 'broker': 'FoxBit' }, + 'BTC/PKR': { 'id': 'BTCPKR', 'symbol': 'BTC/PKR', 'base': 'BTC', 'quote': 'PKR', 'brokerId': 8, 'broker': 'UrduBit' }, + 'BTC/CLP': { 'id': 'BTCCLP', 'symbol': 'BTC/CLP', 'base': 'BTC', 'quote': 'CLP', 'brokerId': 9, 'broker': 'ChileBit' }, + }, + 'options': { + 'brokerId': '4', // https://blinktrade.com/docs/#brokers + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'BalanceReqID': this.nonce (), + }; + const response = await this.privatePostU2 (this.extend (request, params)); + const balances = this.safeValue (response['Responses'], this.options['brokerId']); + const result = { 'info': response }; + if (balances !== undefined) { + const currencyIds = Object.keys (this.currencies_by_id); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + // we only set the balance for the currency if that currency is present in response + // otherwise we will lose the info if the currency balance has been funded or traded or not + if (currencyId in balances) { + const account = this.account (); + let used = this.safeFloat (balances, currencyId + '_locked'); + if (used !== undefined) { + used *= 1e-8; + } + let total = this.safeFloat (balances, currencyId); + if (total !== undefined) { + total *= 1e-8; + } + account['used'] = used; + account['total'] = total; + result[code] = account; + } + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['quote'], + 'crypto_currency': market['base'], + }; + const response = await this.publicGetCurrencyOrderbook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['quote'], + 'crypto_currency': market['base'], + }; + const ticker = await this.publicGetCurrencyTicker (this.extend (request, params)); + const timestamp = this.milliseconds (); + const lowercaseQuote = market['quote'].toLowerCase (); + const quoteVolume = 'vol_' + lowercaseQuote; + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, quoteVolume), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['quote'], + 'crypto_currency': market['base'], + }; + const response = await this.publicGetCurrencyTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const market = this.market (symbol); + const orderSide = (side === 'buy') ? '1' : '2'; + const request = { + 'ClOrdID': this.nonce (), + 'Symbol': market['id'], + 'Side': orderSide, + 'OrdType': '2', + 'Price': price, + 'OrderQty': amount, + 'BrokerID': market['brokerId'], + }; + const response = await this.privatePostD (this.extend (request, params)); + const indexed = this.indexBy (response['Responses'], 'MsgType'); + const execution = indexed['8']; + return { + 'info': response, + 'id': execution['OrderID'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + return await this.privatePostF (this.extend ({ + 'ClOrdID': id, + }, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const request = this.extend ({ 'MsgType': path }, query); + body = this.json (request); + headers = { + 'APIKey': this.apiKey, + 'Nonce': nonce, + 'Signature': this.hmac (this.encode (nonce), this.encode (this.secret)), + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('Status' in response) { + if (response['Status'] !== 200) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ftx.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ftx.js new file mode 100644 index 000000000..8e3992ac5 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ftx.js @@ -0,0 +1,1759 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { ExchangeError, InvalidOrder, BadRequest, InsufficientFunds, OrderNotFound, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class ftx extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'ftx', + 'name': 'FTX', + 'countries': [ 'HK' ], + 'rateLimit': 100, + 'certified': true, + 'pro': true, + 'hostname': 'ftx.com', // or ftx.us + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg', + 'www': 'https://ftx.com', + 'api': { + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + }, + 'doc': 'https://github.com/ftexchange/ftx', + 'fees': 'https://ftexchange.zendesk.com/hc/en-us/articles/360024479432-Fees', + 'referral': 'https://ftx.com/#a=1623029', + }, + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFees': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '15s': '15', + '1m': '60', + '5m': '300', + '15m': '900', + '1h': '3600', + '4h': '14400', + '1d': '86400', + }, + 'api': { + 'public': { + 'get': [ + 'coins', + 'markets', + 'markets/{market_name}', + 'markets/{market_name}/orderbook', // ?depth={depth} + 'markets/{market_name}/trades', // ?limit={limit}&start_time={start_time}&end_time={end_time} + 'markets/{market_name}/candles', // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time} + // futures + 'futures', + 'futures/{future_name}', + 'futures/{future_name}/stats', + 'funding_rates', + 'indexes/{index_name}/weights', + 'expired_futures', + 'indexes/{market_name}/candles', // ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time} + // leverage tokens + 'lt/tokens', + 'lt/{token_name}', + // options + 'options/requests', + 'options/trades', + 'stats/24h_options_volume', + 'options/historical_volumes/BTC', + 'options/open_interest/BTC', + 'options/historical_open_interest/BTC', + ], + }, + 'private': { + 'get': [ + 'account', + 'positions', + 'wallet/coins', + 'wallet/balances', + 'wallet/all_balances', + 'wallet/deposit_address/{coin}', + 'wallet/deposits', + 'wallet/withdrawals', + 'orders', // ?market={market} + 'orders/history', // ?market={market} + 'orders/{order_id}', + 'orders/by_client_id/{client_order_id}', + 'conditional_orders', // ?market={market} + 'conditional_orders/{conditional_order_id}/triggers', + 'conditional_orders/history', // ?market={market} + 'fills', // ?market={market} + 'funding_payments', + // leverage tokens + 'lt/balances', + 'lt/creations', + 'lt/redemptions', + // subaccounts + 'subaccounts', + 'subaccounts/{nickname}/balances', + // otc + 'otc/quotes/{quoteId}', + // options + 'options/my_requests', + 'options/requests/{request_id}/quotes', + 'options/my_quotes', + 'options/account_info', + 'options/positions', + 'options/fills', + ], + 'post': [ + 'account/leverage', + 'wallet/withdrawals', + 'orders', + 'conditional_orders', + 'orders/{order_id}/modify', + 'orders/by_client_id/{client_order_id}/modify', + 'conditional_orders/{order_id}/modify', + // leverage tokens + 'lt/{token_name}/create', + 'lt/{token_name}/redeem', + // subaccounts + 'subaccounts', + 'subaccounts/update_name', + 'subaccounts/transfer', + // otc + 'otc/quotes/{quote_id}/accept', + 'otc/quotes', + // options + 'options/requests', + 'options/requests/{request_id}/quotes', + 'options/quotes/{quote_id}/accept', + ], + 'delete': [ + 'orders/{order_id}', + 'orders/by_client_id/{client_order_id}', + 'orders', + 'conditional_orders/{order_id}', + // subaccounts + 'subaccounts', + // options + 'options/requests/{request_id}', + 'options/quotes/{quote_id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.02 / 100, + 'taker': 0.07 / 100, + 'tiers': { + 'taker': [ + [0, 0.07 / 100], + [1000000, 0.06 / 100], + [5000000, 0.055 / 100], + [10000000, 0.05 / 100], + [15000000, 0.045 / 100], + [35000000, 0.04 / 100], + ], + 'maker': [ + [0, 0.02 / 100], + [1000000, 0.02 / 100], + [5000000, 0.015 / 100], + [10000000, 0.015 / 100], + [15000000, 0.01 / 100], + [35000000, 0.01 / 100], + ], + }, + }, + 'funding': { + 'withdraw': {}, + }, + }, + 'exceptions': { + 'exact': { + 'Not logged in': AuthenticationError, // {"error":"Not logged in","success":false} + 'Not enough balances': InsufficientFunds, // {"error":"Not enough balances","success":false} + 'InvalidPrice': InvalidOrder, // {"error":"Invalid price","success":false} + 'Size too small': InvalidOrder, // {"error":"Size too small","success":false} + 'Missing parameter price': InvalidOrder, // {"error":"Missing parameter price","success":false} + 'Order not found': OrderNotFound, // {"error":"Order not found","success":false} + 'Order already closed': InvalidOrder, // {"error":"Order already closed","success":false} + }, + 'broad': { + 'Invalid parameter': BadRequest, // {"error":"Invalid parameter start_time","success":false} + 'The requested URL was not found on the server': BadRequest, + 'No such coin': BadRequest, + 'No such market': BadRequest, + 'An unexpected error occurred': ExchangeError, // {"error":"An unexpected error occurred, please try again later (58BC21C795).","success":false} + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + 'cancelOrder': { + 'method': 'privateDeleteOrdersOrderId', // privateDeleteConditionalOrdersOrderId + }, + 'fetchOpenOrders': { + 'method': 'privateGetOrders', // privateGetConditionalOrders + }, + 'fetchOrders': { + 'method': 'privateGetOrdersHistory', // privateGetConditionalOrdersHistory + }, + 'sign': { + 'ftx.com': 'FTX', + 'ftx.us': 'FTXUS', + }, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCoins (params); + const currencies = this.safeValue (response, 'result', []); + // + // { + // "success":true, + // "result": [ + // {"id":"BTC","name":"Bitcoin"}, + // {"id":"ETH","name":"Ethereum"}, + // {"id":"ETHMOON","name":"10X Long Ethereum Token","underlying":"ETH"}, + // {"id":"EOSBULL","name":"3X Long EOS Token","underlying":"EOS"}, + // ], + // } + // + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'id'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': name, + 'active': undefined, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'withdraw': { 'min': undefined, 'max': undefined }, + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // { + // 'success': true, + // "result": [ + // { + // "ask":170.37, + // "baseCurrency":null, + // "bid":170.31, + // "change1h":-0.019001554672655036, + // "change24h":-0.024841165359738997, + // "changeBod":-0.03816406029469881, + // "enabled":true, + // "last":170.37, + // "name":"ETH-PERP", + // "price":170.37, + // "priceIncrement":0.01, + // "quoteCurrency":null, + // "quoteVolume24h":7742164.59889, + // "sizeIncrement":0.001, + // "type":"future", + // "underlying":"ETH", + // "volumeUsd24h":7742164.59889 + // }, + // { + // "ask":170.44, + // "baseCurrency":"ETH", + // "bid":170.41, + // "change1h":-0.018485459257126403, + // "change24h":-0.023825887743413515, + // "changeBod":-0.037605872388481086, + // "enabled":true, + // "last":172.72, + // "name":"ETH/USD", + // "price":170.44, + // "priceIncrement":0.01, + // "quoteCurrency":"USD", + // "quoteVolume24h":382802.0252, + // "sizeIncrement":0.001, + // "type":"spot", + // "underlying":null, + // "volumeUsd24h":382802.0252 + // }, + // ], + // } + // + const result = []; + const markets = this.safeValue (response, 'result', []); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString2 (market, 'baseCurrency', 'underlying'); + const quoteId = this.safeString (market, 'quoteCurrency', 'USD'); + const type = this.safeString (market, 'type'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + // check if a market is a spot or future market + const symbol = (type === 'future') ? this.safeString (market, 'name') : (base + '/' + quote); + const active = this.safeValue (market, 'enabled'); + const sizeIncrement = this.safeFloat (market, 'sizeIncrement'); + const priceIncrement = this.safeFloat (market, 'priceIncrement'); + const precision = { + 'amount': sizeIncrement, + 'price': priceIncrement, + }; + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': type, + 'future': (type === 'future'), + 'spot': (type === 'spot'), + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': sizeIncrement, + 'max': undefined, + }, + 'price': { + 'min': priceIncrement, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }; + result.push (entry); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { + // "ask":171.29, + // "baseCurrency":null, // base currency for spot markets + // "bid":171.24, + // "change1h":-0.0012244897959183673, + // "change24h":-0.031603346901854366, + // "changeBod":-0.03297013492914808, + // "enabled":true, + // "last":171.44, + // "name":"ETH-PERP", + // "price":171.29, + // "priceIncrement":0.01, + // "quoteCurrency":null, // quote currency for spot markets + // "quoteVolume24h":8570651.12113, + // "sizeIncrement":0.001, + // "type":"future", + // "underlying":"ETH", // null for spot markets + // "volumeUsd24h":8570651.12113, + // } + // + let symbol = undefined; + const marketId = this.safeString (ticker, 'name'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const type = this.safeString (ticker, 'type'); + if (type === 'future') { + symbol = marketId; + } else { + const base = this.safeCurrencyCode (this.safeString (ticker, 'baseCurrency')); + const quote = this.safeCurrencyCode (this.safeString (ticker, 'quoteCurrency')); + if ((base !== undefined) && (quote !== undefined)) { + symbol = base + '/' + quote; + } + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const timestamp = this.safeTimestamp (ticker, 'time', this.milliseconds ()); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': this.safeFloat (ticker, 'bidSize'), + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': this.safeFloat (ticker, 'askSize'), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'change24h'), + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'quoteVolume24h'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_name': market['id'], + }; + const response = await this.publicGetMarketsMarketName (this.extend (request, params)); + // + // { + // "success":true, + // "result":{ + // "ask":171.29, + // "baseCurrency":null, // base currency for spot markets + // "bid":171.24, + // "change1h":-0.0012244897959183673, + // "change24h":-0.031603346901854366, + // "changeBod":-0.03297013492914808, + // "enabled":true, + // "last":171.44, + // "name":"ETH-PERP", + // "price":171.29, + // "priceIncrement":0.01, + // "quoteCurrency":null, // quote currency for spot markets + // "quoteVolume24h":8570651.12113, + // "sizeIncrement":0.001, + // "type":"future", + // "underlying":"ETH", // null for spot markets + // "volumeUsd24h":8570651.12113, + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTicker (result, market); + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarkets (params); + // + // { + // 'success': true, + // "result": [ + // { + // "ask":170.44, + // "baseCurrency":"ETH", + // "bid":170.41, + // "change1h":-0.018485459257126403, + // "change24h":-0.023825887743413515, + // "changeBod":-0.037605872388481086, + // "enabled":true, + // "last":172.72, + // "name":"ETH/USD", + // "price":170.44, + // "priceIncrement":0.01, + // "quoteCurrency":"USD", + // "quoteVolume24h":382802.0252, + // "sizeIncrement":0.001, + // "type":"spot", + // "underlying":null, + // "volumeUsd24h":382802.0252 + // }, + // ], + // } + // + const tickers = this.safeValue (response, 'result', []); + return this.parseTickers (tickers, symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_name': market['id'], + }; + if (limit !== undefined) { + request['depth'] = limit; // max 100, default 20 + } + const response = await this.publicGetMarketsMarketNameOrderbook (this.extend (request, params)); + // + // { + // "success":true, + // "result":{ + // "asks":[ + // [171.95,279.865], + // [171.98,102.42], + // [171.99,124.11], + // ], + // "bids":[ + // [171.93,69.749], + // [171.9,288.325], + // [171.88,87.47], + // ], + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOrderBook (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "close":177.23, + // "high":177.45, + // "low":177.2, + // "open":177.43, + // "startTime":"2019-10-17T13:27:00+00:00", + // "time":1571318820000.0, + // "volume":0.0 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + getMarketId (symbol, key, params = {}) { + const parts = this.getMarketParams (symbol, key, params); + return this.safeString (parts, 1, symbol); + } + + getMarketParams (symbol, key, params = {}) { + let market = undefined; + let marketId = undefined; + if (symbol in this.markets) { + market = this.market (symbol); + marketId = market['id']; + } else { + marketId = this.safeString (params, key, symbol); + } + return [ market, marketId ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params); + const request = { + 'resolution': this.timeframes[timeframe], + 'market_name': marketId, + }; + // max 1501 candles, including the current candle when since is not specified + limit = (limit === undefined) ? 1501 : limit; + if (since === undefined) { + request['end_time'] = this.seconds (); + request['limit'] = limit; + request['start_time'] = request['end_time'] - limit * this.parseTimeframe (timeframe); + } else { + request['start_time'] = parseInt (since / 1000); + request['limit'] = limit; + request['end_time'] = this.sum (request['start_time'], limit * this.parseTimeframe (timeframe)); + } + const response = await this.publicGetMarketsMarketNameCandles (this.extend (request, params)); + // + // { + // "success": true, + // "result":[ + // { + // "close":177.23, + // "high":177.45, + // "low":177.2, + // "open":177.43, + // "startTime":"2019-10-17T13:27:00+00:00", + // "time":1571318820000.0, + // "volume":0.0 + // }, + // { + // "close":177.26, + // "high":177.33, + // "low":177.23, + // "open":177.23, + // "startTime":"2019-10-17T13:28:00+00:00", + // "time":1571318880000.0, + // "volume":0.0 + // }, + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":1715826, + // "liquidation":false, + // "price":171.62, + // "side":"buy", + // "size":2.095, + // "time":"2019-10-18T12:59:54.288166+00:00" + // } + // + // fetchMyTrades (private) + // + // { + // "fee": 20.1374935, + // "feeRate": 0.0005, + // "feeCurrency": "USD", + // "future": "EOS-0329", + // "id": 11215, + // "liquidity": "taker", + // "market": "EOS-0329", + // "baseCurrency": null, + // "quoteCurrency": null, + // "orderId": 8436981, + // "price": 4.201, + // "side": "buy", + // "size": 9587, + // "time": "2019-03-27T19:15:10.204619+00:00", + // "type": "order" + // } + // + const id = this.safeString (trade, 'id'); + const takerOrMaker = this.safeString (trade, 'liquidity'); + const marketId = this.safeString (trade, 'market'); + let symbol = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + const base = this.safeCurrencyCode (this.safeString (trade, 'baseCurrency')); + const quote = this.safeCurrencyCode (this.safeString (trade, 'quoteCurrency')); + if ((base !== undefined) && (quote !== undefined)) { + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + } + const timestamp = this.parse8601 (this.safeString (trade, 'time')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'side'); + let cost = undefined; + if (price !== undefined && amount !== undefined) { + cost = price * amount; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': this.safeFloat (trade, 'feeRate'), + }; + } + const orderId = this.safeString (trade, 'orderId'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const [ market, marketId ] = this.getMarketParams (symbol, 'market_name', params); + const request = { + 'market_name': marketId, + }; + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + // start_time doesn't work without end_time + request['end_time'] = this.seconds (); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketsMarketNameTrades (this.extend (request, params)); + // + // { + // "success":true, + // "result":[ + // { + // "id":1715826, + // "liquidation":false, + // "price":171.62, + // "side":"buy", + // "size":2.095, + // "time":"2019-10-18T12:59:54.288166+00:00" + // }, + // { + // "id":1715763, + // "liquidation":false, + // "price":171.89, + // "side":"sell", + // "size":1.477, + // "time":"2019-10-18T12:58:38.443734+00:00" + // }, + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseTrades (result, market, since, limit); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + // + // { + // "success": true, + // "result": { + // "backstopProvider": true, + // "collateral": 3568181.02691129, + // "freeCollateral": 1786071.456884368, + // "initialMarginRequirement": 0.12222384240257728, + // "liquidating": false, + // "maintenanceMarginRequirement": 0.07177992558058484, + // "makerFee": 0.0002, + // "marginFraction": 0.5588433331419503, + // "openMarginFraction": 0.2447194090423075, + // "takerFee": 0.0005, + // "totalAccountValue": 3568180.98341129, + // "totalPositionSize": 6384939.6992, + // "username": "user@domain.com", + // "positions": [ + // { + // "cost": -31.7906, + // "entryPrice": 138.22, + // "future": "ETH-PERP", + // "initialMarginRequirement": 0.1, + // "longOrderSize": 1744.55, + // "maintenanceMarginRequirement": 0.04, + // "netSize": -0.23, + // "openSize": 1744.32, + // "realizedPnl": 3.39441714, + // "shortOrderSize": 1732.09, + // "side": "sell", + // "size": 0.23, + // "unrealizedPnl": 0, + // }, + // ], + // }, + // } + // + const result = this.safeValue (response, 'result', {}); + return { + 'info': response, + 'maker': this.safeFloat (result, 'makerFee'), + 'taker': this.safeFloat (result, 'takerFee'), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWalletBalances (params); + // + // { + // "success": true, + // "result": [ + // { + // "coin": "USDTBEAR", + // "free": 2320.2, + // "total": 2340.2 + // }, + // ], + // } + // + const result = { + 'info': response, + }; + const balances = this.safeValue (response, 'result', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const code = this.safeCurrencyCode (this.safeString (balance, 'coin')); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'new': 'open', + 'open': 'open', + 'closed': 'closed', // filled or canceled + 'triggered': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // limit orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder, editOrder + // + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 0, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "remainingSize": 31431, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null, + // } + // + // market orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder + // + // { + // "avgFillPrice": 2666.0, + // "clientId": None, + // "createdAt": "2020-02-12T00: 53: 49.009726+00: 00", + // "filledSize": 0.0007, + // "future": None, + // "id": 3109208514, + // "ioc": True, + // "market": "BNBBULL/USD", + // "postOnly": False, + // "price": None, + // "reduceOnly": False, + // "remainingSize": 0.0, + // "side": "buy", + // "size": 0.0007, + // "status": "closed", + // "type": "market" + // } + // + // createOrder (conditional, "stop", "trailingStop", or "takeProfit") + // + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306525, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false + // } + // + // editOrder (conditional, stop, trailing stop, take profit) + // + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306225, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false, + // "orderType": "market", + // "filledSize": 0, + // "avgFillPrice": null, + // "retryUntilFilled": false + // } + // + // canceled order with a closed status + // + // { + // "avgFillPrice":null, + // "clientId":null, + // "createdAt":"2020-09-01T13:45:57.119695+00:00", + // "filledSize":0.0, + // "future":null, + // "id":8553541288, + // "ioc":false, + // "liquidation":false, + // "market":"XRP/USDT", + // "postOnly":false, + // "price":0.5, + // "reduceOnly":false, + // "remainingSize":0.0, + // "side":"sell", + // "size":46.0, + // "status":"closed", + // "type":"limit" + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'createdAt')); + let status = this.parseOrderStatus (this.safeString (order, 'status')); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'filledSize'); + let remaining = this.safeFloat (order, 'remainingSize'); + if ((remaining === 0.0) && (amount !== undefined) && (filled !== undefined)) { + remaining = Math.max (amount - filled, 0); + if (remaining > 0) { + status = 'canceled'; + } + } + let symbol = undefined; + const marketId = this.safeString (order, 'market'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + // support for delisted market ids + // https://github.com/ccxt/ccxt/issues/7113 + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'type'); + const average = this.safeFloat (order, 'avgFillPrice'); + const price = this.safeFloat2 (order, 'price', 'triggerPrice', average); + let cost = undefined; + if (filled !== undefined && price !== undefined) { + cost = filled * price; + } + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'triggeredAt')); + const clientOrderId = this.safeString (order, 'clientId'); + const stopPrice = this.safeFloat (order, 'triggerPrice'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': side, // "buy" or "sell" + // 'price': 0.306525, // send null for market orders + 'type': type, // "limit", "market", "stop", "trailingStop", or "takeProfit" + 'size': parseFloat (this.amountToPrecision (symbol, amount)), + // 'reduceOnly': false, // optional, default is false + // 'ioc': false, // optional, default is false, limit or market orders only + // 'postOnly': false, // optional, default is false, limit or market orders only + // 'clientId': 'abcdef0123456789', // string, optional, client order id, limit or market orders only + }; + const clientOrderId = this.safeString2 (params, 'clientId', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clientId'] = clientOrderId; + params = this.omit (params, [ 'clientId', 'clientOrderId' ]); + } + let priceToPrecision = undefined; + if (price !== undefined) { + priceToPrecision = parseFloat (this.priceToPrecision (symbol, price)); + } + let method = 'privatePostConditionalOrders'; + if (type === 'limit') { + method = 'privatePostOrders'; + request['price'] = priceToPrecision; + } else if (type === 'market') { + method = 'privatePostOrders'; + request['price'] = null; + } else if ((type === 'stop') || (type === 'takeProfit')) { + request['triggerPrice'] = priceToPrecision; + // request['orderPrice'] = number; // optional, order type is limit if this is specified, otherwise market + } else if (type === 'trailingStop') { + request['trailValue'] = priceToPrecision; // negative for "sell", positive for "buy" + } else { + throw new InvalidOrder (this.id + ' createOrder () does not support order type ' + type + ', only limit, market, stop, trailingStop, or takeProfit orders are supported'); + } + const response = await this[method] (this.extend (request, params)); + // + // orders + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 0, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "remainingSize": 31431, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null, + // } + // ] + // } + // + // conditional orders + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306525, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false + // } + // ] + // } + // + // + const result = this.safeValue (response, 'result', []); + return this.parseOrder (result, market); + } + + async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = {}; + let method = undefined; + const clientOrderId = this.safeString2 (params, 'client_order_id', 'clientOrderId'); + const triggerPrice = this.safeFloat (params, 'triggerPrice'); + const orderPrice = this.safeFloat (params, 'orderPrice'); + const trailValue = this.safeFloat (params, 'trailValue'); + params = this.omit (params, [ 'client_order_id', 'clientOrderId', 'triggerPrice', 'orderPrice', 'trailValue' ]); + const triggerPriceIsDefined = (triggerPrice !== undefined); + const orderPriceIsDefined = (orderPrice !== undefined); + const trailValueIsDefined = (trailValue !== undefined); + if (triggerPriceIsDefined || orderPriceIsDefined || trailValueIsDefined) { + method = 'privatePostConditionalOrdersOrderIdModify'; + request['order_id'] = id; + if (triggerPriceIsDefined) { + request['triggerPrice'] = parseFloat (this.priceToPrecision (symbol, triggerPrice)); + } + if (orderPriceIsDefined) { + // only for stop limit or take profit limit orders + request['orderPrice'] = parseFloat (this.priceToPrecision (symbol, orderPrice)); + } + if (trailValueIsDefined) { + // negative for sell orders, positive for buy orders + request['trailValue'] = parseFloat (this.priceToPrecision (symbol, trailValue)); + } + } else { + if (clientOrderId === undefined) { + method = 'privatePostOrdersByClientIdClientOrderIdModify'; + request['client_order_id'] = clientOrderId; + // request['clientId'] = clientOrderId; + } else { + method = 'privatePostOrdersOrderIdModify'; + request['order_id'] = id; + } + if (price !== undefined) { + request['price'] = parseFloat (this.priceToPrecision (symbol, price)); + } + } + if (amount !== undefined) { + request['size'] = parseFloat (this.amountToPrecision (symbol, amount)); + } + const response = await this[method] (this.extend (request, params)); + // + // regular order + // + // { + // "success": true, + // "result": { + // "createdAt": "2019-03-05T11:56:55.728933+00:00", + // "filledSize": 0, + // "future": "XRP-PERP", + // "id": 9596932, + // "market": "XRP-PERP", + // "price": 0.326525, + // "remainingSize": 31431, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null, + // } + // } + // + // conditional trigger order + // + // { + // "success": true, + // "result": { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "triggerPrice": 0.306225, + // "orderId": null, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "stop", + // "orderPrice": null, + // "error": null, + // "triggeredAt": null, + // "reduceOnly": false, + // "orderType": "market", + // "filledSize": 0, + // "avgFillPrice": null, + // "retryUntilFilled": false + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + const options = this.safeValue (this.options, 'cancelOrder', {}); + const defaultMethod = this.safeString (options, 'method', 'privateDeleteOrdersOrderId'); + let method = this.safeString (params, 'method', defaultMethod); + const type = this.safeValue (params, 'type'); + const clientOrderId = this.safeValue2 (params, 'client_order_id', 'clientOrderId'); + if (clientOrderId === undefined) { + request['order_id'] = parseInt (id); + if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) { + method = 'privateDeleteConditionalOrdersOrderId'; + } + } else { + request['client_order_id'] = clientOrderId; + method = 'privateDeleteOrdersByClientIdClientOrderId'; + } + const query = this.omit (params, [ 'method', 'type', 'client_order_id', 'clientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + // + // { + // "success": true, + // "result": "Order queued for cancelation" + // } + // + const result = this.safeValue (response, 'result', {}); + return result; + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'market': market['id'], // optional + 'conditionalOrdersOnly': false, // cancel conditional orders only + 'limitOrdersOnly': false, // cancel existing limit orders (non-conditional orders) only + }; + const marketId = this.getMarketId (symbol, 'market', params); + if (marketId !== undefined) { + request['market'] = marketId; + } + const response = await this.privateDeleteOrders (this.extend (request, params)); + const result = this.safeValue (response, 'result', {}); + // + // { + // "success": true, + // "result": "Orders queued for cancelation" + // } + // + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const clientOrderId = this.safeValue2 (params, 'client_order_id', 'clientOrderId'); + let method = 'privateGetOrdersOrderId'; + if (clientOrderId === undefined) { + request['order_id'] = id; + } else { + request['client_order_id'] = clientOrderId; + params = this.omit (params, [ 'client_order_id', 'clientOrderId']); + method = 'privateGetOrdersByClientIdClientOrderId'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "success": true, + // "result": { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 10, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "avgFillPrice": 0.306526, + // "remainingSize": 31421, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseOrder (result); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const [ market, marketId ] = this.getMarketParams (symbol, 'market', params); + if (marketId !== undefined) { + request['market'] = marketId; + } + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + const defaultMethod = this.safeString (options, 'method', 'privateGetOrders'); + let method = this.safeString (params, 'method', defaultMethod); + const type = this.safeValue (params, 'type'); + if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) { + method = 'privateGetConditionalOrders'; + } + const query = this.omit (params, [ 'method', 'type' ]); + const response = await this[method] (this.extend (request, query)); + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 10, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "avgFillPrice": 0.306526, + // "remainingSize": 31421, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + // } + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const [ market, marketId ] = this.getMarketParams (symbol, 'market', params); + if (marketId !== undefined) { + request['market'] = marketId; + } + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 100 + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + } + // support for canceling conditional orders + // https://github.com/ccxt/ccxt/issues/6669 + const options = this.safeValue (this.options, 'fetchOrders', {}); + const defaultMethod = this.safeString (options, 'method', 'privateGetOrdersHistory'); + let method = this.safeString (params, 'method', defaultMethod); + const type = this.safeValue (params, 'type'); + if ((type === 'stop') || (type === 'trailingStop') || (type === 'takeProfit')) { + method = 'privateGetConditionalOrdersHistory'; + } + const query = this.omit (params, [ 'method', 'type' ]); + const response = await this[method] (this.extend (request, query)); + // + // { + // "success": true, + // "result": [ + // { + // "createdAt": "2019-03-05T09:56:55.728933+00:00", + // "filledSize": 10, + // "future": "XRP-PERP", + // "id": 9596912, + // "market": "XRP-PERP", + // "price": 0.306525, + // "avgFillPrice": 0.306526, + // "remainingSize": 31421, + // "side": "sell", + // "size": 31431, + // "status": "open", + // "type": "limit", + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + // } + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOrders (result, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const [ market, marketId ] = this.getMarketParams (symbol, 'market', params); + const request = {}; + if (marketId !== undefined) { + request['market'] = marketId; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['start_time'] = parseInt (since / 1000); + } + const response = await this.privateGetFills (this.extend (request, params)); + // + // { + // "success": true, + // "result": [ + // { + // "fee": 20.1374935, + // "feeRate": 0.0005, + // "future": "EOS-0329", + // "id": 11215, + // "liquidity": "taker", + // "market": "EOS-0329", + // "baseCurrency": null, + // "quoteCurrency": null, + // "orderId": 8436981, + // "price": 4.201, + // "side": "buy", + // "size": 9587, + // "time": "2019-03-27T19:15:10.204619+00:00", + // "type": "order" + // } + // ] + // } + // + const trades = this.safeValue (response, 'result', []); + return this.parseTrades (trades, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'size': amount, + 'address': address, + // 'password': 'string', // optional withdrawal password if it is required for your account + // 'code': '192837', // optional 2fa code if it is required for your account + }; + if (this.password !== undefined) { + request['password'] = this.password; + } + if (tag !== undefined) { + request['tag'] = tag; + } + const response = await this.privatePostWalletWithdrawals (this.extend (request, params)); + // + // { + // "success": true, + // "result": { + // "coin": "USDTBEAR", + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null", + // "fee": 0, + // "id": 1, + // "size": "20.2", + // "status": "requested", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "null" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTransaction (result, currency); + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccount (params); + // + // { + // "result":{ + // "backstopProvider":false, + // "chargeInterestOnNegativeUsd":false, + // "collateral":2830.2567913677476, + // "freeCollateral":2829.670741867416, + // "initialMarginRequirement":0.05, + // "leverage":20.0, + // "liquidating":false, + // "maintenanceMarginRequirement":0.03, + // "makerFee":0.0, + // "marginFraction":null, + // "openMarginFraction":null, + // "positionLimit":null, + // "positionLimitUsed":null, + // "positions":[ + // { + // "collateralUsed":0.0, + // "cost":0.0, + // "entryPrice":null, + // "estimatedLiquidationPrice":null, + // "future":"XRP-PERP", + // "initialMarginRequirement":0.05, + // "longOrderSize":0.0, + // "maintenanceMarginRequirement":0.03, + // "netSize":0.0, + // "openSize":0.0, + // "realizedPnl":0.016, + // "shortOrderSize":0.0, + // "side":"buy", + // "size":0.0, + // "unrealizedPnl":0.0, + // } + // ], + // "spotLendingEnabled":false, + // "spotMarginEnabled":false, + // "takerFee":0.0007, + // "totalAccountValue":2830.2567913677476, + // "totalPositionSize":0.0, + // "useFttCollateral":true, + // "username":"igor.kroitor@gmail.com" + // }, + // "success":true + // } + // + const result = this.safeValue (response, 'result', {}); + // todo unify parsePosition/parsePositions + return this.safeValue (result, 'positions', []); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + }; + const response = await this.privateGetWalletDepositAddressCoin (this.extend (request, params)); + // + // { + // "success": true, + // "result": { + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const address = this.safeString (result, 'address'); + const tag = this.safeString (result, 'tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + // what are other statuses here? + 'confirmed': 'ok', // deposits + 'complete': 'ok', // withdrawals + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "coin": "TUSD", + // "confirmations": 64, + // "confirmedTime": "2019-03-05T09:56:55.728933+00:00", + // "fee": 0, + // "id": 1, + // "sentTime": "2019-03-05T09:56:55.735929+00:00", + // "size": "99.0", + // "status": "confirmed", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // + // fetchWithdrawals + // + // { + // "coin": "TUSD", + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null", + // "fee": 0, + // "id": 1, + // "size": "99.0", + // "status": "complete", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // + const code = this.safeCurrencyCode (this.safeString (transaction, 'coin')); + const id = this.safeString (transaction, 'id'); + const amount = this.safeFloat (transaction, 'size'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const timestamp = this.parse8601 (this.safeString (transaction, 'time')); + const txid = this.safeString (transaction, 'txid'); + let tag = undefined; + let address = this.safeValue (transaction, 'address'); + if (typeof address !== 'string') { + tag = this.safeString (address, 'tag'); + address = this.safeString (address, 'address'); + } + const fee = this.safeFloat (transaction, 'fee'); + const type = ('destinationName' in transaction) ? 'withdrawal' : 'deposit'; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': { + 'currency': code, + 'cost': fee, + 'rate': undefined, + }, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWalletDeposits (params); + // + // { + // "success": true, + // "result": { + // "coin": "TUSD", + // "confirmations": 64, + // "confirmedTime": "2019-03-05T09:56:55.728933+00:00", + // "fee": 0, + // "id": 1, + // "sentTime": "2019-03-05T09:56:55.735929+00:00", + // "size": "99.0", + // "status": "confirmed", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // } + // + const result = this.safeValue (response, 'result', []); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (result, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWalletWithdrawals (params); + // + // { + // "success": true, + // "result": { + // "coin": "TUSD", + // "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE", + // "tag": "null", + // "fee": 0, + // "id": 1, + // "size": "99.0", + // "status": "complete", + // "time": "2019-03-05T09:56:55.728933+00:00", + // "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1" + // } + // } + // + const result = this.safeValue (response, 'result', []); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (result, currency, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let request = '/api/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const baseUrl = this.implodeParams (this.urls['api'][api], { 'hostname': this.hostname }); + let url = baseUrl + request; + if (method !== 'POST') { + if (Object.keys (query).length) { + const suffix = '?' + this.urlencode (query); + url += suffix; + request += suffix; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + let auth = timestamp + method + request; + headers = {}; + if ((method === 'POST') || (method === 'DELETE')) { + body = this.json (query); + auth += body; + headers['Content-Type'] = 'application/json'; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + const options = this.safeValue (this.options, 'sign', {}); + const headerPrefix = this.safeString (options, this.hostname, 'FTX'); + const keyField = headerPrefix + '-KEY'; + const tsField = headerPrefix + '-TS'; + const signField = headerPrefix + '-SIGN'; + headers[keyField] = this.apiKey; + headers[tsField] = timestamp; + headers[signField] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to the default error handler + } + // + // {"error":"Invalid parameter start_time","success":false} + // {"error":"Not enough balances","success":false} + // + const success = this.safeValue (response, 'success'); + if (!success) { + const feedback = this.id + ' ' + body; + const error = this.safeString (response, 'error'); + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/gateio.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/gateio.js new file mode 100644 index 000000000..d03f929d8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/gateio.js @@ -0,0 +1,983 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, ArgumentsRequired, InvalidAddress, OrderNotFound, NotSupported, DDoSProtection, InsufficientFunds, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class gateio extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'gateio', + 'name': 'Gate.io', + 'countries': [ 'CN' ], + 'version': '2', + 'rateLimit': 1000, + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 60, + '5m': 300, + '10m': 600, + '15m': 900, + '30m': 1800, + '1h': 3600, + '2h': 7200, + '4h': 14400, + '6h': 21600, + '12h': 43200, + '1d': 86400, + '1w': 604800, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg', + 'api': { + 'public': 'https://data.gate.io/api', + 'private': 'https://data.gate.io/api', + }, + 'www': 'https://gate.io/', + 'doc': 'https://gate.io/api2', + 'fees': [ + 'https://gate.io/fee', + 'https://support.gate.io/hc/en-us/articles/115003577673', + ], + 'referral': 'https://www.gate.io/signup/2436035', + }, + 'api': { + 'public': { + 'get': [ + 'candlestick2/{id}', + 'pairs', + 'coininfo', + 'marketinfo', + 'marketlist', + 'coininfo', + 'tickers', + 'ticker/{id}', + 'orderBook/{id}', + 'trade/{id}', + 'tradeHistory/{id}', + 'tradeHistory/{id}/{tid}', + ], + }, + 'private': { + 'post': [ + 'balances', + 'depositAddress', + 'newAddress', + 'depositsWithdrawals', + 'buy', + 'sell', + 'cancelOrder', + 'cancelAllOrders', + 'getOrder', + 'openOrders', + 'tradeHistory', + 'feelist', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'maker': 0.002, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + '4': DDoSProtection, + '5': AuthenticationError, // { result: "false", code: 5, message: "Error: invalid key or sign, please re-generate it from your account" } + '6': AuthenticationError, // { result: 'false', code: 6, message: 'Error: invalid data ' } + '7': NotSupported, + '8': NotSupported, + '9': NotSupported, + '15': DDoSProtection, + '16': OrderNotFound, + '17': OrderNotFound, + '20': InvalidOrder, + '21': InsufficientFunds, + }, + // https://gate.io/api2#errCode + 'errorCodeNames': { + '1': 'Invalid request', + '2': 'Invalid version', + '3': 'Invalid request', + '4': 'Too many attempts', + '5': 'Invalid sign', + '6': 'Invalid sign', + '7': 'Currency is not supported', + '8': 'Currency is not supported', + '9': 'Currency is not supported', + '10': 'Verified failed', + '11': 'Obtaining address failed', + '12': 'Empty params', + '13': 'Internal error, please report to administrator', + '14': 'Invalid user', + '15': 'Cancel order too fast, please wait 1 min and try again', + '16': 'Invalid order id or order is already closed', + '17': 'Invalid orderid', + '18': 'Invalid amount', + '19': 'Not permitted or trade is disabled', + '20': 'Your order size is too small', + '21': 'You don\'t have enough fund', + }, + }, + 'options': { + 'limits': { + 'cost': { + 'min': { + 'BTC': 0.0001, + 'ETH': 0.001, + 'USDT': 1, + }, + }, + }, + }, + 'commonCurrencies': { + 'BOX': 'DefiBox', + 'BTCBEAR': 'BEAR', + 'BTCBULL': 'BULL', + 'TNC': 'Trinity Network Credit', + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCoininfo (params); + // + // { + // "result":"true", + // "coins":[ + // { + // "CNYX":{ + // "delisted":0, + // "withdraw_disabled":1, + // "withdraw_delayed":0, + // "deposit_disabled":0, + // "trade_disabled":0 + // } + // }, + // { + // "USDT_ETH":{ + // "delisted":0, + // "withdraw_disabled":1, + // "withdraw_delayed":0, + // "deposit_disabled":0, + // "trade_disabled":1 + // } + // } + // ] + // } + // + const coins = this.safeValue (response, 'coins'); + if (!coins) { + throw new ExchangeError (this.id + ' fetchCurrencies got an unrecognized response'); + } + const result = {}; + for (let i = 0; i < coins.length; i++) { + const coin = coins[i]; + const ids = Object.keys (coin); + for (let j = 0; j < ids.length; j++) { + const id = ids[j]; + const currency = coin[id]; + const code = this.safeCurrencyCode (id); + const delisted = this.safeValue (currency, 'delisted', 0); + const withdrawDisabled = this.safeValue (currency, 'withdraw_disabled', 0); + const depositDisabled = this.safeValue (currency, 'deposit_disabled', 0); + const tradeDisabled = this.safeValue (currency, 'trade_disabled', 0); + const listed = (delisted === 0); + const withdrawEnabled = (withdrawDisabled === 0); + const depositEnabled = (depositDisabled === 0); + const tradeEnabled = (tradeDisabled === 0); + const active = listed && withdrawEnabled && depositEnabled && tradeEnabled; + result[code] = { + 'id': id, + 'code': code, + 'active': active, + 'info': currency, + 'name': undefined, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + } + return result; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarketinfo (params); + // + // { + // "result":"true", + // "pairs":[ + // { + // "usdt_cnyx":{ + // "decimal_places":3, + // "amount_decimal_places":3, + // "min_amount":1, + // "min_amount_a":1, + // "min_amount_b":3, + // "fee":0.02, + // "trade_disabled":0, + // "buy_disabled":0, + // "sell_disabled":0 + // } + // }, + // ] + // } + // + const markets = this.safeValue (response, 'pairs'); + if (!markets) { + throw new ExchangeError (this.id + ' fetchMarkets got an unrecognized response'); + } + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const keys = Object.keys (market); + const id = this.safeString (keys, 0); + const details = market[id]; + // all of their symbols are separated with an underscore + // but not boe_eth_eth (BOE_ETH/ETH) which has two underscores + // https://github.com/ccxt/ccxt/issues/4894 + const parts = id.split ('_'); + const numParts = parts.length; + let baseId = parts[0]; + let quoteId = parts[1]; + if (numParts > 2) { + baseId = parts[0] + '_' + parts[1]; + quoteId = parts[2]; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (details, 'amount_decimal_places'), + 'price': this.safeInteger (details, 'decimal_places'), + }; + const amountLimits = { + 'min': this.safeFloat (details, 'min_amount'), + 'max': undefined, + }; + const priceLimits = { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }; + const defaultCost = amountLimits['min'] * priceLimits['min']; + const minCost = this.safeFloat (this.options['limits']['cost']['min'], quote, defaultCost); + const costLimits = { + 'min': minCost, + 'max': undefined, + }; + const limits = { + 'amount': amountLimits, + 'price': priceLimits, + 'cost': costLimits, + }; + const disabled = this.safeValue (details, 'trade_disabled'); + const active = !disabled; + const uppercaseId = id.toUpperCase (); + const fee = this.safeFloat (details, 'fee'); + result.push ({ + 'id': id, + 'uppercaseId': uppercaseId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'maker': fee / 100, + 'taker': fee / 100, + 'precision': precision, + 'limits': limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalances (params); + const result = { 'info': response }; + let available = this.safeValue (response, 'available', {}); + if (Array.isArray (available)) { + available = {}; + } + const locked = this.safeValue (response, 'locked', {}); + const currencyIds = Object.keys (available); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (available, currencyId); + account['used'] = this.safeFloat (locked, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.publicGetOrderBookId (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseOHLCV (ohlcv, market = undefined) { + // they return [ Timestamp, Volume, Close, High, Low, Open ] + return [ + this.safeInteger (ohlcv, 0), // t + this.safeFloat (ohlcv, 5), // o + this.safeFloat (ohlcv, 3), // h + this.safeFloat (ohlcv, 4), // l + this.safeFloat (ohlcv, 2), // c + this.safeFloat (ohlcv, 1), // v + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + 'group_sec': this.timeframes[timeframe], + }; + // max limit = 1001 + if (limit !== undefined) { + const periodDurationInSeconds = this.parseTimeframe (timeframe); + const hours = parseInt ((periodDurationInSeconds * limit) / 3600); + request['range_hour'] = Math.max (0, hours - 1); + } + const response = await this.publicGetCandlestick2Id (this.extend (request, params)); + // + // { + // "elapsed": "15ms", + // "result": "true", + // "data": [ + // [ "1553930820000", "1.005299", "4081.05", "4086.18", "4081.05", "4086.18" ], + // [ "1553930880000", "0.110923277", "4095.2", "4095.23", "4091.15", "4091.15" ], + // ... + // [ "1553934420000", "0", "4089.42", "4089.42", "4089.42", "4089.42" ], + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'percentChange'); + let open = undefined; + let change = undefined; + let average = undefined; + if ((last !== undefined) && (percentage !== undefined)) { + const relativeChange = percentage / 100; + open = last / this.sum (1, relativeChange); + change = last - open; + average = this.sum (last, open) / 2; + } + open = this.safeFloat (ticker, 'open', open); + change = this.safeFloat (ticker, 'change', change); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat2 (ticker, 'high24hr', 'high'), + 'low': this.safeFloat2 (ticker, 'low24hr', 'low'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'quoteVolume'), // gateio has them reversed + 'quoteVolume': this.safeFloat (ticker, 'baseVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const result = {}; + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id, undefined, '_'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (response[id], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const ticker = await this.publicGetTickerId (this.extend ({ + 'id': market['id'], + }, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // { + // "tradeID": 3175762, + // "date": "2017-08-25 07:24:28", + // "type": "sell", + // "rate": 29011, + // "amount": 0.0019, + // "total": 55.1209, + // "fee": "0", + // "fee_coin": "btc", + // "gt_fee":"0", + // "point_fee":"0.1213", + // }, + let timestamp = this.safeTimestamp2 (trade, 'timestamp', 'time_unix'); + timestamp = this.safeTimestamp (trade, 'time', timestamp); + const id = this.safeString2 (trade, 'tradeID', 'id'); + // take either of orderid or orderId + const orderId = this.safeString2 (trade, 'orderid', 'orderNumber'); + const price = this.safeFloat2 (trade, 'rate', 'price'); + const amount = this.safeFloat (trade, 'amount'); + const type = this.safeString (trade, 'type'); + const takerOrMaker = this.safeString (trade, 'role'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let fee = undefined; + let feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee_coin')); + let feeCost = this.safeFloat (trade, 'point_fee'); + if ((feeCost === undefined) || (feeCost === 0)) { + feeCost = this.safeFloat (trade, 'gt_fee'); + if ((feeCost === undefined) || (feeCost === 0)) { + feeCost = this.safeFloat (trade, 'fee'); + } else { + feeCurrency = this.safeCurrencyCode ('GT'); + } + } else { + feeCurrency = this.safeCurrencyCode ('POINT'); + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': type, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + let method = undefined; + if ('tid' in params) { + method = 'publicGetTradeHistoryIdTid'; + } else { + method = 'publicGetTradeHistoryId'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTrades (response['data'], market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.privatePostOpenOrders (params); + return this.parseOrders (response['orders'], undefined, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderNumber': id, + 'currencyPair': this.marketId (symbol), + }; + const response = await this.privatePostGetOrder (this.extend (request, params)); + return this.parseOrder (response['order']); + } + + parseOrderStatus (status) { + const statuses = { + 'cancelled': 'canceled', + // 'closed': 'closed', // these two statuses aren't actually needed + // 'open': 'open', // as they are mapped one-to-one + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "fee": "0 ZEC", + // "code": 0, + // "rate": "0.0055", + // "side": 2, + // "type": "buy", + // "ctime": 1586460839.138, + // "market": "ZEC_BTC", + // "result": "true", + // "status": "open", + // "iceberg": "0", + // "message": "Success", + // "feeValue": "0", + // "filledRate": "0.005500000", + // "leftAmount": "0.60607456", + // "feeCurrency": "ZEC", + // "orderNumber": 10755887009, + // "filledAmount": "0", + // "feePercentage": 0.002, + // "initialAmount": "0.60607456" + // } + // + // { + // 'amount': '0.00000000', + // 'currencyPair': 'xlm_usdt', + // 'fee': '0.0113766632239302 USDT', + // 'feeCurrency': 'USDT', + // 'feePercentage': 0.18, + // 'feeValue': '0.0113766632239302', + // 'filledAmount': '30.14004987', + // 'filledRate': 0.2097, + // 'initialAmount': '30.14004987', + // 'initialRate': '0.2097', + // 'left': 0, + // 'orderNumber': '998307286', + // 'rate': '0.2097', + // 'status': 'closed', + // 'timestamp': 1531158583, + // 'type': 'sell' + // } + // + // { + // "orderNumber": 10802237760, + // "orderType": 1, + // "type": "buy", + // "rate": "0.54250000", + // "amount": "45.55638518", + // "total": "24.71433896", + // "initialRate": "0.54250000", + // "initialAmount": "45.55638518", + // "filledRate": "0.54250000", + // "filledAmount": "0", + // "currencyPair": "nano_usdt", + // "timestamp": 1586556143, + // "status": "open" + // } + // + const id = this.safeString2 (order, 'orderNumber', 'id'); + const marketId = this.safeString (order, 'currencyPair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const timestamp = this.safeTimestamp2 (order, 'timestamp', 'ctime'); + const lastTradeTimestamp = this.safeTimestamp (order, 'mtime'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let side = this.safeString (order, 'type'); + // handling for order.update messages + if (side === '1') { + side = 'sell'; + } else if (side === '2') { + side = 'buy'; + } + const price = this.safeFloat2 (order, 'initialRate', 'rate'); + const average = this.safeFloat (order, 'filledRate'); + const amount = this.safeFloat2 (order, 'initialAmount', 'amount'); + const filled = this.safeFloat (order, 'filledAmount'); + // In the order status response, this field has a different name. + let remaining = this.safeFloat2 (order, 'leftAmount', 'left'); + if (remaining === undefined) { + remaining = amount - filled; + } + const feeCost = this.safeFloat (order, 'feeValue'); + const feeCurrencyId = this.safeString (order, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + let feeRate = this.safeFloat (order, 'feePercentage'); + if (feeRate !== undefined) { + feeRate = feeRate / 100; + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': average, + 'trades': undefined, + 'fee': { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }, + 'info': order, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const method = 'privatePost' + this.capitalize (side); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'rate': price, + 'amount': amount, + }; + const response = await this[method] (this.extend (request, params)); + return this.parseOrder (this.extend ({ + 'status': 'open', + 'type': side, + 'initialAmount': amount, + }, response), market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires symbol argument'); + } + await this.loadMarkets (); + const request = { + 'orderNumber': id, + 'currencyPair': this.marketId (symbol), + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async queryDepositAddress (method, code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + method = 'privatePost' + method + 'Address'; + const request = { + 'currency': currency['id'], + }; + const response = await this[method] (this.extend (request, params)); + let address = this.safeString (response, 'addr'); + let tag = undefined; + if ((address !== undefined) && (address.indexOf ('address') >= 0)) { + throw new InvalidAddress (this.id + ' queryDepositAddress ' + address); + } + if (code === 'XRP') { + const parts = address.split (' '); + address = parts[0]; + tag = parts[1]; + } + return { + 'currency': currency, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async createDepositAddress (code, params = {}) { + return await this.queryDepositAddress ('New', code, params); + } + + async fetchDepositAddress (code, params = {}) { + return await this.queryDepositAddress ('Deposit', code, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privatePostOpenOrders (params); + return this.parseOrders (response['orders'], market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'orderNumber': id, + }; + const response = await this.privatePostTradeHistory (this.extend (request, params)); + return this.parseTrades (response['trades'], market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const response = await this.privatePostTradeHistory (this.extend (request, params)); + return this.parseTrades (response['trades'], market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, // Address must exist in you AddressBook in security settings + }; + if (tag !== undefined) { + request['address'] += ' ' + tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + async fetchTransactionsByType (type = undefined, code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['start'] = since; + } + const response = await this.privatePostDepositsWithdrawals (this.extend (request, params)); + let transactions = undefined; + if (type === undefined) { + const deposits = this.safeValue (response, 'deposits', []); + const withdrawals = this.safeValue (response, 'withdraws', []); + transactions = this.arrayConcat (deposits, withdrawals); + } else { + transactions = this.safeValue (response, type, []); + } + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + return this.parseTransactions (transactions, currency, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType (undefined, code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposits', code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdraws', code, since, limit, params); + } + + parseTransaction (transaction, currency = undefined) { + // + // deposit + // + // { + // 'id': 'd16520849', + // 'currency': 'NEO', + // 'address': False, + // 'amount': '1', + // 'txid': '01acf6b8ce4d24a....', + // 'timestamp': '1553125968', + // 'status': 'DONE', + // 'type': 'deposit' + // } + // + // withdrawal + // + // { + // 'id': 'w5864259', + // 'currency': 'ETH', + // 'address': '0x72632f462....', + // 'amount': '0.4947', + // 'txid': '0x111167d120f736....', + // 'timestamp': '1553123688', + // 'status': 'DONE', + // 'type': 'withdrawal' + // } + // + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const id = this.safeString (transaction, 'id'); + const txid = this.safeString (transaction, 'txid'); + const amount = this.safeFloat (transaction, 'amount'); + let address = this.safeString (transaction, 'address'); + if (address === 'false') { + address = undefined; + } + const timestamp = this.safeTimestamp (transaction, 'timestamp'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const type = this.parseTransactionType (id[0]); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'PEND': 'pending', + 'REQUEST': 'pending', + 'DMOVE': 'pending', + 'CANCEL': 'failed', + 'DONE': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const types = { + 'd': 'deposit', + 'w': 'withdrawal', + }; + return this.safeString (types, type, type); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + const resultString = this.safeString (response, 'result', ''); + if (resultString !== 'false') { + return; + } + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message', body); + if (errorCode !== undefined) { + const feedback = this.safeString (this.exceptions['errorCodeNames'], errorCode, message); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const prefix = (api === 'private') ? (api + '/') : ''; + let url = this.urls['api'][api] + this.version + '/1/' + prefix + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = { 'nonce': nonce }; + body = this.rawencode (this.extend (request, query)); + // gateio does not like the plus sign in the URL query + // https://github.com/ccxt/ccxt/issues/4529 + body = body.replace ('+', ' '); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Key': this.apiKey, + 'Sign': signature, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/gemini.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/gemini.js new file mode 100644 index 000000000..a0894c364 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/gemini.js @@ -0,0 +1,910 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, OrderNotFound, InvalidOrder, InvalidNonce, InsufficientFunds, AuthenticationError, PermissionDenied, NotSupported, OnMaintenance, RateLimitExceeded, ExchangeNotAvailable } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class gemini extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'gemini', + 'name': 'Gemini', + 'countries': [ 'US' ], + 'rateLimit': 1500, // 200 for private API + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchBidsAsks': false, + 'fetchClosedOrders': false, + 'fetchDepositAddress': false, + 'fetchDeposits': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg', + 'api': { + 'public': 'https://api.gemini.com', + 'private': 'https://api.gemini.com', + 'web': 'https://docs.gemini.com', + }, + 'www': 'https://gemini.com/', + 'doc': [ + 'https://docs.gemini.com/rest-api', + 'https://docs.sandbox.gemini.com', + ], + 'test': { + 'public': 'https://api.sandbox.gemini.com', + 'private': 'https://api.sandbox.gemini.com', + // use the true doc instead of the sandbox doc + // since they differ in parsing + // https://github.com/ccxt/ccxt/issues/7874 + // https://github.com/ccxt/ccxt/issues/7894 + 'web': 'https://docs.gemini.com', + }, + 'fees': [ + 'https://gemini.com/api-fee-schedule', + 'https://gemini.com/trading-fees', + 'https://gemini.com/transfer-fees', + ], + }, + 'api': { + 'web': { + 'get': [ + 'rest-api', + ], + }, + 'public': { + 'get': [ + 'v1/symbols', + 'v1/pricefeed', + 'v1/pubticker/{symbol}', + 'v1/book/{symbol}', + 'v1/trades/{symbol}', + 'v1/auction/{symbol}', + 'v1/auction/{symbol}/history', + 'v2/candles/{symbol}/{timeframe}', + 'v2/ticker/{symbol}', + ], + }, + 'private': { + 'post': [ + 'v1/order/new', + 'v1/order/cancel', + 'v1/order/cancel/session', + 'v1/order/cancel/all', + 'v1/order/status', + 'v1/orders', + 'v1/mytrades', + 'v1/notionalvolume', + 'v1/tradevolume', + 'v1/transfers', + 'v1/balances', + 'v1/deposit/{currency}/newAddress', + 'v1/withdraw/{currency}', + 'v1/heartbeat', + 'v1/transfers', + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'taker': 0.0035, + 'maker': 0.001, + }, + }, + 'httpExceptions': { + '400': BadRequest, // Auction not open or paused, ineligible timing, market not open, or the request was malformed, in the case of a private API request, missing or malformed Gemini private API authentication headers + '403': PermissionDenied, // The API key is missing the role necessary to access this private API endpoint + '404': OrderNotFound, // Unknown API entry point or Order not found + '406': InsufficientFunds, // Insufficient Funds + '429': RateLimitExceeded, // Rate Limiting was applied + '500': ExchangeError, // The server encountered an error + '502': ExchangeNotAvailable, // Technical issues are preventing the request from being satisfied + '503': OnMaintenance, // The exchange is down for maintenance + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1hr', + '6h': '6hr', + '1d': '1day', + }, + 'exceptions': { + 'exact': { + 'AuctionNotOpen': BadRequest, // Failed to place an auction-only order because there is no current auction open for this symbol + 'ClientOrderIdTooLong': BadRequest, // The Client Order ID must be under 100 characters + 'ClientOrderIdMustBeString': BadRequest, // The Client Order ID must be a string + 'ConflictingOptions': BadRequest, // New orders using a combination of order execution options are not supported + 'EndpointMismatch': BadRequest, // The request was submitted to an endpoint different than the one in the payload + 'EndpointNotFound': BadRequest, // No endpoint was specified + 'IneligibleTiming': BadRequest, // Failed to place an auction order for the current auction on this symbol because the timing is not eligible, new orders may only be placed before the auction begins. + 'InsufficientFunds': InsufficientFunds, // The order was rejected because of insufficient funds + 'InvalidJson': BadRequest, // The JSON provided is invalid + 'InvalidNonce': InvalidNonce, // The nonce was not greater than the previously used nonce, or was not present + 'InvalidOrderType': InvalidOrder, // An unknown order type was provided + 'InvalidPrice': InvalidOrder, // For new orders, the price was invalid + 'InvalidQuantity': InvalidOrder, // A negative or otherwise invalid quantity was specified + 'InvalidSide': InvalidOrder, // For new orders, and invalid side was specified + 'InvalidSignature': AuthenticationError, // The signature did not match the expected signature + 'InvalidSymbol': BadRequest, // An invalid symbol was specified + 'InvalidTimestampInPayload': BadRequest, // The JSON payload contained a timestamp parameter with an unsupported value. + 'Maintenance': OnMaintenance, // The system is down for maintenance + 'MarketNotOpen': InvalidOrder, // The order was rejected because the market is not accepting new orders + 'MissingApikeyHeader': AuthenticationError, // The X-GEMINI-APIKEY header was missing + 'MissingOrderField': InvalidOrder, // A required order_id field was not specified + 'MissingRole': AuthenticationError, // The API key used to access this endpoint does not have the required role assigned to it + 'MissingPayloadHeader': AuthenticationError, // The X-GEMINI-PAYLOAD header was missing + 'MissingSignatureHeader': AuthenticationError, // The X-GEMINI-SIGNATURE header was missing + 'NoSSL': AuthenticationError, // You must use HTTPS to access the API + 'OptionsMustBeArray': BadRequest, // The options parameter must be an array. + 'OrderNotFound': OrderNotFound, // The order specified was not found + 'RateLimit': RateLimitExceeded, // Requests were made too frequently. See Rate Limits below. + 'System': ExchangeError, // We are experiencing technical issues + 'UnsupportedOption': BadRequest, // This order execution option is not supported. + }, + 'broad': { + 'The Gemini Exchange is currently undergoing maintenance.': OnMaintenance, // The Gemini Exchange is currently undergoing maintenance. Please check https://status.gemini.com/ for more information. + 'We are investigating technical issues with the Gemini Exchange.': ExchangeNotAvailable, // We are investigating technical issues with the Gemini Exchange. Please check https://status.gemini.com/ for more information. + }, + }, + 'options': { + 'fetchMarketsMethod': 'fetch_markets_from_web', + 'fetchTickerMethod': 'fetchTickerV1', // fetchTickerV1, fetchTickerV2, fetchTickerV1AndV2 + }, + }); + } + + async fetchMarkets (params = {}) { + const method = this.safeValue (this.options, 'fetchMarketsMethod', 'fetch_markets_from_api'); + return await this[method] (params); + } + + async fetchMarketsFromWeb (params = {}) { + const response = await this.webGetRestApi (params); + const sections = response.split ('

Symbols and minimums

'); + const numSections = sections.length; + const error = this.id + ' the ' + this.name + ' API doc HTML markup has changed, breaking the parser of order limits and precision info for ' + this.name + ' markets.'; + if (numSections !== 2) { + throw new NotSupported (error); + } + const tables = sections[1].split ('tbody>'); + const numTables = tables.length; + if (numTables < 2) { + throw new NotSupported (error); + } + const rows = tables[1].split ("\n\n"); // eslint-disable-line quotes + const numRows = rows.length; + if (numRows < 2) { + throw new NotSupported (error); + } + const result = []; + // skip the first element (empty string) + for (let i = 1; i < numRows; i++) { + const row = rows[i]; + const cells = row.split ("\n"); // eslint-disable-line quotes + const numCells = cells.length; + if (numCells < 5) { + throw new NotSupported (error); + } + // [ + // 'btcusd', // currency + // '0.00001 BTC (1e-5)', // min order size + // '0.00000001 BTC (1e-8)', // tick size + // '0.01 USD', // quote currency price increment + // '' + // ] + const marketId = cells[0].replace ('', ''); + // const base = this.safeCurrencyCode (baseId); + const minAmountString = cells[1].replace ('', ''); + const minAmountParts = minAmountString.split (' '); + const minAmount = this.safeFloat (minAmountParts, 0); + const amountPrecisionString = cells[2].replace ('', ''); + const amountPrecisionParts = amountPrecisionString.split (' '); + const amountPrecision = this.safeFloat (amountPrecisionParts, 0); + const idLength = marketId.length - 0; + const quoteId = marketId.slice (idLength - 3, idLength); + const quote = this.safeCurrencyCode (quoteId); + const pricePrecisionString = cells[3].replace ('', ''); + const pricePrecisionParts = pricePrecisionString.split (' '); + const pricePrecision = this.safeFloat (pricePrecisionParts, 0); + const baseId = marketId.replace (quoteId, ''); + const base = this.safeCurrencyCode (baseId); + const symbol = base + '/' + quote; + const active = undefined; + result.push ({ + 'id': marketId, + 'info': row, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': { + 'amount': amountPrecision, + 'price': pricePrecision, + }, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchMarketsFromAPI (params = {}) { + const response = await this.publicGetV1Symbols (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const marketId = response[i]; + const market = marketId; + const idLength = marketId.length - 0; + const baseId = marketId.slice (0, idLength - 3); + const quoteId = marketId.slice (idLength - 3, idLength); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': undefined, + 'price': undefined, + }; + result.push ({ + 'id': marketId, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'active': undefined, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit_bids'] = limit; + request['limit_asks'] = limit; + } + const response = await this.publicGetV1BookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTickerV1 (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetV1PubtickerSymbol (this.extend (request, params)); + // + // { + // "bid":"9117.95", + // "ask":"9117.96", + // "volume":{ + // "BTC":"1615.46861748", + // "USD":"14727307.57545006088", + // "timestamp":1594982700000 + // }, + // "last":"9115.23" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickerV2 (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetV2TickerSymbol (this.extend (request, params)); + // + // { + // "symbol":"BTCUSD", + // "open":"9080.58", + // "high":"9184.53", + // "low":"9063.56", + // "close":"9116.08", + // // Hourly prices descending for past 24 hours + // "changes":["9117.33","9105.69","9106.23","9120.35","9098.57","9114.53","9113.55","9128.01","9113.63","9133.49","9133.49","9137.75","9126.73","9103.91","9119.33","9123.04","9124.44","9117.57","9114.22","9102.33","9076.67","9074.72","9074.97","9092.05"], + // "bid":"9115.86", + // "ask":"9115.87" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickerV1AndV2 (symbol, params = {}) { + const tickerA = await this.fetchTickerV1 (symbol, params); + const tickerB = await this.fetchTickerV2 (symbol, params); + return this.deepExtend (tickerA, { + 'open': tickerB['open'], + 'high': tickerB['high'], + 'low': tickerB['low'], + 'change': tickerB['change'], + 'percentage': tickerB['percentage'], + 'average': tickerB['average'], + 'info': tickerB['info'], + }); + } + + async fetchTicker (symbol, params = {}) { + const method = this.safeValue (this.options, 'fetchTickerMethod', 'fetchTickerV1'); + return await this[method] (symbol, params); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTickers + // + // { + // "pair": "BATUSD", + // "price": "0.20687", + // "percentChange24h": "0.0146" + // } + // + // fetchTickerV1 + // + // { + // "bid":"9117.95", + // "ask":"9117.96", + // "volume":{ + // "BTC":"1615.46861748", + // "USD":"14727307.57545006088", + // "timestamp":1594982700000 + // }, + // "last":"9115.23" + // } + // + // fetchTickerV2 + // + // { + // "symbol":"BTCUSD", + // "open":"9080.58", + // "high":"9184.53", + // "low":"9063.56", + // "close":"9116.08", + // // Hourly prices descending for past 24 hours + // "changes":["9117.33","9105.69","9106.23","9120.35","9098.57","9114.53","9113.55","9128.01","9113.63","9133.49","9133.49","9137.75","9126.73","9103.91","9119.33","9123.04","9124.44","9117.57","9114.22","9102.33","9076.67","9074.72","9074.97","9092.05"], + // "bid":"9115.86", + // "ask":"9115.87" + // } + // + const volume = this.safeValue (ticker, 'volume', {}); + const timestamp = this.safeInteger (volume, 'timestamp'); + let symbol = undefined; + const marketId = this.safeString (ticker, 'pair'); + let baseId = undefined; + let quoteId = undefined; + let base = undefined; + let quote = undefined; + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const idLength = marketId.length - 0; + if (idLength === 7) { + baseId = marketId.slice (0, 4); + quoteId = marketId.slice (4, 7); + } else { + baseId = marketId.slice (0, 3); + quoteId = marketId.slice (3, 6); + } + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + baseId = market['baseId'].toUpperCase (); + quoteId = market['quoteId'].toUpperCase (); + base = market['base']; + quote = market['quote']; + } + const price = this.safeFloat (ticker, 'price'); + const last = this.safeFloat2 (ticker, 'last', 'close', price); + let percentage = this.safeFloat (ticker, 'percentChange24h'); + let change = undefined; + let open = this.safeFloat (ticker, 'open'); + let average = undefined; + if (last !== undefined) { + if (open !== undefined) { + change = last - open; + if (open !== 0) { + percentage = change / open * 100; + } + average = this.sum (last, open) / 2; + } else if (percentage !== undefined) { + change = last * percentage; + if (open === undefined) { + open = last - change; + } + average = this.sum (last, open) / 2; + } + } + const baseVolume = this.safeFloat (volume, baseId); + const quoteVolume = this.safeFloat (volume, quoteId); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetV1Pricefeed (params); + // + // [ + // { + // "pair": "BATUSD", + // "price": "0.20687", + // "percentChange24h": "0.0146" + // }, + // { + // "pair": "LINKETH", + // "price": "0.018", + // "percentChange24h": "0.0000" + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "timestamp":1601617445, + // "timestampms":1601617445144, + // "tid":14122489752, + // "price":"0.46476", + // "amount":"28.407209", + // "exchange":"gemini", + // "type":"buy" + // } + // + const timestamp = this.safeInteger (trade, 'timestampms'); + const id = this.safeString (trade, 'tid'); + const orderId = this.safeString (trade, 'order_id'); + const feeCurrencyId = this.safeString (trade, 'fee_currency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + const fee = { + 'cost': this.safeFloat (trade, 'fee_amount'), + 'currency': feeCurrencyCode, + }; + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const type = undefined; + const side = this.safeStringLower (trade, 'type'); + const symbol = this.safeSymbol (undefined, market); + return { + 'id': id, + 'order': orderId, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'cost': cost, + 'amount': amount, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetV1TradesSymbol (this.extend (request, params)); + // + // [ + // { + // "timestamp":1601617445, + // "timestampms":1601617445144, + // "tid":14122489752, + // "price":"0.46476", + // "amount":"28.407209", + // "exchange":"gemini", + // "type":"buy" + // }, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostV1Balances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'amount'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + const timestamp = this.safeInteger (order, 'timestampms'); + const amount = this.safeFloat (order, 'original_amount'); + const remaining = this.safeFloat (order, 'remaining_amount'); + const filled = this.safeFloat (order, 'executed_amount'); + let status = 'closed'; + if (order['is_live']) { + status = 'open'; + } + if (order['is_cancelled']) { + status = 'canceled'; + } + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'avg_execution_price'); + let cost = undefined; + if (filled !== undefined) { + if (average !== undefined) { + cost = filled * average; + } + } + let type = this.safeString (order, 'type'); + if (type === 'exchange limit') { + type = 'limit'; + } else if (type === 'market buy' || type === 'market sell') { + type = 'market'; + } else { + type = order['type']; + } + const fee = undefined; + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const id = this.safeString (order, 'order_id'); + const side = this.safeStringLower (order, 'side'); + const clientOrderId = this.safeString (order, 'client_order_id'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privatePostV1OrderStatus (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostV1Orders (params); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); // throws on non-existent symbol + } + return this.parseOrders (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const nonce = this.nonce (); + const request = { + 'client_order_id': nonce.toString (), + 'symbol': this.marketId (symbol), + 'amount': amount.toString (), + 'price': price.toString (), + 'side': side, + 'type': 'exchange limit', // gemini allows limit orders only + }; + const response = await this.privatePostV1OrderNew (this.extend (request, params)); + return { + 'info': response, + 'id': response['order_id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + return await this.privatePostV1OrderCancel (this.extend (request, params)); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit_trades'] = limit; + } + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.privatePostV1Mytrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + }; + const response = await this.privatePostV1WithdrawCurrency (this.extend (request, params)); + return { + 'info': response, + 'id': this.safeString (response, 'txHash'), + }; + } + + nonce () { + return this.milliseconds (); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (limit !== undefined) { + request['limit_transfers'] = limit; + } + if (since !== undefined) { + request['timestamp'] = since; + } + const response = await this.privatePostV1Transfers (this.extend (request, params)); + return this.parseTransactions (response); + } + + parseTransaction (transaction, currency = undefined) { + const timestamp = this.safeInteger (transaction, 'timestampms'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const address = this.safeString (transaction, 'destination'); + const type = this.safeStringLower (transaction, 'type'); + let status = 'pending'; + // When deposits show as Advanced or Complete they are available for trading. + if (transaction['status']) { + status = 'ok'; + } + let fee = undefined; + const feeAmount = this.safeFloat (transaction, 'feeAmount'); + if (feeAmount !== undefined) { + fee = { + 'cost': feeAmount, + 'currency': code, + }; + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'eid'), + 'txid': this.safeString (transaction, 'txHash'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': undefined, // or is it defined? + 'type': type, // direction of the transaction, ('deposit' | 'withdraw') + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = this.extend ({ + 'request': url, + 'nonce': nonce, + }, query); + let payload = this.json (request); + payload = this.stringToBase64 (payload); + const signature = this.hmac (payload, this.encode (this.secret), 'sha384'); + headers = { + 'Content-Type': 'text/plain', + 'X-GEMINI-APIKEY': this.apiKey, + 'X-GEMINI-PAYLOAD': this.decode (payload), + 'X-GEMINI-SIGNATURE': signature, + }; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + if (typeof body === 'string') { + const feedback = this.id + ' ' + body; + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + } + return; // fallback to default error handler + } + // + // { + // "result": "error", + // "reason": "BadNonce", + // "message": "Out-of-sequence nonce <1234> precedes previously used nonce <2345>" + // } + // + const result = this.safeString (response, 'result'); + if (result === 'error') { + const reason = this.safeString (response, 'reason'); + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + message; + this.throwExactlyMatchedException (this.exceptions['exact'], reason, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostV1DepositCurrencyNewAddress (this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'timeframe': this.timeframes[timeframe], + 'symbol': market['id'], + }; + const response = await this.publicGetV2CandlesSymbolTimeframe (this.extend (request, params)); + // + // [ + // [1591515000000,0.02509,0.02509,0.02509,0.02509,0], + // [1591514700000,0.02503,0.02509,0.02503,0.02509,44.6405], + // [1591514400000,0.02503,0.02503,0.02503,0.02503,0], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hbtc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hbtc.js new file mode 100644 index 000000000..d7bde5c6c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hbtc.js @@ -0,0 +1,2008 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, OrderNotFound, ArgumentsRequired, BadSymbol, BadRequest, NullResponse, InvalidOrder, BadResponse, NotSupported, ExchangeNotAvailable, RequestTimeout, RateLimitExceeded, PermissionDenied, InsufficientFunds, InvalidAddress } = require ('./base/errors'); +const { TICK_SIZE, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class hbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'hbtc', + 'name': 'HBTC', + 'countries': [ 'CN' ], + 'rateLimit': 2000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchBidAsk': true, + 'fetchBidsAsks': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, + 'fetchDepositAddress': false, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingLimits': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/80134449-70663300-85a7-11ea-8942-e204cdeaab5d.jpg', // 交易所LOGO + 'api': { + 'quote': 'https://api.hbtc.com/openapi/quote', // 市场API数据端点 + 'contract': 'https://api.hbtc.com/openapi/contract', // 合约API数据端点 + 'option': 'https://api.hbtc.com/openapi/option', // 合约API数据端点 + 'public': 'https://api.hbtc.com/openapi', // 公共API数据端点 + 'private': 'https://api.hbtc.com/openapi', // 私有API数据端点 + 'zendesk': 'https://hbtc.zendesk.com/hc/en-us', + }, + 'www': 'https://www.hbtc.com', // 公司主页 + 'referral': 'https://www.hbtc.com/register/O2S8NS', // 邀请链接 + 'doc': 'https://github.com/bhexopen/BHEX-OpenApi/tree/master/doc', // openapi文档地址 + 'fees': 'https://hbtc.zendesk.com/hc/zh-cn/articles/360009274694', // 费率介绍 + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'time', + 'brokerInfo', // 查询当前broker交易规则和symbol信息 + 'getOptions', + ], + }, + 'quote': { + 'get': [ + 'depth', // 获取深度 + 'depth/merged', + 'trades', // 获取当前最新成交 + 'klines', // 获取K线数据 + 'ticker/24hr', // 获取24小时价格变化数据 + 'ticker/price', + 'ticker/bookTicker', + 'contract/index', // 获取合约标的指数价格 + 'contract/depth', // 获取合约深度 + 'contract/depth/merged', + 'contract/trades', // 获取合约最近成交, + 'contract/klines', // 获取合约的K线数据 + 'contract/ticker/24hr', + 'option/index', + 'option/depth', + 'option/depth/merged', + 'option/trades', + 'option/klines', + 'option/ticker/24hr', + ], + }, + 'contract': { + 'get': [ + // public + 'insurance', + 'fundingRate', // 获取资金费率信息 + // private + 'openOrders', // 查询合约当前委托 + 'historyOrders', // 查询合约历史委托 + 'getOrder', // 查询合约订单详情 + 'myTrades', // 查询合约历史成交 + 'positions', // 查询合约当前持仓 + 'account', // 查询合约账户信息 + ], + 'post': [ + 'order', // 创建合约订单 + 'modifyMargin', // 修改保证金 + ], + 'delete': [ + 'order/cancel', // 取消合约订单 + 'order/batchCancel', + ], + }, + 'option': { + 'get': [ + 'openOrders', + 'positions', + 'historyOrders', + // 'getOrder', + 'myTrades', + 'settlements', + 'account', + ], + 'post': [ + 'order', + ], + 'delete': [ + 'order/cancel', + ], + }, + 'private': { + 'get': [ + 'order', // 查询订单 + 'openOrders', // 查询当前委托 + 'historyOrders', // 查询历史委托 + 'account', // 获取当前账户信息 + 'myTrades', // 查询历史成交 + 'depositOrders', + 'withdrawalOrders', + 'withdraw/detail', + 'balance_flow', + ], + 'post': [ + 'order', // 创建新订单 + 'order/test', + 'userDataStream', + 'subAccount/query', + 'transfer', + 'user/transfer', + 'withdraw', + ], + 'put': [ + 'userDataStream', + ], + 'delete': [ + 'order', // 取消订单 + 'userDataStream', + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.001, + 'taker': 0.001, + }, + }, + 'exceptions': { + 'exact': { + // general server or network errors + '-1000': ExchangeError, // An unknown error occured while processing the request + '-1001': ExchangeError, // Internal error, unable to process your request. Please try again + '-1002': AuthenticationError, // You are not authorized to execute this request. Request need API Key included in. We suggest that API Key be included in any request + '-1003': RateLimitExceeded, // Too many requests, please use the websocket for live updates + '-1004': BadRequest, + '-1005': PermissionDenied, + '-1006': BadResponse, // An unexpected response was received from the message bus. Execution status unknown. OPEN API server find some exception in execute request.Please report to Customer service + '-1007': RequestTimeout, // Timeout waiting for response from backend server. Send status unknown, execution status unknown + '-1014': InvalidOrder, // Unsupported order combination + '-1015': RateLimitExceeded, // Reach the rate limit.Please slow down your request speed + '-1016': ExchangeNotAvailable, // This service is no longer available + '-1020': NotSupported, // This operation is not supported + '-1021': BadRequest, // Timestamp for this request is outside of the recvWindow + '-1022': AuthenticationError, // Signature for this request is not valid + // request issues + '-1100': BadRequest, // Illegal characters found in a parameter + '-1101': BadRequest, // Too many parameters sent for this endpoint + '-1102': BadRequest, // A mandatory parameter was not sent, was empty/null, or malformed + '-1103': BadRequest, // An unknown parameter was sent + '-1104': BadRequest, // Not all sent parameters were read + '-1105': BadRequest, // A parameter was empty + '-1106': BadRequest, // A parameter was sent when not required + '-1111': BadRequest, // Precision is over the maximum defined for this asset + '-1112': NullResponse, // No orders on book for symbol + '-1114': InvalidOrder, // TimeInForce parameter sent when not required + '-1115': InvalidOrder, // Invalid timeInForce + '-1116': InvalidOrder, // Invalid orderType + '-1117': InvalidOrder, // Invalid side + '-1118': InvalidOrder, // New client order ID was empty + '-1119': InvalidOrder, // Original client order ID was empty + '-1120': BadRequest, // Invalid interval + '-1121': BadSymbol, // Invalid symbol + '-1125': AuthenticationError, // This listenKey does not exist + '-1127': BadRequest, // Lookup interval is too big + '-1128': BadRequest, // Combination of optional parameters invalid + '-1130': BadRequest, // Invalid data sent for a parameter + '-1131': InsufficientFunds, + '-1132': InvalidOrder, // Order price too high + '-1133': InvalidOrder, // Order price lower than the minimum,please check general broker info + '-1134': InvalidOrder, // Order price decimal too long,please check general broker info + '-1135': InvalidOrder, // Order quantity too large + '-1136': InvalidOrder, // Order quantity lower than the minimum + '-1137': InvalidOrder, // Order quantity decimal too long + '-1138': InvalidOrder, // Order price exceeds permissible range + '-1139': InvalidOrder, // Order has been filled + '-1140': InvalidOrder, // Transaction amount lower than the minimum + '-1141': InvalidOrder, // Duplicate clientOrderId + '-1142': InvalidOrder, // Order has been canceled + '-1143': OrderNotFound, // Cannot be found on order book + '-1144': InvalidOrder, // Order has been locked + '-1145': InvalidOrder, // This order type does not support cancellation + '-1146': RequestTimeout, // Order creation timeout + '-1147': RequestTimeout, // Order cancellation timeout + '-1149': InvalidOrder, // Create order failed + '-1187': InvalidAddress, // Withdrawal address not in whitelist + '-2010': InvalidOrder, // NEW_ORDER_REJECTED + '-2011': InvalidOrder, // CANCEL_REJECTED + '-2013': OrderNotFound, // Order does not exist + '-2014': AuthenticationError, // API-key format invalid + '-2015': AuthenticationError, // Invalid API-key, IP, or permissions for action + '-2016': ExchangeError, // No trading window could be found for the symbol. Try ticker/24hrs instead + }, + }, + // exchange-specific options + 'options': { + 'fetchTickers': { + 'method': 'quoteGetTicker24hr', + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { + // "serverTime": 1527777538000 + // } + // + return this.safeInteger (response, 'serverTime'); + } + + parseMarket (market, type = 'spot') { + const filters = this.safeValue (market, 'filters', []); + const id = this.safeString (market, 'symbol'); + let baseId = this.safeString (market, 'baseAsset'); + const quoteId = this.safeString (market, 'quoteAsset'); + let base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + let spot = true; + let future = false; + let option = false; + let inverse = false; + if (type === 'future') { + symbol = id; + spot = false; + future = true; + inverse = this.safeValue (market, 'inverse', false); + baseId = this.safeString (market, 'underlying'); + base = this.safeCurrencyCode (baseId); + } else if (type === 'option') { + symbol = id; + spot = false; + option = true; + } + let amountMin = undefined; + let amountMax = undefined; + let priceMin = undefined; + let priceMax = undefined; + let costMin = undefined; + for (let j = 0; j < filters.length; j++) { + const filter = filters[j]; + const filterType = this.safeString (filter, 'filterType'); + if (filterType === 'LOT_SIZE') { + amountMin = this.safeFloat (filter, 'minQty'); + amountMax = this.safeFloat (filter, 'maxQty'); + } + if (filterType === 'PRICE_FILTER') { + priceMin = this.safeFloat (filter, 'minPrice'); + priceMax = this.safeFloat (filter, 'maxPrice'); + } + if (filterType === 'MIN_NOTIONAL') { + costMin = this.safeFloat (filter, 'minNotional'); + } + } + if ((costMin === undefined) && (amountMin !== undefined) && (priceMin !== undefined)) { + costMin = amountMin * priceMin; + } + const precision = { + 'price': this.safeFloat2 (market, 'quotePrecision', 'quoteAssetPrecision'), + 'amount': this.safeFloat (market, 'baseAssetPrecision'), + }; + const limits = { + 'amount': { + 'min': amountMin, + 'max': amountMax, + }, + 'price': { + 'min': priceMin, + 'max': priceMax, + }, + 'cost': { + 'min': costMin, + 'max': undefined, + }, + }; + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'type': type, + 'spot': spot, + 'future': future, + 'option': option, + 'inverse': inverse, + 'precision': precision, + 'limits': limits, + 'info': market, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetBrokerInfo (params); + // + // { + // "timezone":"UTC", + // "serverTime":"1588015885118", + // "brokerFilters":[], + // "symbols":[ + // { + // "filters":[ + // {"minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"}, + // {"minQty":"0.0005","maxQty":"100000.00000000","stepSize":"0.000001","filterType":"LOT_SIZE"}, + // {"minNotional":"5","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTCUSDT", + // "symbolName":"BTCUSDT", + // "status":"TRADING", + // "baseAsset":"BTC", + // "baseAssetPrecision":"0.000001", + // "quoteAsset":"USDT", + // "quotePrecision":"0.01", + // "icebergAllowed":false + // }, + // ], + // "options":[ + // { + // "filters":[ + // {"minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"}, + // {"minQty":"0.01","maxQty":"100000.00000000","stepSize":"0.001","filterType":"LOT_SIZE"}, + // {"minNotional":"1","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC0501CS8500", + // "symbolName":"BTC0501CS8500", + // "status":"TRADING", + // "baseAsset":"BTC0501CS8500", + // "baseAssetPrecision":"0.001", + // "quoteAsset":"BUSDT", + // "quotePrecision":"0.01", + // "icebergAllowed":false + // }, + // ], + // "contracts":[ + // { + // "filters":[ + // {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"}, + // {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"}, + // {"minNotional":"0.000001","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC-PERP-REV", + // "symbolName":"BTC-PERP-REV", + // "status":"TRADING", + // "baseAsset":"BTC-PERP-REV", + // "baseAssetPrecision":"1", + // "quoteAsset":"USDT", + // "quoteAssetPrecision":"0.1", + // "icebergAllowed":false, + // "inverse":true, + // "index":"BTCUSDT", + // "marginToken":"TBTC", + // "marginPrecision":"0.00000001", + // "contractMultiplier":"1.0", + // "underlying":"TBTC", + // "riskLimits":[ + // {"riskLimitId":"200000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"}, + // {"riskLimitId":"200000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"}, + // {"riskLimitId":"200000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"}, + // {"riskLimitId":"200000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"} + // ] + // }, + // { + // "filters":[ + // {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"}, + // {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"}, + // {"minNotional":"0.000001","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC-SWAP", + // "symbolName":"BTC-SWAP", + // "status":"TRADING", + // "baseAsset":"BTC-SWAP", + // "baseAssetPrecision":"1", + // "quoteAsset":"USDT", + // "quoteAssetPrecision":"0.1", + // "icebergAllowed":false, + // "inverse":true, + // "index":"BTCUSDT", + // "marginToken":"BTC", + // "marginPrecision":"0.00000001", + // "contractMultiplier":"1.0", + // "underlying":"BTC", + // "riskLimits":[ + // {"riskLimitId":"500000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"}, + // {"riskLimitId":"500000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"}, + // {"riskLimitId":"500000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"}, + // {"riskLimitId":"500000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"} + // ] + // }, + // { + // "filters":[ + // {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"}, + // {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"}, + // {"minNotional":"0.000000001","filterType":"MIN_NOTIONAL"} + // ], + // "exchangeId":"301", + // "symbol":"BTC-PERP-BUSDT", + // "symbolName":"BTC-PERP-BUSDT", + // "status":"TRADING", + // "baseAsset":"BTC-PERP-BUSDT", + // "baseAssetPrecision":"1", + // "quoteAsset":"BUSDT", + // "quoteAssetPrecision":"0.1", + // "icebergAllowed":false, + // "inverse":false, + // "index":"BTCUSDT", + // "marginToken":"BUSDT", + // "marginPrecision":"0.0001", + // "contractMultiplier":"0.0001", + // "underlying":"TBTC", + // "riskLimits":[ + // {"riskLimitId":"600000132","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"}, + // {"riskLimitId":"600000133","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"}, + // {"riskLimitId":"600000134","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"}, + // {"riskLimitId":"600000135","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"} + // ] + // }, + // ] + // } + // + const result = []; + const symbols = this.safeValue (response, 'symbols', []); + for (let i = 0; i < symbols.length; i++) { + const market = this.parseMarket (symbols[i], 'spot'); + result.push (market); + } + const options = this.safeValue (response, 'options', []); + for (let i = 0; i < options.length; i++) { + const market = this.parseMarket (options[i], 'option'); + result.push (market); + } + const contracts = this.safeValue (response, 'contracts', []); + for (let i = 0; i < contracts.length; i++) { + const market = this.parseMarket (contracts[i], 'future'); + result.push (market); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 40, max 40 + } + const response = await this.quoteGetDepth (this.extend (request, params)); + // + // { + // "time":1588068913453, + // "bids":[ + // ["0.025278","0.0202"], + // ["0.025277","16.1132"], + // ["0.025276","7.9056"], + // ] + // "asks":[ + // ["0.025302","5.9999"], + // ["0.025303","34.9151"], + // ["0.025304","92.391"], + // ] + // } + // + const timestamp = this.safeInteger (response, 'time'); + return this.parseOrderBook (response, timestamp); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.quoteGetTicker24hr (this.extend (request, params)); + // + // { + // "time":1588069860794, + // "symbol":"BNB0501PS16", + // "bestBidPrice":"0.2129", + // "bestAskPrice":"0.3163", + // "volume":"33547", + // "quoteVolume":"10801.987", + // "lastPrice":"0.2625", + // "highPrice":"0.3918", + // "lowPrice":"0.2625", + // "openPrice":"0.362", + // } + // + return this.parseTicker (response, market); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchBidAsk (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.quoteGetTickerBookTicker (this.extend (request, params)); + // + // { + // "symbol": "LTCBTC", + // "bidPrice": "4.00000000", + // "bidQty": "431.00000000", + // "askPrice": "4.00000200", + // "askQty": "9.00000000" + // } + // + return this.parseTicker (response, market); + } + + async fetchBidsAsks (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.quoteGetTickerBookTicker (params); + // + // [ + // { + // "symbol": "LTCBTC", + // "bidPrice": "4.00000000", + // "bidQty": "431.00000000", + // "askPrice": "4.00000200", + // "askQty": "9.00000000" + // }, + // { + // "symbol": "ETHBTC", + // "bidPrice": "0.07946700", + // "bidQty": "9.00000000", + // "askPrice": "100000.00000000", + // "askQty": "1000.00000000" + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchTickers', {}); + const defaultMethod = this.safeString (options, 'method', 'quoteGetTicker24hr'); + const defaultType = this.safeString (options, 'type', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = defaultMethod; + if (type === 'future') { + method = 'quoteGetContractTicker24hr'; + } else if (type === 'option') { + method = 'quoteGetOptionTicker24hr'; + } + const response = await this[method] (query); + // + // [ + // { + // "time": 1538725500422, + // "symbol": "ETHBTC", + // "lastPrice": "4.00000200", + // "openPrice": "99.00000000", + // "highPrice": "100.00000000", + // "lowPrice": "0.10000000", + // "volume": "8913.30000000" + // }, + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchBalance', {}); + const defaultType = this.safeString (options, 'type', 'spot'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + let method = 'privateGetAccount'; + if (type === 'future') { + method = 'contractGetAccount'; + } else if (type === 'option') { + method = 'optionGetAccount'; + } + const response = await this[method] (query); + // + // spot + // + // { + // 'balances': [ + // { + // 'asset': 'ALGO', + // 'free': '0', + // 'locked': '0' + // }, + // { + // 'asset': 'BHT', + // 'free': '0', + // 'locked': '0' + // } + // ] + // } + // + // contract + // + // { + // "BUSDT":{ + // "total":"1000", + // "availableMargin":"1000", + // "positionMargin":"0", + // "orderMargin":"0", + // "tokenId":"BUSDT" + // }, + // "TBTC":{ + // "total":"0.5", + // "availableMargin":"0.5", + // "positionMargin":"0", + // "orderMargin":"0", + // "tokenId":"TBTC" + // } + // } + // + // option + // + // { + // "optionAsset":"", + // "balances":[ + // { + // "tokenName":"USDT", + // "free":"0.0", + // "locked":"0.0", + // "margin":"0.0" + // }, + // { + // "tokenName":"BUSDT", + // "free":"0.0", + // "locked":"0.0", + // "margin":"0.0" + // } + // ] + // } + // + const balances = this.safeValue (response, 'balances'); + const result = { 'info': response }; + if (balances !== undefined) { + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString2 (balance, 'asset', 'tokenName'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'free'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + } else { + const currencyIds = Object.keys (response); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = response[currencyId]; + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableMargin'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchTrades (symbol, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.quoteGetTrades (this.extend (request, params)); + // + // [ + // {"price":"0.025344","time":1588084082060,"qty":"1","isBuyerMaker":false}, + // {"price":"0.02535","time":1588084086021,"qty":"0.553","isBuyerMaker":true}, + // {"price":"0.025348","time":1588084097037,"qty":"1","isBuyerMaker":false}, + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1587906000000, // open time + // "0.1761", // open + // "0.1761", // high + // "0.1761", // low + // "0.1761", // close + // "0", // base volume + // 0, // close time + // "0", // quote volume + // 0, // number of trades + // "0", // taker buy base asset volume + // "0" // taker buy quote asset volume + // ] + // + return [ + this.safeInteger (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 500 + } + const response = await this.quoteGetKlines (this.extend (request, params)); + // + // [ + // [1587906000000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"], + // [1587906180000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"], + // [1587906360000,"0.1761","0.1848","0.1761","0.1848","53",0,"9.7944",1,"0","0"], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // if only fromId is set,it will get orders < that fromId in descending order + // if only toId is set, it will get orders > that toId in ascending order + // if fromId is set and toId is set, it will get orders < that fromId and > that toId in descending order + // if fromId is not set and toId it not set, most recent order are returned in descending order + // 'fromId': '43287482374', + // 'toId': '43287482374', + // 'endTime': this.milliseconds (), // optional, spot only + }; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchMyTrades', {}); + const fetchMyTradesType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', fetchMyTradesType); + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + type = market['type']; + } + const query = this.omit (params, 'type'); + if (limit !== undefined) { + // spot default 500, max 1000 + // futures and options default 20, max 1000 + request['limit'] = limit; + } + let method = 'privateGetMyTrades'; + if (type === 'future') { + method = 'contractGetMyTrades'; + } else { + if (type === 'option') { + method = 'optionGetMyTrades'; + } else { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument for ' + type + ' markets'); + } + const market = this.market (symbol); + request['symbol'] = market['id']; + // spot only? + if (since !== undefined) { + request['startTime'] = since; + } + } + } + if (since !== undefined) { + request['startTime'] = since; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // "id":"616384027512920576", + // "symbol":"TBTCBUSDT", + // "orderId":"616384027202542080", + // "matchOrderId":"605124954767266560", + // "price":"6826.06", + // "qty":"0.1", + // "commission":"0.682606", + // "commissionAsset":"BUSDT", + // "time":"1588214701982", + // "isBuyer":false, + // "isMaker":false, + // "fee":{ + // "feeTokenId":"BUSDT", + // "feeTokenName":"BUSDT", + // "fee":"0.682606" + // } + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const orderSide = side.toUpperCase (); + const orderType = type.toUpperCase (); + const request = { + 'symbol': market['id'], + // BUY or SELL for spot and options + 'side': orderSide, + // GTC, FOK, IOC for spot and options + // GTC, FOK, IOC, LIMIT_MAKER for futures + // 'timeInForce': 'GTC', + }; + let query = params; + let method = 'privatePostOrder'; + if (market['type'] === 'future') { + if ((orderSide !== 'BUY_OPEN') && (orderSide !== 'SELL_OPEN') && (orderSide !== 'BUY_CLOSE') && (orderSide !== 'SELL_CLOSE')) { + throw new NotSupported (this.id + ' createOrder() does not support order side ' + side + ' for ' + market['type'] + ' markets, only BUY_OPEN, SELL_OPEN, BUY_CLOSE and SELL_CLOSE are supported'); + } + if ((orderType !== 'LIMIT') && (orderType !== 'STOP')) { + throw new NotSupported (this.id + ' createOrder() does not support order type ' + type + ' for ' + market['type'] + ' markets, only LIMIT and STOP are supported'); + } + const clientOrderId = this.safeValue (params, 'clientOrderId'); + if (clientOrderId === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder() requires a clientOrderId parameter for ' + market['type'] + ' markets, supply clientOrderId in the params argument'); + } + const leverage = this.safeValue (params, 'leverage'); + if (leverage === undefined && (orderSide === 'BUY_OPEN' || orderSide === 'SELL_OPEN')) { + throw new NotSupported (this.id + ' createOrder() requires a leverage parameter for ' + market['type'] + ' markets if orderSide is BUY_OPEN or SELL_OPEN'); + } + method = 'contractPostOrder'; + const priceType = this.safeString (params, 'priceType'); + if (priceType === undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } else { + request['priceType'] = priceType; + if (priceType === 'INPUT') { + request['price'] = this.priceToPrecision (symbol, price); + } + } + request['orderType'] = type.toUpperCase (); // LIMIT, STOP + request['quantity'] = this.amountToPrecision (symbol, amount); + // request['leverage'] = 1; // not required for closing orders + request['leverage'] = leverage; + request['clientOrderId'] = clientOrderId; + // optional + // request['priceType'] = 'INPUT', // INPUT, OPPONENT, QUEUE, OVER, MARKET + // request['triggerPrice'] = 123.45; + } else { + if (market['type'] === 'option') { + method = 'optionPostOrder'; + } + const newClientOrderId = this.safeValue2 (params, 'clientOrderId', 'newClientOrderId'); + if (newClientOrderId !== undefined) { + request['newClientOrderId'] = newClientOrderId; + } + request['type'] = orderType; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + request['quantity'] = this.amountToPrecision (symbol, amount); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + amount = amount * price; + } else { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument (the exchange-specific behaviour)"); + } + } + const precision = market['precision']['price']; + request['quantity'] = this.decimalToPrecision (amount, TRUNCATE, precision, this.precisionMode); + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + } + } + query = this.omit (query, [ 'clientOrderId', 'newClientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // "symbol":"TBTCBUSDT", + // "orderId":"616376654496877056", + // "clientOrderId":"158821382304516955", + // "transactTime":"1588213823080", + // "price":"0", + // "origQty":"1000", + // "executedQty":"0", + // "status":"NEW", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"BUY" + // } + // + // contract + // + // { + // 'time': '1570759718825', + // 'updateTime': '0', + // 'orderId': '469961015902208000', + // 'clientOrderId': '6423344174', + // 'symbol': 'BTC-PERP-REV', + // 'price': '8200', + // 'leverage': '12.08', + // 'origQty': '5', + // 'executedQty': '0', + // 'avgPrice': '0', + // 'marginLocked': '0.00005047', + // 'orderType': 'LIMIT', + // 'side': 'BUY_OPEN', + // 'fees': [], + // 'timeInForce': 'GTC', + // 'status': 'NEW', + // 'priceType': 'INPUT' + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + const request = {}; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'cancelOrder', {}); + const cancelOrderType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', cancelOrderType); + let query = this.omit (params, 'type'); + if (clientOrderId !== undefined) { + request['origClientOrderId'] = clientOrderId; + query = this.omit (query, [ 'origClientOrderId', 'clientOrderId' ]); + } else { + request['orderId'] = id; + } + let method = 'privateDeleteOrder'; + const orderType = this.safeString (query, 'orderType'); + if (orderType !== undefined) { + type = 'future'; + } + if (type === 'future') { + method = 'contractDeleteOrderCancel'; + if (orderType === undefined) { + throw new ArgumentsRequired (this.id + " cancelOrder() requires an orderType parameter, pass the { 'orderType': 'LIMIT' } or { 'orderType': 'STOP' } in params argument"); + } + request['orderType'] = orderType; + } else { + if (type === 'option') { + method = 'optionDeleteOrderCancel'; + } + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // { + // 'exchangeId': '301', + // 'symbol': 'BHTUSDT', + // 'clientOrderId': '0', + // 'orderId': '499890200602846976', + // 'status': 'CANCELED' + // } + // + // futures + // + // { + // "time":"1588353669383", + // "updateTime":"0", + // "orderId":"617549770304599296", + // "clientOrderId":"test-001", + // "symbol":"BTC-PERP-REV", + // "price":"10000", + // "leverage":"1", + // "origQty":"100", + // "executedQty":"0", + // "avgPrice":"0", + // "marginLocked":"0", + // "orderType":"LIMIT", + // "side":"SELL_OPEN", + // "fees":[], + // "timeInForce":"GTC", + // "status":"CANCELED", + // "priceType":"INPUT", + // } + // + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // if orderId is set, it will get orders < that orderId otherwise most recent orders are returned + // 'orderId': '43287482374', + }; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + const fetchOpenOrdersType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', fetchOpenOrdersType); + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + type = market['type']; + } + const query = this.omit (params, 'type'); + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + let method = 'privateGetOpenOrders'; + if (type === 'future') { + method = 'contractGetOpenOrders'; + } else if (type === 'option') { + method = 'optionGetOpenOrders'; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // 'orderId': '499902955766523648', + // 'clientOrderId': '157432907618453', + // 'exchangeId': '301', + // 'symbol': 'BHTUSDT', + // 'price': '0.01', + // 'origQty': '50', + // 'executedQty': '0', + // 'cummulativeQuoteQty': '0', + // 'avgPrice': '0', + // 'status': 'NEW', + // 'timeInForce': 'GTC', + // 'type': 'LIMIT', + // 'side': 'BUY', + // 'stopPrice': '0.0', + // 'icebergQty': '0.0', + // 'time': '1574329076202', + // 'updateTime': '0', + // 'isWorking': true + // } + // ] + // + // futures + // + // [ + // { + // "time":"1588353669383", + // "updateTime":"0", + // "orderId":"617549770304599296", + // "clientOrderId":"test-001", + // "symbol":"BTC-PERP-REV", + // "price":"10000", + // "leverage":"1", + // "origQty":"100", + // "executedQty":"0", + // "avgPrice":"0", + // "marginLocked":"0.01", + // "orderType":"LIMIT", + // "side":"SELL_OPEN", + // "fees":[], + // "timeInForce":"GTC", + // "status":"NEW", + // "priceType":"INPUT" + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // if orderId is set, it will get orders < that orderId otherwise most recent orders are returned + // 'orderId': '43287482374', + // 'endTime': this.milliseconds (), // optional + }; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + const fetchClosedOrdersType = this.safeString (options, 'type', defaultType); + let type = this.safeString (params, 'type', fetchClosedOrdersType); + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + type = market['type']; + } + const query = this.omit (params, 'type'); + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + if (since !== undefined) { + request['startTime'] = since; + } + let method = 'privateGetHistoryOrders'; + if (type === 'future') { + method = 'contractGetHistoryOrders'; + } else if (type === 'option') { + method = 'optionGetHistoryOrders'; + } + const response = await this[method] (this.extend (request, query)); + // + // spot + // + // [ + // { + // "orderId":"616384027202542080", + // "clientOrderId":"158821470194414688", + // "exchangeId":"301", + // "symbol":"TBTCBUSDT", + // "price":"0", + // "origQty":"0.1", + // "executedQty":"0.1", + // "cummulativeQuoteQty":"682.606", + // "avgPrice":"6826.06", + // "status":"FILLED", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"SELL", + // "stopPrice":"0.0", + // "icebergQty":"0.0", + // "time":"1588214701974", + // "updateTime":"0", + // "isWorking":true + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeValue2 (params, 'origClientOrderId', 'clientOrderId'); + const request = {}; + const defaultType = this.safeString (this.options, 'type', 'spot'); + const options = this.safeValue (this.options, 'fetchOrder', {}); + const fetchOrderType = this.safeString (options, 'type', defaultType); + const type = this.safeString (params, 'type', fetchOrderType); + let query = this.omit (params, 'type'); + if (clientOrderId !== undefined) { + request['origClientOrderId'] = clientOrderId; + query = this.omit (query, [ 'origClientOrderId', 'clientOrderId' ]); + } else { + request['orderId'] = id; + } + let method = 'privateGetOrder'; + if (type === 'future') { + method = 'contractGetGetOrder'; + } else if (type === 'option') { + method = 'optionGetGetOrder'; + } + const response = await this[method] (this.extend (request, query)); + return this.parseOrder (response); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + // 'fromId': 'string', // if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned + }; + if (code !== undefined) { + currency = this.currency (code); + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetDepositOrders (this.extend (request, params)); + // + // [ + // { + // 'time': '1565769575929', + // 'orderId': '428100569859739648', + // 'token': 'USDT', + // 'address': '', + // 'addressTag': '', + // 'fromAddress': '', + // 'fromAddressTag': '', + // 'quantity': '1100', + // }, + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = { + // 'fromId': 'string', // if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned + }; + if (code !== undefined) { + currency = this.currency (code); + request['token'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 1000 + } + const response = await this.privateGetWithdrawalOrders (this.extend (request, params)); + // + // [ + // { + // "time":"1536232111669", + // "orderId":"90161227158286336", + // "accountId":"517256161325920", + // "tokenId":"BHC", + // "tokenName":"BHC", + // "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209", + // "addressExt":"address tag", + // "quantity":"14", // Withdrawal qty + // "arriveQuantity":"14", // Arrived qty + // "statusCode":"PROCESSING_STATUS", + // "status":3, + // "txid":"", + // "txidUrl":"", + // "walletHandleTime":"1536232111669", + // "feeTokenId":"BHC", + // "feeTokenName":"BHC", + // "fee":"0.1", + // "requiredConfirmNum":0, // Required confirmations + // "confirmNum":0, // Confirmations + // "kernelId":"", // BEAM and GRIN only + // "isInternalTransfer": false // True if this transfer is internal + // } + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const clientOrderId = this.safeString (params, 'clientOrderId', this.uuid ()); + const request = { + 'clientOrderId': clientOrderId, + 'tokenId': currency['id'], + 'address': address, // the withdrawal address must be in current tag list in your PC/APP client + 'withdrawQuantity': amount, + // 'chainType': 'OMNI', // OMNI, ERC20, TRC20 + }; + if (tag !== undefined) { + request['addressExt'] = tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + // + // { + // "status": 0, + // "success": true, + // "needBrokerAudit": false, // Whether this request needs broker auit + // "orderId": "423885103582776064" // Id for successful withdrawal + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'orderId'), + }; + } + + async fetchAccounts (params = {}) { + const response = await this.privatePostSubAccountQuery (params); + // + // [ + // { + // "accountId": "122216245228131", + // "accountName": "createSubAccountByCurl", // sub-account name + // "accountType": 1, // 1 token trading, 2 options, 3 futures + // "accountIndex": 1, // 0 main account, 1 sub-account + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const account = response[i]; + const accountId = this.safeString (account, 'accountId'); + const accountType = this.safeString (account, 'accountType'); + let type = accountType; + if (accountType === '1') { + type = 'spot'; + } else if (accountType === '2') { + type = 'option'; + } else if (accountType === '3') { + type = 'future'; + } + result.push ({ + 'id': accountId, + 'type': type, + 'currency': undefined, + 'info': account, + }); + } + return result; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'accountType': 1, // spot 1, options 2, futures 3 + 'accountIndex': 0, // main 0, sub-account 1 + 'fromFlowId': '', // flowId to start from + 'endFlowId': '', // flowId to end with + 'endTime': 1588450533040, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['tokenId'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; // default 500, max 500 + } + const response = await this.privateGetBalanceFlow (this.extend (request, params)); + // + // [ + // { + // "id": "539870570957903104", + // "accountId": "122216245228131", + // "tokenId": "BTC", + // "tokenName": "BTC", + // "flowTypeValue": 51, + // "flowType": "USER_ACCOUNT_TRANSFER", + // "flowName": "Transfer", + // "change": "-12.5", + // "total": "379.624059937852365", // after change + // "created": "1579093587214" + // }, + // { + // "id": "536072393645448960", + // "accountId": "122216245228131", + // "tokenId": "USDT", + // "tokenName": "USDT", + // "flowTypeValue": 7, + // "flowType": "AIRDROP", + // "flowName": "Airdrop", + // "change": "-2000", + // "total": "918662.0917630848", + // "created": "1578640809195" + // } + // ] + // + return this.parseLedger (response, currency, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "id": "539870570957903104", + // "accountId": "122216245228131", + // "tokenId": "BTC", + // "tokenName": "BTC", + // "flowTypeValue": 51, + // "flowType": "USER_ACCOUNT_TRANSFER", + // "flowName": "Transfer", + // "change": "-12.5", + // "total": "379.624059937852365", // after change + // "created": "1579093587214" + // } + // + // { + // "id": "536072393645448960", + // "accountId": "122216245228131", + // "tokenId": "USDT", + // "tokenName": "USDT", + // "flowTypeValue": 7, + // "flowType": "AIRDROP", + // "flowName": "Airdrop", + // "change": "-2000", + // "total": "918662.0917630848", + // "created": "1578640809195" + // } + // + const currencyId = this.safeString (item, 'tokenId'); + const code = this.safeCurrencyCode (currencyId, currency); + const amount = this.safeFloat (item, 'change'); + const after = this.safeFloat (item, 'total'); + const direction = (amount < 0) ? 'out' : 'in'; + let before = undefined; + if (after !== undefined && amount !== undefined) { + const difference = (direction === 'out') ? amount : -amount; + before = this.sum (after, difference); + } + const timestamp = this.safeInteger (item, 'created'); + const type = this.parseLedgerEntryType (this.safeString (item, 'flowType')); + const id = this.safeString (item, 'id'); + const account = this.safeString (item, 'accountId'); + return { + 'id': id, + 'currency': code, + 'account': account, + 'referenceAccount': undefined, + 'referenceId': undefined, + 'status': undefined, + 'amount': amount, + 'before': before, + 'after': after, + 'fee': undefined, + 'direction': direction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'type': type, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'TRADE': 'trade', + 'FEE': 'fee', + 'TRANSFER': 'transfer', + 'DEPOSIT': 'transaction', + 'MAKER_REWARD': 'rebate', + 'PNL': 'pnl', + 'SETTLEMENT': 'settlement', + 'LIQUIDATION': 'liquidation', + 'FUNDING_SETTLEMENT': 'settlement', + 'USER_ACCOUNT_TRANSFER': 'transfer', + 'OTC_BUY_COIN': 'trade', + 'OTC_SELL_COIN': 'trade', + 'OTC_FEE': 'fee', + 'OTC_TRADE': 'trade', + 'ACTIVITY_AWARD': 'referral', + 'INVITATION_REFERRAL_BONUS': 'referral', + 'REGISTER_BONUS': 'referral', + 'AIRDROP': 'airdrop', + 'MINE_REWARD': 'reward', + }; + return this.safeString (types, type, type); + } + + parseTransactionStatus (status) { + const statuses = { + 'BROKER_AUDITING_STATUS': 'pending', + 'BROKER_REJECT_STATUS': 'failed', + 'AUDITING_STATUS': 'pending', + 'AUDIT_REJECT_STATUS': 'failed', + 'PROCESSING_STATUS': 'pending', + 'WITHDRAWAL_SUCCESS_STATUS': 'ok', + 'WITHDRAWAL_FAILURE_STATUS': 'failed', + 'BLOCK_MINING_STATUS': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // 'time': '1565769575929', + // 'orderId': '428100569859739648', + // 'token': 'USDT', + // 'address': '', + // 'addressTag': '', + // 'fromAddress': '', + // 'fromAddressTag': '', + // 'quantity': '1100', + // } + // + // fetchWithdrawals + // + // { + // "time":"1536232111669", + // "orderId":"90161227158286336", + // "accountId":"517256161325920", + // "tokenId":"BHC", + // "tokenName":"BHC", + // "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209", + // "addressExt":"address tag", + // "quantity":"14", // Withdrawal qty + // "arriveQuantity":"14", // Arrived qty + // "statusCode":"PROCESSING_STATUS", + // "status":3, + // "txid":"", + // "txidUrl":"", + // "walletHandleTime":"1536232111669", + // "feeTokenId":"BHC", + // "feeTokenName":"BHC", + // "fee":"0.1", + // "requiredConfirmNum":0, // Required confirmations + // "confirmNum":0, // Confirmations + // "kernelId":"", // BEAM and GRIN only + // "isInternalTransfer": false // True if this transfer is internal + // } + // + const id = this.safeString (transaction, 'orderId'); + const address = this.safeString (transaction, 'address'); + let tag = this.safeString2 (transaction, 'addressExt', 'addressTag'); + if (tag !== undefined) { + if (tag.length < 1) { + tag = undefined; + } + } + const addressFrom = this.safeString (transaction, 'fromAddress'); + let tagFrom = this.safeString (transaction, 'fromAddressTag'); + if (tagFrom !== undefined) { + if (tagFrom.length < 1) { + tagFrom = undefined; + } + } + const currencyId = this.safeString (transaction, 'tokenId'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeInteger (transaction, 'time'); + let txid = this.safeString (transaction, 'txid'); + if (txid === '') { + txid = undefined; + } + let type = undefined; + let status = this.parseTransactionStatus (this.safeString (transaction, 'statusCode')); + if (status === undefined) { + type = 'deposit'; + status = 'ok'; + } else { + type = 'withdrawal'; + } + const amount = this.safeFloat (transaction, 'quantity'); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (transaction, 'feeTokenId'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'currency': feeCurrencyCode, + 'cost': feeCost, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'address': address, + 'addressTo': address, + 'tagFrom': tagFrom, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "time":1588069860794, + // "symbol":"BNB0501PS16", + // "bestBidPrice":"0.2129", + // "bestAskPrice":"0.3163", + // "volume":"33547", + // "quoteVolume":"10801.987", + // "lastPrice":"0.2625", + // "highPrice":"0.3918", + // "lowPrice":"0.2625", + // "openPrice":"0.362", + // } + // + // fetchBidAsk, fetchBidAsks + // + // { + // "symbol": "LTCBTC", + // "bidPrice": "4.00000000", + // "bidQty": "431.00000000", + // "askPrice": "4.00000200", + // "askQty": "9.00000000" + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger (ticker, 'time'); + const open = this.safeFloat (ticker, 'openPrice'); + const close = this.safeFloat (ticker, 'lastPrice'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((open !== undefined) && (close !== undefined)) { + change = close - open; + average = this.sum (open, close) / 2; + if ((close !== undefined) && (close > 0)) { + percentage = (change / open) * 100; + } + } + const quoteVolume = this.safeFloat (ticker, 'quoteVolume'); + const baseVolume = this.safeFloat (ticker, 'volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPrice'), + 'low': this.safeFloat (ticker, 'lowPrice'), + 'bid': this.safeFloat2 (ticker, 'bestBidPrice', 'bidPrice'), + 'bidVolume': this.safeFloat (ticker, 'bidQty'), + 'ask': this.safeFloat2 (ticker, 'bestAskPrice', 'askPrice'), + 'askVolume': this.safeFloat (ticker, 'askQty'), + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market) { + // + // fetchTrades (public) + // + // { + // "price":"0.025344", + // "time":1588084082060, + // "qty":"1", + // "isBuyerMaker":false + // } + // + // fetchMyTrades (private) + // + // spot + // + // { + // "id":"616384027512920576", + // "symbol":"TBTCBUSDT", + // "orderId":"616384027202542080", + // "matchOrderId":"605124954767266560", + // "price":"6826.06", + // "qty":"0.1", + // "commission":"0.682606", + // "commissionAsset":"BUSDT", + // "time":"1588214701982", + // "isBuyer":false, + // "isMaker":false, + // "fee":{ + // "feeTokenId":"BUSDT", + // "feeTokenName":"BUSDT", + // "fee":"0.682606" + // } + // } + // + const id = this.safeString (trade, 'id'); + const timestamp = this.safeFloat (trade, 'time'); + const type = undefined; + const orderId = this.safeString (trade, 'orderId'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'qty'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let side = undefined; + let takerOrMaker = undefined; + if ('isBuyerMaker' in trade) { + side = trade['isBuyerMaker'] ? 'sell' : 'buy'; + } else { + const isMaker = this.safeValue (trade, 'isMaker'); + if (isMaker !== undefined) { + takerOrMaker = isMaker ? 'maker' : 'taker'; + } + const isBuyer = this.safeValue (trade, 'isBuyer'); + side = isBuyer ? 'buy' : 'sell'; + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionAsset'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'order': orderId, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "symbol":"TBTCBUSDT", + // "orderId":"616376654496877056", + // "clientOrderId":"158821382304516955", + // "transactTime":"1588213823080", + // "price":"0", + // "origQty":"1000", + // "executedQty":"0", + // "status":"NEW", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"BUY" + // } + // + // fetchOrder, fetchOpenOrders, fetchClosedOrders + // + // spot + // + // { + // "orderId":"616384027202542080", + // "clientOrderId":"158821470194414688", + // "exchangeId":"301", + // "symbol":"TBTCBUSDT", + // "price":"0", + // "origQty":"0.1", + // "executedQty":"0.1", + // "cummulativeQuoteQty":"682.606", + // "avgPrice":"6826.06", + // "status":"FILLED", + // "timeInForce":"GTC", + // "type":"MARKET", + // "side":"SELL", + // "stopPrice":"0.0", + // "icebergQty":"0.0", + // "time":"1588214701974", + // "updateTime":"0", + // "isWorking":true + // } + // + // future + // + // { + // time: "1588353669383", + // updateTime: "0", + // orderId: "617549770304599296", + // clientOrderId: "test-001", + // symbol: "BTC-PERP-REV", + // price: "10000", + // leverage: "1", + // origQty: "100", + // executedQty: "0", + // avgPrice: "0", + // marginLocked: "0", + // orderType: "LIMIT", + // side: "SELL_OPEN", + // fees: [], + // timeInForce: "GTC", + // status: "CANCELED", + // priceType: "INPUT" + // } + // + // + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + let timestamp = this.safeInteger (order, 'time'); + if (timestamp === undefined) { + timestamp = this.safeInteger (order, 'transactTime'); + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + let price = this.safeFloat (order, 'price'); + let average = this.safeFloat (order, 'avgPrice'); + let amount = undefined; + let cost = this.safeFloat (order, 'cummulativeQuoteQty'); + let filled = undefined; + let remaining = undefined; + if (type === undefined) { + type = this.safeStringLower (order, 'orderType'); + if ((market !== undefined) && market['inverse']) { + cost = this.safeFloat (order, 'executedQty'); + amount = undefined; + } + if (cost === 0.0) { + filled = 0; + } + } else { + amount = this.safeFloat (order, 'origQty'); + if (type === 'market') { + price = undefined; + if (side === 'buy') { + amount = undefined; + } + } + filled = this.safeFloat (order, 'executedQty'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + } + } + if (average === 0.0) { + average = undefined; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'trades': undefined, + 'fee': undefined, + 'fees': undefined, + }; + const fees = this.safeValue (order, 'fees', []); + const numFees = fees.length; + if (numFees > 0) { + result['fees'] = []; + for (let i = 0; i < fees.length; i++) { + const feeCost = this.safeFloat (fees[i], 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (fees[i], 'feeToken'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + result['fees'].push ({ + 'cost': feeCost, + 'currency': feeCurrencyCode, + }); + } + } + } + return result; + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'CANCELED': 'canceled', + 'FILLED': 'closed', + 'PENDING_CANCEL': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + const isPublicContract = (api === 'contract') && ((path === 'insurance') || (path === 'fundingRate')); + if ((api === 'public') || (api === 'quote') || isPublicContract) { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + const timestamp = this.milliseconds (); + this.checkRequiredCredentials (); + const request = this.extend ({ + 'timestamp': timestamp, + }, query); + // 准备待签名数据 + const auth = this.urlencode (request); + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + request['signature'] = signature; + headers = { + 'X-BH-APIKEY': this.apiKey, + }; + if (method === 'POST') { + body = this.urlencode (request); + headers = this.extend ({ + 'Content-Type': 'application/x-www-form-urlencoded', + }, headers); + } else { + url += '?' + this.urlencode (request); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('code' in response) { + const code = this.safeString (response, 'code'); + if (code !== '0') { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hitbtc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hitbtc.js new file mode 100644 index 000000000..0ec19ca80 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hitbtc.js @@ -0,0 +1,1159 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, PermissionDenied, ExchangeError, ExchangeNotAvailable, OrderNotFound, InsufficientFunds, InvalidOrder, RequestTimeout, AuthenticationError } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class hitbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'hitbtc', + 'name': 'HitBTC', + 'countries': [ 'HK' ], + 'rateLimit': 1500, + 'version': '2', + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrder': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTransactions': true, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'M1', + '3m': 'M3', + '5m': 'M5', + '15m': 'M15', + '30m': 'M30', // default + '1h': 'H1', + '4h': 'H4', + '1d': 'D1', + '1w': 'D7', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766555-8eaec20e-5edc-11e7-9c5b-6dc69fc42f5e.jpg', + 'api': { + 'public': 'https://api.hitbtc.com', + 'private': 'https://api.hitbtc.com', + }, + 'www': 'https://hitbtc.com', + 'referral': 'https://hitbtc.com/?ref_id=5a5d39a65d466', + 'doc': [ + 'https://api.hitbtc.com', + 'https://github.com/hitbtc-com/hitbtc-api/blob/master/APIv2.md', + ], + 'fees': [ + 'https://hitbtc.com/fees-and-limits', + 'https://support.hitbtc.com/hc/en-us/articles/115005148605-Fees-and-limits', + ], + }, + 'api': { + 'public': { + 'get': [ + 'symbol', // Available Currency Symbols + 'symbol/{symbol}', // Get symbol info + 'currency', // Available Currencies + 'currency/{currency}', // Get currency info + 'ticker', // Ticker list for all symbols + 'ticker/{symbol}', // Ticker for symbol + 'trades', + 'trades/{symbol}', // Trades + 'orderbook', + 'orderbook/{symbol}', // Orderbook + 'candles', + 'candles/{symbol}', // Candles + ], + }, + 'private': { + 'get': [ + 'trading/balance', // Get trading balance + 'order', // List your current open orders + 'order/{clientOrderId}', // Get a single order by clientOrderId + 'trading/fee/all', // Get trading fee rate + 'trading/fee/{symbol}', // Get trading fee rate + 'history/order', // Get historical orders + 'history/trades', // Get historical trades + 'history/order/{orderId}/trades', // Get historical trades by specified order + 'account/balance', // Get main acccount balance + 'account/crypto/address/{currency}', // Get deposit crypro address + 'account/crypto/is-mine/{address}', + 'account/transactions', // Get account transactions + 'account/transactions/{id}', // Get account transaction by id + 'sub-acc', + 'sub-acc/acl', + 'sub-acc/balance/{subAccountUserID}', + 'sub-acc/deposit-address/{subAccountUserId}/{currency}', + ], + 'post': [ + 'order', // Create new order + 'account/crypto/address/{currency}', // Create new deposit crypro address + 'account/crypto/withdraw', // Withdraw crypro + 'account/crypto/transfer-convert', + 'account/transfer', // Transfer amount to trading + 'sub-acc/freeze', + 'sub-acc/activate', + 'sub-acc/transfer', + ], + 'put': [ + 'order/{clientOrderId}', // Create new order + 'account/crypto/withdraw/{id}', // Commit withdraw crypro + 'sub-acc/acl/{subAccountUserId}', + ], + 'delete': [ + 'order', // Cancel all open orders + 'order/{clientOrderId}', // Cancel order + 'account/crypto/withdraw/{id}', // Rollback withdraw crypro + ], + // outdated? + 'patch': [ + 'order/{clientOrderId}', // Cancel Replace order + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + }, + 'options': { + 'defaultTimeInForce': 'FOK', + }, + 'commonCurrencies': { + 'BCC': 'BCC', // initial symbol for Bitcoin Cash, now inactive + 'BET': 'DAO.Casino', + 'BOX': 'BOX Token', + 'CPT': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081 + 'GET': 'Themis', + 'HSR': 'HC', + 'IQ': 'IQ.Cash', + 'LNC': 'LinkerCoin', + 'PLA': 'PlayChip', + 'PNT': 'Penta', + 'TV': 'Tokenville', + 'USD': 'USDT', + 'XPNT': 'PNT', + }, + 'exceptions': { + '504': RequestTimeout, // {"error":{"code":504,"message":"Gateway Timeout"}} + '1002': AuthenticationError, // {"error":{"code":1002,"message":"Authorization failed","description":""}} + '1003': PermissionDenied, // "Action is forbidden for this API key" + '2010': InvalidOrder, // "Quantity not a valid number" + '2001': BadSymbol, // "Symbol not found" + '2011': InvalidOrder, // "Quantity too low" + '2020': InvalidOrder, // "Price not a valid number" + '20002': OrderNotFound, // canceling non-existent order + '20001': InsufficientFunds, // {"error":{"code":20001,"message":"Insufficient funds","description":"Check that the funds are sufficient, given commissions"}} + }, + 'orders': {}, // orders cache / emulation + }); + } + + feeToPrecision (symbol, fee) { + return this.decimalToPrecision (fee, TRUNCATE, 8, DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetSymbol (params); + // + // [ + // { + // "id":"BCNBTC", + // "baseCurrency":"BCN", + // "quoteCurrency":"BTC", + // "quantityIncrement":"100", + // "tickSize":"0.00000000001", + // "takeLiquidityRate":"0.002", + // "provideLiquidityRate":"0.001", + // "feeCurrency":"BTC" + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + // bequant fix + if (id.indexOf ('_') >= 0) { + symbol = id; + } + const lot = this.safeFloat (market, 'quantityIncrement'); + const step = this.safeFloat (market, 'tickSize'); + const precision = { + 'price': step, + 'amount': lot, + }; + const taker = this.safeFloat (market, 'takeLiquidityRate'); + const maker = this.safeFloat (market, 'provideLiquidityRate'); + const feeCurrencyId = this.safeString (market, 'feeCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + result.push (this.extend (this.fees['trading'], { + 'info': market, + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'taker': taker, + 'maker': maker, + 'precision': precision, + 'feeCurrency': feeCurrencyCode, + 'limits': { + 'amount': { + 'min': lot, + 'max': undefined, + }, + 'price': { + 'min': step, + 'max': undefined, + }, + 'cost': { + 'min': lot * step, + 'max': undefined, + }, + }, + })); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrency (params); + // + // [ + // { + // "id":"XPNT", + // "fullName":"pToken", + // "crypto":true, + // "payinEnabled":true, + // "payinPaymentId":false, + // "payinConfirmations":9, + // "payoutEnabled":true, + // "payoutIsPaymentId":false, + // "transferEnabled":true, + // "delisted":false, + // "payoutFee":"26.510000000000", + // "precisionPayout":18, + // "precisionTransfer":8 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'id'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const decimals = this.safeInteger (currency, 'precisionTransfer', 8); + const precision = 1 / Math.pow (10, decimals); + const code = this.safeCurrencyCode (id); + const payin = this.safeValue (currency, 'payinEnabled'); + const payout = this.safeValue (currency, 'payoutEnabled'); + const transfer = this.safeValue (currency, 'transferEnabled'); + let active = payin && payout && transfer; + if ('disabled' in currency) { + if (currency['disabled']) { + active = false; + } + } + let type = 'fiat'; + if (('crypto' in currency) && currency['crypto']) { + type = 'crypto'; + } + const name = this.safeString (currency, 'fullName'); + result[code] = { + 'id': id, + 'code': code, + 'type': type, + 'payin': payin, + 'payout': payout, + 'transfer': transfer, + 'info': currency, + 'name': name, + 'active': active, + 'fee': this.safeFloat (currency, 'payoutFee'), // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { + 'min': 1 / Math.pow (10, decimals), + 'max': Math.pow (10, decimals), + }, + 'price': { + 'min': 1 / Math.pow (10, decimals), + 'max': Math.pow (10, decimals), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = this.extend ({ + 'symbol': market['id'], + }, this.omit (params, 'symbol')); + const response = await this.privateGetTradingFeeSymbol (request); + // + // { + // takeLiquidityRate: '0.001', + // provideLiquidityRate: '-0.0001' + // } + // + return { + 'info': response, + 'maker': this.safeFloat (response, 'provideLiquidityRate'), + 'taker': this.safeFloat (response, 'takeLiquidityRate'), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const type = this.safeString (params, 'type', 'trading'); + const method = 'privateGet' + this.capitalize (type) + 'Balance'; + const query = this.omit (params, 'type'); + const response = await this[method] (query); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "timestamp":"2015-08-20T19:01:00.000Z", + // "open":"0.006", + // "close":"0.006", + // "min":"0.006", + // "max":"0.006", + // "volume":"0.003", + // "volumeQuote":"0.000018" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'max'), + this.safeFloat (ohlcv, 'min'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetCandlesSymbol (this.extend (request, params)); + // + // [ + // {"timestamp":"2015-08-20T19:01:00.000Z","open":"0.006","close":"0.006","min":"0.006","max":"0.006","volume":"0.003","volumeQuote":"0.000018"}, + // {"timestamp":"2015-08-20T19:03:00.000Z","open":"0.006","close":"0.006","min":"0.006","max":"0.006","volume":"0.013","volumeQuote":"0.000078"}, + // {"timestamp":"2015-08-20T19:06:00.000Z","open":"0.0055","close":"0.005","min":"0.005","max":"0.0055","volume":"0.003","volumeQuote":"0.0000155"}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 100, 0 = unlimited + } + const response = await this.publicGetOrderbookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bid', 'ask', 'price', 'size'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.parse8601 (ticker['timestamp']); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const baseVolume = this.safeFloat (ticker, 'volume'); + const quoteVolume = this.safeFloat (ticker, 'volumeQuote'); + const open = this.safeFloat (ticker, 'open'); + const last = this.safeFloat (ticker, 'last'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = response[i]; + const marketId = this.safeString (ticker, 'symbol'); + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTickerSymbol (this.extend (request, params)); + if ('message' in response) { + throw new ExchangeError (this.id + ' ' + response['message']); + } + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // createMarketOrder + // + // { fee: "0.0004644", + // id: 386394956, + // price: "0.4644", + // quantity: "1", + // timestamp: "2018-10-25T16:41:44.780Z" } + // + // fetchTrades + // + // { id: 974786185, + // price: '0.032462', + // quantity: '0.3673', + // side: 'buy', + // timestamp: '2020-10-16T12:57:39.846Z' } + // + // fetchMyTrades + // + // { id: 277210397, + // clientOrderId: '6e102f3e7f3f4e04aeeb1cdc95592f1a', + // orderId: 28102855393, + // symbol: 'ETHBTC', + // side: 'sell', + // quantity: '0.002', + // price: '0.073365', + // fee: '0.000000147', + // timestamp: '2018-04-28T18:39:55.345Z' } + const timestamp = this.parse8601 (trade['timestamp']); + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = market ? market['feeCurrency'] : undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const orderId = this.safeString (trade, 'clientOrderId'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const cost = price * amount; + const side = this.safeString (trade, 'side'); + const id = this.safeString (trade, 'id'); + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['startTime'] = since; + } + const response = await this.privateGetAccountTransactions (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // id: 'd53ee9df-89bf-4d09-886e-849f8be64647', + // index: 1044718371, + // type: 'payout', + // status: 'success', + // currency: 'ETH', + // amount: '4.522683200000000000000000', + // createdAt: '2018-06-07T00:43:32.426Z', + // updatedAt: '2018-06-07T00:45:36.447Z', + // hash: '0x973e5683dfdf80a1fb1e0b96e19085b6489221d2ddf864daa46903c5ec283a0f', + // address: '0xC5a59b21948C1d230c8C54f05590000Eb3e1252c', + // fee: '0.00958', + // }, + // { + // id: 'e6c63331-467e-4922-9edc-019e75d20ba3', + // index: 1044714672, + // type: 'exchangeToBank', + // status: 'success', + // currency: 'ETH', + // amount: '4.532263200000000000', + // createdAt: '2018-06-07T00:42:39.543Z', + // updatedAt: '2018-06-07T00:42:39.683Z', + // }, + // { + // id: '3b052faa-bf97-4636-a95c-3b5260015a10', + // index: 1009280164, + // type: 'bankToExchange', + // status: 'success', + // currency: 'CAS', + // amount: '104797.875800000000000000', + // createdAt: '2018-05-19T02:34:36.750Z', + // updatedAt: '2018-05-19T02:34:36.857Z', + // }, + // { + // id: 'd525249f-7498-4c81-ba7b-b6ae2037dc08', + // index: 1009279948, + // type: 'payin', + // status: 'success', + // currency: 'CAS', + // amount: '104797.875800000000000000', + // createdAt: '2018-05-19T02:30:16.698Z', + // updatedAt: '2018-05-19T02:34:28.159Z', + // hash: '0xa6530e1231de409cf1f282196ed66533b103eac1df2aa4a7739d56b02c5f0388', + // address: '0xd53ed559a6d963af7cb3f3fcd0e7ca499054db8b', + // } + // + // { + // "id": "4f351f4f-a8ee-4984-a468-189ed590ddbd", + // "index": 3112719565, + // "type": "withdraw", + // "status": "success", + // "currency": "BCHOLD", + // "amount": "0.02423133", + // "createdAt": "2019-07-16T16:52:04.494Z", + // "updatedAt": "2019-07-16T16:54:07.753Z" + // } + const id = this.safeString (transaction, 'id'); + const timestamp = this.parse8601 (this.safeString (transaction, 'createdAt')); + const updated = this.parse8601 (this.safeString (transaction, 'updatedAt')); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const txid = this.safeString (transaction, 'hash'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + const type = this.parseTransactionType (this.safeString (transaction, 'type')); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'failed': 'failed', + 'success': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const types = { + 'payin': 'deposit', + 'payout': 'withdrawal', + 'withdraw': 'withdrawal', + }; + return this.safeString (types, type, type); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['sort'] = 'ASC'; + request['from'] = this.iso8601 (since); + } + const response = await this.publicGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + // their max accepted length is 32 characters + const uuid = this.uuid (); + const parts = uuid.split ('-'); + let clientOrderId = parts.join (''); + clientOrderId = clientOrderId.slice (0, 32); + amount = parseFloat (amount); + const request = { + 'clientOrderId': clientOrderId, + 'symbol': market['id'], + 'side': side, + 'quantity': this.amountToPrecision (symbol, amount), + 'type': type, + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } else { + request['timeInForce'] = this.options['defaultTimeInForce']; + } + const response = await this.privatePostOrder (this.extend (request, params)); + const order = this.parseOrder (response); + if (order['status'] === 'rejected') { + throw new InvalidOrder (this.id + ' order was rejected by the exchange ' + this.json (order)); + } + return order; + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + // their max accepted length is 32 characters + const uuid = this.uuid (); + const parts = uuid.split ('-'); + let requestClientId = parts.join (''); + requestClientId = requestClientId.slice (0, 32); + const request = { + 'clientOrderId': id, + 'requestClientId': requestClientId, + }; + if (amount !== undefined) { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePatchOrderClientOrderId (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const request = { + 'clientOrderId': id, + }; + const response = await this.privateDeleteOrderClientOrderId (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + 'new': 'open', + 'suspended': 'open', + 'partiallyFilled': 'open', + 'filled': 'closed', + 'canceled': 'canceled', + 'expired': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createMarketOrder + // + // { clientOrderId: "fe36aa5e190149bf9985fb673bbb2ea0", + // createdAt: "2018-10-25T16:41:44.780Z", + // cumQuantity: "1", + // id: "66799540063", + // quantity: "1", + // side: "sell", + // status: "filled", + // symbol: "XRPUSDT", + // timeInForce: "FOK", + // tradesReport: [ { fee: "0.0004644", + // id: 386394956, + // price: "0.4644", + // quantity: "1", + // timestamp: "2018-10-25T16:41:44.780Z" } ], + // type: "market", + // updatedAt: "2018-10-25T16:41:44.780Z" } + // + const created = this.parse8601 (this.safeString (order, 'createdAt')); + const updated = this.parse8601 (this.safeString (order, 'updatedAt')); + const marketId = this.safeString (order, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const amount = this.safeFloat (order, 'quantity'); + const filled = this.safeFloat (order, 'cumQuantity'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const id = this.safeString (order, 'clientOrderId'); + const clientOrderId = id; + let price = this.safeFloat (order, 'price'); + let remaining = undefined; + let cost = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + if (price !== undefined) { + cost = filled * price; + } + } + } + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + let trades = this.safeValue (order, 'tradesReport'); + let fee = undefined; + let average = undefined; + if (trades !== undefined) { + trades = this.parseTrades (trades, market); + let feeCost = undefined; + const numTrades = trades.length; + let tradesCost = 0; + for (let i = 0; i < numTrades; i++) { + if (feeCost === undefined) { + feeCost = 0; + } + tradesCost = this.sum (tradesCost, trades[i]['cost']); + const tradeFee = this.safeValue (trades[i], 'fee', {}); + const tradeFeeCost = this.safeFloat (tradeFee, 'cost'); + if (tradeFeeCost !== undefined) { + feeCost = this.sum (feeCost, tradeFeeCost); + } + } + cost = tradesCost; + if ((filled !== undefined) && (filled > 0)) { + average = cost / filled; + if (type === 'market') { + if (price === undefined) { + price = average; + } + } + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + } + const timeInForce = this.safeString (order, 'timeInForce'); + return { + 'id': id, + 'clientOrderId': clientOrderId, // https://github.com/ccxt/ccxt/issues/5674 + 'timestamp': created, + 'datetime': this.iso8601 (created), + 'lastTradeTimestamp': updated, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'trades': trades, + 'info': order, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const request = { + 'clientOrderId': id, + }; + const response = await this.privateGetHistoryOrder (this.extend (request, params)); + const numOrders = response.length; + if (numOrders > 0) { + return this.parseOrder (response[0]); + } + throw new OrderNotFound (this.id + ' order ' + id + ' not found'); + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // we use clientOrderId as the order id with this exchange intentionally + // because most of their endpoints will require clientOrderId + // explained here: https://github.com/ccxt/ccxt/issues/5674 + const request = { + 'clientOrderId': id, + }; + const response = await this.privateGetOrderClientOrderId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetOrder (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + const response = await this.privateGetHistoryOrder (this.extend (request, params)); + const parsedOrders = this.parseOrders (response, market); + const orders = []; + for (let i = 0; i < parsedOrders.length; i++) { + const order = parsedOrders[i]; + const status = order['status']; + if ((status === 'closed') || (status === 'canceled')) { + orders.push (order); + } + } + return this.filterBySinceLimit (orders, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': 'BTC/USD', // optional + // 'sort': 'DESC', // or 'ASC' + // 'by': 'timestamp', // or 'id' String timestamp by default, or id + // 'from': 'Datetime or Number', // ISO 8601 + // 'till': 'Datetime or Number', + // 'limit': 100, + // 'offset': 0, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetHistoryTrades (this.extend (request, params)); + // + // [ + // { + // "id": 9535486, + // "clientOrderId": "f8dbaab336d44d5ba3ff578098a68454", + // "orderId": 816088377, + // "symbol": "ETHBTC", + // "side": "sell", + // "quantity": "0.061", + // "price": "0.045487", + // "fee": "0.000002775", + // "timestamp": "2017-05-17T12:32:57.848Z" + // }, + // { + // "id": 9535437, + // "clientOrderId": "27b9bfc068b44194b1f453c7af511ed6", + // "orderId": 816088021, + // "symbol": "ETHBTC", + // "side": "buy", + // "quantity": "0.038", + // "price": "0.046000", + // "fee": "-0.000000174", + // "timestamp": "2017-05-17T12:30:57.848Z" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + // The id needed here is the exchange's id, and not the clientOrderID, + // which is the id that is stored in the unified order id + // To get the exchange's id you need to grab it from order['info']['id'] + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { + 'orderId': id, + }; + const response = await this.privateGetHistoryOrderOrderIdTrades (this.extend (request, params)); + const numOrders = response.length; + if (numOrders > 0) { + return this.parseTrades (response, market, since, limit); + } + throw new OrderNotFound (this.id + ' order ' + id + ' not found, ' + this.id + '.fetchOrderTrades() requires an exchange-specific order id, you need to grab it from order["info"]["id"]'); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostAccountCryptoAddressCurrency (this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + const tag = this.safeString (response, 'paymentId'); + return { + 'currency': currency, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetAccountCryptoAddressCurrency (this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + const tag = this.safeString (response, 'paymentId'); + return { + 'currency': currency['code'], + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': parseFloat (amount), + 'address': address, + }; + if (tag) { + request['paymentId'] = tag; + } + const response = await this.privatePostAccountCryptoWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/api/' + this.version + '/'; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + url += api + '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + url += this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length) { + body = this.json (query); + } + const payload = this.encode (this.apiKey + ':' + this.secret); + const auth = this.stringToBase64 (payload); + headers = { + 'Authorization': 'Basic ' + this.decode (auth), + 'Content-Type': 'application/json', + }; + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (code >= 400) { + const feedback = this.id + ' ' + body; + // {"code":504,"message":"Gateway Timeout","description":""} + if ((code === 503) || (code === 504)) { + throw new ExchangeNotAvailable (feedback); + } + // fallback to default error handler on rate limit errors + // {"code":429,"message":"Too many requests","description":"Too many requests"} + if (code === 429) { + return; + } + // {"error":{"code":20002,"message":"Order not found","description":""}} + if (body[0] === '{') { + if ('error' in response) { + const errorCode = this.safeString (response['error'], 'code'); + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + const message = this.safeString (response['error'], 'message'); + if (message === 'Duplicate clientOrderId') { + throw new InvalidOrder (feedback); + } + } + } + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hollaex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hollaex.js new file mode 100644 index 000000000..47b127d50 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/hollaex.js @@ -0,0 +1,1172 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadRequest, AuthenticationError, NetworkError, ArgumentsRequired, OrderNotFound, InsufficientFunds } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class hollaex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'hollaex', + 'name': 'HollaEx', + 'countries': [ 'KR' ], + 'rateLimit': 333, + 'version': 'v1', + 'has': { + 'CORS': false, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTrades': true, + 'fetchOHLCV': true, + 'fetchBalance': true, + 'createOrder': true, + 'createLimitBuyOrder': true, + 'createLimitSellOrder': true, + 'createMarketBuyOrder': true, + 'createMarketSellOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': false, + 'fetchOpenOrder': true, + 'fetchOrder': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchTransactions': false, + 'fetchOrders': false, + 'fetchMyTrades': true, + 'withdraw': true, + 'fetchDepositAddress': true, + }, + 'timeframes': { + '1h': '1h', + '1d': '1d', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/75841031-ca375180-5ddd-11ea-8417-b975674c23cb.jpg', + 'api': 'https://api.hollaex.com', + 'www': 'https://hollaex.com', + 'doc': 'https://apidocs.hollaex.com', + 'referral': 'https://pro.hollaex.com/signup?affiliation_code=QSWA6G', + }, + 'precisionMode': TICK_SIZE, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'api': { + 'public': { + 'get': [ + 'health', + 'constant', + 'ticker', + 'ticker/all', + 'orderbooks', + 'trades', + 'chart', + // TradingView data + 'udf/config', + 'udf/history', + 'udf/symbols', + ], + }, + 'private': { + 'get': [ + 'user', + 'user/balance', + 'user/trades', + 'user/orders', + 'user/orders/{order_id}', + 'user/deposits', + 'user/withdrawals', + 'user/withdraw/{currency}/fee', + ], + 'post': [ + 'user/request-withdrawal', + 'order', + ], + 'delete': [ + 'user/orders', + 'user/orders/{order_id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + }, + }, + 'exceptions': { + 'broad': { + 'Invalid token': AuthenticationError, + 'Order not found': OrderNotFound, + 'Insufficient balance': InsufficientFunds, + }, + 'exact': { + '400': BadRequest, + '403': AuthenticationError, + '404': BadRequest, + '405': BadRequest, + '410': BadRequest, + '429': BadRequest, + '500': NetworkError, + '503': NetworkError, + }, + }, + 'options': { + // how many seconds before the authenticated request expires + 'api-expires': parseInt (this.timeout / 1000), + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetConstant (params); + // + // { + // coins: { + // xmr: { + // id: 7, + // fullname: "Monero", + // symbol: "xmr", + // active: true, + // allow_deposit: true, + // allow_withdrawal: true, + // withdrawal_fee: 0.02, + // min: 0.001, + // max: 100000, + // increment_unit: 0.001, + // deposit_limits: { '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0 }, + // withdrawal_limits: { '1': 10, '2': 15, '3': 100, '4': 100, '5': 200, '6': 300, '7': 350, '8': 400, '9': 500, '10': -1 }, + // created_at: "2019-12-09T07:14:02.720Z", + // updated_at: "2020-01-16T12:12:53.162Z" + // }, + // // ... + // }, + // pairs: { + // 'btc-usdt': { + // id: 2, + // name: "btc-usdt", + // pair_base: "btc", + // pair_2: "usdt", + // taker_fees: { '1': 0.3, '2': 0.25, '3': 0.2, '4': 0.18, '5': 0.1, '6': 0.09, '7': 0.08, '8': 0.06, '9': 0.04, '10': 0 }, + // maker_fees: { '1': 0.1, '2': 0.08, '3': 0.05, '4': 0.03, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0, '10': 0 }, + // min_size: 0.0001, + // max_size: 1000, + // min_price: 100, + // max_price: 100000, + // increment_size: 0.0001, + // increment_price: 0.05, + // active: true, + // created_at: "2019-12-09T07:15:54.537Z", + // updated_at: "2019-12-09T07:15:54.537Z" + // }, + // }, + // config: { tiers: 10 }, + // status: true + // } + // + const pairs = this.safeValue (response, 'pairs', {}); + const keys = Object.keys (pairs); + const result = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const market = pairs[key]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'pair_base'); + const quoteId = this.safeString (market, 'pair_2'); + const base = this.commonCurrencyCode (baseId.toUpperCase ()); + const quote = this.commonCurrencyCode (quoteId.toUpperCase ()); + const symbol = base + '/' + quote; + const active = this.safeValue (market, 'active'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': { + 'price': this.safeFloat (market, 'increment_price'), + 'amount': this.safeFloat (market, 'increment_size'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_size'), + 'max': this.safeFloat (market, 'max_size'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetConstant (params); + const coins = this.safeValue (response, 'coins', {}); + const keys = Object.keys (coins); + const result = {}; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const currency = coins[key]; + const id = this.safeString (currency, 'symbol'); + const numericId = this.safeInteger (currency, 'id'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'fullname'); + const active = this.safeValue (currency, 'active'); + const fee = this.safeFloat (currency, 'withdrawal_fee'); + const precision = this.safeFloat (currency, 'increment_unit'); + const withdrawalLimits = this.safeValue (currency, 'withdrawal_limits', []); + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'min'), + 'max': this.safeFloat (currency, 'max'), + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': this.safeValue (withdrawalLimits, 0), + }, + }, + }; + } + return result; + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetOrderbooks (params); + const result = {}; + const marketIds = Object.keys (response); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const orderbook = response[marketId]; + const symbol = this.safeSymbol (marketId, undefined, '-'); + const timestamp = this.parse8601 (this.safeString (orderbook, 'timestamp')); + result[symbol] = this.parseOrderBook (response[marketId], timestamp); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + const response = await this.publicGetOrderbooks (this.extend (request, params)); + // + // { + // "btc-usdt": { + // "bids": [ + // [ 8836.4, 1.022 ], + // [ 8800, 0.0668 ], + // [ 8797.75, 0.2398 ], + // ], + // "asks": [ + // [ 8839.35, 1.5334 ], + // [ 8852.6, 0.0579 ], + // [ 8860.45, 0.1815 ], + // ], + // "timestamp": "2020-03-03T02:27:25.147Z" + // }, + // "eth-usdt": {}, + // // ... + // } + // + const orderbook = this.safeValue (response, marketId); + const timestamp = this.parse8601 (this.safeString (orderbook, 'timestamp')); + return this.parseOrderBook (orderbook, timestamp); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // open: 8615.55, + // close: 8841.05, + // high: 8921.1, + // low: 8607, + // last: 8841.05, + // volume: 20.2802, + // timestamp: '2020-03-03T03:11:18.964Z' + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickerAll (this.extend (params)); + // + // { + // "bch-usdt": { + // "time": "2020-03-02T04:29:45.011Z", + // "open": 341.65, + // "close":337.9, + // "high":341.65, + // "low":337.3, + // "last":337.9, + // "volume":0.054, + // "symbol":"bch-usdt" + // }, + // // ... + // } + // + return this.parseTickers (response, symbols); + } + + parseTickers (response, symbols = undefined) { + const result = {}; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const ticker = response[key]; + const marketId = this.safeString (ticker, 'symbol', key); + const market = this.safeMarket (marketId, undefined, '-'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // open: 8615.55, + // close: 8841.05, + // high: 8921.1, + // low: 8607, + // last: 8841.05, + // volume: 20.2802, + // timestamp: '2020-03-03T03:11:18.964Z', + // } + // + // fetchTickers + // + // { + // "time": "2020-03-02T04:29:45.011Z", + // "open": 341.65, + // "close": 337.9, + // "high": 341.65, + // "low": 337.3, + // "last": 337.9, + // "volume": 0.054, + // "symbol": "bch-usdt" + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString2 (ticker, 'time', 'timestamp')); + const close = this.safeFloat (ticker, 'close'); + const result = { + 'symbol': symbol, + 'info': ticker, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': close, + 'last': this.safeFloat (ticker, 'last', close), + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + }; + return result; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "btc-usdt": [ + // { + // "size": 0.5, + // "price": 8830, + // "side": "buy", + // "timestamp": "2020-03-03T04:44:33.034Z" + // }, + // // ... + // ] + // } + // + const trades = this.safeValue (response, market['id'], []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "size": 0.5, + // "price": 8830, + // "side": "buy", + // "timestamp": "2020-03-03T04:44:33.034Z" + // } + // + // fetchMyTrades (private) + // + // { + // "side": "buy", + // "symbol": "eth-usdt", + // "size": 0.086, + // "price": 226.19, + // "timestamp": "2020-03-03T08:03:55.459Z", + // "fee": 0.1 + // } + // + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market, '-'); + const symbol = market['symbol']; + const datetime = this.safeString (trade, 'timestamp'); + const timestamp = this.parse8601 (datetime); + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'size'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const quote = market['quote']; + const feeCurrencyCode = (market !== undefined) ? market['quote'] : quote; + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': trade, + 'id': undefined, + 'timestamp': timestamp, + 'datetime': datetime, + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOHLCV (symbol, timeframe = '1h', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + " fetchOHLCV requires a 'since' or a 'limit' argument"); + } else { + const end = this.seconds (); + const start = end - duration * limit; + request['to'] = end; + request['from'] = start; + } + } else { + if (limit === undefined) { + request['from'] = parseInt (since / 1000); + request['to'] = this.seconds (); + } else { + const start = parseInt (since / 1000); + request['from'] = start; + request['to'] = this.sum (start, duration * limit); + } + } + const response = await this.publicGetChart (this.extend (request, params)); + // + // [ + // { + // "time":"2020-03-02T20:00:00.000Z", + // "close":8872.1, + // "high":8872.1, + // "low":8858.6, + // "open":8858.6, + // "symbol":"btc-usdt", + // "volume":1.2922 + // }, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOHLCV (response, market = undefined, timeframe = '1h', since = undefined, limit = undefined) { + // + // { + // "time":"2020-03-02T20:00:00.000Z", + // "close":8872.1, + // "high":8872.1, + // "low":8858.6, + // "open":8858.6, + // "symbol":"btc-usdt", + // "volume":1.2922 + // } + // + return [ + this.parse8601 (this.safeString (response, 'time')), + this.safeFloat (response, 'open'), + this.safeFloat (response, 'high'), + this.safeFloat (response, 'low'), + this.safeFloat (response, 'close'), + this.safeFloat (response, 'volume'), + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUserBalance (params); + // + // { + // "updated_at": "2020-03-02T22:27:38.428Z", + // "btc_balance": 0, + // "btc_pending": 0, + // "btc_available": 0, + // "eth_balance": 0, + // "eth_pending": 0, + // "eth_available": 0, + // // ... + // } + // + const result = { 'info': response }; + const currencyIds = Object.keys (this.currencies_by_id); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (response, currencyId + '_available'); + account['total'] = this.safeFloat (response, currencyId + '_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetUserOrdersOrderId (this.extend (request, params)); + // + // { + // "created_at": "2018-03-23T04:14:08.663Z", + // "title": "string", + // "side": "sell", + // "type": "limit", + // "price": 0, + // "size": 0, + // "symbol": "xht-usdt", + // "id": "string", + // "created_by": 1, + // "filled": 0 + // } + // + return this.parseOrder (response); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetUserOrders (this.extend (request, params)); + // + // [ + // { + // "created_at":"2020-03-03T08:02:18.639Z", + // "title":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "side":"buy", + // "type":"limit", + // "price":226.19, + // "size":0.086, + // "symbol":"eth-usdt", + // "id":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "created_by":620, + // "filled":0 + // } + // ] + // + return this.parseOrders (response, market); + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrder, fetchOpenOrders + // + // { + // "created_at":"2020-03-03T08:02:18.639Z", + // "title":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "side":"buy", + // "type":"limit", + // "price":226.19, + // "size":0.086, + // "symbol":"eth-usdt", + // "id":"5419ff3f-9d25-4af7-bcc2-803926518d76", + // "created_by":620, + // "filled":0 + // } + // + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'filled'); + let cost = undefined; + let remaining = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (price !== undefined) { + cost = filled * price; + } + } + const status = (type === 'market') ? 'closed' : 'open'; + const result = { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const order = { + 'symbol': market['id'], + 'side': side, + 'size': amount, + 'type': type, + }; + if (type !== 'market') { + order['price'] = price; + } + const response = await this.privatePostOrder (this.extend (order, params)); + // + // { + // "symbol": "xht-usdt", + // "side": "sell", + // "size": 1, + // "type": "limit", + // "price": 0.1, + // "id": "string", + // "created_by": 34, + // "filled": 0, + // "status": "pending" + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateDeleteUserOrdersOrderId (this.extend (request, params)); + // + // { + // "title": "string", + // "symbol": "xht-usdt", + // "side": "sell", + // "size": 1, + // "type": "limit", + // "price": 0.1, + // "id": "string", + // "created_by": 34, + // "filled": 0 + // } + // + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.markets (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateDeleteUserOrders (this.extend (request, params)); + // + // [ + // { + // "title": "string", + // "symbol": "xht-usdt", + // "side": "sell", + // "size": 1, + // "type": "limit", + // "price": 0.1, + // "id": "string", + // "created_by": 34, + // "filled": 0 + // } + // ] + // + return this.parseOrders (response, market); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': market['id'], + // 'limit': 50, // default 50, max 100 + // 'page': 1, // page of data to retrieve + // 'order_by': 'timestamp', // field to order data + // 'order': 'asc', // asc or desc + // 'start_date': 123, // starting date of queried data + // 'end_date': 321, // ending date of queried data + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + const response = await this.privateGetUserTrades (this.extend (request, params)); + // + // { + // "count": 1, + // "data": [ + // { + // "side": "buy", + // "symbol": "eth-usdt", + // "size": 0.086, + // "price": 226.19, + // "timestamp": "2020-03-03T08:03:55.459Z", + // "fee": 0.1 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const response = await this.privateGetUser (params); + // + // { + // "id": 620, + // "email": "email@gmail.com", + // "full_name": "", + // "name_verified": false, + // "gender": false, + // "nationality": "", + // "phone_number": "", + // "address": { "city": "", "address": "", "country": "", "postal_code": "" }, + // "id_data": { "note": "", "type": "", "number": "", "status": 0 }, + // "bank_account":[], + // "crypto_wallet":{ + // "xrp": "rJtoECs6rPkJoAfgtR8SDDshV6hRHe3X7y:391496555" + // "usdt":"0x1fb4248e167901dfa0d8cdda2243a2126d7ce48d" + // // ... + // }, + // "verification_level": 1, + // "otp_enabled": true, + // "activated": true, + // "note": "", + // "username": "user", + // "affiliation_code": "QSWA6G", + // "settings": { + // "chat": { "set_username": false }, + // "risk": { "order_portfolio_percentage": 20 }, + // "audio": { + // "public_trade": false, + // "order_completed": true, + // "order_partially_completed": true + // }, + // "language": "en", + // "interface": { "theme": "white","order_book_levels": 10 }, + // "notification": { + // "popup_order_completed": true, + // "popup_order_confirmation": true, + // "popup_order_partially_filled": true + // } + // }, + // "flagged": false, + // "is_hap": false, + // "pin": false, + // "discount": 0, + // "created_at": "2020-03-02T22:27:38.331Z", + // "updated_at": "2020-03-03T07:54:58.315Z", + // "balance": { + // "xht_balance": 0, + // "xht_pending": 0, + // "xht_available": 0, + // // ... + // "updated_at": "2020-03-03T10:21:05.430Z" + // }, + // "images": [], + // "fees": { + // "btc-usdt": { "maker_fee": 0.1, "taker_fee": 0.3 }, + // "eth-usdt": { "maker_fee": 0.1, "taker_fee": 0.3 }, + // // ... + // } + // } + // + const cryptoWallet = this.safeValue (response, 'crypto_wallet'); + let address = this.safeString (cryptoWallet, currency['id']); + let tag = undefined; + if (address !== undefined) { + const parts = address.split (':'); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': currency['id'], + // 'limit': 50, // default 50, max 100 + // 'page': 1, // page of data to retrieve + // 'order_by': 'timestamp', // field to order data + // 'order': 'asc', // asc or desc + // 'start_date': 123, // starting date of queried data + // 'end_date': 321, // ending date of queried data + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + const response = await this.privateGetUserDeposits (this.extend (request, params)); + // + // { + // "count": 1, + // "data": [ + // { + // "id": 539, + // "amount": 20, + // "fee": 0, + // "address": "0x5c0cc98270d7089408fcbcc8e2131287f5be2306", + // "transaction_id": "0xd4006327a5ec2c41adbdcf566eaaba6597c3d45906abe78ea1a4a022647c2e28", + // "status": true, + // "dismissed": false, + // "rejected": false, + // "description": "", + // "type": "deposit", + // "currency": "usdt", + // "created_at": "2020-03-03T07:56:36.198Z", + // "updated_at": "2020-03-03T08:00:05.674Z", + // "user_id": 620 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': currency['id'], + // 'limit': 50, // default 50, max 100 + // 'page': 1, // page of data to retrieve + // 'order_by': 'timestamp', // field to order data + // 'order': 'asc', // asc or desc + // 'start_date': 123, // starting date of queried data + // 'end_date': 321, // ending date of queried data + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + if (since !== undefined) { + request['start_date'] = this.iso8601 (since); + } + const response = await this.privateGetUserWithdrawals (this.extend (request, params)); + // + // { + // "count": 1, + // "data": [ + // { + // "id": 539, + // "amount": 20, + // "fee": 0, + // "address": "0x5c0cc98270d7089408fcbcc8e2131287f5be2306", + // "transaction_id": "0xd4006327a5ec2c41adbdcf566eaaba6597c3d45906abe78ea1a4a022647c2e28", + // "status": true, + // "dismissed": false, + // "rejected": false, + // "description": "", + // "type": "withdrawal", + // "currency": "usdt", + // "created_at": "2020-03-03T07:56:36.198Z", + // "updated_at": "2020-03-03T08:00:05.674Z", + // "user_id": 620 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "id": 539, + // "amount": 20, + // "fee": 0, + // "address": "0x5c0cc98270d7089408fcbcc8e2131287f5be2306", + // "transaction_id": "0xd4006327a5ec2c41adbdcf566eaaba6597c3d45906abe78ea1a4a022647c2e28", + // "status": true, + // "dismissed": false, + // "rejected": false, + // "description": "", + // "type": "withdrawal", + // "currency": "usdt", + // "created_at": "2020-03-03T07:56:36.198Z", + // "updated_at": "2020-03-03T08:00:05.674Z", + // "user_id": 620 + // } + // + const id = this.safeString (transaction, 'id'); + const txid = this.safeString (transaction, 'transaction_id'); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const updated = this.parse8601 (this.safeString (transaction, 'updated_at')); + const type = this.safeString (transaction, 'type'); + const amount = this.safeFloat (transaction, 'amount'); + let address = this.safeString (transaction, 'address'); + let addressTo = undefined; + const addressFrom = undefined; + let tag = undefined; + let tagTo = undefined; + const tagFrom = undefined; + if (address !== undefined) { + const parts = address.split (':'); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + addressTo = address; + tagTo = tag; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let status = this.safeValue (transaction, 'status'); + const dismissed = this.safeValue (transaction, 'dismissed'); + const rejected = this.safeValue (transaction, 'rejected'); + if (status) { + status = 'ok'; + } else if (dismissed) { + status = 'canceled'; + } else if (rejected) { + status = 'failed'; + } else { + status = 'pending'; + } + const fee = { + 'currency': code, + 'cost': this.safeFloat (transaction, 'fee'), + }; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'address': address, + 'addressTo': addressTo, + 'tagFrom': tagFrom, + 'tag': tag, + 'tagTo': tagTo, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag !== undefined) { + address += ':' + tag; + } + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + }; + // one time password + let otp = this.safeString (params, 'otp_code'); + if ((otp !== undefined) || (this.twofa !== undefined)) { + if (otp === undefined) { + otp = this.oath (); + } + request['otp_code'] = otp; + } + const response = await this.privatePostUserRequestWithdrawal (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + path = '/' + this.version + '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + path += '?' + this.urlencode (query); + } + } + const url = this.urls['api'] + path; + if (api === 'private') { + this.checkRequiredCredentials (); + const defaultExpires = this.safeInteger2 (this.options, 'api-expires', 'expires', parseInt (this.timeout / 1000)); + const expires = this.sum (this.seconds (), defaultExpires); + const expiresString = expires.toString (); + let auth = method + path + expiresString; + headers = { + 'api-key': this.encode (this.apiKey), + 'api-expires': expiresString, + }; + if (method === 'POST') { + headers['Content-type'] = 'application/json'; + if (Object.keys (query).length) { + body = this.json (query); + auth += body; + } + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret)); + headers['api-signature'] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if ((code >= 400) && (code <= 503)) { + // + // { "message": "Invalid token" } + // + const feedback = this.id + ' ' + body; + const message = this.safeString (response, 'message'); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + const status = code.toString (); + this.throwExactlyMatchedException (this.exceptions['exact'], status, feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobijp.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobijp.js new file mode 100644 index 000000000..45fafe606 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobijp.js @@ -0,0 +1,39 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const huobipro = require ('./huobipro.js'); +const { NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class huobijp extends huobipro { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'huobijp', + 'name': 'Huobi Japan', + 'countries': [ 'JP' ], + 'hostname': 'api-cloud.huobi.co.jp', + 'pro': true, + 'has': { + 'fetchDepositAddress': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/85734211-85755480-b705-11ea-8b35-0b7f1db33a2f.jpg', + 'api': { + 'market': 'https://{hostname}', + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + }, + 'www': 'https://www.huobi.co.jp', + 'referral': 'https://www.huobi.co.jp/register/?invite_code=znnq3', + 'doc': 'https://api-doc.huobi.co.jp', + 'fees': 'https://www.huobi.co.jp/support/fee', + }, + }); + } + + async fetchDepositAddress (code, params = {}) { + throw new NotSupported (this.id + ' fetchDepositAddress not supported yet'); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobipro.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobipro.js new file mode 100644 index 000000000..62edb18d3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobipro.js @@ -0,0 +1,1494 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, PermissionDenied, ExchangeNotAvailable, OnMaintenance, InvalidOrder, OrderNotFound, InsufficientFunds, ArgumentsRequired, BadSymbol, BadRequest, RequestTimeout, NetworkError } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class huobipro extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'huobipro', + 'name': 'Huobi Pro', + 'countries': [ 'CN' ], + 'rateLimit': 2000, + 'userAgent': this.userAgents['chrome39'], + 'version': 'v1', + 'accounts': undefined, + 'accountsById': undefined, + 'hostname': 'api.huobi.pro', // api.testnet.huobi.pro + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingLimits': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '60min', + '4h': '4hour', + '1d': '1day', + '1w': '1week', + '1M': '1mon', + '1y': '1year', + }, + 'urls': { + 'test': { + 'market': 'https://api.testnet.huobi.pro', + 'public': 'https://api.testnet.huobi.pro', + 'private': 'https://api.testnet.huobi.pro', + }, + 'logo': 'https://user-images.githubusercontent.com/1294454/76137448-22748a80-604e-11ea-8069-6e389271911d.jpg', + 'api': { + 'market': 'https://{hostname}', + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + 'v2Public': 'https://{hostname}', + 'v2Private': 'https://{hostname}', + }, + 'www': 'https://www.huobi.com', + 'referral': 'https://www.huobi.com/en-us/topic/invited/?invite_code=rwrd3', + 'doc': 'https://huobiapi.github.io/docs/spot/v1/cn/', + 'fees': 'https://www.huobi.com/about/fee/', + }, + 'api': { + 'v2Public': { + 'get': [ + 'reference/currencies', + ], + }, + 'v2Private': { + 'get': [ + 'account/ledger', + 'account/withdraw/quota', + 'account/withdraw/address', // 提币地址查询(限母用户可用) + 'account/deposit/address', + 'reference/transact-fee-rate', + 'account/asset-valuation', // 获取账户资产估值 + 'point/account', // 点卡余额查询 + 'sub-user/user-list', // 获取子用户列表 + 'sub-user/user-state', // 获取特定子用户的用户状态 + 'sub-user/account-list', // 获取特定子用户的账户列表 + 'sub-user/deposit-address', // 子用户充币地址查询 + 'sub-user/query-deposit', // 子用户充币记录查询 + 'user/api-key', // 母子用户API key信息查询 + ], + 'post': [ + 'point/transfer', // 点卡划转 + 'sub-user/management', // 冻结/解冻子用户 + 'sub-user/creation', // 子用户创建 + 'sub-user/tradable-market', // 设置子用户交易权限 + 'sub-user/transferability', // 设置子用户资产转出权限 + 'sub-user/api-key-generation', // 子用户API key创建 + 'sub-user/api-key-modification', // 修改子用户API key + 'sub-user/api-key-deletion', // 删除子用户API key + ], + }, + 'market': { + 'get': [ + 'history/kline', // 获取K线数据 + 'detail/merged', // 获取聚合行情(Ticker) + 'depth', // 获取 Market Depth 数据 + 'trade', // 获取 Trade Detail 数据 + 'history/trade', // 批量获取最近的交易记录 + 'detail', // 获取 Market Detail 24小时成交量数据 + 'tickers', + ], + }, + 'public': { + 'get': [ + 'common/symbols', // 查询系统支持的所有交易对 + 'common/currencys', // 查询系统支持的所有币种 + 'common/timestamp', // 查询系统当前时间 + 'common/exchange', // order limits + 'settings/currencys', // ?language=en-US + ], + }, + 'private': { + 'get': [ + 'account/accounts', // 查询当前用户的所有账户(即account-id) + 'account/accounts/{id}/balance', // 查询指定账户的余额 + 'account/accounts/{sub-uid}', + 'account/history', + 'cross-margin/loan-info', + 'margin/loan-info', // 查询借币币息率及额度 + 'fee/fee-rate/get', + 'order/openOrders', + 'order/orders', + 'order/orders/{id}', // 查询某个订单详情 + 'order/orders/{id}/matchresults', // 查询某个订单的成交明细 + 'order/orders/getClientOrder', + 'order/history', // 查询当前委托、历史委托 + 'order/matchresults', // 查询当前成交、历史成交 + 'dw/withdraw-virtual/addresses', // 查询虚拟币提现地址(Deprecated) + 'query/deposit-withdraw', + 'margin/loan-info', + 'margin/loan-orders', // 借贷订单 + 'margin/accounts/balance', // 借贷账户详情 + 'cross-margin/loan-orders', // 查询借币订单 + 'cross-margin/accounts/balance', // 借币账户详情 + 'points/actions', + 'points/orders', + 'subuser/aggregate-balance', + 'stable-coin/exchange_rate', + 'stable-coin/quote', + ], + 'post': [ + 'account/transfer', // 资产划转(该节点为母用户和子用户进行资产划转的通用接口。) + 'futures/transfer', + 'order/batch-orders', + 'order/orders/place', // 创建并执行一个新订单 (一步下单, 推荐使用) + 'order/orders/submitCancelClientOrder', + 'order/orders/batchCancelOpenOrders', + 'order/orders', // 创建一个新的订单请求 (仅创建订单,不执行下单) + 'order/orders/{id}/place', // 执行一个订单 (仅执行已创建的订单) + 'order/orders/{id}/submitcancel', // 申请撤销一个订单请求 + 'order/orders/batchcancel', // 批量撤销订单 + 'dw/balance/transfer', // 资产划转 + 'dw/withdraw/api/create', // 申请提现虚拟币 + 'dw/withdraw-virtual/create', // 申请提现虚拟币 + 'dw/withdraw-virtual/{id}/place', // 确认申请虚拟币提现(Deprecated) + 'dw/withdraw-virtual/{id}/cancel', // 申请取消提现虚拟币 + 'dw/transfer-in/margin', // 现货账户划入至借贷账户 + 'dw/transfer-out/margin', // 借贷账户划出至现货账户 + 'margin/orders', // 申请借贷 + 'margin/orders/{id}/repay', // 归还借贷 + 'cross-margin/transfer-in', // 资产划转 + 'cross-margin/transfer-out', // 资产划转 + 'cross-margin/orders', // 申请借币 + 'cross-margin/orders/{id}/repay', // 归还借币 + 'stable-coin/exchange', + 'subuser/transfer', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.002, + 'taker': 0.002, + }, + }, + 'exceptions': { + 'exact': { + // err-code + 'bad-request': BadRequest, + 'api-not-support-temp-addr': PermissionDenied, // {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null} + 'timeout': RequestTimeout, // {"ts":1571653730865,"status":"error","err-code":"timeout","err-msg":"Request Timeout"} + 'gateway-internal-error': ExchangeNotAvailable, // {"status":"error","err-code":"gateway-internal-error","err-msg":"Failed to load data. Try again later.","data":null} + 'account-frozen-balance-insufficient-error': InsufficientFunds, // {"status":"error","err-code":"account-frozen-balance-insufficient-error","err-msg":"trade account balance is not enough, left: `0.0027`","data":null} + 'invalid-amount': InvalidOrder, // eg "Paramemter `amount` is invalid." + 'order-limitorder-amount-min-error': InvalidOrder, // limit order amount error, min: `0.001` + 'order-limitorder-amount-max-error': InvalidOrder, // market order amount error, max: `1000000` + 'order-marketorder-amount-min-error': InvalidOrder, // market order amount error, min: `0.01` + 'order-limitorder-price-min-error': InvalidOrder, // limit order price error + 'order-limitorder-price-max-error': InvalidOrder, // limit order price error + 'order-orderstate-error': OrderNotFound, // canceling an already canceled order + 'order-queryorder-invalid': OrderNotFound, // querying a non-existent order + 'order-update-error': ExchangeNotAvailable, // undocumented error + 'api-signature-check-failed': AuthenticationError, + 'api-signature-not-valid': AuthenticationError, // {"status":"error","err-code":"api-signature-not-valid","err-msg":"Signature not valid: Incorrect Access key [Access key错误]","data":null} + 'base-record-invalid': OrderNotFound, // https://github.com/ccxt/ccxt/issues/5750 + 'base-symbol-trade-disabled': BadSymbol, // {"status":"error","err-code":"base-symbol-trade-disabled","err-msg":"Trading is disabled for this symbol","data":null} + 'base-symbol-error': BadSymbol, // {"status":"error","err-code":"base-symbol-error","err-msg":"The symbol is invalid","data":null} + 'system-maintenance': OnMaintenance, // {"status": "error", "err-code": "system-maintenance", "err-msg": "System is in maintenance!", "data": null} + // err-msg + 'invalid symbol': BadSymbol, // {"ts":1568813334794,"status":"error","err-code":"invalid-parameter","err-msg":"invalid symbol"} + 'symbol trade not open now': BadSymbol, // {"ts":1576210479343,"status":"error","err-code":"invalid-parameter","err-msg":"symbol trade not open now"} + }, + }, + 'options': { + // https://github.com/ccxt/ccxt/issues/5376 + 'fetchOrdersByStatesMethod': 'private_get_order_orders', // 'private_get_order_history' // https://github.com/ccxt/ccxt/pull/5392 + 'fetchOpenOrdersMethod': 'fetch_open_orders_v1', // 'fetch_open_orders_v2' // https://github.com/ccxt/ccxt/issues/5388 + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarketsMethod': 'publicGetCommonSymbols', + 'fetchBalanceMethod': 'privateGetAccountAccountsIdBalance', + 'createOrderMethod': 'privatePostOrderOrdersPlace', + 'language': 'en-US', + }, + 'commonCurrencies': { + // https://github.com/ccxt/ccxt/issues/6081 + // https://github.com/ccxt/ccxt/issues/3365 + // https://github.com/ccxt/ccxt/issues/2873 + 'GET': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol) + 'HOT': 'Hydro Protocol', // conflict with HOT (Holo) https://github.com/ccxt/ccxt/issues/4929 + // https://github.com/ccxt/ccxt/issues/7399 + // https://coinmarketcap.com/currencies/pnetwork/ + // https://coinmarketcap.com/currencies/penta/markets/ + // https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/ + 'PNT': 'Penta', + }, + }); + } + + async fetchTradingLimits (symbols = undefined, params = {}) { + // this method should not be called directly, use loadTradingLimits () instead + // by default it will try load withdrawal fees of all currencies (with separate requests) + // however if you define symbols = [ 'ETH/BTC', 'LTC/BTC' ] in args it will only load those + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const result = {}; + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + result[symbol] = await this.fetchTradingLimitsById (this.marketId (symbol), params); + } + return result; + } + + async fetchTradingLimitsById (id, params = {}) { + const request = { + 'symbol': id, + }; + const response = await this.publicGetCommonExchange (this.extend (request, params)); + // + // { status: "ok", + // data: { symbol: "aidocbtc", + // 'buy-limit-must-less-than': 1.1, + // 'sell-limit-must-greater-than': 0.9, + // 'limit-order-must-greater-than': 1, + // 'limit-order-must-less-than': 5000000, + // 'market-buy-order-must-greater-than': 0.0001, + // 'market-buy-order-must-less-than': 100, + // 'market-sell-order-must-greater-than': 1, + // 'market-sell-order-must-less-than': 500000, + // 'circuit-break-when-greater-than': 10000, + // 'circuit-break-when-less-than': 10, + // 'market-sell-order-rate-must-less-than': 0.1, + // 'market-buy-order-rate-must-less-than': 0.1 } } + // + return this.parseTradingLimits (this.safeValue (response, 'data', {})); + } + + parseTradingLimits (limits, symbol = undefined, params = {}) { + // + // { symbol: "aidocbtc", + // 'buy-limit-must-less-than': 1.1, + // 'sell-limit-must-greater-than': 0.9, + // 'limit-order-must-greater-than': 1, + // 'limit-order-must-less-than': 5000000, + // 'market-buy-order-must-greater-than': 0.0001, + // 'market-buy-order-must-less-than': 100, + // 'market-sell-order-must-greater-than': 1, + // 'market-sell-order-must-less-than': 500000, + // 'circuit-break-when-greater-than': 10000, + // 'circuit-break-when-less-than': 10, + // 'market-sell-order-rate-must-less-than': 0.1, + // 'market-buy-order-rate-must-less-than': 0.1 } + // + return { + 'info': limits, + 'limits': { + 'amount': { + 'min': this.safeFloat (limits, 'limit-order-must-greater-than'), + 'max': this.safeFloat (limits, 'limit-order-must-less-than'), + }, + }, + }; + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['cost'], this.precisionMode); + } + + async fetchMarkets (params = {}) { + const method = this.options['fetchMarketsMethod']; + const response = await this[method] (params); + const markets = this.safeValue (response, 'data'); + const numMarkets = markets.length; + if (numMarkets < 1) { + throw new NetworkError (this.id + ' publicGetCommonSymbols returned empty response: ' + this.json (markets)); + } + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const baseId = this.safeString (market, 'base-currency'); + const quoteId = this.safeString (market, 'quote-currency'); + const id = baseId + quoteId; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amount-precision'), + 'price': this.safeInteger (market, 'price-precision'), + 'cost': this.safeInteger (market, 'value-precision'), + }; + const maker = (base === 'OMG') ? 0 : 0.2 / 100; + const taker = (base === 'OMG') ? 0 : 0.2 / 100; + const minAmount = this.safeFloat (market, 'min-order-amt', Math.pow (10, -precision['amount'])); + const maxAmount = this.safeFloat (market, 'max-order-amt'); + const minCost = this.safeFloat (market, 'min-order-value', 0); + const state = this.safeString (market, 'state'); + const active = (state === 'online'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'taker': taker, + 'maker': maker, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "amount": 26228.672978342216, + // "open": 9078.95, + // "close": 9146.86, + // "high": 9155.41, + // "id": 209988544334, + // "count": 265846, + // "low": 8988.0, + // "version": 209988544334, + // "ask": [ 9146.87, 0.156134 ], + // "vol": 2.3822168242201668E8, + // "bid": [ 9146.86, 0.080758 ], + // } + // + // fetchTickers + // { + // symbol: "bhdht", + // open: 2.3938, + // high: 2.4151, + // low: 2.3323, + // close: 2.3909, + // amount: 628.992, + // vol: 1493.71841095, + // count: 2088, + // bid: 2.3643, + // bidSize: 0.7136, + // ask: 2.4061, + // askSize: 0.4156 + // } + // + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (ticker, 'ts'); + let bid = undefined; + let bidVolume = undefined; + let ask = undefined; + let askVolume = undefined; + if ('bid' in ticker) { + if (Array.isArray (ticker['bid'])) { + bid = this.safeFloat (ticker['bid'], 0); + bidVolume = this.safeFloat (ticker['bid'], 1); + } else { + bid = this.safeFloat (ticker, 'bid'); + bidVolume = this.safeValue (ticker, 'bidSize'); + } + } + if ('ask' in ticker) { + if (Array.isArray (ticker['ask'])) { + ask = this.safeFloat (ticker['ask'], 0); + askVolume = this.safeFloat (ticker['ask'], 1); + } else { + ask = this.safeFloat (ticker, 'ask'); + askVolume = this.safeValue (ticker, 'askSize'); + } + } + const open = this.safeFloat (ticker, 'open'); + const close = this.safeFloat (ticker, 'close'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if ((open !== undefined) && (close !== undefined)) { + change = close - open; + average = this.sum (open, close) / 2; + if ((close !== undefined) && (close > 0)) { + percentage = (change / open) * 100; + } + } + const baseVolume = this.safeFloat (ticker, 'amount'); + const quoteVolume = this.safeFloat (ticker, 'vol'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': bid, + 'bidVolume': bidVolume, + 'ask': ask, + 'askVolume': askVolume, + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'type': 'step0', + }; + const response = await this.marketGetDepth (this.extend (request, params)); + // + // { + // "status": "ok", + // "ch": "market.btcusdt.depth.step0", + // "ts": 1583474832790, + // "tick": { + // "bids": [ + // [ 9100.290000000000000000, 0.200000000000000000 ], + // [ 9099.820000000000000000, 0.200000000000000000 ], + // [ 9099.610000000000000000, 0.205000000000000000 ], + // ], + // "asks": [ + // [ 9100.640000000000000000, 0.005904000000000000 ], + // [ 9101.010000000000000000, 0.287311000000000000 ], + // [ 9101.030000000000000000, 0.012121000000000000 ], + // ], + // "ts":1583474832008, + // "version":104999698780 + // } + // } + // + if ('tick' in response) { + if (!response['tick']) { + throw new BadSymbol (this.id + ' fetchOrderBook() returned empty response: ' + this.json (response)); + } + const tick = this.safeValue (response, 'tick'); + const timestamp = this.safeInteger (tick, 'ts', this.safeInteger (response, 'ts')); + const result = this.parseOrderBook (tick, timestamp); + result['nonce'] = this.safeInteger (tick, 'version'); + return result; + } + throw new ExchangeError (this.id + ' fetchOrderBook() returned unrecognized response: ' + this.json (response)); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.marketGetDetailMerged (this.extend (request, params)); + // + // { + // "status": "ok", + // "ch": "market.btcusdt.detail.merged", + // "ts": 1583494336669, + // "tick": { + // "amount": 26228.672978342216, + // "open": 9078.95, + // "close": 9146.86, + // "high": 9155.41, + // "id": 209988544334, + // "count": 265846, + // "low": 8988.0, + // "version": 209988544334, + // "ask": [ 9146.87, 0.156134 ], + // "vol": 2.3822168242201668E8, + // "bid": [ 9146.86, 0.080758 ], + // } + // } + // + const ticker = this.parseTicker (response['tick'], market); + const timestamp = this.safeValue (response, 'ts'); + ticker['timestamp'] = timestamp; + ticker['datetime'] = this.iso8601 (timestamp); + return ticker; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.marketGetTickers (params); + const tickers = this.safeValue (response, 'data'); + const timestamp = this.safeInteger (response, 'ts'); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const marketId = this.safeString (tickers[i], 'symbol'); + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + const ticker = this.parseTicker (tickers[i], market); + ticker['timestamp'] = timestamp; + ticker['datetime'] = this.iso8601 (timestamp); + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "amount": 0.010411000000000000, + // "trade-id": 102090736910, + // "ts": 1583497692182, + // "id": 10500517034273194594947, + // "price": 9096.050000000000000000, + // "direction": "sell" + // } + // + // fetchMyTrades (private) + // + // { + // 'symbol': 'swftcbtc', + // 'fee-currency': 'swftc', + // 'filled-fees': '0', + // 'source': 'spot-api', + // 'id': 83789509854000, + // 'type': 'buy-limit', + // 'order-id': 83711103204909, + // 'filled-points': '0.005826843283532154', + // 'fee-deduct-currency': 'ht', + // 'filled-amount': '45941.53', + // 'price': '0.0000001401', + // 'created-at': 1597933260729, + // 'match-id': 100087455560, + // 'role': 'maker', + // 'trade-id': 100050305348 + // }, + // + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeInteger2 (trade, 'ts', 'created-at'); + const order = this.safeString (trade, 'order-id'); + let side = this.safeString (trade, 'direction'); + let type = this.safeString (trade, 'type'); + if (type !== undefined) { + const typeParts = type.split ('-'); + side = typeParts[0]; + type = typeParts[1]; + } + const takerOrMaker = this.safeString (trade, 'role'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'filled-amount', 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + let fee = undefined; + let feeCost = this.safeFloat (trade, 'filled-fees'); + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee-currency')); + } + const filledPoints = this.safeFloat (trade, 'filled-points'); + if (filledPoints !== undefined) { + if ((feeCost === undefined) || (feeCost === 0.0)) { + feeCost = filledPoints; + feeCurrency = this.safeCurrencyCode (this.safeString (trade, 'fee-deduct-currency')); + } + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const tradeId = this.safeString2 (trade, 'trade-id', 'tradeId'); + const id = this.safeString (trade, 'id', tradeId); + return { + 'id': id, + 'info': trade, + 'order': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['size'] = limit; // 1-100 orders, default is 100 + } + if (since !== undefined) { + request['start-date'] = this.ymd (since); // maximum query window size is 2 days, query window shift should be within past 120 days + } + const response = await this.privateGetOrderMatchresults (this.extend (request, params)); + const trades = this.parseTrades (response['data'], market, since, limit); + return trades; + } + + async fetchTrades (symbol, since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.marketGetHistoryTrade (this.extend (request, params)); + // + // { + // "status": "ok", + // "ch": "market.btcusdt.trade.detail", + // "ts": 1583497692365, + // "data": [ + // { + // "id": 105005170342, + // "ts": 1583497692182, + // "data": [ + // { + // "amount": 0.010411000000000000, + // "trade-id": 102090736910, + // "ts": 1583497692182, + // "id": 10500517034273194594947, + // "price": 9096.050000000000000000, + // "direction": "sell" + // } + // ] + // }, + // // ... + // ] + // } + // + const data = this.safeValue (response, 'data'); + let result = []; + for (let i = 0; i < data.length; i++) { + const trades = this.safeValue (data[i], 'data', []); + for (let j = 0; j < trades.length; j++) { + const trade = this.parseTrade (trades[j], market); + result.push (trade); + } + } + result = this.sortBy (result, 'timestamp'); + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "amount":1.2082, + // "open":0.025096, + // "close":0.025095, + // "high":0.025096, + // "id":1591515300, + // "count":6, + // "low":0.025095, + // "vol":0.0303205097 + // } + // + return [ + this.safeTimestamp (ohlcv, 'id'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'amount'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'period': this.timeframes[timeframe], + }; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.marketGetHistoryKline (this.extend (request, params)); + // + // { + // "status":"ok", + // "ch":"market.ethbtc.kline.1min", + // "ts":1591515374371, + // "data":[ + // {"amount":0.0,"open":0.025095,"close":0.025095,"high":0.025095,"id":1591515360,"count":0,"low":0.025095,"vol":0.0}, + // {"amount":1.2082,"open":0.025096,"close":0.025095,"high":0.025096,"id":1591515300,"count":6,"low":0.025095,"vol":0.0303205097}, + // {"amount":0.0648,"open":0.025096,"close":0.025096,"high":0.025096,"id":1591515240,"count":2,"low":0.025096,"vol":0.0016262208}, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async fetchAccounts (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountAccounts (params); + return response['data']; + } + + async fetchCurrencies (params = {}) { + const request = { + 'language': this.options['language'], + }; + const response = await this.publicGetSettingsCurrencys (this.extend (request, params)); + const currencies = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + // + // { name: "ctxc", + // 'display-name': "CTXC", + // 'withdraw-precision': 8, + // 'currency-type': "eth", + // 'currency-partition': "pro", + // 'support-sites': null, + // 'otc-enable': 0, + // 'deposit-min-amount': "2", + // 'withdraw-min-amount': "4", + // 'show-precision': "8", + // weight: "2988", + // visible: true, + // 'deposit-desc': "Please don’t deposit any other digital assets except CTXC t…", + // 'withdraw-desc': "Minimum withdrawal amount: 4 CTXC. !>_ 0)) { + average = cost / filled; + } + } + const feeCost = this.safeFloat2 (order, 'filled-fees', 'field-fees'); // typo in their API, filled fees + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = (side === 'sell') ? market['quote'] : market['base']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const market = this.market (symbol); + const request = { + 'account-id': this.accounts[0]['id'], + 'symbol': market['id'], + 'type': side + '-' + type, + }; + if ((type === 'market') && (side === 'buy')) { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " market buy order requires price argument to calculate cost (total amount of quote currency to spend for buying, amount * price). To switch off this warning exception and specify cost in the amount argument, set .options['createMarketBuyOrderRequiresPrice'] = false. Make sure you know what you're doing."); + } else { + // despite that cost = amount * price is in quote currency and should have quote precision + // the exchange API requires the cost supplied in 'amount' to be of base precision + // more about it here: + // https://github.com/ccxt/ccxt/pull/4395 + // https://github.com/ccxt/ccxt/issues/7611 + // we use amountToPrecision here because the exchange requires cost in base precision + request['amount'] = this.costToPrecision (symbol, parseFloat (amount) * parseFloat (price)); + } + } else { + request['amount'] = this.costToPrecision (symbol, amount); + } + } else { + request['amount'] = this.amountToPrecision (symbol, amount); + } + if (type === 'limit' || type === 'ioc' || type === 'limit-maker') { + request['price'] = this.priceToPrecision (symbol, price); + } + const method = this.options['createOrderMethod']; + const response = await this[method] (this.extend (request, params)); + const timestamp = this.milliseconds (); + const id = this.safeString (response, 'data'); + return { + 'info': response, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'filled': undefined, + 'remaining': undefined, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + 'clientOrderId': undefined, + 'average': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const response = await this.privatePostOrderOrdersIdSubmitcancel ({ 'id': id }); + // + // let response = { + // 'status': 'ok', + // 'data': '10138899000', + // }; + // + return this.extend (this.parseOrder (response), { + 'id': id, + 'status': 'canceled', + }); + } + + currencyToPrecision (currency, fee) { + return this.decimalToPrecision (fee, 0, this.currencies[currency]['precision']); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let key = 'quote'; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (this.currencyToPrecision (market[key], cost)), + }; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // currency: "eth", + // address: "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", + // addressTag: "", + // chain: "eth" + // } + // + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'addressTag'); + const currencyId = this.safeString (depositAddress, 'currency'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.v2PrivateGetAccountDepositAddress (this.extend (request, params)); + // + // { + // code: 200, + // data: [ + // { + // currency: "eth", + // address: "0xf7292eb9ba7bc50358e27f0e025a4d225a64127b", + // addressTag: "", + // chain: "eth" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseDepositAddress (this.safeValue (data, 0, {}), currency); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + if (limit === undefined || limit > 100) { + limit = 100; + } + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = { + 'type': 'deposit', + 'from': 0, // From 'id' ... if you want to get results after a particular transaction id, pass the id in params.from + }; + if (currency !== undefined) { + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; // max 100 + } + const response = await this.privateGetQueryDepositWithdraw (this.extend (request, params)); + // return response + return this.parseTransactions (response['data'], currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + if (limit === undefined || limit > 100) { + limit = 100; + } + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const request = { + 'type': 'withdraw', + 'from': 0, // From 'id' ... if you want to get results after a particular transaction id, pass the id in params.from + }; + if (currency !== undefined) { + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; // max 100 + } + const response = await this.privateGetQueryDepositWithdraw (this.extend (request, params)); + // return response + return this.parseTransactions (response['data'], currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // 'id': 8211029, + // 'type': 'deposit', + // 'currency': 'eth', + // 'chain': 'eth', + // 'tx-hash': 'bd315....', + // 'amount': 0.81162421, + // 'address': '4b8b....', + // 'address-tag': '', + // 'fee': 0, + // 'state': 'safe', + // 'created-at': 1542180380965, + // 'updated-at': 1542180788077 + // } + // + // fetchWithdrawals + // + // { + // 'id': 6908275, + // 'type': 'withdraw', + // 'currency': 'btc', + // 'chain': 'btc', + // 'tx-hash': 'c1a1a....', + // 'amount': 0.80257005, + // 'address': '1QR....', + // 'address-tag': '', + // 'fee': 0.0005, + // 'state': 'confirmed', + // 'created-at': 1552107295685, + // 'updated-at': 1552108032859 + // } + // + const timestamp = this.safeInteger (transaction, 'created-at'); + const updated = this.safeInteger (transaction, 'updated-at'); + const code = this.safeCurrencyCode (this.safeString (transaction, 'currency')); + let type = this.safeString (transaction, 'type'); + if (type === 'withdraw') { + type = 'withdrawal'; + } + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const tag = this.safeString (transaction, 'address-tag'); + let feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + feeCost = Math.abs (feeCost); + } + return { + 'info': transaction, + 'id': this.safeString (transaction, 'id'), + 'txid': this.safeString (transaction, 'tx-hash'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': this.safeString (transaction, 'address'), + 'tag': tag, + 'type': type, + 'amount': this.safeFloat (transaction, 'amount'), + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': { + 'currency': code, + 'cost': feeCost, + 'rate': undefined, + }, + }; + } + + parseTransactionStatus (status) { + const statuses = { + // deposit statuses + 'unknown': 'failed', + 'confirming': 'pending', + 'confirmed': 'ok', + 'safe': 'ok', + 'orphan': 'failed', + // withdrawal statuses + 'submitted': 'pending', + 'canceled': 'canceled', + 'reexamine': 'pending', + 'reject': 'failed', + 'pass': 'pending', + 'wallet-reject': 'failed', + // 'confirmed': 'ok', // present in deposit statuses + 'confirm-error': 'failed', + 'repealed': 'failed', + 'wallet-transfer': 'pending', + 'pre-transfer': 'pending', + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currency (code); + const request = { + 'address': address, // only supports existing addresses in your withdraw address list + 'amount': amount, + 'currency': currency['id'].toLowerCase (), + }; + if (tag !== undefined) { + request['addr-tag'] = tag; // only for XRP? + } + const response = await this.privatePostDwWithdrawApiCreate (this.extend (request, params)); + const id = this.safeString (response, 'data'); + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/'; + if (api === 'market') { + url += api; + } else if ((api === 'public') || (api === 'private')) { + url += this.version; + } else if ((api === 'v2Public') || (api === 'v2Private')) { + url += 'v2'; + } + url += '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'private' || api === 'v2Private') { + this.checkRequiredCredentials (); + const timestamp = this.ymdhms (this.milliseconds (), 'T'); + let request = { + 'SignatureMethod': 'HmacSHA256', + 'SignatureVersion': '2', + 'AccessKeyId': this.apiKey, + 'Timestamp': timestamp, + }; + if (method !== 'POST') { + request = this.extend (request, query); + } + request = this.keysort (request); + let auth = this.urlencode (request); + // unfortunately, PHP demands double quotes for the escaped newline symbol + // eslint-disable-next-line quotes + const payload = [ method, this.hostname, url, auth ].join ("\n"); + const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64'); + auth += '&' + this.urlencode ({ 'Signature': signature }); + url += '?' + auth; + if (method === 'POST') { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } else { + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + } else { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } + url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }) + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('status' in response) { + // + // {"status":"error","err-code":"order-limitorder-amount-min-error","err-msg":"limit order amount error, min: `0.001`","data":null} + // + const status = this.safeString (response, 'status'); + if (status === 'error') { + const code = this.safeString (response, 'err-code'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + const message = this.safeString (response, 'err-msg'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobiru.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobiru.js new file mode 100644 index 000000000..21e0de149 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/huobiru.js @@ -0,0 +1,31 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const huobipro = require ('./huobipro.js'); + +// --------------------------------------------------------------------------- + +module.exports = class huobiru extends huobipro { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'huobiru', + 'name': 'Huobi Russia', + 'countries': [ 'RU' ], + 'hostname': 'www.huobi.com.ru', + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/52978816-e8552e00-33e3-11e9-98ed-845acfece834.jpg', + 'api': { + 'market': 'https://{hostname}/api', + 'public': 'https://{hostname}/api', + 'private': 'https://{hostname}/api', + }, + 'www': 'https://www.huobi.com.ru/ru-ru', + 'referral': 'https://www.huobi.com.ru/invite?invite_code=esc74', + 'doc': 'https://github.com/cloudapidoc/API_Docs_en', + 'fees': 'https://www.huobi.com.ru/ru-ru/about/fee/', + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ice3x.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ice3x.js new file mode 100644 index 000000000..42e54b79f --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ice3x.js @@ -0,0 +1,494 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class ice3x extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'ice3x', + 'name': 'ICE3X', + 'countries': [ 'ZA' ], // South Africa + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460809-1dd06c00-c616-11ea-98ad-7d5e1cb7fcdd.jpg', + 'api': 'https://ice3x.com/api', + 'www': 'https://ice3x.com', // 'https://ice3x.co.za', + 'doc': 'https://ice3x.co.za/ice-cubed-bitcoin-exchange-api-documentation-1-june-2017', + 'fees': [ + 'https://help.ice3.com/support/solutions/articles/11000033293-trading-fees', + 'https://help.ice3.com/support/solutions/articles/11000033288-fees-explained', + 'https://help.ice3.com/support/solutions/articles/11000008131-what-are-your-fiat-deposit-and-withdrawal-fees-', + 'https://help.ice3.com/support/solutions/articles/11000033289-deposit-fees', + ], + 'referral': 'https://ice3x.com?ref=14341802', + }, + 'api': { + 'public': { + 'get': [ + 'currency/list', + 'currency/info', + 'pair/list', + 'pair/info', + 'stats/marketdepthfull', + 'stats/marketdepthbtcav', + 'stats/marketdepth', + 'orderbook/info', + 'trade/list', + 'trade/info', + ], + }, + 'private': { + 'post': [ + 'balance/list', + 'balance/info', + 'order/new', + 'order/cancel', + 'order/list', + 'order/info', + 'trade/list', + 'trade/info', + 'transaction/list', + 'transaction/info', + 'invoice/list', + 'invoice/info', + 'invoice/pdf', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.005, + 'taker': 0.005, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencyList (params); + const currencies = response['response']['entities']; + const precision = this.precision['amount']; + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currency_id'); + const currencyId = this.safeString (currency, 'iso'); + const code = this.safeCurrencyCode (currencyId); + result[code] = { + 'id': id, + 'code': code, + 'name': currency['name'], + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': currency, + 'fee': undefined, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + if (this.currencies_by_id === undefined) { + this.currencies = await this.fetchCurrencies (); + this.currencies_by_id = this.indexBy (this.currencies, 'id'); + } + const response = await this.publicGetPairList (params); + const markets = this.safeValue (response['response'], 'entities'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'pair_id'); + const baseId = this.safeString (market, 'currency_id_from'); + const quoteId = this.safeString (market, 'currency_id_to'); + const baseCurrency = this.currencies_by_id[baseId]; + const quoteCurrency = this.currencies_by_id[quoteId]; + const base = baseCurrency['code']; + const quote = quoteCurrency['code']; + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'info': market, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + const symbol = market['symbol']; + const last = this.safeFloat (ticker, 'last_price'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max'), + 'low': this.safeFloat (ticker, 'min'), + 'bid': this.safeFloat (ticker, 'max_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'min_ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'vol'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + }; + const response = await this.publicGetStatsMarketdepthfull (this.extend (request, params)); + const ticker = this.safeValue (response['response'], 'entity'); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetStatsMarketdepthfull (params); + const tickers = this.safeValue (response['response'], 'entities'); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const marketId = this.safeString (ticker, 'pair_id'); + const market = this.safeValue (this.marketsById, marketId); + if (market !== undefined) { + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair_id': this.marketId (symbol), + }; + if (limit !== undefined) { + const type = this.safeString (params, 'type'); + if ((type !== 'ask') && (type !== 'bid')) { + // eslint-disable-next-line quotes + throw new ArgumentsRequired (this.id + " fetchOrderBook requires an exchange-specific extra 'type' param ('bid' or 'ask') when used with a limit"); + } else { + request['items_per_page'] = limit; + } + } + const response = await this.publicGetOrderbookInfo (this.extend (request, params)); + const orderbook = this.safeValue (response['response'], 'entities'); + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'amount'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'created'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + const type = 'limit'; + const side = this.safeString (trade, 'type'); + const id = this.safeString (trade, 'trade_id'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + }; + const response = await this.publicGetTradeList (this.extend (request, params)); + const trades = this.safeValue (response['response'], 'entities'); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostBalanceList (params); + const result = { 'info': response }; + const balances = this.safeValue (response['response'], 'entities', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + // currency ids are numeric strings + const currencyId = this.safeString (balance, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrder (order, market = undefined) { + const pairId = this.safeInteger (order, 'pair_id'); + let symbol = undefined; + if (pairId && !market && (pairId in this.marketsById)) { + market = this.marketsById[pairId]; + symbol = market['symbol']; + } + const timestamp = this.safeTimestamp (order, 'created'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + let status = this.safeInteger (order, 'active'); + let remaining = this.safeFloat (order, 'remaining'); + let filled = undefined; + if (status === 1) { + status = 'open'; + } else { + status = 'closed'; + remaining = 0; + filled = amount; + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + }; + if (market !== undefined) { + fee['currency'] = market['quote']; + } + } + return { + 'id': this.safeString (order, 'order_id'), + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': this.safeString (order, 'type'), + 'price': price, + 'stopPrice': undefined, + 'cost': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + 'type': side, + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostOrderNew (this.extend (request, params)); + const order = this.parseOrder ({ + 'order_id': response['response']['entity']['order_id'], + 'created': this.seconds (), + 'active': 1, + 'type': side, + 'price': price, + 'volume': amount, + 'remaining': amount, + 'info': response, + }, market); + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.privatePostOrderCancel (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order _id': id, + }; + const response = await this.privatePostOrderInfo (this.extend (request, params)); + const data = this.safeValue (response, 'response', {}); + const order = this.safeValue (data, 'entity'); + return this.parseOrder (order); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrderList (params); + const data = this.safeValue (response, 'response', {}); + const orders = this.safeValue (data, 'entities', []); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair_id': market['id'], + }; + if (limit !== undefined) { + request['items_per_page'] = limit; + } + if (since !== undefined) { + request['date_from'] = parseInt (since / 1000); + } + const response = await this.privatePostTradeList (this.extend (request, params)); + const data = this.safeValue (response, 'response', {}); + const trades = this.safeValue (data, 'entities', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_id': currency['id'], + }; + const response = await this.privatePostBalanceInfo (this.extend (request, params)); + const data = this.safeValue (response, 'response', {}); + const balance = this.safeValue (data, 'entity', {}); + const address = this.safeString (balance, 'address'); + const status = address ? 'ok' : 'none'; + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'status': status, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'nonce': this.nonce (), + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const errors = this.safeValue (response, 'errors'); + const data = this.safeValue (response, 'response'); + if (errors || !data) { + const authErrorKeys = [ 'Key', 'user_id', 'Sign' ]; + for (let i = 0; i < authErrorKeys.length; i++) { + const errorKey = authErrorKeys[i]; + const errorMessage = this.safeString (errors, errorKey); + if (!errorMessage) { + continue; + } + if (errorKey === 'user_id' && errorMessage.indexOf ('authorization') < 0) { + continue; + } + throw new AuthenticationError (errorMessage); + } + throw new ExchangeError (this.json (errors)); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/idex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/idex.js new file mode 100644 index 000000000..d70fa94f4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/idex.js @@ -0,0 +1,1305 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { PAD_WITH_ZERO } = require ('./base/functions/number'); +const { InvalidOrder, InsufficientFunds, ExchangeError, ExchangeNotAvailable, DDoSProtection, BadRequest, NotSupported, InvalidAddress, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class idex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'idex', + 'name': 'IDEX', + 'countries': [ 'US' ], + 'rateLimit': 1500, + 'version': 'v2', + 'certified': true, + 'pro': true, + 'requiresWeb3': true, + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchOrders': false, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '1d': '1d', + }, + 'urls': { + 'test': { + 'public': 'https://api-sandbox.idex.io', + 'private': 'https://api-sandbox.idex.io', + }, + 'logo': 'https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg', + 'api': { + 'public': 'https://api.idex.io', + 'private': 'https://api.idex.io', + }, + 'www': 'https://idex.io', + 'doc': [ + 'https://docs.idex.io/', + ], + }, + 'api': { + 'public': { + 'get': [ + 'ping', + 'time', + 'exchange', + 'assets', + 'markets', + 'tickers', + 'candles', + 'trades', + 'orderbook', + 'wsToken', + ], + }, + 'private': { + 'get': [ + 'user', + 'wallets', + 'balances', + 'orders', + 'fills', + 'deposits', + 'withdrawals', + ], + 'post': [ + 'wallets', + 'orders', + 'orders/test', + 'withdrawals', + ], + 'delete': [ + 'orders', + ], + }, + }, + 'options': { + 'defaultTimeInForce': 'gtc', + 'defaultSelfTradePrevention': 'cn', + }, + 'exceptions': { + 'INVALID_ORDER_QUANTITY': InvalidOrder, + 'INSUFFICIENT_FUNDS': InsufficientFunds, + 'SERVICE_UNAVAILABLE': ExchangeNotAvailable, + 'EXCEEDED_RATE_LIMIT': DDoSProtection, + 'INVALID_PARAMETER': BadRequest, + 'WALLET_NOT_ASSOCIATED': InvalidAddress, + 'INVALID_WALLET_SIGNATURE': AuthenticationError, + }, + 'requiredCredentials': { + 'walletAddress': true, + 'privateKey': true, + 'apiKey': true, + 'secret': true, + }, + 'paddingMode': PAD_WITH_ZERO, + 'commonCurrencies': {}, + 'requireCredentials': { + 'privateKey': true, + 'apiKey': true, + 'secret': true, + }, + }); + } + + async fetchMarkets (params = {}) { + // [ + // { + // market: 'DIL-ETH', + // status: 'active', + // baseAsset: 'DIL', + // baseAssetPrecision: 8, + // quoteAsset: 'ETH', + // quoteAssetPrecision: 8 + // }, ... + // ] + const response = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const marketId = this.safeString (entry, 'market'); + const baseId = this.safeString (entry, 'baseAsset'); + const quoteId = this.safeString (entry, 'quoteAsset'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const basePrecision = this.safeInteger (entry, 'baseAssetPrecision'); + const quotePrecision = this.safeInteger (entry, 'quoteAssetPrecision'); + const status = this.safeString (entry, 'status'); + const active = status === 'active'; + const precision = { + 'amount': basePrecision, + 'price': quotePrecision, + }; + result.push ({ + 'symbol': symbol, + 'id': marketId, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': entry, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + // [ + // { + // market: 'DIL-ETH', + // time: 1598367493008, + // open: '0.09695361', + // high: '0.10245881', + // low: '0.09572507', + // close: '0.09917079', + // closeQuantity: '0.71320950', + // baseVolume: '309.17380612', + // quoteVolume: '30.57633981', + // percentChange: '2.28', + // numTrades: 205, + // ask: '0.09910476', + // bid: '0.09688340', + // sequence: 3902 + // } + // ] + const response = await this.publicGetTickers (this.extend (request, params)); + const ticker = this.safeValue (response, 0); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + // [ + // { + // market: 'DIL-ETH', + // time: 1598367493008, + // open: '0.09695361', + // high: '0.10245881', + // low: '0.09572507', + // close: '0.09917079', + // closeQuantity: '0.71320950', + // baseVolume: '309.17380612', + // quoteVolume: '30.57633981', + // percentChange: '2.28', + // numTrades: 205, + // ask: '0.09910476', + // bid: '0.09688340', + // sequence: 3902 + // }, ... + // ] + const response = await this.publicGetTickers (params); + return this.parseTickers (response, symbols); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // { + // market: 'DIL-ETH', + // time: 1598367493008, + // open: '0.09695361', + // high: '0.10245881', + // low: '0.09572507', + // close: '0.09917079', + // closeQuantity: '0.71320950', + // baseVolume: '309.17380612', + // quoteVolume: '30.57633981', + // percentChange: '2.28', + // numTrades: 205, + // ask: '0.09910476', + // bid: '0.09688340', + // sequence: 3902 + // } + const marketId = this.safeString (ticker, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + const baseVolume = this.safeFloat (ticker, 'baseVolume'); + const quoteVolume = this.safeFloat (ticker, 'quoteVolume'); + const timestamp = this.safeInteger (ticker, 'time'); + const open = this.safeFloat (ticker, 'open'); + const high = this.safeFloat (ticker, 'high'); + const low = this.safeFloat (ticker, 'low'); + const close = this.safeFloat (ticker, 'close'); + const ask = this.safeFloat (ticker, 'ask'); + const bid = this.safeFloat (ticker, 'bid'); + let percentage = this.safeFloat (ticker, 'percentChange'); + if (percentage !== undefined) { + percentage = 1 + percentage / 100; + } + let change = undefined; + if ((close !== undefined) && (open !== undefined)) { + change = close - open; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'interval': timeframe, + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetCandles (this.extend (request, params)); + if (Array.isArray (response)) { + // [ + // { + // start: 1598345580000, + // open: '0.09771286', + // high: '0.09771286', + // low: '0.09771286', + // close: '0.09771286', + // volume: '1.45340410', + // sequence: 3853 + // }, ... + // ] + return this.parseOHLCVs (response, market, timeframe, since, limit); + } else { + // {"nextTime":1595536440000} + return []; + } + } + + parseOHLCV (ohlcv, market = undefined) { + // { + // start: 1598345580000, + // open: '0.09771286', + // high: '0.09771286', + // low: '0.09771286', + // close: '0.09771286', + // volume: '1.45340410', + // sequence: 3853 + // } + const timestamp = this.safeInteger (ohlcv, 'start'); + const open = this.safeFloat (ohlcv, 'open'); + const high = this.safeFloat (ohlcv, 'high'); + const low = this.safeFloat (ohlcv, 'low'); + const close = this.safeFloat (ohlcv, 'close'); + const volume = this.safeFloat (ohlcv, 'volume'); + return [ timestamp, open, high, low, close, volume ]; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + // [ + // { + // fillId: 'b5467d00-b13e-3fa9-8216-dd66735550fc', + // price: '0.09771286', + // quantity: '1.45340410', + // quoteQuantity: '0.14201627', + // time: 1598345638994, + // makerSide: 'buy', + // sequence: 3853 + // }, ... + // ] + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // public trades + // { + // fillId: 'b5467d00-b13e-3fa9-8216-dd66735550fc', + // price: '0.09771286', + // quantity: '1.45340410', + // quoteQuantity: '0.14201627', + // time: 1598345638994, + // makerSide: 'buy', + // sequence: 3853 + // } + // private trades + // { + // fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478762, + // makerSide: 'sell', + // sequence: 5053, + // market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // side: 'buy', + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65', + // txStatus: 'mined' + // } + const id = this.safeString (trade, 'fillId'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const cost = this.safeFloat (trade, 'quoteQuantity'); + const timestamp = this.safeInteger (trade, 'time'); + const marketId = this.safeString (trade, 'market'); + const symbol = this.safeSymbol (marketId, market, '-'); + // this code handles the duality of public vs private trades + const makerSide = this.safeString (trade, 'makerSide'); + const oppositeSide = (makerSide === 'buy') ? 'sell' : 'buy'; + const side = this.safeString (trade, 'side', oppositeSide); + const takerOrMaker = this.safeString (trade, 'liquidity', 'taker'); + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeAsset'); + fee = { + 'cost': feeCost, + 'currency': this.safeCurrencyCode (feeCurrencyId), + }; + } + const orderId = this.safeString (trade, 'orderId'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'level': 2, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + // { + // sequence: 36416753, + // bids: [ + // [ '0.09672815', '8.22284267', 1 ], + // [ '0.09672814', '1.83685554', 1 ], + // [ '0.09672143', '4.10962617', 1 ], + // [ '0.09658884', '4.03863759', 1 ], + // [ '0.09653781', '3.35730684', 1 ], + // [ '0.09624660', '2.54163586', 1 ], + // [ '0.09617490', '1.93065030', 1 ] + // ], + // asks: [ + // [ '0.09910476', '3.22840154', 1 ], + // [ '0.09940587', '3.39796593', 1 ], + // [ '0.09948189', '4.25088898', 1 ], + // [ '0.09958362', '2.42195784', 1 ], + // [ '0.09974393', '4.25234367', 1 ], + // [ '0.09995250', '3.40192141', 1 ] + // ] + // } + const response = await this.publicGetOrderbook (this.extend (request, params)); + const nonce = this.safeInteger (response, 'sequence'); + return { + 'timestamp': undefined, + 'datetime': undefined, + 'nonce': nonce, + 'bids': this.parseSide (response, 'bids'), + 'asks': this.parseSide (response, 'asks'), + }; + } + + parseSide (book, side) { + const bookSide = this.safeValue (book, side, []); + const result = []; + for (let i = 0; i < bookSide.length; i++) { + const order = bookSide[i]; + const price = this.safeFloat (order, 0); + const amount = this.safeFloat (order, 1); + const orderCount = this.safeInteger (order, 2); + result.push ([ price, amount, orderCount ]); + } + const descending = side === 'bids'; + return this.sortBy (result, 0, descending); + } + + async fetchCurrencies (params = {}) { + // [ + // { + // name: 'Ether', + // symbol: 'ETH', + // contractAddress: '0x0000000000000000000000000000000000000000', + // assetDecimals: 18, + // exchangeDecimals: 8 + // }, .. + // ] + const response = await this.publicGetAssets (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const name = this.safeString (entry, 'name'); + const currencyId = this.safeString (entry, 'symbol'); + const precision = this.safeInteger (entry, 'exchangeDecimals'); + const code = this.safeCurrencyCode (currencyId); + const lot = Math.pow (-10, precision); + result[code] = { + 'id': currencyId, + 'code': code, + 'info': entry, + 'type': undefined, + 'name': name, + 'active': undefined, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { 'min': lot, 'max': undefined }, + 'price': { 'min': lot, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': lot, 'max': undefined }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + const nonce1 = this.uuidv1 (); + const request = { + 'nonce': nonce1, + 'wallet': this.walletAddress, + }; + // [ + // { + // asset: 'DIL', + // quantity: '0.00000000', + // availableForTrade: '0.00000000', + // locked: '0.00000000', + // usdValue: null + // }, ... + // ] + const extendedRequest = this.extend (request, params); + if (extendedRequest['wallet'] === undefined) { + throw new BadRequest (this.id + ' wallet is undefined, set this.walletAddress or "address" in params'); + } + let response = undefined; + try { + response = await this.privateGetBalances (extendedRequest); + } catch (e) { + if (e instanceof InvalidAddress) { + const walletAddress = extendedRequest['wallet']; + await this.associateWallet (walletAddress); + response = await this.privateGetBalances (extendedRequest); + } else { + throw e; + } + } + const result = { + 'info': response, + }; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const currencyId = this.safeString (entry, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const total = this.safeFloat (entry, 'quantity'); + const free = this.safeFloat (entry, 'availableForTrade'); + const used = this.safeFloat (entry, 'locked'); + result[code] = { + 'free': free, + 'used': used, + 'total': total, + }; + } + return this.parseBalance (result); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + let market = undefined; + const request = { + 'nonce': this.uuidv1 (), + 'wallet': this.walletAddress, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + // [ + // { + // fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478762, + // makerSide: 'sell', + // sequence: 5053, + // market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // side: 'buy', + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65', + // txStatus: 'mined' + // } + // ] + const extendedRequest = this.extend (request, params); + if (extendedRequest['wallet'] === undefined) { + throw new BadRequest (this.id + ' walletAddress is undefined, set this.walletAddress or "address" in params'); + } + let response = undefined; + try { + response = await this.privateGetFills (extendedRequest); + } catch (e) { + if (e instanceof InvalidAddress) { + const walletAddress = extendedRequest['wallet']; + await this.associateWallet (walletAddress); + response = await this.privateGetFills (extendedRequest); + } else { + throw e; + } + } + return this.parseTrades (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + const request = { + 'orderId': id, + }; + return await this.fetchOrdersHelper (symbol, undefined, undefined, this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'closed': false, + }; + return await this.fetchOrdersHelper (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'closed': true, + }; + return await this.fetchOrdersHelper (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrdersHelper (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'nonce': this.uuidv1 (), + 'wallet': this.walletAddress, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // fetchClosedOrders / fetchOpenOrders + // [ + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + // } + // ] + // fetchOrder + // { market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // wallet: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1', + // time: 1598873478650, + // status: 'filled', + // type: 'limit', + // side: 'buy', + // originalQuantity: '0.40000000', + // executedQuantity: '0.40000000', + // cumulativeQuoteQuantity: '0.03962396', + // avgExecutionPrice: '0.09905990', + // price: '1.00000000', + // fills: + // [ { fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478650, + // makerSide: 'sell', + // sequence: 5053, + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65', + // txStatus: 'mined' } ] } + if (Array.isArray (response)) { + return this.parseOrders (response, market, since, limit); + } else { + return this.parseOrder (response, market); + } + } + + parseOrderStatus (status) { + // https://docs.idex.io/#order-states-amp-lifecycle + const statuses = { + 'active': 'open', + 'partiallyFilled': 'open', + 'rejected': 'canceled', + 'filled': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "market": "DIL-ETH", + // "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63", + // "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1", + // "time": 1598873478650, + // "status": "filled", + // "type": "limit", + // "side": "buy", + // "originalQuantity": "0.40000000", + // "executedQuantity": "0.40000000", + // "cumulativeQuoteQuantity": "0.03962396", + // "avgExecutionPrice": "0.09905990", + // "price": "1.00000000", + // "fills": [ + // { + // "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472", + // "price": "0.09905990", + // "quantity": "0.40000000", + // "quoteQuantity": "0.03962396", + // "time": 1598873478650, + // "makerSide": "sell", + // "sequence": 5053, + // "fee": "0.00080000", + // "feeAsset": "DIL", + // "gas": "0.00857497", + // "liquidity": "taker", + // "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65", + // "txStatus": "mined" + // } + // ] + // } + // + const timestamp = this.safeInteger (order, 'time'); + const fills = this.safeValue (order, 'fills', []); + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clientOrderId'); + const marketId = this.safeString (order, 'market'); + const side = this.safeString (order, 'side'); + const symbol = this.safeSymbol (marketId, market, '-'); + const trades = this.parseTrades (fills, market); + const type = this.safeString (order, 'type'); + const amount = this.safeFloat (order, 'originalQuantity'); + const filled = this.safeFloat (order, 'executedQuantity'); + let remaining = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = amount - filled; + } + const average = this.safeFloat (order, 'avgExecutionPrice'); + const price = this.safeFloat (order, 'price', average); // for market orders + let cost = undefined; + if ((amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const rawStatus = this.safeString (order, 'status'); + const status = this.parseOrderStatus (rawStatus); + const fee = { + 'currency': undefined, + 'cost': undefined, + }; + let lastTrade = undefined; + for (let i = 0; i < trades.length; i++) { + lastTrade = trades[i]; + fee['currency'] = lastTrade['fee']['currency']; + fee['cost'] = this.sum (fee['cost'], lastTrade['fee']['cost']); + } + const lastTradeTimestamp = this.safeInteger (lastTrade, 'timestamp'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + async associateWallet (walletAddress, params = {}) { + const nonce = this.uuidv1 (); + const noPrefix = this.remove0xPrefix (walletAddress); + const byteArray = [ + this.base16ToBinary (nonce), + this.base16ToBinary (noPrefix), + ]; + const binary = this.binaryConcatArray (byteArray); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + // { + // address: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1', + // totalPortfolioValueUsd: '0.00', + // time: 1598468353626 + // } + const request = { + 'parameters': { + 'nonce': nonce, + 'wallet': walletAddress, + }, + 'signature': signature, + }; + const result = await this.privatePostWallets (request); + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + // https://docs.idex.io/#create-order + this.checkRequiredCredentials (); + await this.loadMarkets (); + const market = this.market (symbol); + const nonce = this.uuidv1 (); + let typeEnum = undefined; + const stopLossTypeEnums = { + 'stopLoss': 3, + 'stopLossLimit': 4, + 'takeProfit': 5, + 'takeProfitLimit': 6, + }; + let stopPriceString = undefined; + if ((type === 'stopLossLimit') || (type === 'takeProfitLimit') || ('stopPrice' in params)) { + if (!('stopPrice' in params)) { + throw new BadRequest (this.id + ' stopPrice is a required parameter for ' + type + 'orders'); + } + stopPriceString = this.priceToPrecision (symbol, params['stopPrice']); + } + const limitTypeEnums = { + 'limit': 1, + 'limitMaker': 2, + }; + let priceString = undefined; + const typeLower = type.toLowerCase (); + const limitOrder = typeLower.indexOf ('limit') > -1; + if (type in limitTypeEnums) { + typeEnum = limitTypeEnums[type]; + priceString = this.priceToPrecision (symbol, price); + } else if (type in stopLossTypeEnums) { + typeEnum = stopLossTypeEnums[type]; + priceString = this.priceToPrecision (symbol, price); + } else if (type === 'market') { + typeEnum = 0; + } else { + throw new BadRequest (this.id + ' ' + type + ' is not a valid order type'); + } + let amountEnum = 0; // base quantity + if ('quoteOrderQuantity' in params) { + if (type !== 'market') { + throw new NotSupported (this.id + ' quoteOrderQuantity is not supported for ' + type + ' orders, only supported for market orders'); + } + amountEnum = 1; + amount = this.safeFloat (params, 'quoteOrderQuantity'); + } + const sideEnum = (side === 'buy') ? 0 : 1; + const walletBytes = this.remove0xPrefix (this.walletAddress); + const orderVersion = 1; + const amountString = this.amountToPrecision (symbol, amount); + // https://docs.idex.io/#time-in-force + const timeInForceEnums = { + 'gtc': 0, + 'ioc': 2, + 'fok': 3, + }; + const defaultTimeInForce = this.safeString (this.options, 'defaultTimeInForce', 'gtc'); + const timeInForce = this.safeString (params, 'timeInForce', defaultTimeInForce); + let timeInForceEnum = undefined; + if (timeInForce in timeInForceEnums) { + timeInForceEnum = timeInForceEnums[timeInForce]; + } else { + const allOptions = Object.keys (timeInForceEnums); + const asString = allOptions.join (', '); + throw new BadRequest (this.id + ' ' + timeInForce + ' is not a valid timeInForce, please choose one of ' + asString); + } + // https://docs.idex.io/#self-trade-prevention + const selfTradePreventionEnums = { + 'dc': 0, + 'co': 1, + 'cn': 2, + 'cb': 3, + }; + const defaultSelfTradePrevention = this.safeString (this.options, 'defaultSelfTradePrevention', 'cn'); + const selfTradePrevention = this.safeString (params, 'selfTradePrevention', defaultSelfTradePrevention); + let selfTradePreventionEnum = undefined; + if (selfTradePrevention in selfTradePreventionEnums) { + selfTradePreventionEnum = selfTradePreventionEnums[selfTradePrevention]; + } else { + const allOptions = Object.keys (selfTradePreventionEnums); + const asString = allOptions.join (', '); + throw new BadRequest (this.id + ' ' + selfTradePrevention + ' is not a valid selfTradePrevention, please choose one of ' + asString); + } + const byteArray = [ + this.numberToBE (orderVersion, 1), + this.base16ToBinary (nonce), + this.base16ToBinary (walletBytes), + this.stringToBinary (this.encode (market['id'])), // TODO: refactor to remove either encode or stringToBinary + this.numberToBE (typeEnum, 1), + this.numberToBE (sideEnum, 1), + this.stringToBinary (this.encode (amountString)), + this.numberToBE (amountEnum, 1), + ]; + if (limitOrder) { + const encodedPrice = this.stringToBinary (this.encode (priceString)); + byteArray.push (encodedPrice); + } + if (type in stopLossTypeEnums) { + const encodedPrice = this.stringToBinary (this.encode (stopPriceString || priceString)); + byteArray.push (encodedPrice); + } + const clientOrderId = this.safeString (params, 'clientOrderId'); + if (clientOrderId !== undefined) { + byteArray.push (this.stringToBinary (this.encode (clientOrderId))); + } + const after = [ + this.numberToBE (timeInForceEnum, 1), + this.numberToBE (selfTradePreventionEnum, 1), + this.numberToBE (0, 8), // unused + ]; + const allBytes = this.arrayConcat (byteArray, after); + const binary = this.binaryConcatArray (allBytes); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + const request = { + 'parameters': { + 'nonce': nonce, + 'market': market['id'], + 'side': side, + 'type': type, + 'wallet': this.walletAddress, + 'timeInForce': timeInForce, + 'selfTradePrevention': selfTradePrevention, + }, + 'signature': signature, + }; + if (limitOrder) { + request['parameters']['price'] = priceString; + } + if (type in stopLossTypeEnums) { + request['parameters']['stopPrice'] = stopPriceString || priceString; + } + if (amountEnum === 0) { + request['parameters']['quantity'] = amountString; + } else { + request['parameters']['quoteOrderQuantity'] = amountString; + } + if (clientOrderId !== undefined) { + request['parameters']['clientOrderId'] = clientOrderId; + } + // { + // market: 'DIL-ETH', + // orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63', + // wallet: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1', + // time: 1598873478650, + // status: 'filled', + // type: 'limit', + // side: 'buy', + // originalQuantity: '0.40000000', + // executedQuantity: '0.40000000', + // cumulativeQuoteQuantity: '0.03962396', + // price: '1.00000000', + // fills: [ + // { + // fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472', + // price: '0.09905990', + // quantity: '0.40000000', + // quoteQuantity: '0.03962396', + // time: 1598873478650, + // makerSide: 'sell', + // sequence: 5053, + // fee: '0.00080000', + // feeAsset: 'DIL', + // gas: '0.00857497', + // liquidity: 'taker', + // txStatus: 'pending' + // } + // ], + // avgExecutionPrice: '0.09905990' + // } + // we don't use extend here because it is a signed endpoint + const response = await this.privatePostOrders (request); + return this.parseOrder (response, market); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + const nonce = this.uuidv1 (); + const amountString = this.currencyToPrecision (code, amount); + const currency = this.currency (code); + const walletBytes = this.remove0xPrefix (this.walletAddress); + const byteArray = [ + this.base16ToBinary (nonce), + this.base16ToBinary (walletBytes), + this.stringToBinary (this.encode (currency['id'])), + this.stringToBinary (this.encode (amountString)), + this.numberToBE (1, 1), // bool set to true + ]; + const binary = this.binaryConcatArray (byteArray); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + const request = { + 'parameters': { + 'nonce': nonce, + 'wallet': address, + 'asset': currency['id'], + 'quantity': amountString, + }, + 'signature': signature, + }; + // { + // withdrawalId: 'a61dcff0-ec4d-11ea-8b83-c78a6ecb3180', + // asset: 'ETH', + // assetContractAddress: '0x0000000000000000000000000000000000000000', + // quantity: '0.20000000', + // time: 1598962883190, + // fee: '0.00024000', + // txStatus: 'pending', + // txId: null + // } + const response = await this.privatePostWithdrawals (request); + const id = this.safeString (response, 'withdrawalId'); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + this.checkRequiredCredentials (); + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const nonce = this.uuidv1 (); + const walletBytes = this.remove0xPrefix (this.walletAddress); + const byteArray = [ + this.base16ToBinary (nonce), + this.base16ToBinary (walletBytes), + this.stringToBinary (this.encode (id)), + ]; + const binary = this.binaryConcatArray (byteArray); + const hash = this.hash (binary, 'keccak', 'hex'); + const signature = this.signMessageString (hash, this.privateKey); + const request = { + 'parameters': { + 'nonce': nonce, + 'wallet': this.walletAddress, + 'orderId': id, + }, + 'signature': signature, + }; + // [ { orderId: '688336f0-ec50-11ea-9842-b332f8a34d0e' } ] + const response = await this.privateDeleteOrders (this.extend (request, params)); + const canceledOrder = this.safeValue (response, 0); + return this.parseOrder (canceledOrder, market); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + if (errorCode in this.exceptions) { + const Exception = this.exceptions[errorCode]; + throw new Exception (this.id + ' ' + message); + } + if (errorCode !== undefined) { + throw new ExchangeError (this.id + ' ' + message); + } + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + params = this.extend ({ + 'method': 'privateGetDeposits', + }, params); + return this.fetchTransactionsHelper (code, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + params = this.extend ({ + 'method': 'privateGetWithdrawals', + }, params); + return this.fetchTransactionsHelper (code, since, limit, params); + } + + async fetchTransactionsHelper (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const nonce = this.uuidv1 (); + const request = { + 'nonce': nonce, + 'wallet': this.walletAddress, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + // [ + // { + // depositId: 'e9970cc0-eb6b-11ea-9e89-09a5ebc1f98e', + // asset: 'ETH', + // quantity: '1.00000000', + // txId: '0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142', + // txTime: 1598865853000, + // confirmationTime: 1598865930231 + // } + // ] + const method = params['method']; + params = this.omit (params, 'method'); + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'mined': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // fetchDeposits + // { + // depositId: 'e9970cc0-eb6b-11ea-9e89-09a5ebc1f98f', + // asset: 'ETH', + // quantity: '1.00000000', + // txId: '0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142', + // txTime: 1598865853000, + // confirmationTime: 1598865930231 + // } + // fetchWithdrwalas + // { + // withdrawalId: 'a62d8760-ec4d-11ea-9fa6-47904c19499b', + // asset: 'ETH', + // assetContractAddress: '0x0000000000000000000000000000000000000000', + // quantity: '0.20000000', + // time: 1598962883288, + // fee: '0.00024000', + // txId: '0x305e9cdbaa85ad029f50578d13d31d777c085de573ed5334d95c19116d8c03ce', + // txStatus: 'mined' + // } + let type = undefined; + if ('depositId' in transaction) { + type = 'deposit'; + } else if ('withdrawalId' in transaction) { + type = 'withdrawal'; + } + const id = this.safeString2 (transaction, 'depositId', 'withdrawId'); + const code = this.safeCurrencyCode (this.safeString (transaction, 'asset'), currency); + const amount = this.safeFloat (transaction, 'quantity'); + const txid = this.safeString (transaction, 'txId'); + const timestamp = this.safeInteger (transaction, 'txTime'); + let fee = undefined; + if ('fee' in transaction) { + fee = { + 'cost': this.safeFloat (transaction, 'fee'), + 'currency': 'ETH', + }; + } + const rawStatus = this.safeString (transaction, 'txStatus'); + const status = this.parseTransactionStatus (rawStatus); + const updated = this.safeInteger (transaction, 'confirmationTime'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': undefined, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const version = this.safeString (this.options, 'version', 'v1'); + let url = this.urls['api'][api] + '/' + version + '/' + path; + const keys = Object.keys (params); + const length = keys.length; + let query = undefined; + if (length > 0) { + if (method === 'GET') { + query = this.urlencode (params); + url = url + '?' + query; + } else { + body = this.json (params); + } + } + headers = { + 'Content-Type': 'application/json', + }; + if (this.apiKey !== undefined) { + headers['IDEX-API-Key'] = this.apiKey; + } + if (api === 'private') { + let payload = undefined; + if (method === 'GET') { + payload = query; + } else { + payload = body; + } + headers['IDEX-HMAC-Signature'] = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'hex'); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/independentreserve.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/independentreserve.js new file mode 100644 index 000000000..30f75d9a6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/independentreserve.js @@ -0,0 +1,504 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class independentreserve extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'independentreserve', + 'name': 'Independent Reserve', + 'countries': [ 'AU', 'NZ' ], // Australia, New Zealand + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182090-1e9e9080-c2ec-11ea-8e49-563db9a38f37.jpg', + 'api': { + 'public': 'https://api.independentreserve.com/Public', + 'private': 'https://api.independentreserve.com/Private', + }, + 'www': 'https://www.independentreserve.com', + 'doc': 'https://www.independentreserve.com/API', + }, + 'api': { + 'public': { + 'get': [ + 'GetValidPrimaryCurrencyCodes', + 'GetValidSecondaryCurrencyCodes', + 'GetValidLimitOrderTypes', + 'GetValidMarketOrderTypes', + 'GetValidOrderTypes', + 'GetValidTransactionTypes', + 'GetMarketSummary', + 'GetOrderBook', + 'GetAllOrders', + 'GetTradeHistorySummary', + 'GetRecentTrades', + 'GetFxRates', + ], + }, + 'private': { + 'post': [ + 'PlaceLimitOrder', + 'PlaceMarketOrder', + 'CancelOrder', + 'GetOpenOrders', + 'GetClosedOrders', + 'GetClosedFilledOrders', + 'GetOrderDetails', + 'GetAccounts', + 'GetTransactions', + 'GetDigitalCurrencyDepositAddress', + 'GetDigitalCurrencyDepositAddresses', + 'SynchDigitalCurrencyDepositAddressWithBlockchain', + 'WithdrawDigitalCurrency', + 'RequestFiatWithdrawal', + 'GetTrades', + 'GetBrokerageFees', + ], + }, + }, + 'fees': { + 'trading': { + 'taker': 0.5 / 100, + 'maker': 0.5 / 100, + 'percentage': true, + 'tierBased': false, + }, + }, + 'commonCurrencies': { + 'PLA': 'PlayChip', + }, + }); + } + + async fetchMarkets (params = {}) { + const baseCurrencies = await this.publicGetGetValidPrimaryCurrencyCodes (params); + const quoteCurrencies = await this.publicGetGetValidSecondaryCurrencyCodes (params); + const result = []; + for (let i = 0; i < baseCurrencies.length; i++) { + const baseId = baseCurrencies[i]; + const base = this.safeCurrencyCode (baseId); + for (let j = 0; j < quoteCurrencies.length; j++) { + const quoteId = quoteCurrencies[j]; + const quote = this.safeCurrencyCode (quoteId); + const id = baseId + '/' + quoteId; + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': id, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privatePostGetAccounts (params); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'CurrencyCode'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'AvailableBalance'); + account['total'] = this.safeFloat (balance, 'TotalBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + }; + const response = await this.publicGetGetOrderBook (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (response, 'CreatedTimestampUtc')); + return this.parseOrderBook (response, timestamp, 'BuyOrders', 'SellOrders', 'Price', 'Volume'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.parse8601 (this.safeString (ticker, 'CreatedTimestampUtc')); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'LastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'DayHighestPrice'), + 'low': this.safeFloat (ticker, 'DayLowestPrice'), + 'bid': this.safeFloat (ticker, 'CurrentHighestBidPrice'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'CurrentLowestOfferPrice'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'DayAvgPrice'), + 'baseVolume': this.safeFloat (ticker, 'DayVolumeXbtInSecondaryCurrrency'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + }; + const response = await this.publicGetGetMarketSummary (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // "OrderGuid": "c7347e4c-b865-4c94-8f74-d934d4b0b177", + // "CreatedTimestampUtc": "2014-09-23T12:39:34.3817763Z", + // "Type": "MarketBid", + // "VolumeOrdered": 5.0, + // "VolumeFilled": 5.0, + // "Price": null, + // "AvgPrice": 100.0, + // "ReservedAmount": 0.0, + // "Status": "Filled", + // "PrimaryCurrencyCode": "Xbt", + // "SecondaryCurrencyCode": "Usd" + // } + // + // fetchOpenOrders & fetchClosedOrders + // + // { + // "OrderGuid": "b8f7ad89-e4e4-4dfe-9ea3-514d38b5edb3", + // "CreatedTimestampUtc": "2020-09-08T03:04:18.616367Z", + // "OrderType": "LimitOffer", + // "Volume": 0.0005, + // "Outstanding": 0.0005, + // "Price": 113885.83, + // "AvgPrice": 113885.83, + // "Value": 56.94, + // "Status": "Open", + // "PrimaryCurrencyCode": "Xbt", + // "SecondaryCurrencyCode": "Usd", + // "FeePercent": 0.005, + // } + // + let symbol = undefined; + const baseId = this.safeString (order, 'PrimaryCurrencyCode'); + const quoteId = this.safeString (order, 'SecondaryCurrencyCode'); + let base = undefined; + let quote = undefined; + if ((baseId !== undefined) && (quoteId !== undefined)) { + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else if (market !== undefined) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + let orderType = this.safeString2 (order, 'Type', 'OrderType'); + let side = undefined; + if (orderType.indexOf ('Bid') >= 0) { + side = 'buy'; + } else if (orderType.indexOf ('Offer') >= 0) { + side = 'sell'; + } + if (orderType.indexOf ('Market') >= 0) { + orderType = 'market'; + } else if (orderType.indexOf ('Limit') >= 0) { + orderType = 'limit'; + } + const timestamp = this.parse8601 (this.safeString (order, 'CreatedTimestampUtc')); + const amount = this.safeFloat2 (order, 'VolumeOrdered', 'Volume'); + let filled = this.safeFloat (order, 'VolumeFilled'); + let remaining = this.safeFloat (order, 'Outstanding'); + if (filled === undefined) { + if ((remaining !== undefined) && (amount !== undefined)) { + filled = Math.max (0, amount - remaining); + } + } + if (remaining === undefined) { + if ((filled !== undefined) && (amount !== undefined)) { + remaining = Math.max (0, amount - filled); + } + } + const feeRate = this.safeFloat (order, 'FeePercent'); + let feeCost = undefined; + if (feeRate !== undefined) { + feeCost = feeRate * filled; + } + const fee = { + 'rate': feeRate, + 'cost': feeCost, + 'currency': base, + }; + const id = this.safeString (order, 'OrderGuid'); + const status = this.parseOrderStatus (this.safeString (order, 'Status')); + const cost = this.safeFloat (order, 'Value'); + const average = this.safeFloat (order, 'AvgPrice'); + const price = this.safeFloat (order, 'Price', average); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'Open': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'PartiallyFilledAndCancelled': 'canceled', + 'Cancelled': 'canceled', + 'PartiallyFilledAndExpired': 'canceled', + 'Expired': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetOrderDetails (this.extend ({ + 'orderGuid': id, + }, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrder (response, market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = this.ordered ({}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['primaryCurrencyCode'] = market['baseId']; + request['secondaryCurrencyCode'] = market['quoteId']; + } + if (limit === undefined) { + limit = 50; + } + request['pageIndex'] = 1; + request['pageSize'] = limit; + const response = await this.privatePostGetOpenOrders (this.extend (request, params)); + const data = this.safeValue (response, 'Data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = this.ordered ({}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['primaryCurrencyCode'] = market['baseId']; + request['secondaryCurrencyCode'] = market['quoteId']; + } + if (limit === undefined) { + limit = 50; + } + request['pageIndex'] = 1; + request['pageSize'] = limit; + const response = await this.privatePostGetClosedOrders (this.extend (request, params)); + const data = this.safeValue (response, 'Data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = 50, params = {}) { + await this.loadMarkets (); + const pageIndex = this.safeInteger (params, 'pageIndex', 1); + if (limit === undefined) { + limit = 50; + } + const request = this.ordered ({ + 'pageIndex': pageIndex, + 'pageSize': limit, + }); + const response = await this.privatePostGetTrades (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (response['Data'], market, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (trade['TradeTimestampUtc']); + const id = this.safeString (trade, 'TradeGuid'); + const orderId = this.safeString (trade, 'OrderGuid'); + const price = this.safeFloat2 (trade, 'Price', 'SecondaryCurrencyTradePrice'); + const amount = this.safeFloat2 (trade, 'VolumeTraded', 'PrimaryCurrencyAmount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let side = this.safeString (trade, 'OrderType'); + if (side !== undefined) { + if (side.indexOf ('Bid') >= 0) { + side = 'buy'; + } else if (side.indexOf ('Offer') >= 0) { + side = 'sell'; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + 'numberOfRecentTradesToRetrieve': 50, // max = 50 + }; + const response = await this.publicGetGetRecentTrades (this.extend (request, params)); + return this.parseTrades (response['Trades'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const capitalizedOrderType = this.capitalize (type); + const method = 'privatePostPlace' + capitalizedOrderType + 'Order'; + let orderType = capitalizedOrderType; + orderType += (side === 'sell') ? 'Offer' : 'Bid'; + const request = this.ordered ({ + 'primaryCurrencyCode': market['baseId'], + 'secondaryCurrencyCode': market['quoteId'], + 'orderType': orderType, + }); + if (type === 'limit') { + request['price'] = price; + } + request['volume'] = amount; + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['OrderGuid'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderGuid': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const auth = [ + url, + 'apiKey=' + this.apiKey, + 'nonce=' + nonce.toString (), + ]; + const keys = Object.keys (params); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = params[key].toString (); + auth.push (key + '=' + value); + } + const message = auth.join (','); + const signature = this.hmac (this.encode (message), this.encode (this.secret)); + const query = this.ordered ({}); + query['apiKey'] = this.apiKey; + query['nonce'] = nonce; + query['signature'] = signature.toUpperCase (); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + query[key] = params[key]; + } + body = this.json (query); + headers = { 'Content-Type': 'application/json' }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/indodax.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/indodax.js new file mode 100644 index 000000000..30457eca2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/indodax.js @@ -0,0 +1,618 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InsufficientFunds, InvalidOrder, OrderNotFound, AuthenticationError, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class indodax extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'indodax', + 'name': 'INDODAX', + 'countries': [ 'ID' ], // Indonesia + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTime': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'version': '2.0', // as of 9 April 2018 + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87070508-9358c880-c221-11ea-8dc5-5391afbbb422.jpg', + 'api': { + 'public': 'https://indodax.com/api', + 'private': 'https://indodax.com/tapi', + }, + 'www': 'https://www.indodax.com', + 'doc': 'https://github.com/btcid/indodax-official-api-docs', + 'referral': 'https://indodax.com/ref/testbitcoincoid/1', + }, + 'api': { + 'public': { + 'get': [ + 'server_time', + 'pairs', + '{pair}/ticker', + '{pair}/trades', + '{pair}/depth', + ], + }, + 'private': { + 'post': [ + 'getInfo', + 'transHistory', + 'trade', + 'tradeHistory', + 'getOrder', + 'openOrders', + 'cancelOrder', + 'orderHistory', + 'withdrawCoin', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0, + 'taker': 0.003, + }, + }, + 'exceptions': { + 'exact': { + 'invalid_pair': BadSymbol, // {"error":"invalid_pair","error_description":"Invalid Pair"} + 'Insufficient balance.': InsufficientFunds, + 'invalid order.': OrderNotFound, + 'Invalid credentials. API not found or session has expired.': AuthenticationError, + 'Invalid credentials. Bad sign.': AuthenticationError, + }, + 'broad': { + 'Minimum price': InvalidOrder, + 'Minimum order': InvalidOrder, + }, + }, + // exchange-specific options + 'options': { + 'recvWindow': 5 * 1000, // default 5 sec + 'timeDifference': 0, // the difference between system clock and exchange clock + 'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation + }, + 'commonCurrencies': { + 'STR': 'XLM', + 'BCHABC': 'BCH', + 'BCHSV': 'BSV', + 'DRK': 'DASH', + 'NEM': 'XEM', + }, + }); + } + + nonce () { + return this.milliseconds () - this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetServerTime (params); + // + // { + // "timezone": "UTC", + // "server_time": 1571205969552 + // } + // + return this.safeInteger (response, 'server_time'); + } + + async loadTimeDifference (params = {}) { + const serverTime = await this.fetchTime (params); + const after = this.milliseconds (); + this.options['timeDifference'] = after - serverTime; + return this.options['timeDifference']; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPairs (params); + // + // [ + // { + // "id": "btcidr", + // "symbol": "BTCIDR", + // "base_currency": "idr", + // "traded_currency": "btc", + // "traded_currency_unit": "BTC", + // "description": "BTC/IDR", + // "ticker_id": "btc_idr", + // "volume_precision": 0, + // "price_precision": 1000, + // "price_round": 8, + // "pricescale": 1000, + // "trade_min_base_currency": 10000, + // "trade_min_traded_currency": 0.00007457, + // "has_memo": false, + // "memo_name": false, + // "has_payment_id": false, + // "trade_fee_percent": 0.3, + // "url_logo": "https://indodax.com/v2/logo/svg/color/btc.svg", + // "url_logo_png": "https://indodax.com/v2/logo/png/color/btc.png", + // "is_maintenance": 0 + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'ticker_id'); + const baseId = this.safeString (market, 'traded_currency'); + const quoteId = this.safeString (market, 'base_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const taker = this.safeFloat (market, 'trade_fee_percent'); + const isMaintenance = this.safeInteger (market, 'is_maintenance'); + const active = (isMaintenance) ? false : true; + const pricePrecision = this.safeInteger (market, 'price_round'); + const precision = { + 'amount': 8, + 'price': pricePrecision, + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'trade_min_traded_currency'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (market, 'trade_min_base_currency'), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'taker': taker, + 'percentage': true, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfo (params); + const balances = this.safeValue (response, 'return', {}); + const free = this.safeValue (balances, 'balance', {}); + const used = this.safeValue (balances, 'balance_hold', {}); + const result = { 'info': response }; + const currencyIds = Object.keys (free); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (free, currencyId); + account['used'] = this.safeFloat (used, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const orderbook = await this.publicGetPairDepth (this.extend (request, params)); + return this.parseOrderBook (orderbook, undefined, 'buy', 'sell'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTicker (this.extend (request, params)); + // + // { + // "ticker": { + // "high":"0.01951", + // "low":"0.01877", + // "vol_eth":"39.38839319", + // "vol_btc":"0.75320886", + // "last":"0.01896", + // "buy":"0.01896", + // "sell":"0.019", + // "server_time":1565248908 + // } + // } + // + const ticker = response['ticker']; + const timestamp = this.safeTimestamp (ticker, 'server_time'); + const baseVolume = 'vol_' + market['baseId'].toLowerCase (); + const quoteVolume = 'vol_' + market['quoteId'].toLowerCase (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, baseVolume), + 'quoteVolume': this.safeFloat (ticker, quoteVolume), + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const type = undefined; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetPairTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'open': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "order_id": "12345", + // "submit_time": "1392228122", + // "price": "8000000", + // "type": "sell", + // "order_ltc": "100000000", + // "remain_ltc": "100000000" + // } + // + let side = undefined; + if ('type' in order) { + side = order['type']; + } + const status = this.parseOrderStatus (this.safeString (order, 'status', 'open')); + let symbol = undefined; + let cost = undefined; + const price = this.safeFloat (order, 'price'); + let amount = undefined; + let remaining = undefined; + let filled = undefined; + if (market !== undefined) { + symbol = market['symbol']; + let quoteId = market['quoteId']; + let baseId = market['baseId']; + if ((market['quoteId'] === 'idr') && ('order_rp' in order)) { + quoteId = 'rp'; + } + if ((market['baseId'] === 'idr') && ('remain_rp' in order)) { + baseId = 'rp'; + } + cost = this.safeFloat (order, 'order_' + quoteId); + if (cost) { + amount = cost / price; + const remainingCost = this.safeFloat (order, 'remain_' + quoteId); + if (remainingCost !== undefined) { + remaining = remainingCost / price; + filled = amount - remaining; + } + } else { + amount = this.safeFloat (order, 'order_' + baseId); + cost = price * amount; + remaining = this.safeFloat (order, 'remain_' + baseId); + filled = amount - remaining; + } + } + let average = undefined; + if (filled) { + average = cost / filled; + } + const timestamp = this.safeInteger (order, 'submit_time'); + const fee = undefined; + const id = this.safeString (order, 'order_id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'order_id': id, + }; + const response = await this.privatePostGetOrder (this.extend (request, params)); + const orders = response['return']; + const order = this.parseOrder (this.extend ({ 'id': id }, orders['order']), market); + return this.extend ({ 'info': response }, order); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostOpenOrders (this.extend (request, params)); + const rawOrders = response['return']['orders']; + // { success: 1, return: { orders: null }} if no orders + if (!rawOrders) { + return []; + } + // { success: 1, return: { orders: [ ... objects ] }} for orders fetched by symbol + if (symbol !== undefined) { + return this.parseOrders (rawOrders, market, since, limit); + } + // { success: 1, return: { orders: { marketid: [ ... objects ] }}} if all orders are fetched + const marketIds = Object.keys (rawOrders); + let exchangeOrders = []; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const marketOrders = rawOrders[marketId]; + market = this.markets_by_id[marketId]; + const parsedOrders = this.parseOrders (marketOrders, market, since, limit); + exchangeOrders = this.arrayConcat (exchangeOrders, parsedOrders); + } + return exchangeOrders; + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostOrderHistory (this.extend (request, params)); + let orders = this.parseOrders (response['return']['orders'], market); + orders = this.filterBy (orders, 'status', 'closed'); + return this.filterBySymbolSinceLimit (orders, symbol, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'price': price, + }; + const currency = market['baseId']; + if (side === 'buy') { + request[market['quoteId']] = amount * price; + } else { + request[market['baseId']] = amount; + } + request[currency] = amount; + const result = await this.privatePostTrade (this.extend (request, params)); + const data = this.safeValue (result, 'return', {}); + const id = this.safeString (data, 'order_id'); + return { + 'info': result, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + const side = this.safeValue (params, 'side'); + if (side === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires an extra "side" param'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'order_id': id, + 'pair': market['id'], + 'type': side, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + // Custom string you need to provide to identify each withdrawal. + // Will be passed to callback URL (assigned via website to the API key) + // so your system can identify the request and confirm it. + // Alphanumeric, max length 255. + const requestId = this.milliseconds (); + // Alternatively: + // let requestId = this.uuid (); + const request = { + 'currency': currency['id'], + 'withdraw_amount': amount, + 'withdraw_address': address, + 'request_id': requestId.toString (), + }; + if (tag) { + request['withdraw_memo'] = tag; + } + const response = await this.privatePostWithdrawCoin (this.extend (request, params)); + // + // { + // "success": 1, + // "status": "approved", + // "withdraw_currency": "xrp", + // "withdraw_address": "rwWr7KUZ3ZFwzgaDGjKBysADByzxvohQ3C", + // "withdraw_amount": "10000.00000000", + // "fee": "2.00000000", + // "amount_after_fee": "9998.00000000", + // "submit_time": "1509469200", + // "withdraw_id": "xrp-12345", + // "txid": "", + // "withdraw_memo": "123123" + // } + // + let id = undefined; + if (('txid' in response) && (response['txid'].length > 0)) { + id = response['txid']; + } + return { + 'info': response, + 'id': id, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api === 'public') { + url += '/' + this.implodeParams (path, params); + } else { + this.checkRequiredCredentials (); + body = this.urlencode (this.extend ({ + 'method': path, + 'timestamp': this.nonce (), + 'recvWindow': this.options['recvWindow'], + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // { success: 0, error: "invalid order." } + // or + // [{ data, ... }, { ... }, ... ] + if (Array.isArray (response)) { + return; // public endpoints may return []-arrays + } + const error = this.safeValue (response, 'error', ''); + if (!('success' in response) && error === '') { + return; // no 'success' property on public responses + } + if (this.safeInteger (response, 'success', 0) === 1) { + // { success: 1, return: { orders: [] }} + if (!('return' in response)) { + throw new ExchangeError (this.id + ': malformed response: ' + this.json (response)); + } else { + return; + } + } + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/itbit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/itbit.js new file mode 100644 index 000000000..f26cf47eb --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/itbit.js @@ -0,0 +1,650 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class itbit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'itbit', + 'name': 'itBit', + 'countries': [ 'US' ], + 'rateLimit': 2000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchTransactions': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27822159-66153620-60ad-11e7-89e7-005f6d7f3de0.jpg', + 'api': 'https://api.itbit.com', + 'www': 'https://www.itbit.com', + 'doc': [ + 'https://api.itbit.com/docs', + 'https://www.itbit.com/api', + ], + }, + 'api': { + 'public': { + 'get': [ + 'markets/{symbol}/ticker', + 'markets/{symbol}/order_book', + 'markets/{symbol}/trades', + ], + }, + 'private': { + 'get': [ + 'wallets', + 'wallets/{walletId}', + 'wallets/{walletId}/balances/{currencyCode}', + 'wallets/{walletId}/funding_history', + 'wallets/{walletId}/trades', + 'wallets/{walletId}/orders', + 'wallets/{walletId}/orders/{id}', + ], + 'post': [ + 'wallet_transfers', + 'wallets', + 'wallets/{walletId}/cryptocurrency_deposits', + 'wallets/{walletId}/cryptocurrency_withdrawals', + 'wallets/{walletId}/orders', + 'wire_withdrawal', + ], + 'delete': [ + 'wallets/{walletId}/orders/{id}', + ], + }, + }, + 'markets': { + 'BTC/USD': { 'id': 'XBTUSD', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'XBT', 'quoteId': 'USD' }, + 'BTC/SGD': { 'id': 'XBTSGD', 'symbol': 'BTC/SGD', 'base': 'BTC', 'quote': 'SGD', 'baseId': 'XBT', 'quoteId': 'SGD' }, + 'BTC/EUR': { 'id': 'XBTEUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'XBT', 'quoteId': 'EUR' }, + 'ETH/USD': { 'id': 'ETHUSD', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD', 'baseId': 'ETH', 'quoteId': 'USD' }, + 'ETH/EUR': { 'id': 'ETHEUR', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR', 'baseId': 'ETH', 'quoteId': 'EUR' }, + 'ETH/SGD': { 'id': 'ETHSGD', 'symbol': 'ETH/SGD', 'base': 'ETH', 'quote': 'SGD', 'baseId': 'ETH', 'quoteId': 'SGD' }, + 'PAXGUSD': { 'id': 'PAXGUSD', 'symbol': 'PAXG/USD', 'base': 'PAXG', 'quote': 'USD', 'baseId': 'PAXG', 'quoteId': 'USD' }, + 'BCHUSD': { 'id': 'BCHUSD', 'symbol': 'BCH/USD', 'base': 'BCH', 'quote': 'USD', 'baseId': 'BCH', 'quoteId': 'USD' }, + 'LTCUSD': { 'id': 'LTCUSD', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD', 'baseId': 'LTC', 'quoteId': 'USD' }, + }, + 'fees': { + 'trading': { + 'maker': -0.03 / 100, + 'taker': 0.35 / 100, + }, + }, + 'commonCurrencies': { + 'XBT': 'BTC', + }, + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const orderbook = await this.publicGetMarketsSymbolOrderBook (this.extend (request, params)); + return this.parseOrderBook (orderbook); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const ticker = await this.publicGetMarketsSymbolTicker (this.extend (request, params)); + const serverTimeUTC = this.safeString (ticker, 'serverTimeUTC'); + if (!serverTimeUTC) { + throw new ExchangeError (this.id + ' fetchTicker returned a bad response: ' + this.json (ticker)); + } + const timestamp = this.parse8601 (serverTimeUTC); + const vwap = this.safeFloat (ticker, 'vwap24h'); + const baseVolume = this.safeFloat (ticker, 'volume24h'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'lastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24h'), + 'low': this.safeFloat (ticker, 'low24h'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'openToday'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // timestamp: "2015-05-22T17:45:34.7570000Z", + // matchNumber: "5CR1JEUBBM8J", + // price: "351.45000000", + // amount: "0.00010000" + // } + // + // fetchMyTrades (private) + // + // { + // "orderId": "248ffda4-83a0-4033-a5bb-8929d523f59f", + // "timestamp": "2015-05-11T14:48:01.9870000Z", + // "instrument": "XBTUSD", + // "direction": "buy", // buy or sell + // "currency1": "XBT", // base currency + // "currency1Amount": "0.00010000", // order amount in base currency + // "currency2": "USD", // quote currency + // "currency2Amount": "0.0250530000000000", // order cost in quote currency + // "rate": "250.53000000", + // "commissionPaid": "0.00000000", // net trade fee paid after using any available rebate balance + // "commissionCurrency": "USD", + // "rebatesApplied": "-0.000125265", // negative values represent amount of rebate balance used for trades removing liquidity from order book; positive values represent amount of rebate balance earned from trades adding liquidity to order book + // "rebateCurrency": "USD", + // "executionId": "23132" + // } + // + const id = this.safeString2 (trade, 'executionId', 'matchNumber'); + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const side = this.safeString (trade, 'direction'); + const orderId = this.safeString (trade, 'orderId'); + let feeCost = this.safeFloat (trade, 'commissionPaid'); + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + let rebatesApplied = this.safeFloat (trade, 'rebatesApplied'); + if (rebatesApplied !== undefined) { + rebatesApplied = -rebatesApplied; + } + const rebateCurrencyId = this.safeString (trade, 'rebateCurrency'); + const rebateCurrency = this.safeCurrencyCode (rebateCurrencyId); + const price = this.safeFloat2 (trade, 'price', 'rate'); + const amount = this.safeFloat2 (trade, 'currency1Amount', 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + const marketId = this.safeString (trade, 'instrument'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (trade, 'currency1'); + const quoteId = this.safeString (trade, 'currency2'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if (symbol === undefined) { + if (market !== undefined) { + symbol = market['symbol']; + } + } + const result = { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + if (feeCost !== undefined) { + if (rebatesApplied !== undefined) { + if (feeCurrency === rebateCurrency) { + feeCost = this.sum (feeCost, rebatesApplied); + result['fee'] = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } else { + result['fees'] = [ + { + 'cost': feeCost, + 'currency': feeCurrency, + }, + { + 'cost': rebatesApplied, + 'currency': rebateCurrency, + }, + ]; + } + } else { + result['fee'] = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + } + if (!('fee' in result)) { + if (!('fees' in result)) { + result['fee'] = undefined; + } + } + return result; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const walletId = this.safeString (params, 'walletId'); + if (walletId === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a walletId parameter'); + } + const request = { + 'walletId': walletId, + }; + if (limit !== undefined) { + request['perPage'] = limit; // default 50, max 50 + } + const response = await this.privateGetWalletsWalletIdFundingHistory (this.extend (request, params)); + // { bankName: 'USBC (usd)', + // withdrawalId: 94740, + // holdingPeriodCompletionDate: '2018-04-16T07:57:05.9606869', + // time: '2018-04-16T07:57:05.9600000', + // currency: 'USD', + // transactionType: 'Withdrawal', + // amount: '2186.72000000', + // walletName: 'Wallet', + // status: 'completed' }, + // + // { "time": "2018-01-02T19:52:22.4176503", + // "amount": "0.50000000", + // "status": "completed", + // "txnHash": "1b6fff67ed83cb9e9a38ca4976981fc047322bc088430508fe764a127d3ace95", + // "currency": "XBT", + // "walletName": "Wallet", + // "transactionType": "Deposit", + // "destinationAddress": "3AAWTH9et4e8o51YKp9qPpmujrNXKwHWNX"} + const items = response['fundingHistory']; + const result = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const time = this.safeString (item, 'time'); + const timestamp = this.parse8601 (time); + const currency = this.safeString (item, 'currency'); + const destinationAddress = this.safeString (item, 'destinationAddress'); + const txnHash = this.safeString (item, 'txnHash'); + const transactionType = this.safeStringLower (item, 'transactionType'); + const transactionStatus = this.safeString (item, 'status'); + const status = this.parseTransferStatus (transactionStatus); + result.push ({ + 'id': this.safeString (item, 'withdrawalId'), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'currency': this.safeCurrencyCode (currency), + 'address': destinationAddress, + 'tag': undefined, + 'txid': txnHash, + 'type': transactionType, + 'status': status, + 'amount': this.safeFloat (item, 'amount'), + 'fee': undefined, + 'info': item, + }); + } + return result; + } + + parseTransferStatus (status) { + const options = { + 'cancelled': 'canceled', + 'completed': 'ok', + }; + return this.safeString (options, status, 'pending'); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const walletId = this.safeString (params, 'walletId'); + if (walletId === undefined) { + throw new ExchangeError (this.id + ' fetchMyTrades requires a walletId parameter'); + } + const request = { + 'walletId': walletId, + }; + if (since !== undefined) { + request['rangeStart'] = this.ymdhms (since, 'T'); + } + if (limit !== undefined) { + request['perPage'] = limit; // default 50, max 50 + } + const response = await this.privateGetWalletsWalletIdTrades (this.extend (request, params)); + // + // { + // "totalNumberOfRecords": "2", + // "currentPageNumber": "1", + // "latestExecutionId": "332", // most recent execution at time of response + // "recordsPerPage": "50", + // "tradingHistory": [ + // { + // "orderId": "248ffda4-83a0-4033-a5bb-8929d523f59f", + // "timestamp": "2015-05-11T14:48:01.9870000Z", + // "instrument": "XBTUSD", + // "direction": "buy", // buy or sell + // "currency1": "XBT", // base currency + // "currency1Amount": "0.00010000", // order amount in base currency + // "currency2": "USD", // quote currency + // "currency2Amount": "0.0250530000000000", // order cost in quote currency + // "rate": "250.53000000", + // "commissionPaid": "0.00000000", // net trade fee paid after using any available rebate balance + // "commissionCurrency": "USD", + // "rebatesApplied": "-0.000125265", // negative values represent amount of rebate balance used for trades removing liquidity from order book; positive values represent amount of rebate balance earned from trades adding liquidity to order book + // "rebateCurrency": "USD", + // "executionId": "23132" + // }, + // ], + // } + // + const trades = this.safeValue (response, 'tradingHistory', []); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (trades, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketsSymbolTrades (this.extend (request, params)); + // + // { + // count: 3, + // recentTrades: [ + // { + // timestamp: "2015-05-22T17:45:34.7570000Z", + // matchNumber: "5CR1JEUBBM8J", + // price: "351.45000000", + // amount: "0.00010000" + // }, + // ] + // } + // + const trades = this.safeValue (response, 'recentTrades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.fetchWallets (params); + const balances = response[0]['balances']; + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'availableBalance'); + account['total'] = this.safeFloat (balance, 'totalBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchWallets (params = {}) { + await this.loadMarkets (); + if (!this.uid) { + throw new AuthenticationError (this.id + ' fetchWallets requires uid API credential'); + } + const request = { + 'userId': this.uid, + }; + return await this.privateGetWallets (this.extend (request, params)); + } + + async fetchWallet (walletId, params = {}) { + await this.loadMarkets (); + const request = { + 'walletId': walletId, + }; + return await this.privateGetWalletsWalletId (this.extend (request, params)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'open', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'filled', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' fetchOrders requires a walletId parameter'); + } + const walletId = params['walletId']; + const request = { + 'walletId': walletId, + }; + const response = await this.privateGetWalletsWalletIdOrders (this.extend (request, params)); + return this.parseOrders (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'submitted': 'open', // order pending book entry + 'open': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + 'rejected': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": "13d6af57-8b0b-41e5-af30-becf0bcc574d", + // "walletId": "7e037345-1288-4c39-12fe-d0f99a475a98", + // "side": "buy", + // "instrument": "XBTUSD", + // "type": "limit", + // "currency": "XBT", + // "amount": "2.50000000", + // "displayAmount": "2.50000000", + // "price": "650.00000000", + // "volumeWeightedAveragePrice": "0.00000000", + // "amountFilled": "0.00000000", + // "createdTime": "2014-02-11T17:05:15Z", + // "status": "submitted", + // "funds": null, + // "metadata": {}, + // "clientOrderIdentifier": null, + // "postOnly": "False" + // } + // + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'type'); + const symbol = this.markets_by_id[order['instrument']]['symbol']; + const timestamp = this.parse8601 (order['createdTime']); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'amountFilled'); + let remaining = undefined; + let cost = undefined; + const fee = undefined; + const price = this.safeFloat (order, 'price'); + const average = this.safeFloat (order, 'volumeWeightedAveragePrice'); + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + } + if (average !== undefined) { + cost = filled * average; + } + } + const clientOrderId = this.safeString (order, 'clientOrderIdentifier'); + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': this.parseOrderStatus (this.safeString (order, 'status')), + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + // 'trades': this.parseTrades (order['trades'], market), + 'trades': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' createOrder requires a walletId parameter'); + } + amount = amount.toString (); + price = price.toString (); + const market = this.market (symbol); + const request = { + 'side': side, + 'type': type, + 'currency': market['id'].replace (market['quote'], ''), + 'amount': amount, + 'display': amount, + 'price': price, + 'instrument': market['id'], + }; + const response = await this.privatePostWalletsWalletIdOrders (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' fetchOrder requires a walletId parameter'); + } + const request = { + 'id': id, + }; + const response = await this.privateGetWalletsWalletIdOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const walletIdInParams = ('walletId' in params); + if (!walletIdInParams) { + throw new ExchangeError (this.id + ' cancelOrder requires a walletId parameter'); + } + const request = { + 'id': id, + }; + return await this.privateDeleteWalletsWalletIdOrdersId (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method === 'GET' && Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + if (method === 'POST' && Object.keys (query).length) { + body = this.json (query); + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const timestamp = nonce; + const authBody = (method === 'POST') ? body : ''; + const auth = [ method, url, authBody, nonce, timestamp ]; + const message = nonce + this.json (auth).replace ('\\/', '/'); + const hash = this.hash (this.encode (message), 'sha256', 'binary'); + const binaryUrl = this.stringToBinary (this.encode (url)); + const binhash = this.binaryConcat (binaryUrl, hash); + const signature = this.hmac (binhash, this.encode (this.secret), 'sha512', 'base64'); + headers = { + 'Authorization': this.apiKey + ':' + signature, + 'Content-Type': 'application/json', + 'X-Auth-Timestamp': timestamp, + 'X-Auth-Nonce': nonce, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('code' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kraken.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kraken.js new file mode 100644 index 000000000..78e997c6d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kraken.js @@ -0,0 +1,1736 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadSymbol, ExchangeNotAvailable, ArgumentsRequired, PermissionDenied, AuthenticationError, ExchangeError, OrderNotFound, DDoSProtection, InvalidNonce, InsufficientFunds, CancelPending, InvalidOrder, InvalidAddress } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class kraken extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'kraken', + 'name': 'Kraken', + 'countries': [ 'US' ], + 'version': '0', + 'rateLimit': 3000, + 'certified': true, + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchLedgerEntry': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': 'emulated', + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'marketsByAltname': {}, + 'timeframes': { + '1m': 1, + '5m': 5, + '15m': 15, + '30m': 30, + '1h': 60, + '4h': 240, + '1d': 1440, + '1w': 10080, + '2w': 21600, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg', + 'api': { + 'public': 'https://api.kraken.com', + 'private': 'https://api.kraken.com', + 'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles', // use the public zendesk api to receive article bodies and bypass new anti-spam protections + }, + 'www': 'https://www.kraken.com', + 'doc': 'https://www.kraken.com/features/api', + 'fees': 'https://www.kraken.com/en-us/features/fee-schedule', + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.26 / 100, + 'maker': 0.16 / 100, + 'tiers': { + 'taker': [ + [0, 0.0026], + [50000, 0.0024], + [100000, 0.0022], + [250000, 0.0020], + [500000, 0.0018], + [1000000, 0.0016], + [2500000, 0.0014], + [5000000, 0.0012], + [10000000, 0.0001], + ], + 'maker': [ + [0, 0.0016], + [50000, 0.0014], + [100000, 0.0012], + [250000, 0.0010], + [500000, 0.0008], + [1000000, 0.0006], + [2500000, 0.0004], + [5000000, 0.0002], + [10000000, 0.0], + ], + }, + }, + // this is a bad way of hardcoding fees that change on daily basis + // hardcoding is now considered obsolete, we will remove all of it eventually + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.001, + 'ETH': 0.005, + 'XRP': 0.02, + 'XLM': 0.00002, + 'LTC': 0.02, + 'DOGE': 2, + 'ZEC': 0.00010, + 'ICN': 0.02, + 'REP': 0.01, + 'ETC': 0.005, + 'MLN': 0.003, + 'XMR': 0.05, + 'DASH': 0.005, + 'GNO': 0.01, + 'EOS': 0.5, + 'BCH': 0.001, + 'XTZ': 0.05, + 'USD': 5, // if domestic wire + 'EUR': 5, // if domestic wire + 'CAD': 10, // CAD EFT Withdrawal + 'JPY': 300, // if domestic wire + }, + 'deposit': { + 'BTC': 0, + 'ETH': 0, + 'XRP': 0, + 'XLM': 0, + 'LTC': 0, + 'DOGE': 0, + 'ZEC': 0, + 'ICN': 0, + 'REP': 0, + 'ETC': 0, + 'MLN': 0, + 'XMR': 0, + 'DASH': 0, + 'GNO': 0, + 'EOS': 0, + 'BCH': 0, + 'XTZ': 0.05, + 'USD': 5, // if domestic wire + 'EUR': 0, // free deposit if EUR SEPA Deposit + 'CAD': 5, // if domestic wire + 'JPY': 0, // Domestic Deposit (Free, ¥5,000 deposit minimum) + }, + }, + }, + 'api': { + 'zendesk': { + 'get': [ + // we should really refrain from putting fixed fee numbers and stop hardcoding + // we will be using their web APIs to scrape all numbers from these articles + '360000292886', // -What-are-the-deposit-fees- + '201893608', // -What-are-the-withdrawal-fees- + ], + }, + 'public': { + 'get': [ + 'Assets', + 'AssetPairs', + 'Depth', + 'OHLC', + 'Spread', + 'Ticker', + 'Time', + 'Trades', + ], + }, + 'private': { + 'post': [ + 'AddOrder', + 'AddExport', + 'Balance', + 'CancelOrder', + 'ClosedOrders', + 'DepositAddresses', + 'DepositMethods', + 'DepositStatus', + 'ExportStatus', + 'GetWebSocketsToken', + 'Ledgers', + 'OpenOrders', + 'OpenPositions', + 'QueryLedgers', + 'QueryOrders', + 'QueryTrades', + 'RetrieveExport', + 'RemoveExport', + 'TradeBalance', + 'TradesHistory', + 'TradeVolume', + 'Withdraw', + 'WithdrawCancel', + 'WithdrawInfo', + 'WithdrawStatus', + ], + }, + }, + 'commonCurrencies': { + 'XBT': 'BTC', + 'XDG': 'DOGE', + 'REPV2': 'REP', + 'REP': 'REPV1', + }, + 'options': { + 'cacheDepositMethodsOnFetchDepositAddress': true, // will issue up to two calls in fetchDepositAddress + 'depositMethods': {}, + 'delistedMarketsById': {}, + // cannot withdraw/deposit these + 'inactiveCurrencies': [ 'CAD', 'USD', 'JPY', 'GBP' ], + }, + 'exceptions': { + 'EQuery:Invalid asset pair': BadSymbol, // {"error":["EQuery:Invalid asset pair"]} + 'EAPI:Invalid key': AuthenticationError, + 'EFunding:Unknown withdraw key': ExchangeError, + 'EFunding:Invalid amount': InsufficientFunds, + 'EService:Unavailable': ExchangeNotAvailable, + 'EDatabase:Internal error': ExchangeNotAvailable, + 'EService:Busy': ExchangeNotAvailable, + 'EQuery:Unknown asset': ExchangeError, + 'EAPI:Rate limit exceeded': DDoSProtection, + 'EOrder:Rate limit exceeded': DDoSProtection, + 'EGeneral:Internal error': ExchangeNotAvailable, + 'EGeneral:Temporary lockout': DDoSProtection, + 'EGeneral:Permission denied': PermissionDenied, + 'EOrder:Unknown order': InvalidOrder, + 'EOrder:Order minimum not met': InvalidOrder, + }, + }); + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); + } + + feeToPrecision (symbol, fee) { + return this.decimalToPrecision (fee, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAssetPairs (params); + // + // { + // "error":[], + // "result":{ + // "ADAETH":{ + // "altname":"ADAETH", + // "wsname":"ADA\/ETH", + // "aclass_base":"currency", + // "base":"ADA", + // "aclass_quote":"currency", + // "quote":"XETH", + // "lot":"unit", + // "pair_decimals":7, + // "lot_decimals":8, + // "lot_multiplier":1, + // "leverage_buy":[], + // "leverage_sell":[], + // "fees":[ + // [0,0.26], + // [50000,0.24], + // [100000,0.22], + // [250000,0.2], + // [500000,0.18], + // [1000000,0.16], + // [2500000,0.14], + // [5000000,0.12], + // [10000000,0.1] + // ], + // "fees_maker":[ + // [0,0.16], + // [50000,0.14], + // [100000,0.12], + // [250000,0.1], + // [500000,0.08], + // [1000000,0.06], + // [2500000,0.04], + // [5000000,0.02], + // [10000000,0] + // ], + // "fee_volume_currency":"ZUSD", + // "margin_call":80, + // "margin_stop":40, + // "ordermin": "1" + // }, + // } + // } + // + const keys = Object.keys (response['result']); + let result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = response['result'][id]; + const baseId = market['base']; + const quoteId = market['quote']; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const darkpool = id.indexOf ('.d') >= 0; + const symbol = darkpool ? market['altname'] : (base + '/' + quote); + let maker = undefined; + if ('fees_maker' in market) { + maker = parseFloat (market['fees_maker'][0][1]) / 100; + } + const precision = { + 'amount': market['lot_decimals'], + 'price': market['pair_decimals'], + }; + const minAmount = this.safeFloat (market, 'ordermin'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'darkpool': darkpool, + 'info': market, + 'altname': market['altname'], + 'maker': maker, + 'taker': parseFloat (market['fees'][0][1]) / 100, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + }); + } + result = this.appendInactiveMarkets (result); + this.marketsByAltname = this.indexBy (result, 'altname'); + return result; + } + + safeCurrencyCode (currencyId, currency = undefined) { + if (currencyId.length > 3) { + if ((currencyId.indexOf ('X') === 0) || (currencyId.indexOf ('Z') === 0)) { + currencyId = currencyId.slice (1); + } + } + return super.safeCurrencyCode (currencyId, currency); + } + + appendInactiveMarkets (result) { + // result should be an array to append to + const precision = { 'amount': 8, 'price': 8 }; + const costLimits = { 'min': 0, 'max': undefined }; + const priceLimits = { 'min': Math.pow (10, -precision['price']), 'max': undefined }; + const amountLimits = { 'min': Math.pow (10, -precision['amount']), 'max': Math.pow (10, precision['amount']) }; + const limits = { 'amount': amountLimits, 'price': priceLimits, 'cost': costLimits }; + const defaults = { + 'darkpool': false, + 'info': undefined, + 'maker': undefined, + 'taker': undefined, + 'active': false, + 'precision': precision, + 'limits': limits, + }; + const markets = [ + // { 'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR' }, + ]; + for (let i = 0; i < markets.length; i++) { + result.push (this.extend (defaults, markets[i])); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetAssets (params); + // + // { + // "error": [], + // "result": { + // "ADA": { "aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6 }, + // "BCH": { "aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5 }, + // ... + // }, + // } + // + const currencies = this.safeValue (response, 'result'); + const ids = Object.keys (currencies); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = currencies[id]; + // todo: will need to rethink the fees + // see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees- + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const code = this.safeCurrencyCode (this.safeString (currency, 'altname')); + const precision = this.safeInteger (currency, 'decimals'); + // assumes all currencies are active except those listed above + const active = !this.inArray (code, this.options['inactiveCurrencies']); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + this.checkRequiredCredentials (); + const response = await this.privatePostTradeVolume (params); + const tradedVolume = this.safeFloat (response['result'], 'volume'); + const tiers = this.fees['trading']['tiers']; + let taker = tiers['taker'][1]; + let maker = tiers['maker'][1]; + for (let i = 0; i < tiers['taker'].length; i++) { + if (tradedVolume >= tiers['taker'][i][0]) { + taker = tiers['taker'][i][1]; + } + } + for (let i = 0; i < tiers['maker'].length; i++) { + if (tradedVolume >= tiers['maker'][i][0]) { + maker = tiers['maker'][i][1]; + } + } + return { + 'info': response, + 'maker': maker, + 'taker': taker, + }; + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + const price = this.safeFloat (bidask, priceKey); + const amount = this.safeFloat (bidask, amountKey); + const timestamp = this.safeInteger (bidask, 2); + return [ price, amount, timestamp ]; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (market['darkpool']) { + throw new ExchangeError (this.id + ' does not provide an order book for darkpool symbol ' + symbol); + } + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['count'] = limit; // 100 + } + const response = await this.publicGetDepth (this.extend (request, params)); + // + // { + // "error":[], + // "result":{ + // "XETHXXBT":{ + // "asks":[ + // ["0.023480","4.000",1586321307], + // ["0.023490","50.095",1586321306], + // ["0.023500","28.535",1586321302], + // ], + // "bids":[ + // ["0.023470","59.580",1586321307], + // ["0.023460","20.000",1586321301], + // ["0.023440","67.832",1586321306], + // ] + // } + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const orderbook = this.safeValue (result, market['id']); + return this.parseOrderBook (orderbook); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const baseVolume = parseFloat (ticker['v'][1]); + const vwap = parseFloat (ticker['p'][1]); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = parseFloat (ticker['c'][0]); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': parseFloat (ticker['h'][1]), + 'low': parseFloat (ticker['l'][1]), + 'bid': parseFloat (ticker['b'][0]), + 'bidVolume': undefined, + 'ask': parseFloat (ticker['a'][0]), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'o'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + symbols = (symbols === undefined) ? this.symbols : symbols; + const marketIds = []; + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + const market = this.markets[symbol]; + if (market['active'] && !market['darkpool']) { + marketIds.push (market['id']); + } + } + const request = { + 'pair': marketIds.join (','), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const tickers = response['result']; + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.markets_by_id[id]; + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const darkpool = symbol.indexOf ('.d') >= 0; + if (darkpool) { + throw new ExchangeError (this.id + ' does not provide a ticker for darkpool symbol ' + symbol); + } + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = response['result'][market['id']]; + return this.parseTicker (ticker, market); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591475640, + // "0.02500", + // "0.02500", + // "0.02500", + // "0.02500", + // "0.02500", + // "9.12201000", + // 5 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 6), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['since'] = parseInt ((since - 1) / 1000); + } + const response = await this.publicGetOHLC (this.extend (request, params)); + // + // { + // "error":[], + // "result":{ + // "XETHXXBT":[ + // [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0], + // [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5], + // [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2], + // [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1], + // ], + // "last":1591517580 + // } + // } + const result = this.safeValue (response, 'result', {}); + const ohlcvs = this.safeValue (result, market['id'], []); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } + + parseLedgerEntryType (type) { + const types = { + 'trade': 'trade', + 'withdrawal': 'transaction', + 'deposit': 'transaction', + 'transfer': 'transfer', + 'margin': 'margin', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // 'LTFK7F-N2CUX-PNY4SX': { + // refid: "TSJTGT-DT7WN-GPPQMJ", + // time: 1520102320.555, + // type: "trade", + // aclass: "currency", + // asset: "XETH", + // amount: "0.1087194600", + // fee: "0.0000000000", + // balance: "0.2855851000" + // }, + // ... + // } + // + const id = this.safeString (item, 'id'); + let direction = undefined; + const account = undefined; + const referenceId = this.safeString (item, 'refid'); + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const code = this.safeCurrencyCode (this.safeString (item, 'asset'), currency); + let amount = this.safeFloat (item, 'amount'); + if (amount < 0) { + direction = 'out'; + amount = Math.abs (amount); + } else { + direction = 'in'; + } + const time = this.safeFloat (item, 'time'); + let timestamp = undefined; + if (time !== undefined) { + timestamp = parseInt (time * 1000); + } + const fee = { + 'cost': this.safeFloat (item, 'fee'), + 'currency': code, + }; + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + // https://www.kraken.com/features/api#get-ledgers-info + await this.loadMarkets (); + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['asset'] = currency['id']; + } + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostLedgers (this.extend (request, params)); + // { error: [], + // result: { ledger: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", + // time: 1520103488.314, + // type: "withdrawal", + // aclass: "currency", + // asset: "XETH", + // amount: "-0.2805800000", + // fee: "0.0050000000", + // balance: "0.0000051000" }, + const result = this.safeValue (response, 'result', {}); + const ledger = this.safeValue (result, 'ledger', {}); + const keys = Object.keys (ledger); + const items = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = ledger[key]; + value['id'] = key; + items.push (value); + } + return this.parseLedger (items, currency, since, limit); + } + + async fetchLedgerEntriesByIds (ids, code = undefined, params = {}) { + // https://www.kraken.com/features/api#query-ledgers + await this.loadMarkets (); + ids = ids.join (','); + const request = this.extend ({ + 'id': ids, + }, params); + const response = await this.privatePostQueryLedgers (request); + // { error: [], + // result: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", + // time: 1520103488.314, + // type: "withdrawal", + // aclass: "currency", + // asset: "XETH", + // amount: "-0.2805800000", + // fee: "0.0050000000", + // balance: "0.0000051000" } } } + const result = response['result']; + const keys = Object.keys (result); + const items = []; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = result[key]; + value['id'] = key; + items.push (value); + } + return this.parseLedger (items); + } + + async fetchLedgerEntry (id, code = undefined, params = {}) { + const items = await this.fetchLedgerEntriesByIds ([ id ], code, params); + return items[0]; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // "0.032310", // price + // "4.28169434", // amount + // 1541390792.763, // timestamp + // "s", // sell or buy + // "l", // limit or market + // "" + // ] + // + // fetchOrderTrades (private) + // + // { + // id: 'TIMIRG-WUNNE-RRJ6GT', // injected from outside + // ordertxid: 'OQRPN2-LRHFY-HIFA7D', + // postxid: 'TKH2SE-M7IF5-CFI7LT', + // pair: 'USDCUSDT', + // time: 1586340086.457, + // type: 'sell', + // ordertype: 'market', + // price: '0.99860000', + // cost: '22.16892001', + // fee: '0.04433784', + // vol: '22.20000000', + // margin: '0.00000000', + // misc: '' + // } + // + let timestamp = undefined; + let side = undefined; + let type = undefined; + let price = undefined; + let amount = undefined; + let cost = undefined; + let id = undefined; + let order = undefined; + let fee = undefined; + let symbol = undefined; + if (Array.isArray (trade)) { + timestamp = this.safeTimestamp (trade, 2); + side = (trade[3] === 's') ? 'sell' : 'buy'; + type = (trade[4] === 'l') ? 'limit' : 'market'; + price = this.safeFloat (trade, 0); + amount = this.safeFloat (trade, 1); + const tradeLength = trade.length; + if (tradeLength > 6) { + id = this.safeString (trade, 6); // artificially added as per #1794 + } + } else if (typeof trade === 'string') { + id = trade; + } else if ('ordertxid' in trade) { + const marketId = this.safeString (trade, 'pair'); + const foundMarket = this.findMarketByAltnameOrId (marketId); + if (foundMarket !== undefined) { + market = foundMarket; + } else if (marketId !== undefined) { + // delisted market ids go here + market = this.getDelistedMarketById (marketId); + } + order = trade['ordertxid']; + id = this.safeString2 (trade, 'id', 'postxid'); + timestamp = this.safeTimestamp (trade, 'time'); + side = this.safeString (trade, 'type'); + type = this.safeString (trade, 'ordertype'); + price = this.safeFloat (trade, 'price'); + amount = this.safeFloat (trade, 'vol'); + if ('fee' in trade) { + let currency = undefined; + if (market !== undefined) { + currency = market['quote']; + } + fee = { + 'cost': this.safeFloat (trade, 'fee'), + 'currency': currency, + }; + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'order': order, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const id = market['id']; + const request = { + 'pair': id, + }; + // https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API + // https://github.com/ccxt/ccxt/issues/5677 + if (since !== undefined) { + // php does not format it properly + // therefore we use string concatenation here + request['since'] = since * 1e6; + request['since'] = since.toString () + '000000'; // expected to be in nanoseconds + } + // https://github.com/ccxt/ccxt/issues/5698 + if (limit !== undefined && limit !== 1000) { + const fetchTradesWarning = this.safeValue (this.options, 'fetchTradesWarning', true); + if (fetchTradesWarning) { + throw new ExchangeError (this.id + ' fetchTrades() cannot serve ' + limit.toString () + " trades without breaking the pagination, see https://github.com/ccxt/ccxt/issues/5698 for more details. Set exchange.options['fetchTradesWarning'] to acknowledge this warning and silence it."); + } + } + const response = await this.publicGetTrades (this.extend (request, params)); + // + // { + // "error": [], + // "result": { + // "XETHXXBT": [ + // ["0.032310","4.28169434",1541390792.763,"s","l",""] + // ], + // "last": "1541439421200678657" + // } + // } + // + const result = response['result']; + const trades = result[id]; + // trades is a sorted array: last (most recent trade) goes last + const length = trades.length; + if (length <= 0) { + return []; + } + const lastTrade = trades[length - 1]; + const lastTradeId = this.safeString (result, 'last'); + lastTrade.push (lastTradeId); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + const response = await this.privatePostBalance (params); + const balances = this.safeValue (response, 'result', {}); + const result = { 'info': balances }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balances, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'ordertype': type, + 'volume': this.amountToPrecision (symbol, amount), + }; + const clientOrderId = this.safeString2 (params, 'userref', 'clientOrderId'); + const query = this.omit (params, [ 'userref', 'clientOrderId' ]); + if (clientOrderId !== undefined) { + request['userref'] = clientOrderId; + } + const priceIsDefined = (price !== undefined); + const marketOrder = (type === 'market'); + const limitOrder = (type === 'limit'); + const shouldIncludePrice = limitOrder || (!marketOrder && priceIsDefined); + if (shouldIncludePrice) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostAddOrder (this.extend (request, query)); + // + // { + // error: [], + // result: { + // descr: { order: 'buy 0.02100000 ETHUSDT @ limit 330.00' }, + // txid: [ 'OEKVV2-IH52O-TPL6GZ' ] + // } + // } + // + const result = this.safeValue (response, 'result'); + return this.parseOrder (result); + } + + findMarketByAltnameOrId (id) { + if (id in this.marketsByAltname) { + return this.marketsByAltname[id]; + } else if (id in this.markets_by_id) { + return this.markets_by_id[id]; + } + return undefined; + } + + getDelistedMarketById (id) { + if (id === undefined) { + return id; + } + let market = this.safeValue (this.options['delistedMarketsById'], id); + if (market !== undefined) { + return market; + } + const baseIdStart = 0; + let baseIdEnd = 3; + let quoteIdStart = 3; + let quoteIdEnd = 6; + if (id.length === 8) { + baseIdEnd = 4; + quoteIdStart = 4; + quoteIdEnd = 8; + } else if (id.length === 7) { + baseIdEnd = 4; + quoteIdStart = 4; + quoteIdEnd = 7; + } + const baseId = id.slice (baseIdStart, baseIdEnd); + const quoteId = id.slice (quoteIdStart, quoteIdEnd); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + market = { + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + }; + this.options['delistedMarketsById'][id] = market; + return market; + } + + parseOrderStatus (status) { + const statuses = { + 'pending': 'open', // order pending book entry + 'open': 'open', + 'closed': 'closed', + 'canceled': 'canceled', + 'expired': 'expired', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // descr: { order: 'buy 0.02100000 ETHUSDT @ limit 330.00' }, + // txid: [ 'OEKVV2-IH52O-TPL6GZ' ] + // } + // + const description = this.safeValue (order, 'descr', {}); + const orderDescription = this.safeString (description, 'order'); + let side = undefined; + let type = undefined; + let marketId = undefined; + let price = undefined; + let amount = undefined; + if (orderDescription !== undefined) { + const parts = orderDescription.split (' '); + side = this.safeString (parts, 0); + amount = this.safeFloat (parts, 1); + marketId = this.safeString (parts, 2); + type = this.safeString (parts, 4); + price = this.safeFloat (parts, 5); + } + side = this.safeString (description, 'type', side); + type = this.safeString (description, 'ordertype', type); + marketId = this.safeString (description, 'pair', marketId); + const foundMarket = this.findMarketByAltnameOrId (marketId); + let symbol = undefined; + if (foundMarket !== undefined) { + market = foundMarket; + } else if (marketId !== undefined) { + // delisted market ids go here + market = this.getDelistedMarketById (marketId); + } + const timestamp = this.safeTimestamp (order, 'opentm'); + amount = this.safeFloat (order, 'vol', amount); + const filled = this.safeFloat (order, 'vol_exec'); + let remaining = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = amount - filled; + } + let fee = undefined; + const cost = this.safeFloat (order, 'cost'); + price = this.safeFloat (description, 'price', price); + if ((price === undefined) || (price === 0.0)) { + price = this.safeFloat (description, 'price2'); + } + if ((price === undefined) || (price === 0.0)) { + price = this.safeFloat (order, 'price', price); + } + const average = this.safeFloat (order, 'price'); + if (market !== undefined) { + symbol = market['symbol']; + if ('fee' in order) { + const flags = order['oflags']; + const feeCost = this.safeFloat (order, 'fee'); + fee = { + 'cost': feeCost, + 'rate': undefined, + }; + if (flags.indexOf ('fciq') >= 0) { + fee['currency'] = market['quote']; + } else if (flags.indexOf ('fcib') >= 0) { + fee['currency'] = market['base']; + } + } + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let id = this.safeString (order, 'id'); + if (id === undefined) { + const txid = this.safeValue (order, 'txid'); + id = this.safeString (txid, 0); + } + const clientOrderId = this.safeString (order, 'userref'); + const rawTrades = this.safeValue (order, 'trades'); + let trades = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { 'order': id }); + } + const stopPrice = this.safeFloat (order, 'stopprice'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'average': average, + 'remaining': remaining, + 'fee': fee, + 'trades': trades, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeValue2 (params, 'userref', 'clientOrderId'); + const request = { + 'trades': true, // whether or not to include trades in output (optional, default false) + // 'txid': id, // do not comma separate a list of ids - use fetchOrdersByIds instead + // 'userref': 'optional', // restrict results to given user reference id (optional) + }; + let query = params; + if (clientOrderId !== undefined) { + request['userref'] = clientOrderId; + query = this.omit (params, [ 'userref', 'clientOrderId' ]); + } else { + request['txid'] = id; + } + const response = await this.privatePostQueryOrders (this.extend (request, query)); + // + // { + // "error":[], + // "result":{ + // "OTLAS3-RRHUF-NDWH5A":{ + // "refid":null, + // "userref":null, + // "status":"closed", + // "reason":null, + // "opentm":1586822919.3342, + // "closetm":1586822919.365, + // "starttm":0, + // "expiretm":0, + // "descr":{ + // "pair":"XBTUSDT", + // "type":"sell", + // "ordertype":"market", + // "price":"0", + // "price2":"0", + // "leverage":"none", + // "order":"sell 0.21804000 XBTUSDT @ market", + // "close":"" + // }, + // "vol":"0.21804000", + // "vol_exec":"0.21804000", + // "cost":"1493.9", + // "fee":"3.8", + // "price":"6851.5", + // "stopprice":"0.00000", + // "limitprice":"0.00000", + // "misc":"", + // "oflags":"fciq", + // "trades":["TT5UC3-GOIRW-6AZZ6R"] + // } + // } + // } + // + const orders = this.safeValue (response, 'result', []); + const order = this.parseOrder (this.extend ({ 'id': id }, orders[id])); + return this.extend ({ 'info': response }, order); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orderTrades = this.safeValue (params, 'trades'); + const tradeIds = []; + if (orderTrades === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrderTrades requires a unified order structure in the params argument or a 'trades' param (an array of trade id strings)"); + } else { + for (let i = 0; i < orderTrades.length; i++) { + const orderTrade = orderTrades[i]; + if (typeof orderTrade === 'string') { + tradeIds.push (orderTrade); + } else { + tradeIds.push (orderTrade['id']); + } + } + } + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchOrderTrades', {}); + const batchSize = this.safeInteger (options, 'batchSize', 20); + let numBatches = parseInt (tradeIds / batchSize); + numBatches = this.sum (numBatches, 1); + const numTradeIds = tradeIds.length; + let result = []; + for (let j = 0; j < numBatches; j++) { + const requestIds = []; + for (let k = 0; k < batchSize; k++) { + const index = this.sum (j * batchSize, k); + if (index < numTradeIds) { + requestIds.push (tradeIds[index]); + } + } + const request = { + 'txid': requestIds.join (','), + }; + const response = await this.privatePostQueryTrades (request); + // + // { + // error: [], + // result: { + // 'TIMIRG-WUNNE-RRJ6GT': { + // ordertxid: 'OQRPN2-LRHFY-HIFA7D', + // postxid: 'TKH2SE-M7IF5-CFI7LT', + // pair: 'USDCUSDT', + // time: 1586340086.457, + // type: 'sell', + // ordertype: 'market', + // price: '0.99860000', + // cost: '22.16892001', + // fee: '0.04433784', + // vol: '22.20000000', + // margin: '0.00000000', + // misc: '' + // } + // } + // } + // + const rawTrades = this.safeValue (response, 'result'); + const ids = Object.keys (rawTrades); + for (let i = 0; i < ids.length; i++) { + rawTrades[ids[i]]['id'] = ids[i]; + } + const trades = this.parseTrades (rawTrades, undefined, since, limit); + const tradesFilteredBySymbol = this.filterBySymbol (trades, symbol); + result = this.arrayConcat (result, tradesFilteredBySymbol); + } + return result; + } + + async fetchOrdersByIds (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostQueryOrders (this.extend ({ + 'trades': true, // whether or not to include trades in output (optional, default false) + 'txid': ids.join (','), // comma delimited list of transaction ids to query info about (20 maximum) + }, params)); + const result = this.safeValue (response, 'result', {}); + const orders = []; + const orderIds = Object.keys (result); + for (let i = 0; i < orderIds.length; i++) { + const id = orderIds[i]; + const item = result[id]; + const order = this.parseOrder (this.extend ({ 'id': id }, item)); + orders.push (order); + } + return orders; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'type': 'all', // any position, closed position, closing position, no position + // 'trades': false, // whether or not to include trades related to position in output + // 'start': 1234567890, // starting unix timestamp or trade tx id of results (exclusive) + // 'end': 1234567890, // ending unix timestamp or trade tx id of results (inclusive) + // 'ofs' = result offset + }; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostTradesHistory (this.extend (request, params)); + // + // { + // "error": [], + // "result": { + // "trades": { + // "GJ3NYQ-XJRTF-THZABF": { + // "ordertxid": "TKH2SE-ZIF5E-CFI7LT", + // "postxid": "OEN3VX-M7IF5-JNBJAM", + // "pair": "XICNXETH", + // "time": 1527213229.4491, + // "type": "sell", + // "ordertype": "limit", + // "price": "0.001612", + // "cost": "0.025792", + // "fee": "0.000026", + // "vol": "16.00000000", + // "margin": "0.000000", + // "misc": "" + // }, + // ... + // }, + // "count": 9760, + // }, + // } + // + const trades = response['result']['trades']; + const ids = Object.keys (trades); + for (let i = 0; i < ids.length; i++) { + trades[ids[i]]['id'] = ids[i]; + } + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseTrades (trades, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + let response = undefined; + try { + response = await this.privatePostCancelOrder (this.extend ({ + 'txid': id, + }, params)); + } catch (e) { + if (this.last_http_response) { + if (this.last_http_response.indexOf ('EOrder:Unknown order') >= 0) { + throw new OrderNotFound (this.id + ' cancelOrder() error ' + this.last_http_response); + } + } + throw e; + } + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostOpenOrders (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const result = this.safeValue (response, 'result', {}); + const orders = this.safeValue (result, 'open', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + } + const response = await this.privatePostClosedOrders (this.extend (request, params)); + // + // { + // "error":[], + // "result":{ + // "closed":{ + // "OETZYO-UL524-QJMXCT":{ + // "refid":null, + // "userref":null, + // "status":"canceled", + // "reason":"User requested", + // "opentm":1601489313.3898, + // "closetm":1601489346.5507, + // "starttm":0, + // "expiretm":0, + // "descr":{ + // "pair":"ETHUSDT", + // "type":"buy", + // "ordertype":"limit", + // "price":"330.00", + // "price2":"0", + // "leverage":"none", + // "order":"buy 0.02100000 ETHUSDT @ limit 330.00", + // "close":"" + // }, + // "vol":"0.02100000", + // "vol_exec":"0.00000000", + // "cost":"0.00000", + // "fee":"0.00000", + // "price":"0.00000", + // "stopprice":"0.00000", + // "limitprice":"0.00000", + // "misc":"", + // "oflags":"fciq" + // }, + // }, + // "count":16 + // } + // } + // + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const result = this.safeValue (response, 'result', {}); + const orders = this.safeValue (result, 'closed', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchDepositMethods (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privatePostDepositMethods (this.extend (request, params)); + return this.safeValue (response, 'result'); + } + + parseTransactionStatus (status) { + // IFEX transaction states + const statuses = { + 'Initial': 'pending', + 'Pending': 'pending', + 'Success': 'ok', + 'Settled': 'pending', + 'Failure': 'failed', + 'Partial': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { method: "Ether (Hex)", + // aclass: "currency", + // asset: "XETH", + // refid: "Q2CANKL-LBFVEE-U4Y2WQ", + // txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…", + // info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290", + // amount: "7.9999257900", + // fee: "0.0000000000", + // time: 1529223212, + // status: "Success" } + // + // fetchWithdrawals + // + // { method: "Ether", + // aclass: "currency", + // asset: "XETH", + // refid: "A2BF34S-O7LBNQ-UE4Y4O", + // txid: "0x288b83c6b0904d8400ef44e1c9e2187b5c8f7ea3d838222d53f701a15b5c274d", + // info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0", + // amount: "9.9950000000", + // fee: "0.0050000000", + // time: 1530481750, + // status: "Success" } + // + const id = this.safeString (transaction, 'refid'); + const txid = this.safeString (transaction, 'txid'); + const timestamp = this.safeTimestamp (transaction, 'time'); + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const address = this.safeString (transaction, 'info'); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const type = this.safeString (transaction, 'type'); // injected from the outside + let feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost === undefined) { + if (type === 'deposit') { + feeCost = 0; + } + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseTransactionsByType (type, transactions, code = undefined, since = undefined, limit = undefined) { + const result = []; + for (let i = 0; i < transactions.length; i++) { + const transaction = this.parseTransaction (this.extend ({ + 'type': type, + }, transactions[i])); + result.push (transaction); + } + return this.filterByCurrencySinceLimit (result, code, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + // https://www.kraken.com/en-us/help/api#deposit-status + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchDeposits requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privatePostDepositStatus (this.extend (request, params)); + // + // { error: [], + // result: [ { method: "Ether (Hex)", + // aclass: "currency", + // asset: "XETH", + // refid: "Q2CANKL-LBFVEE-U4Y2WQ", + // txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…", + // info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290", + // amount: "7.9999257900", + // fee: "0.0000000000", + // time: 1529223212, + // status: "Success" } ] } + // + return this.parseTransactionsByType ('deposit', response['result'], code, since, limit); + } + + async fetchTime (params = {}) { + // https://www.kraken.com/en-us/features/api#get-server-time + const response = await this.publicGetTime (params); + // + // { + // "error": [], + // "result": { + // "unixtime": 1591502873, + // "rfc1123": "Sun, 7 Jun 20 04:07:53 +0000" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.safeTimestamp (result, 'unixtime'); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + // https://www.kraken.com/en-us/help/api#withdraw-status + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchWithdrawals requires a currency code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + }; + const response = await this.privatePostWithdrawStatus (this.extend (request, params)); + // + // { error: [], + // result: [ { method: "Ether", + // aclass: "currency", + // asset: "XETH", + // refid: "A2BF34S-O7LBNQ-UE4Y4O", + // txid: "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d", + // info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0", + // amount: "9.9950000000", + // fee: "0.0050000000", + // time: 1530481750, + // status: "Success" } ] } + // + return this.parseTransactionsByType ('withdrawal', response['result'], code, since, limit); + } + + async createDepositAddress (code, params = {}) { + const request = { + 'new': 'true', + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + // eslint-disable-next-line quotes + let method = this.safeString (params, 'method'); + if (method === undefined) { + if (this.options['cacheDepositMethodsOnFetchDepositAddress']) { + // cache depositMethods + if (!(code in this.options['depositMethods'])) { + this.options['depositMethods'][code] = await this.fetchDepositMethods (code); + } + method = this.options['depositMethods'][code][0]['method']; + } else { + throw new ArgumentsRequired (this.id + ' fetchDepositAddress() requires an extra `method` parameter. Use fetchDepositMethods ("' + code + '") to get a list of available deposit methods or enable the exchange property .options["cacheDepositMethodsOnFetchDepositAddress"] = true'); + } + } + const request = { + 'asset': currency['id'], + 'method': method, + }; + const response = await this.privatePostDepositAddresses (this.extend (request, params)); // overwrite methods + const result = response['result']; + const numResults = result.length; + if (numResults < 1) { + throw new InvalidAddress (this.id + ' privatePostDepositAddresses() returned no addresses'); + } + const address = this.safeString (result[0], 'address'); + const tag = this.safeString2 (result[0], 'tag', 'memo'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + if ('key' in params) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'asset': currency['id'], + 'amount': amount, + // 'address': address, // they don't allow withdrawals to direct addresses + }; + const response = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': response['result'], + }; + } + throw new ExchangeError (this.id + " withdraw requires a 'key' parameter (withdrawal key name, as set up on your account)"); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.version + '/' + api + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + body = this.urlencode (this.extend ({ 'nonce': nonce }, params)); + const auth = this.encode (nonce + body); + const hash = this.hash (auth, 'sha256', 'binary'); + const binary = this.stringToBinary (this.encode (url)); + const binhash = this.binaryConcat (binary, hash); + const secret = this.base64ToBinary (this.secret); + const signature = this.hmac (binhash, secret, 'sha512', 'base64'); + headers = { + 'API-Key': this.apiKey, + 'API-Sign': signature, + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } else { + url = '/' + path; + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + nonce () { + return this.milliseconds (); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code === 520) { + throw new ExchangeNotAvailable (this.id + ' ' + code.toString () + ' ' + reason); + } + // todo: rewrite this for "broad" exceptions matching + if (body.indexOf ('Invalid order') >= 0) { + throw new InvalidOrder (this.id + ' ' + body); + } + if (body.indexOf ('Invalid nonce') >= 0) { + throw new InvalidNonce (this.id + ' ' + body); + } + if (body.indexOf ('Insufficient funds') >= 0) { + throw new InsufficientFunds (this.id + ' ' + body); + } + if (body.indexOf ('Cancel pending') >= 0) { + throw new CancelPending (this.id + ' ' + body); + } + if (body.indexOf ('Invalid arguments:volume') >= 0) { + throw new InvalidOrder (this.id + ' ' + body); + } + if (body[0] === '{') { + if (typeof response !== 'string') { + if ('error' in response) { + const numErrors = response['error'].length; + if (numErrors) { + const message = this.id + ' ' + body; + for (let i = 0; i < response['error'].length; i++) { + const error = response['error'][i]; + this.throwExactlyMatchedException (this.exceptions, error, message); + } + throw new ExchangeError (message); + } + } + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kucoin.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kucoin.js new file mode 100644 index 000000000..84a9bebe8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kucoin.js @@ -0,0 +1,1811 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, AccountSuspended, InvalidNonce, NotSupported, BadRequest, AuthenticationError, BadSymbol, RateLimitExceeded, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class kucoin extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'kucoin', + 'name': 'KuCoin', + 'countries': [ 'SC' ], + 'rateLimit': 334, + 'version': 'v2', + 'certified': false, + 'pro': true, + 'comment': 'Platform 2.0', + 'has': { + 'CORS': false, + 'fetchStatus': true, + 'fetchTime': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOrderBook': true, + 'fetchOrder': true, + 'fetchClosedOrders': true, + 'fetchOpenOrders': true, + 'fetchDepositAddress': true, + 'createDepositAddress': true, + 'withdraw': true, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchBalance': true, + 'fetchTrades': true, + 'fetchMyTrades': true, + 'createOrder': true, + 'cancelOrder': true, + 'fetchAccounts': true, + 'fetchFundingFee': true, + 'fetchOHLCV': true, + 'fetchLedger': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg', + 'referral': 'https://www.kucoin.com/?rcode=E5wkqe', + 'api': { + 'public': 'https://openapi-v2.kucoin.com', + 'private': 'https://openapi-v2.kucoin.com', + }, + 'test': { + 'public': 'https://openapi-sandbox.kucoin.com', + 'private': 'https://openapi-sandbox.kucoin.com', + }, + 'www': 'https://www.kucoin.com', + 'doc': [ + 'https://docs.kucoin.com', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'api': { + 'public': { + 'get': [ + 'timestamp', + 'status', + 'symbols', + 'markets', + 'market/allTickers', + 'market/orderbook/level{level}', + 'market/orderbook/level2', + 'market/orderbook/level2_20', + 'market/orderbook/level2_100', + 'market/orderbook/level3', + 'market/histories', + 'market/candles', + 'market/stats', + 'currencies', + 'currencies/{currency}', + 'prices', + 'mark-price/{symbol}/current', + 'margin/config', + ], + 'post': [ + 'bullet-public', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'accounts/{accountId}', + 'accounts/{accountId}/ledgers', + 'accounts/{accountId}/holds', + 'accounts/transferable', + 'sub/user', + 'sub-accounts', + 'sub-accounts/{subUserId}', + 'deposit-addresses', + 'deposits', + 'hist-deposits', + 'hist-orders', + 'hist-withdrawals', + 'withdrawals', + 'withdrawals/quotas', + 'orders', + 'orders/{orderId}', + 'limit/orders', + 'fills', + 'limit/fills', + 'margin/account', + 'margin/borrow', + 'margin/borrow/outstanding', + 'margin/borrow/borrow/repaid', + 'margin/lend/active', + 'margin/lend/done', + 'margin/lend/trade/unsettled', + 'margin/lend/trade/settled', + 'margin/lend/assets', + 'margin/market', + 'margin/trade/last', + ], + 'post': [ + 'accounts', + 'accounts/inner-transfer', + 'accounts/sub-transfer', + 'deposit-addresses', + 'withdrawals', + 'orders', + 'orders/multi', + 'margin/borrow', + 'margin/repay/all', + 'margin/repay/single', + 'margin/lend', + 'margin/toggle-auto-lend', + 'bullet-private', + ], + 'delete': [ + 'withdrawals/{withdrawalId}', + 'orders', + 'orders/{orderId}', + 'margin/lend/{orderId}', + ], + }, + }, + 'timeframes': { + '1m': '1min', + '3m': '3min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '8h': '8hour', + '12h': '12hour', + '1d': '1day', + '1w': '1week', + }, + 'exceptions': { + 'exact': { + 'order not exist': OrderNotFound, + 'order not exist.': OrderNotFound, // duplicated error temporarily + 'order_not_exist': OrderNotFound, // {"code":"order_not_exist","msg":"order_not_exist"} ¯\_(ツ)_/¯ + 'order_not_exist_or_not_allow_to_cancel': InvalidOrder, // {"code":"400100","msg":"order_not_exist_or_not_allow_to_cancel"} + 'Order size below the minimum requirement.': InvalidOrder, // {"code":"400100","msg":"Order size below the minimum requirement."} + 'The withdrawal amount is below the minimum requirement.': ExchangeError, // {"code":"400100","msg":"The withdrawal amount is below the minimum requirement."} + '400': BadRequest, + '401': AuthenticationError, + '403': NotSupported, + '404': NotSupported, + '405': NotSupported, + '429': RateLimitExceeded, + '500': ExchangeNotAvailable, // Internal Server Error -- We had a problem with our server. Try again later. + '503': ExchangeNotAvailable, + '101030': PermissionDenied, // {"code":"101030","msg":"You haven't yet enabled the margin trading"} + '200004': InsufficientFunds, + '230003': InsufficientFunds, // {"code":"230003","msg":"Balance insufficient!"} + '260100': InsufficientFunds, // {"code":"260100","msg":"account.noBalance"} + '300000': InvalidOrder, + '400000': BadSymbol, + '400001': AuthenticationError, + '400002': InvalidNonce, + '400003': AuthenticationError, + '400004': AuthenticationError, + '400005': AuthenticationError, + '400006': AuthenticationError, + '400007': AuthenticationError, + '400008': NotSupported, + '400100': BadRequest, + '411100': AccountSuspended, + '415000': BadRequest, // {"code":"415000","msg":"Unsupported Media Type"} + '500000': ExchangeError, + }, + 'broad': { + 'Exceeded the access frequency': RateLimitExceeded, + 'require more permission': PermissionDenied, + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'commonCurrencies': { + 'HOT': 'HOTNOW', + 'EDGE': 'DADI', // https://github.com/ccxt/ccxt/issues/5756 + 'WAX': 'WAXP', + 'TRY': 'Trias', + }, + 'options': { + 'version': 'v1', + 'symbolSeparator': '-', + 'fetchMyTradesMethod': 'private_get_fills', + 'fetchBalance': { + 'type': 'trade', // or 'main' + }, + // endpoint versions + 'versions': { + 'public': { + 'GET': { + 'status': 'v1', + 'market/orderbook/level{level}': 'v1', + 'market/orderbook/level2': 'v2', + 'market/orderbook/level2_20': 'v1', + 'market/orderbook/level2_100': 'v1', + }, + }, + 'private': { + 'POST': { + 'accounts/inner-transfer': 'v2', + 'accounts/sub-transfer': 'v2', + }, + }, + }, + }, + }); + } + + nonce () { + return this.milliseconds (); + } + + async loadTimeDifference (params = {}) { + const response = await this.publicGetTimestamp (params); + const after = this.milliseconds (); + const kucoinTime = this.safeInteger (response, 'data'); + this.options['timeDifference'] = parseInt (after - kucoinTime); + return this.options['timeDifference']; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTimestamp (params); + // + // { + // "code":"200000", + // "msg":"success", + // "data":1546837113087 + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchStatus (params = {}) { + const response = await this.publicGetStatus (params); + // + // { + // "code":"200000", + // "data":{ + // "msg":"", + // "status":"open" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let status = this.safeValue (data, 'status'); + if (status !== undefined) { + status = (status === 'open') ? 'ok' : 'maintenance'; + this.status = this.extend (this.status, { + 'status': status, + 'updated': this.milliseconds (), + }); + } + return this.status; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetSymbols (params); + // + // { + // quoteCurrency: 'BTC', + // symbol: 'KCS-BTC', + // quoteMaxSize: '9999999', + // quoteIncrement: '0.000001', + // baseMinSize: '0.01', + // quoteMinSize: '0.00001', + // enableTrading: true, + // priceIncrement: '0.00000001', + // name: 'KCS-BTC', + // baseIncrement: '0.01', + // baseMaxSize: '9999999', + // baseCurrency: 'KCS' + // } + // + const data = response['data']; + const result = []; + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const id = this.safeString (market, 'symbol'); + const [ baseId, quoteId ] = id.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = this.safeValue (market, 'enableTrading'); + const baseMaxSize = this.safeFloat (market, 'baseMaxSize'); + const baseMinSize = this.safeFloat (market, 'baseMinSize'); + const quoteMaxSize = this.safeFloat (market, 'quoteMaxSize'); + const quoteMinSize = this.safeFloat (market, 'quoteMinSize'); + // const quoteIncrement = this.safeFloat (market, 'quoteIncrement'); + const precision = { + 'amount': this.precisionFromString (this.safeString (market, 'baseIncrement')), + 'price': this.precisionFromString (this.safeString (market, 'priceIncrement')), + }; + const limits = { + 'amount': { + 'min': baseMinSize, + 'max': baseMaxSize, + }, + 'price': { + 'min': this.safeFloat (market, 'priceIncrement'), + 'max': quoteMaxSize / baseMinSize, + }, + 'cost': { + 'min': quoteMinSize, + 'max': quoteMaxSize, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "currency": "OMG", + // "name": "OMG", + // "fullName": "OmiseGO", + // "precision": 8, + // "confirms": 12, + // "withdrawalMinSize": "4", + // "withdrawalMinFee": "1.25", + // "isWithdrawEnabled": false, + // "isDepositEnabled": false, + // "isMarginEnabled": false, + // "isDebitEnabled": false + // } + // + const responseData = response['data']; + const result = {}; + for (let i = 0; i < responseData.length; i++) { + const entry = responseData[i]; + const id = this.safeString (entry, 'currency'); + const name = this.safeString (entry, 'fullName'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (entry, 'precision'); + const isWithdrawEnabled = this.safeValue (entry, 'isWithdrawEnabled', false); + const isDepositEnabled = this.safeValue (entry, 'isDepositEnabled', false); + const fee = this.safeFloat (entry, 'withdrawalMinFee'); + const active = (isWithdrawEnabled && isDepositEnabled); + result[code] = { + 'id': id, + 'name': name, + 'code': code, + 'precision': precision, + 'info': entry, + 'active': active, + 'fee': fee, + 'limits': this.limits, + }; + } + return result; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccounts (params); + // + // { + // code: "200000", + // data: [ + // { + // balance: "0.00009788", + // available: "0.00009788", + // holds: "0", + // currency: "BTC", + // id: "5c6a4fd399a1d81c4f9cc4d0", + // type: "trade" + // }, + // { + // balance: "0.00000001", + // available: "0.00000001", + // holds: "0", + // currency: "ETH", + // id: "5c6a49ec99a1d819392e8e9f", + // type: "trade" + // } + // ] + // } + // + const data = this.safeValue (response, 'data'); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const accountId = this.safeString (account, 'id'); + const currencyId = this.safeString (account, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const type = this.safeString (account, 'type'); // main or trade + result.push ({ + 'id': accountId, + 'type': type, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async fetchFundingFee (code, params = {}) { + const currencyId = this.currencyId (code); + const request = { + 'currency': currencyId, + }; + const response = await this.privateGetWithdrawalsQuotas (this.extend (request, params)); + const data = response['data']; + const withdrawFees = {}; + withdrawFees[code] = this.safeFloat (data, 'withdrawMinFee'); + return { + 'info': response, + 'withdraw': withdrawFees, + 'deposit': {}, + }; + } + + parseTicker (ticker, market = undefined) { + // + // { + // symbol: "ETH-BTC", + // high: "0.019518", + // vol: "7997.82836194", + // last: "0.019329", + // low: "0.019", + // buy: "0.019329", + // sell: "0.01933", + // changePrice: "-0.000139", + // time: 1580553706304, + // averagePrice: "0.01926386", + // changeRate: "-0.0071", + // volValue: "154.40791568183474" + // } + // + // { + // "trading": true, + // "symbol": "KCS-BTC", + // "buy": 0.00011, + // "sell": 0.00012, + // "sort": 100, + // "volValue": 3.13851792584, //total + // "baseCurrency": "KCS", + // "market": "BTC", + // "quoteCurrency": "BTC", + // "symbolCode": "KCS-BTC", + // "datetime": 1548388122031, + // "high": 0.00013, + // "vol": 27514.34842, + // "low": 0.0001, + // "changePrice": -1.0e-5, + // "changeRate": -0.0769, + // "lastTradedPrice": 0.00012, + // "board": 0, + // "mark": 0 + // } + // + let percentage = this.safeFloat (ticker, 'changeRate'); + if (percentage !== undefined) { + percentage = percentage * 100; + } + const last = this.safeFloat2 (ticker, 'last', 'lastTradedPrice'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const baseVolume = this.safeFloat (ticker, 'vol'); + const quoteVolume = this.safeFloat (ticker, 'volValue'); + const vwap = this.vwap (baseVolume, quoteVolume); + const timestamp = this.safeInteger2 (ticker, 'time', 'datetime'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': this.safeFloat (ticker, 'changePrice'), + 'percentage': percentage, + 'average': this.safeFloat (ticker, 'averagePrice'), + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketAllTickers (params); + // + // { + // "code": "200000", + // "data": { + // "date": 1550661940645, + // "ticker": [ + // 'buy': '0.00001168', + // 'changePrice': '-0.00000018', + // 'changeRate': '-0.0151', + // 'datetime': 1550661146316, + // 'high': '0.0000123', + // 'last': '0.00001169', + // 'low': '0.00001159', + // 'sell': '0.00001182', + // 'symbol': 'LOOM-BTC', + // 'vol': '44399.5669' + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'ticker', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = this.safeString (ticker, 'symbol'); + if (symbol !== undefined) { + result[symbol] = ticker; + } + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketStats (this.extend (request, params)); + // + // { + // "code": "200000", + // "data": { + // 'buy': '0.00001168', + // 'changePrice': '-0.00000018', + // 'changeRate': '-0.0151', + // 'datetime': 1550661146316, + // 'high': '0.0000123', + // 'last': '0.00001169', + // 'low': '0.00001159', + // 'sell': '0.00001182', + // 'symbol': 'LOOM-BTC', + // 'vol': '44399.5669' + // }, + // } + // + return this.parseTicker (response['data'], market); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // "1545904980", // Start time of the candle cycle + // "0.058", // opening price + // "0.049", // closing price + // "0.058", // highest price + // "0.049", // lowest price + // "0.018", // base volume + // "0.000945", // quote volume + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '15m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketId = market['id']; + const request = { + 'symbol': marketId, + 'type': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe) * 1000; + let endAt = this.milliseconds (); // required param + if (since !== undefined) { + request['startAt'] = parseInt (Math.floor (since / 1000)); + if (limit === undefined) { + // https://docs.kucoin.com/#get-klines + // https://docs.kucoin.com/#details + // For each query, the system would return at most 1500 pieces of data. + // To obtain more data, please page the data by time. + limit = this.safeInteger (this.options, 'fetchOHLCVLimit', 1500); + } + endAt = this.sum (since, limit * duration); + } else if (limit !== undefined) { + since = endAt - limit * duration; + request['startAt'] = parseInt (Math.floor (since / 1000)); + } + request['endAt'] = parseInt (Math.floor (endAt / 1000)); + const response = await this.publicGetMarketCandles (this.extend (request, params)); + // + // { + // "code":"200000", + // "data":[ + // ["1591517700","0.025078","0.025069","0.025084","0.025064","18.9883256","0.4761861079404"], + // ["1591516800","0.025089","0.025079","0.025089","0.02506","99.4716622","2.494143499081"], + // ["1591515900","0.025079","0.02509","0.025091","0.025068","59.83701271","1.50060885172798"], + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currencyId = this.currencyId (code); + const request = { 'currency': currencyId }; + const response = await this.privatePostDepositAddresses (this.extend (request, params)); + // BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}} + // BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}} + const data = this.safeValue (response, 'data', {}); + let address = this.safeString (data, 'address'); + // BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address + if (address !== undefined) { + address = address.replace ('bitcoincash:', ''); + } + const tag = this.safeString (data, 'memo'); + this.checkAddress (address); + return { + 'info': response, + 'currency': code, + 'address': address, + 'tag': tag, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currencyId = this.currencyId (code); + const request = { 'currency': currencyId }; + const response = await this.privateGetDepositAddresses (this.extend (request, params)); + // BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}} + // BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}} + const data = this.safeValue (response, 'data', {}); + let address = this.safeString (data, 'address'); + // BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address + if (address !== undefined) { + address = address.replace ('bitcoincash:', ''); + } + const tag = this.safeString (data, 'memo'); + this.checkAddress (address); + return { + 'info': response, + 'currency': code, + 'address': address, + 'tag': tag, + }; + } + + async fetchL3OrderBook (symbol, limit = undefined, params = {}) { + return await this.fetchOrderBook (symbol, limit, { 'level': 3 }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + const level = this.safeInteger (params, 'level', 2); + let levelLimit = level.toString (); + if (levelLimit === '2') { + if (limit !== undefined) { + if ((limit !== 20) && (limit !== 100)) { + throw new ExchangeError (this.id + ' fetchOrderBook limit argument must be undefined, 20 or 100'); + } + levelLimit += '_' + limit.toString (); + } + } + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { 'symbol': marketId, 'level': levelLimit }; + const response = await this.publicGetMarketOrderbookLevelLevel (this.extend (request, params)); + // + // 'market/orderbook/level2' + // 'market/orderbook/level2_20' + // 'market/orderbook/level2_100' + // + // { + // "code":"200000", + // "data":{ + // "sequence":"1583235112106", + // "asks":[ + // // ... + // ["0.023197","12.5067468"], + // ["0.023194","1.8"], + // ["0.023191","8.1069672"] + // ], + // "bids":[ + // ["0.02319","1.6000002"], + // ["0.023189","2.2842325"], + // ], + // "time":1586584067274 + // } + // } + // + // 'market/orderbook/level3' + // + // { + // "code":"200000", + // "data":{ + // "sequence":"1583731857120", + // "asks":[ + // // id, price, size, timestamp in nanoseconds + // ["5e915f8acd26670009675300","6925.7","0.2","1586585482194286069"], + // ["5e915f8ace35a200090bba48","6925.7","0.001","1586585482229569826"], + // ["5e915f8a8857740009ca7d33","6926","0.00001819","1586585482149148621"], + // ], + // "bids":[ + // ["5e915f8acca406000ac88194","6925.6","0.05","1586585482384384842"], + // ["5e915f93cd26670009676075","6925.6","0.08","1586585491334914600"], + // ["5e915f906aa6e200099b49f6","6925.4","0.2","1586585488941126340"], + // ], + // "time":1586585492487 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'time'); + const orderbook = this.parseOrderBook (data, timestamp, 'bids', 'asks', level - 2, level - 1); + orderbook['nonce'] = this.safeInteger (data, 'sequence'); + return orderbook; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + // required param, cannot be used twice + const clientOrderId = this.safeString2 (params, 'clientOid', 'clientOrderId', this.uuid ()); + params = this.omit (params, [ 'clientOid', 'clientOrderId' ]); + const request = { + 'clientOid': clientOrderId, + 'side': side, + 'symbol': marketId, + 'type': type, + }; + if (type !== 'market') { + request['price'] = this.priceToPrecision (symbol, price); + request['size'] = this.amountToPrecision (symbol, amount); + } else { + if (this.safeValue (params, 'quoteAmount')) { + // used to create market order by quote amount - https://github.com/ccxt/ccxt/issues/4876 + request['funds'] = this.amountToPrecision (symbol, amount); + } else { + request['size'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "orderId": "5bd6e9286d99522a52e458de" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.milliseconds (); + const id = this.safeString (data, 'orderId'); + const order = { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': data, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': undefined, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': undefined, + 'fee': undefined, + 'trades': undefined, + }; + if (!this.safeValue (params, 'quoteAmount')) { + order['amount'] = amount; + } + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { 'orderId': id }; + const response = await this.privateDeleteOrdersOrderId (this.extend (request, params)); + return response; + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'status': status, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['startAt'] = since; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "currentPage": 1, + // "pageSize": 1, + // "totalNum": 153408, + // "totalPage": 153408, + // "items": [ + // { + // "id": "5c35c02703aa673ceec2a168", //orderid + // "symbol": "BTC-USDT", //symbol + // "opType": "DEAL", // operation type,deal is pending order,cancel is cancel order + // "type": "limit", // order type,e.g. limit,markrt,stop_limit. + // "side": "buy", // transaction direction,include buy and sell + // "price": "10", // order price + // "size": "2", // order quantity + // "funds": "0", // order funds + // "dealFunds": "0.166", // deal funds + // "dealSize": "2", // deal quantity + // "fee": "0", // fee + // "feeCurrency": "USDT", // charge fee currency + // "stp": "", // self trade prevention,include CN,CO,DC,CB + // "stop": "", // stop type + // "stopTriggered": false, // stop order is triggered + // "stopPrice": "0", // stop price + // "timeInForce": "GTC", // time InForce,include GTC,GTT,IOC,FOK + // "postOnly": false, // postOnly + // "hidden": false, // hidden order + // "iceberg": false, // iceberg order + // "visibleSize": "0", // display quantity for iceberg order + // "cancelAfter": 0, // cancel orders time,requires timeInForce to be GTT + // "channel": "IOS", // order source + // "clientOid": "", // user-entered order unique mark + // "remark": "", // remark + // "tags": "", // tag order source + // "isActive": false, // status before unfilled or uncancelled + // "cancelExist": false, // order cancellation transaction record + // "createdAt": 1547026471000 // time + // }, + // ] + // } + // } + const responseData = this.safeValue (response, 'data', {}); + const orders = this.safeValue (responseData, 'items', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('done', symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByStatus ('active', symbol, since, limit, params); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + // a special case for undefined ids + // otherwise a wrong endpoint for all orders will be triggered + // https://github.com/ccxt/ccxt/issues/7234 + if (id === undefined) { + throw new InvalidOrder (this.id + ' fetchOrder requires an order id'); + } + const request = { + 'orderId': id, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privateGetOrdersOrderId (this.extend (request, params)); + const responseData = this.safeValue (response, 'data'); + return this.parseOrder (responseData, market); + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrders, fetchClosedOrders + // + // { + // "id": "5c35c02703aa673ceec2a168", //orderid + // "symbol": "BTC-USDT", //symbol + // "opType": "DEAL", // operation type,deal is pending order,cancel is cancel order + // "type": "limit", // order type,e.g. limit,markrt,stop_limit. + // "side": "buy", // transaction direction,include buy and sell + // "price": "10", // order price + // "size": "2", // order quantity + // "funds": "0", // order funds + // "dealFunds": "0.166", // deal funds + // "dealSize": "2", // deal quantity + // "fee": "0", // fee + // "feeCurrency": "USDT", // charge fee currency + // "stp": "", // self trade prevention,include CN,CO,DC,CB + // "stop": "", // stop type + // "stopTriggered": false, // stop order is triggered + // "stopPrice": "0", // stop price + // "timeInForce": "GTC", // time InForce,include GTC,GTT,IOC,FOK + // "postOnly": false, // postOnly + // "hidden": false, // hidden order + // "iceberg": false, // iceberg order + // "visibleSize": "0", // display quantity for iceberg order + // "cancelAfter": 0, // cancel orders time,requires timeInForce to be GTT + // "channel": "IOS", // order source + // "clientOid": "", // user-entered order unique mark + // "remark": "", // remark + // "tags": "", // tag order source + // "isActive": false, // status before unfilled or uncancelled + // "cancelExist": false, // order cancellation transaction record + // "createdAt": 1547026471000 // time + // } + // + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const orderId = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const timestamp = this.safeInteger (order, 'createdAt'); + const datetime = this.iso8601 (timestamp); + let price = this.safeFloat (order, 'price'); + const side = this.safeString (order, 'side'); + const feeCurrencyId = this.safeString (order, 'feeCurrency'); + const feeCurrency = this.safeCurrencyCode (feeCurrencyId); + const feeCost = this.safeFloat (order, 'fee'); + const amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat (order, 'dealSize'); + const cost = this.safeFloat (order, 'dealFunds'); + const remaining = amount - filled; + // bool + const isActive = this.safeValue (order, 'isActive', false); + const cancelExist = this.safeValue (order, 'cancelExist', false); + let status = isActive ? 'open' : 'closed'; + status = cancelExist ? 'canceled' : status; + const fee = { + 'currency': feeCurrency, + 'cost': feeCost, + }; + if (type === 'market') { + if (price === 0.0) { + if ((cost !== undefined) && (filled !== undefined)) { + if ((cost > 0) && (filled > 0)) { + price = cost / filled; + } + } + } + } + const clientOrderId = this.safeString (order, 'clientOid'); + const timeInForce = this.safeString (order, 'timeInForce'); + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'id': orderId, + 'clientOrderId': clientOrderId, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'amount': amount, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'timestamp': timestamp, + 'datetime': datetime, + 'fee': fee, + 'status': status, + 'info': order, + 'lastTradeTimestamp': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const method = this.options['fetchMyTradesMethod']; + let parseResponseData = false; + if (method === 'private_get_fills') { + // does not return trades earlier than 2019-02-18T00:00:00Z + if (since !== undefined) { + // only returns trades up to one week after the since param + request['startAt'] = since; + } + } else if (method === 'private_get_limit_fills') { + // does not return trades earlier than 2019-02-18T00:00:00Z + // takes no params + // only returns first 1000 trades (not only "in the last 24 hours" as stated in the docs) + parseResponseData = true; + } else if (method === 'private_get_hist_orders') { + // despite that this endpoint is called `HistOrders` + // it returns historical trades instead of orders + // returns trades earlier than 2019-02-18T00:00:00Z only + if (since !== undefined) { + request['startAt'] = parseInt (since / 1000); + } + } else { + throw new ExchangeError (this.id + ' invalid fetchClosedOrder method'); + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "currentPage": 1, + // "pageSize": 50, + // "totalNum": 1, + // "totalPage": 1, + // "items": [ + // { + // "symbol":"BTC-USDT", // symbol + // "tradeId":"5c35c02709e4f67d5266954e", // trade id + // "orderId":"5c35c02703aa673ceec2a168", // order id + // "counterOrderId":"5c1ab46003aa676e487fa8e3", // counter order id + // "side":"buy", // transaction direction,include buy and sell + // "liquidity":"taker", // include taker and maker + // "forceTaker":true, // forced to become taker + // "price":"0.083", // order price + // "size":"0.8424304", // order quantity + // "funds":"0.0699217232", // order funds + // "fee":"0", // fee + // "feeRate":"0", // fee rate + // "feeCurrency":"USDT", // charge fee currency + // "stop":"", // stop type + // "type":"limit", // order type, e.g. limit, market, stop_limit. + // "createdAt":1547026472000 // time + // }, + // //------------------------------------------------------ + // // v1 (historical) trade response structure + // { + // "symbol": "SNOV-ETH", + // "dealPrice": "0.0000246", + // "dealValue": "0.018942", + // "amount": "770", + // "fee": "0.00001137", + // "side": "sell", + // "createdAt": 1540080199 + // "id":"5c4d389e4c8c60413f78e2e5", + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + let trades = undefined; + if (parseResponseData) { + trades = data; + } else { + trades = this.safeValue (data, 'items', []); + } + return this.parseTrades (trades, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['startAt'] = Math.floor (since / 1000); + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + const response = await this.publicGetMarketHistories (this.extend (request, params)); + // + // { + // "code": "200000", + // "data": [ + // { + // "sequence": "1548764654235", + // "side": "sell", + // "size":"0.6841354", + // "price":"0.03202", + // "time":1548848575203567174 + // } + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "sequence": "1548764654235", + // "side": "sell", + // "size":"0.6841354", + // "price":"0.03202", + // "time":1548848575203567174 + // } + // + // { + // sequence: '1568787654360', + // symbol: 'BTC-USDT', + // side: 'buy', + // size: '0.00536577', + // price: '9345', + // takerOrderId: '5e356c4a9f1a790008f8d921', + // time: '1580559434436443257', + // type: 'match', + // makerOrderId: '5e356bffedf0010008fa5d7f', + // tradeId: '5e356c4aeefabd62c62a1ece' + // } + // + // fetchMyTrades (private) v2 + // + // { + // "symbol":"BTC-USDT", + // "tradeId":"5c35c02709e4f67d5266954e", + // "orderId":"5c35c02703aa673ceec2a168", + // "counterOrderId":"5c1ab46003aa676e487fa8e3", + // "side":"buy", + // "liquidity":"taker", + // "forceTaker":true, + // "price":"0.083", + // "size":"0.8424304", + // "funds":"0.0699217232", + // "fee":"0", + // "feeRate":"0", + // "feeCurrency":"USDT", + // "stop":"", + // "type":"limit", + // "createdAt":1547026472000 + // } + // + // fetchMyTrades v2 alternative format since 2019-05-21 https://github.com/ccxt/ccxt/pull/5162 + // + // { + // symbol: "OPEN-BTC", + // forceTaker: false, + // orderId: "5ce36420054b4663b1fff2c9", + // fee: "0", + // feeCurrency: "", + // type: "", + // feeRate: "0", + // createdAt: 1558417615000, + // size: "12.8206", + // stop: "", + // price: "0", + // funds: "0", + // tradeId: "5ce390cf6e0db23b861c6e80" + // } + // + // fetchMyTrades (private) v1 (historical) + // + // { + // "symbol": "SNOV-ETH", + // "dealPrice": "0.0000246", + // "dealValue": "0.018942", + // "amount": "770", + // "fee": "0.00001137", + // "side": "sell", + // "createdAt": 1540080199 + // "id":"5c4d389e4c8c60413f78e2e5", + // } + // + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '-'); + const id = this.safeString2 (trade, 'tradeId', 'id'); + const orderId = this.safeString (trade, 'orderId'); + const takerOrMaker = this.safeString (trade, 'liquidity'); + const amount = this.safeFloat2 (trade, 'size', 'amount'); + let timestamp = this.safeInteger (trade, 'time'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp / 1000000); + } else { + timestamp = this.safeInteger (trade, 'createdAt'); + // if it's a historical v1 trade, the exchange returns timestamp in seconds + if (('dealValue' in trade) && (timestamp !== undefined)) { + timestamp = timestamp * 1000; + } + } + const price = this.safeFloat2 (trade, 'price', 'dealPrice'); + const side = this.safeString (trade, 'side'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'feeCurrency'); + let feeCurrency = this.safeCurrencyCode (feeCurrencyId); + if (feeCurrency === undefined) { + if (market !== undefined) { + feeCurrency = (side === 'sell') ? market['quote'] : market['base']; + } + } + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': this.safeFloat (trade, 'feeRate'), + }; + } + let type = this.safeString (trade, 'type'); + if (type === 'match') { + type = undefined; + } + let cost = this.safeFloat2 (trade, 'funds', 'dealValue'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + this.checkAddress (address); + const currency = this.currencyId (code); + const request = { + 'currency': currency, + 'address': address, + 'amount': amount, + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const response = await this.privatePostWithdrawals (this.extend (request, params)); + // + // https://github.com/ccxt/ccxt/issues/5558 + // + // { + // "code": 200000, + // "data": { + // "withdrawalId": "abcdefghijklmnopqrstuvwxyz" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return { + 'id': this.safeString (data, 'withdrawalId'), + 'info': response, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'SUCCESS': 'ok', + 'PROCESSING': 'ok', + 'FAILURE': 'failed', + }; + return this.safeString (statuses, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989", + // "memo": "5c247c8a03aa677cea2a251d", + // "amount": 1, + // "fee": 0.0001, + // "currency": "KCS", + // "isInner": false, + // "walletTxId": "5bbb57386d99522d9f954c5a@test004", + // "status": "SUCCESS", + // "createdAt": 1544178843000, + // "updatedAt": 1544178891000 + // } + // + // fetchWithdrawals + // + // { + // "id": "5c2dc64e03aa675aa263f1ac", + // "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9", + // "memo": "", + // "currency": "ETH", + // "amount": 1.0000000, + // "fee": 0.0100000, + // "walletTxId": "3e2414d82acce78d38be7fe9", + // "isInner": false, + // "status": "FAILURE", + // "createdAt": 1546503758000, + // "updatedAt": 1546504603000 + // } + // + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + let address = this.safeString (transaction, 'address'); + const amount = this.safeFloat (transaction, 'amount'); + let txid = this.safeString (transaction, 'walletTxId'); + if (txid !== undefined) { + const txidParts = txid.split ('@'); + const numTxidParts = txidParts.length; + if (numTxidParts > 1) { + if (address === undefined) { + if (txidParts[1].length > 1) { + address = txidParts[1]; + } + } + } + txid = txidParts[0]; + } + let type = (txid === undefined) ? 'withdrawal' : 'deposit'; + const rawStatus = this.safeString (transaction, 'status'); + const status = this.parseTransactionStatus (rawStatus); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + let rate = undefined; + if (amount !== undefined) { + rate = feeCost / amount; + } + fee = { + 'cost': feeCost, + 'rate': rate, + 'currency': code, + }; + } + const tag = this.safeString (transaction, 'memo'); + let timestamp = this.safeInteger2 (transaction, 'createdAt', 'createAt'); + const id = this.safeString (transaction, 'id'); + let updated = this.safeInteger (transaction, 'updatedAt'); + const isV1 = !('createdAt' in transaction); + // if it's a v1 structure + if (isV1) { + type = ('address' in transaction) ? 'withdrawal' : 'deposit'; + if (timestamp !== undefined) { + timestamp = timestamp * 1000; + } + if (updated !== undefined) { + updated = updated * 1000; + } + } + return { + 'id': id, + 'address': address, + 'tag': tag, + 'currency': code, + 'amount': amount, + 'txid': txid, + 'type': type, + 'status': status, + 'fee': fee, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'updated': updated, + 'info': transaction, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + let method = 'privateGetDeposits'; + if (since !== undefined) { + // if since is earlier than 2019-02-18T00:00:00Z + if (since < 1550448000000) { + request['startAt'] = parseInt (since / 1000); + method = 'privateGetHistDeposits'; + } else { + request['startAt'] = since; + } + } + const response = await this[method] (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "currentPage": 1, + // "pageSize": 5, + // "totalNum": 2, + // "totalPage": 1, + // "items": [ + // //-------------------------------------------------- + // // version 2 deposit response structure + // { + // "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989", + // "memo": "5c247c8a03aa677cea2a251d", + // "amount": 1, + // "fee": 0.0001, + // "currency": "KCS", + // "isInner": false, + // "walletTxId": "5bbb57386d99522d9f954c5a@test004", + // "status": "SUCCESS", + // "createdAt": 1544178843000, + // "updatedAt": 1544178891000 + // }, + // //-------------------------------------------------- + // // version 1 (historical) deposit response structure + // { + // "currency": "BTC", + // "createAt": 1528536998, + // "amount": "0.03266638", + // "walletTxId": "55c643bc2c68d6f17266383ac1be9e454038864b929ae7cee0bc408cc5c869e8@12ffGWmMMD1zA1WbFm7Ho3JZ1w6NYXjpFk@234", + // "isInner": false, + // "status": "SUCCESS", + // } + // ] + // } + // } + // + const responseData = response['data']['items']; + return this.parseTransactions (responseData, currency, since, limit, { 'type': 'deposit' }); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['pageSize'] = limit; + } + let method = 'privateGetWithdrawals'; + if (since !== undefined) { + // if since is earlier than 2019-02-18T00:00:00Z + if (since < 1550448000000) { + request['startAt'] = parseInt (since / 1000); + method = 'privateGetHistWithdrawals'; + } else { + request['startAt'] = since; + } + } + const response = await this[method] (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // "currentPage": 1, + // "pageSize": 5, + // "totalNum": 2, + // "totalPage": 1, + // "items": [ + // //-------------------------------------------------- + // // version 2 withdrawal response structure + // { + // "id": "5c2dc64e03aa675aa263f1ac", + // "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9", + // "memo": "", + // "currency": "ETH", + // "amount": 1.0000000, + // "fee": 0.0100000, + // "walletTxId": "3e2414d82acce78d38be7fe9", + // "isInner": false, + // "status": "FAILURE", + // "createdAt": 1546503758000, + // "updatedAt": 1546504603000 + // }, + // //-------------------------------------------------- + // // version 1 (historical) withdrawal response structure + // { + // "currency": "BTC", + // "createAt": 1526723468, + // "amount": "0.534", + // "address": "33xW37ZSW4tQvg443Pc7NLCAs167Yc2XUV", + // "walletTxId": "aeacea864c020acf58e51606169240e96774838dcd4f7ce48acf38e3651323f4", + // "isInner": false, + // "status": "SUCCESS" + // } + // ] + // } + // } + // + const responseData = response['data']['items']; + return this.parseTransactions (responseData, currency, since, limit, { 'type': 'withdrawal' }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + let type = undefined; + const request = {}; + if ('type' in params) { + type = params['type']; + if (type !== undefined) { + request['type'] = type; + } + params = this.omit (params, 'type'); + } else { + const options = this.safeValue (this.options, 'fetchBalance', {}); + type = this.safeString (options, 'type', 'trade'); + } + const response = await this.privateGetAccounts (this.extend (request, params)); + // + // { + // "code":"200000", + // "data":[ + // {"balance":"0.00009788","available":"0.00009788","holds":"0","currency":"BTC","id":"5c6a4fd399a1d81c4f9cc4d0","type":"trade"}, + // {"balance":"3.41060034","available":"3.41060034","holds":"0","currency":"SOUL","id":"5c6a4d5d99a1d8182d37046d","type":"trade"}, + // {"balance":"0.01562641","available":"0.01562641","holds":"0","currency":"NEO","id":"5c6a4f1199a1d8165a99edb1","type":"trade"}, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const result = { 'info': response }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const balanceType = this.safeString (balance, 'type'); + if (balanceType === type) { + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'holds'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchLedger requires a code param'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const currency = this.currency (code); + let accountId = this.safeString (params, 'accountId'); + if (accountId === undefined) { + for (let i = 0; i < this.accounts.length; i++) { + const account = this.accounts[i]; + if (account['currency'] === code && account['type'] === 'main') { + accountId = account['id']; + break; + } + } + } + if (accountId === undefined) { + throw new ExchangeError (this.id + ' ' + code + 'main account is not loaded in loadAccounts'); + } + const request = { + 'accountId': accountId, + }; + if (since !== undefined) { + request['startAt'] = Math.floor (since / 1000); + } + const response = await this.privateGetAccountsAccountIdLedgers (this.extend (request, params)); + // + // { + // code: '200000', + // data: { + // totalNum: 1, + // totalPage: 1, + // pageSize: 50, + // currentPage: 1, + // items: [ + // { + // createdAt: 1561897880000, + // amount: '0.0111123', + // bizType: 'Exchange', + // balance: '0.13224427', + // fee: '0.0000111', + // context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}', + // currency: 'ETH', + // direction: 'out' + // } + // ] + // } + // } + // + const items = response['data']['items']; + return this.parseLedger (items, currency, since, limit); + } + + parseLedgerEntry (item, currency = undefined) { + // + // trade + // + // { + // createdAt: 1561897880000, + // amount: '0.0111123', + // bizType: 'Exchange', + // balance: '0.13224427', + // fee: '0.0000111', + // context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}', + // currency: 'ETH', + // direction: 'out' + // } + // + // withdrawal + // + // { + // createdAt: 1561900264000, + // amount: '0.14333217', + // bizType: 'Withdrawal', + // balance: '0', + // fee: '0.01', + // context: '{"orderId":"5d18b4e687111437cf1c48b9","txId":"0x1d136ee065c5c4c5caa293faa90d43e213c953d7cdd575c89ed0b54eb87228b8"}', + // currency: 'ETH', + // direction: 'out' + // } + // + const currencyId = this.safeString (item, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const fee = { + 'cost': this.safeFloat (item, 'fee'), + 'code': code, + }; + const amount = this.safeFloat (item, 'amount'); + const after = this.safeFloat (item, 'balance'); + const direction = this.safeString (item, 'direction'); + let before = undefined; + if (after !== undefined && amount !== undefined) { + const difference = (direction === 'out') ? amount : -amount; + before = this.sum (after, difference); + } + const timestamp = this.safeInteger (item, 'createdAt'); + const type = this.parseLedgerEntryType (this.safeString (item, 'bizType')); + const contextString = this.safeString (item, 'context'); + let id = undefined; + let referenceId = undefined; + if (this.isJsonEncodedObject (contextString)) { + const context = this.parseJson (contextString); + id = this.safeString (context, 'orderId'); + if (type === 'trade') { + referenceId = this.safeString (context, 'tradeId'); + } else if (type === 'transaction') { + referenceId = this.safeString (context, 'txId'); + } + } + return { + 'id': id, + 'currency': code, + 'account': undefined, + 'referenceAccount': undefined, + 'referenceId': referenceId, + 'status': undefined, + 'amount': amount, + 'before': before, + 'after': after, + 'fee': fee, + 'direction': direction, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'type': type, + 'info': item, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'Exchange': 'trade', + 'Withdrawal': 'transaction', + 'Deposit': 'transaction', + 'Transfer': 'transfer', + }; + return this.safeString (types, type, type); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + // + // the v2 URL is https://openapi-v2.kucoin.com/api/v1/endpoint + // † ↑ + // + const versions = this.safeValue (this.options, 'versions', {}); + const apiVersions = this.safeValue (versions, api); + const methodVersions = this.safeValue (apiVersions, method, {}); + const defaultVersion = this.safeString (methodVersions, path, this.options['version']); + const version = this.safeString (params, 'version', defaultVersion); + params = this.omit (params, 'version'); + let endpoint = '/api/' + version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + let endpart = ''; + headers = (headers !== undefined) ? headers : {}; + if (Object.keys (query).length) { + if (method !== 'GET') { + body = this.json (query); + endpart = body; + headers['Content-Type'] = 'application/json'; + } else { + endpoint += '?' + this.urlencode (query); + } + } + const url = this.urls['api'][api] + endpoint; + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.nonce ().toString (); + headers = this.extend ({ + 'KC-API-KEY': this.apiKey, + 'KC-API-TIMESTAMP': timestamp, + 'KC-API-PASSPHRASE': this.password, + }, headers); + const payload = timestamp + method + endpoint + endpart; + const signature = this.hmac (this.encode (payload), this.encode (this.secret), 'sha256', 'base64'); + headers['KC-API-SIGN'] = signature; + const partner = this.safeValue (this.options, 'partner', {}); + const partnerId = this.safeString (partner, 'id'); + const partnerSecret = this.safeString (partner, 'secret'); + if ((partnerId !== undefined) && (partnerSecret !== undefined)) { + const partnerPayload = timestamp + partnerId + this.apiKey; + const partnerSignature = this.hmac (this.encode (partnerPayload), this.encode (partnerSecret), 'sha256', 'base64'); + headers['KC-API-PARTNER-SIGN'] = partnerSignature; + headers['KC-API-PARTNER'] = partnerId; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + this.throwBroadlyMatchedException (this.exceptions['broad'], body, body); + return; + } + // + // bad + // { "code": "400100", "msg": "validation.createOrder.clientOidIsRequired" } + // good + // { code: '200000', data: { ... }} + // + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'msg'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, message); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, message); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kuna.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kuna.js new file mode 100644 index 000000000..8b80f40a4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/kuna.js @@ -0,0 +1,214 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const acx = require ('./acx.js'); +const { ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class kuna extends acx { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'kuna', + 'name': 'Kuna', + 'countries': [ 'UA' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'CORS': false, + 'fetchTickers': true, + 'fetchOHLCV': 'emulated', + 'fetchOpenOrders': true, + 'fetchMyTrades': true, + 'withdraw': false, + }, + 'timeframes': undefined, + 'urls': { + 'referral': 'https://kuna.io?r=kunaid-gvfihe8az7o4', + 'logo': 'https://user-images.githubusercontent.com/51840849/87153927-f0578b80-c2c0-11ea-84b6-74612568e9e1.jpg', + 'api': 'https://kuna.io', + 'www': 'https://kuna.io', + 'doc': 'https://kuna.io/documents/api', + 'fees': 'https://kuna.io/documents/api', + }, + 'fees': { + 'trading': { + 'taker': 0.25 / 100, + 'maker': 0.25 / 100, + }, + 'funding': { + 'withdraw': { + 'UAH': '1%', + 'BTC': 0.001, + 'BCH': 0.001, + 'ETH': 0.01, + 'WAVES': 0.01, + 'GOL': 0.0, + 'GBG': 0.0, + // 'RMC': 0.001 BTC + // 'ARN': 0.01 ETH + // 'R': 0.01 ETH + // 'EVR': 0.01 ETH + }, + 'deposit': { + // 'UAH': (amount) => amount * 0.001 + 5 + }, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const quotes = [ 'btc', 'eth', 'eurs', 'rub', 'uah', 'usd', 'usdt', 'gol' ]; + const pricePrecisions = { + 'UAH': 0, + }; + const markets = []; + const response = await this.publicGetTickers (params); + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + for (let j = 0; j < quotes.length; j++) { + const quoteId = quotes[j]; + const index = id.indexOf (quoteId); + const slice = id.slice (index); + if ((index > 0) && (slice === quoteId)) { + const baseId = id.replace (quoteId, ''); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 6, + 'price': this.safeInteger (pricePrecisions, quote, 6), + }; + markets.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'active': undefined, + 'info': undefined, + }); + break; + } + } + } + return markets; + } + + async fetchL3OrderBook (symbol, limit = undefined, params = {}) { + return await this.fetchOrderBook (symbol, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privateGetOrders (this.extend (request, params)); + // todo emulation of fetchClosedOrders, fetchOrders, fetchOrder + // with order cache + fetchOpenOrders + // as in BTC-e, Liqui, Yobit, DSX, Tidex, WEX + return this.parseOrders (response, market, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + let side = this.safeString2 (trade, 'side', 'trend'); + if (side !== undefined) { + const sideMap = { + 'ask': 'sell', + 'bid': 'buy', + }; + side = this.safeString (sideMap, side, side); + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + const cost = this.safeFloat (trade, 'funds'); + const orderId = this.safeString (trade, 'order_id'); + const id = this.safeString (trade, 'id'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': orderId, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.privateGetTradesMy (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const trades = await this.fetchTrades (symbol, since, limit, params); + const ohlcvc = this.buildOHLCVC (trades, timeframe, since, limit); + const result = []; + for (let i = 0; i < ohlcvc.length; i++) { + const ohlcv = ohlcvc[i]; + result.push ([ + ohlcv[0], + ohlcv[1], + ohlcv[2], + ohlcv[3], + ohlcv[4], + ohlcv[5], + ]); + } + return result; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lakebtc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lakebtc.js new file mode 100644 index 000000000..8acd8cc41 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lakebtc.js @@ -0,0 +1,328 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, BadSymbol, InvalidOrder, InsufficientFunds } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class lakebtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'lakebtc', + 'name': 'LakeBTC', + 'countries': [ 'US' ], + 'version': 'api_v2', + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/28074120-72b7c38a-6660-11e7-92d9-d9027502281d.jpg', + 'api': 'https://api.lakebtc.com', + 'www': 'https://www.lakebtc.com', + 'doc': [ + 'https://www.lakebtc.com/s/api_v2', + 'https://www.lakebtc.com/s/api', + ], + }, + 'api': { + 'public': { + 'get': [ + 'bcorderbook', + 'bctrades', + 'ticker', + ], + }, + 'private': { + 'post': [ + 'buyOrder', + 'cancelOrders', + 'getAccountInfo', + 'getExternalAccounts', + 'getOrders', + 'getTrades', + 'openOrders', + 'sellOrder', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.15 / 100, + 'taker': 0.2 / 100, + }, + }, + 'exceptions': { + 'broad': { + 'Signature': AuthenticationError, + 'invalid symbol': BadSymbol, + 'Volume doit': InvalidOrder, + 'insufficient_balance': InsufficientFunds, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTicker (params); + const result = []; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = response[id]; + const baseId = id.slice (0, 3); + const quoteId = id.slice (3, 6); + const base = baseId.toUpperCase (); + const quote = quoteId.toUpperCase (); + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetAccountInfo (params); + const balances = this.safeValue (response, 'balance', {}); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balances, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetBcorderbook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const marketId = ids[i]; + const ticker = response[marketId]; + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const tickers = await this.publicGetTicker (params); + return this.parseTicker (tickers[market['id']], market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetBctrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const method = 'privatePost' + this.capitalize (side) + 'Order'; + const market = this.market (symbol); + const order = { + 'params': [ price, amount, market['id'] ], + }; + const response = await this[method] (this.extend (order, params)); + return { + 'info': response, + 'id': this.safeString (response, 'id'), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'params': [ id ], + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + nonce () { + return this.microseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version; + if (api === 'public') { + url += '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const nonceAsString = nonce.toString (); + const requestId = this.seconds (); + let queryParams = ''; + if ('params' in params) { + const paramsList = params['params']; + const stringParams = []; + for (let i = 0; i < paramsList.length; i++) { + let param = paramsList[i]; + if (typeof paramsList !== 'string') { + param = param.toString (); + } + stringParams.push (param); + } + queryParams = stringParams.join (','); + body = { + 'method': path, + 'params': params['params'], + 'id': requestId, + }; + } else { + body = { + 'method': path, + 'params': '', + 'id': requestId, + }; + } + body = this.json (body); + let query = [ + 'tonce=' + nonceAsString, + 'accesskey=' + this.apiKey, + 'requestmethod=' + method.toLowerCase (), + 'id=' + requestId.toString (), + 'method=' + path, + 'params=' + queryParams, + ]; + query = query.join ('&'); + const signature = this.hmac (this.encode (query), this.encode (this.secret), 'sha1'); + const auth = this.apiKey + ':' + signature; + const signature64 = this.decode (this.stringToBase64 (auth)); + headers = { + 'Json-Rpc-Tonce': nonceAsString, + 'Authorization': 'Basic ' + signature64, + 'Content-Type': 'application/json', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to the default error handler + } + // + // {"error":"Failed to submit order: invalid symbol"} + // {"error":"Failed to submit order: La validation a échoué : Volume doit être supérieur ou égal à 1.0"} + // {"error":"Failed to submit order: insufficient_balance"} + // + const feedback = this.id + ' ' + body; + const error = this.safeString (response, 'error'); + if (error !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/latoken.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/latoken.js new file mode 100644 index 000000000..f7e8b4a69 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/latoken.js @@ -0,0 +1,887 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InvalidNonce, OrderNotFound, InvalidOrder, DDoSProtection, BadRequest, AuthenticationError } = require ('./base/errors'); +const { ROUND } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class latoken extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'latoken', + 'name': 'Latoken', + 'countries': [ 'KY' ], // Cayman Islands + 'version': 'v1', + 'rateLimit': 2000, + 'certified': false, + 'userAgent': this.userAgents['chrome'], + 'has': { + 'CORS': false, + 'publicAPI': true, + 'privateAPI': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCanceledOrders': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': false, + 'fetchOrdersByStatus': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg', + 'api': 'https://api.latoken.com', + 'www': 'https://latoken.com', + 'doc': [ + 'https://api.latoken.com', + ], + }, + 'api': { + 'public': { + 'get': [ + 'ExchangeInfo/time', + 'ExchangeInfo/limits', + 'ExchangeInfo/pairs', + 'ExchangeInfo/pairs/{currency}', + 'ExchangeInfo/pair', + 'ExchangeInfo/currencies', + 'ExchangeInfo/currencies/{symbol}', + 'MarketData/tickers', + 'MarketData/ticker/{symbol}', + 'MarketData/orderBook/{symbol}', + 'MarketData/orderBook/{symbol}/{limit}', + 'MarketData/trades/{symbol}', + 'MarketData/trades/{symbol}/{limit}', + ], + }, + 'private': { + 'get': [ + 'Account/balances', + 'Account/balances/{currency}', + 'Order/status', + 'Order/active', + 'Order/get_order', + 'Order/trades', + ], + 'post': [ + 'Order/new', + 'Order/test-order', + 'Order/cancel', + 'Order/cancel_all', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + }, + 'commonCurrencies': { + 'MT': 'Monarch', + 'TSL': 'Treasure SL', + }, + 'options': { + 'createOrderMethod': 'private_post_order_new', // private_post_order_test_order + }, + 'exceptions': { + 'exact': { + 'Signature or ApiKey is not valid': AuthenticationError, + 'Request is out of time': InvalidNonce, + 'Symbol must be specified': BadRequest, + }, + 'broad': { + 'Request limit reached': DDoSProtection, + 'Pair': BadRequest, + 'Price needs to be greater than': InvalidOrder, + 'Amount needs to be greater than': InvalidOrder, + 'The Symbol field is required': InvalidOrder, + 'OrderType is not valid': InvalidOrder, + 'Side is not valid': InvalidOrder, + 'Cancelable order whit': OrderNotFound, + 'Order': OrderNotFound, + }, + }, + }); + } + + nonce () { + return this.milliseconds (); + } + + async fetchTime (params = {}) { + const response = await this.publicGetExchangeInfoTime (params); + // + // { + // "time": "2019-04-18T9:00:00.0Z", + // "unixTimeSeconds": 1555578000, + // "unixTimeMiliseconds": 1555578000000 + // } + // + return this.safeInteger (response, 'unixTimeMiliseconds'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeInfoPairs (params); + // + // [ + // { + // "pairId": 502, + // "symbol": "LAETH", + // "baseCurrency": "LA", + // "quotedCurrency": "ETH", + // "makerFee": 0.01, + // "takerFee": 0.01, + // "pricePrecision": 8, + // "amountPrecision": 8, + // "minQty": 0.1 + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'symbol'); + // the exchange shows them inverted + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quotedCurrency'); + const numericId = this.safeInteger (market, 'pairId'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'pricePrecision'), + 'amount': this.safeInteger (market, 'amountPrecision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minQty'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'numericId': numericId, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, // assuming true + 'precision': precision, + 'limits': limits, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetExchangeInfoCurrencies (params); + // + // [ + // { + // "currencyId": 102, + // "symbol": "LA", + // "name": "Latoken", + // "precission": 8, + // "type": "ERC20", + // "fee": 0.1 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const numericId = this.safeInteger (currency, 'currencyId'); + const code = this.safeCurrencyCode (id); + const precision = this.safeInteger (currency, 'precission'); + const fee = this.safeFloat (currency, 'fee'); + const active = undefined; + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = amount * rate; + let precision = market['precision']['price']; + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + precision = market['precision']['amount']; + } + cost = this.decimalToPrecision (cost, ROUND, precision, this.precisionMode); + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (cost), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountBalances (params); + // + // [ + // { + // "currencyId": 102, + // "symbol": "LA", + // "name": "Latoken", + // "amount": 1054.66, + // "available": 900.66, + // "frozen": 154, + // "pending": 0 + // } + // ] + // + const result = { + 'info': response, + }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'symbol'); + const code = this.safeCurrencyCode (currencyId); + const frozen = this.safeFloat (balance, 'frozen'); + const pending = this.safeFloat (balance, 'pending'); + const used = this.sum (frozen, pending); + const account = { + 'free': this.safeFloat (balance, 'available'), + 'used': used, + 'total': this.safeFloat (balance, 'amount'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'limit': 10, + }; + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 100 + } + const response = await this.publicGetMarketDataOrderBookSymbolLimit (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "spread": 0.07, + // "asks": [ + // { "price": 136.3, "quantity": 7.024 } + // ], + // "bids": [ + // { "price": 136.2, "quantity": 6.554 } + // ] + // } + // + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'quantity'); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "pairId":"63b41092-f3f6-4ea4-9e7c-4525ed250dad", + // "symbol":"ETHBTC", + // "volume":11317.037494474000000000, + // "open":0.020033000000000000, + // "low":0.019791000000000000, + // "high":0.020375000000000000, + // "close":0.019923000000000000, + // "priceChange":-0.1500 + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const open = this.safeFloat (ticker, 'open'); + const close = this.safeFloat (ticker, 'close'); + let change = undefined; + if (open !== undefined && close !== undefined) { + change = close - open; + } + const percentage = this.safeFloat (ticker, 'priceChange'); + const timestamp = this.nonce (); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'low': this.safeFloat (ticker, 'low'), + 'high': this.safeFloat (ticker, 'high'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketDataTickerSymbol (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "volume": 1023314.3202, + // "open": 134.82, + // "low": 133.95, + // "high": 136.22, + // "close": 135.12, + // "priceChange": 0.22 + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketDataTickers (params); + // + // [ + // { + // "pairId": 502, + // "symbol": "LAETH", + // "volume": 1023314.3202, + // "open": 134.82, + // "low": 133.95, + // "high": 136.22, + // "close": 135.12, + // "priceChange": 0.22 + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // side: 'buy', + // price: 0.33634, + // amount: 0.01, + // timestamp: 1564240008000 // milliseconds + // } + // + // fetchMyTrades (private) + // + // { + // id: '1564223032.892829.3.tg15', + // orderId: '1564223032.671436.707548@1379:1', + // commission: 0, + // side: 'buy', + // price: 0.32874, + // amount: 0.607, + // timestamp: 1564223033 // seconds + // } + // + const type = undefined; + let timestamp = this.safeInteger2 (trade, 'timestamp', 'time'); + if (timestamp !== undefined) { + // 03 Jan 2009 - first block + if (timestamp < 1230940800000) { + timestamp *= 1000; + } + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + const side = this.safeString (trade, 'side'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'orderId'); + const feeCost = this.safeFloat (trade, 'commission'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': type, + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 50, max 100 + } + const response = await this.publicGetMarketDataTradesSymbol (this.extend (request, params)); + // + // { + // "pairId":370, + // "symbol":"ETHBTC", + // "tradeCount":51, + // "trades": [ + // { + // side: 'buy', + // price: 0.33634, + // amount: 0.01, + // timestamp: 1564240008000 // milliseconds + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.privateGetOrderTrades (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "tradeCount": 1, + // "trades": [ + // { + // id: '1564223032.892829.3.tg15', + // orderId: '1564223032.671436.707548@1379:1', + // commission: 0, + // side: 'buy', + // price: 0.32874, + // amount: 0.607, + // timestamp: 1564223033 // seconds + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'active': 'open', + 'partiallyFilled': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "orderId":"1563460093.134037.704945@0370:2", + // "cliOrdId":"", + // "pairId":370, + // "symbol":"ETHBTC", + // "side":"sell", + // "orderType":"limit", + // "price":1.0, + // "amount":1.0 + // } + // + // cancelOrder, fetchOrder, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders + // + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // + const id = this.safeString (order, 'orderId'); + const timestamp = this.safeTimestamp (order, 'timeCreated'); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const side = this.safeString (order, 'side'); + const type = this.safeString (order, 'orderType'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + const filled = this.safeFloat (order, 'executedAmount'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'orderStatus')); + let cost = undefined; + if (filled !== undefined) { + if (price !== undefined) { + cost = filled * price; + } + } + const timeFilled = this.safeTimestamp (order, 'timeFilled'); + let lastTradeTimestamp = undefined; + if ((timeFilled !== undefined) && (timeFilled > 0)) { + lastTradeTimestamp = timeFilled; + } + const clientOrderId = this.safeString (order, 'cliOrdId'); + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'average': undefined, + 'remaining': remaining, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersWithMethod ('private_get_order_active', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByStatus ('filled', symbol, since, limit, params); + } + + async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return this.fetchOrdersByStatus ('cancelled', symbol, since, limit, params); + } + + async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': status, + }; + return this.fetchOrdersWithMethod ('private_get_order_status', symbol, since, limit, this.extend (request, params)); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrdersWithMethod requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100 + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateGetOrderGetOrder (this.extend (request, params)); + // + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // + return this.parseOrder (response); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'symbol': this.marketId (symbol), + 'side': side, + 'price': this.priceToPrecision (symbol, price), + 'amount': this.amountToPrecision (symbol, amount), + 'orderType': type, + }; + const method = this.safeString (this.options, 'createOrderMethod', 'private_post_order_new'); + const response = await this[method] (this.extend (request, params)); + // + // { + // "orderId":"1563460093.134037.704945@0370:2", + // "cliOrdId":"", + // "pairId":370, + // "symbol":"ETHBTC", + // "side":"sell", + // "orderType":"limit", + // "price":1.0, + // "amount":1.0 + // } + // + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privatePostOrderCancel (this.extend (request, params)); + // + // { + // "orderId": "1555492358.126073.126767@0502:2", + // "cliOrdId": "myNewOrder", + // "pairId": 502, + // "symbol": "LAETH", + // "side": "buy", + // "orderType": "limit", + // "price": 136.2, + // "amount": 0.57, + // "orderStatus": "partiallyFilled", + // "executedAmount": 0.27, + // "reaminingAmount": 0.3, + // "timeCreated": 155551580736, + // "timeFilled": 0 + // } + // + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); + } + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { + 'symbol': marketId, + }; + const response = await this.privatePostOrderCancelAll (this.extend (request, params)); + // + // { + // "pairId": 502, + // "symbol": "LAETH", + // "cancelledOrders": [ + // "1555492358.126073.126767@0502:2" + // ] + // } + // + const result = []; + const canceledOrders = this.safeValue (response, 'cancelledOrders', []); + for (let i = 0; i < canceledOrders.length; i++) { + const order = this.parseOrder ({ + 'symbol': marketId, + 'orderId': canceledOrders[i], + 'orderStatus': 'canceled', + }); + result.push (order); + } + return result; + } + + sign (path, api = 'public', method = 'GET', params = undefined, headers = undefined, body = undefined) { + let request = '/api/' + this.version + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + const nonce = this.nonce (); + query = this.extend ({ + 'timestamp': nonce, + }, query); + } + const urlencodedQuery = this.urlencode (query); + if (Object.keys (query).length) { + request += '?' + urlencodedQuery; + } + if (api === 'private') { + this.checkRequiredCredentials (); + const signature = this.hmac (this.encode (request), this.encode (this.secret)); + headers = { + 'X-LA-KEY': this.apiKey, + 'X-LA-SIGNATURE': signature, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + body = urlencodedQuery; + } + } + const url = this.urls['api'] + request; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; + } + // + // { "message": "Request limit reached!", "details": "Request limit reached. Maximum allowed: 1 per 1s. Please try again in 1 second(s)." } + // { "error": { "message": "Pair 370 is not found","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Signature or ApiKey is not valid","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Request is out of time", "errorType": "RequestError", "statusCode":400 }} + // { "error": { "message": "Price needs to be greater than 0","errorType":"ValidationError","statusCode":400 }} + // { "error": { "message": "Side is not valid, Price needs to be greater than 0, Amount needs to be greater than 0, The Symbol field is required., OrderType is not valid","errorType":"ValidationError","statusCode":400 }} + // { "error": { "message": "Cancelable order whit ID 1563460289.571254.704945@0370:1 not found","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Symbol must be specified","errorType":"RequestError","statusCode":400 }} + // { "error": { "message": "Order 1563460289.571254.704945@0370:1 is not found","errorType":"RequestError","statusCode":400 }} + // + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + if (message !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + const error = this.safeValue (response, 'error', {}); + const errorMessage = this.safeString (error, 'message'); + if (errorMessage !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], errorMessage, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], errorMessage, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lbank.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lbank.js new file mode 100644 index 000000000..c90433927 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lbank.js @@ -0,0 +1,698 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, DDoSProtection, AuthenticationError, InvalidOrder, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class lbank extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'lbank', + 'name': 'LBank', + 'countries': [ 'CN' ], + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': false, // status 0 API doesn't work + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'minute1', + '5m': 'minute5', + '15m': 'minute15', + '30m': 'minute30', + '1h': 'hour1', + '2h': 'hour2', + '4h': 'hour4', + '6h': 'hour6', + '8h': 'hour8', + '12h': 'hour12', + '1d': 'day1', + '1w': 'week1', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/38063602-9605e28a-3302-11e8-81be-64b1e53c4cfb.jpg', + 'api': 'https://api.lbank.info', + 'www': 'https://www.lbank.info', + 'doc': 'https://github.com/LBank-exchange/lbank-official-api-docs', + 'fees': 'https://lbankinfo.zendesk.com/hc/en-gb/articles/360012072873-Trading-Fees', + 'referral': 'https://www.lbex.io/invite?icode=7QCY', + }, + 'api': { + 'public': { + 'get': [ + 'currencyPairs', + 'ticker', + 'depth', + 'trades', + 'kline', + 'accuracy', + ], + }, + 'private': { + 'post': [ + 'user_info', + 'create_order', + 'cancel_order', + 'orders_info', + 'orders_info_history', + 'withdraw', + 'withdrawCancel', + 'withdraws', + 'withdrawConfigs', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + 'funding': { + 'withdraw': { + 'BTC': undefined, + 'ZEC': 0.01, + 'ETH': 0.01, + 'ETC': 0.01, + // 'QTUM': amount => Math.max (0.01, amount * (0.1 / 100)), + 'VEN': 10.0, + 'BCH': 0.0002, + 'SC': 50.0, + 'BTM': 20.0, + 'NAS': 1.0, + 'EOS': 1.0, + 'XWC': 5.0, + 'BTS': 1.0, + 'INK': 10.0, + 'BOT': 3.0, + 'YOYOW': 15.0, + 'TGC': 10.0, + 'NEO': 0.0, + 'CMT': 20.0, + 'SEER': 2000.0, + 'FIL': undefined, + 'BTG': undefined, + }, + }, + }, + 'commonCurrencies': { + 'VET_ERC20': 'VEN', + 'PNT': 'Penta', + }, + 'options': { + 'cacheSecretAsPem': true, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetAccuracy (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = market['symbol']; + const parts = id.split ('_'); + let baseId = undefined; + let quoteId = undefined; + const numParts = parts.length; + // lbank will return symbols like "vet_erc20_usdt" + if (numParts > 2) { + baseId = parts[0] + '_' + parts[1]; + quoteId = parts[2]; + } else { + baseId = parts[0]; + quoteId = parts[1]; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'quantityAccuracy'), + 'price': this.safeInteger (market, 'priceAccuracy'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': id, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeString (ticker, 'symbol'); + if (marketId in this.markets_by_id) { + const market = this.marketsById[marketId]; + symbol = market['symbol']; + } else { + const parts = marketId.split ('_'); + let baseId = undefined; + let quoteId = undefined; + const numParts = parts.length; + // lbank will return symbols like "vet_erc20_usdt" + if (numParts > 2) { + baseId = parts[0] + '_' + parts[1]; + quoteId = parts[2]; + } else { + baseId = parts[0]; + quoteId = parts[1]; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + const timestamp = this.safeInteger (ticker, 'timestamp'); + const info = ticker; + ticker = info['ticker']; + const last = this.safeFloat (ticker, 'latest'); + const percentage = this.safeFloat (ticker, 'change'); + let open = undefined; + if (percentage !== undefined) { + const relativeChange = this.sum (1, percentage / 100); + if (relativeChange > 0) { + open = last / this.sum (1, relativeChange); + } + } + let change = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + } + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': this.safeFloat (ticker, 'turnover'), + 'info': info, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': 'all', + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = 60, params = {}) { + await this.loadMarkets (); + let size = 60; + if (limit !== undefined) { + size = Math.min (limit, size); + } + const request = { + 'symbol': this.marketId (symbol), + 'size': size, + }; + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response); + } + + parseTrade (trade, market = undefined) { + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (trade, 'date_ms'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = parseFloat (this.costToPrecision (symbol, price * amount)); + } + } + const id = this.safeString (trade, 'tid'); + const type = undefined; + const side = this.safeString (trade, 'type'); + return { + 'id': id, + 'info': this.safeValue (trade, 'info', trade), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'size': 100, + }; + if (since !== undefined) { + request['time'] = parseInt (since); + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1590969600, + // 0.02451657, + // 0.02452675, + // 0.02443701, + // 0.02447814, + // 238.38210000 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = 1000, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (since === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a `since` argument'); + } + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a `limit` argument'); + } + const request = { + 'symbol': market['id'], + 'type': this.timeframes[timeframe], + 'size': limit, + 'time': parseInt (since / 1000), + }; + const response = await this.publicGetKline (this.extend (request, params)); + // + // [ + // [1590969600,0.02451657,0.02452675,0.02443701,0.02447814,238.38210000], + // [1590969660,0.02447814,0.02449883,0.02443209,0.02445973,212.40270000], + // [1590969720,0.02445973,0.02452067,0.02445909,0.02446151,266.16920000], + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostUserInfo (params); + // + // { + // "result":"true", + // "info":{ + // "freeze":{ + // "iog":"0.00000000", + // "ssc":"0.00000000", + // "eon":"0.00000000", + // }, + // "asset":{ + // "iog":"0.00000000", + // "ssc":"0.00000000", + // "eon":"0.00000000", + // }, + // "free":{ + // "iog":"0.00000000", + // "ssc":"0.00000000", + // "eon":"0.00000000", + // }, + // } + // } + // + const result = { 'info': response }; + const info = this.safeValue (response, 'info', {}); + const free = this.safeValue (info, 'free', {}); + const freeze = this.safeValue (info, 'freeze', {}); + const asset = this.safeValue (info, 'asset', {}); + const currencyIds = Object.keys (free); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (free, currencyId); + account['used'] = this.safeFloat (freeze, currencyId); + account['total'] = this.safeFloat (asset, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + '-1': 'cancelled', // cancelled + '0': 'open', // not traded + '1': 'open', // partial deal + '2': 'closed', // complete deal + '4': 'closed', // disposal processing + }; + return this.safeString (statuses, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "symbol":"eth_btc", + // "amount":10.000000, + // "create_time":1484289832081, + // "price":5000.000000, + // "avg_price":5277.301200, + // "type":"sell", + // "order_id":"ab704110-af0d-48fd-a083-c218f19a4a55", + // "deal_amount":10.000000, + // "status":2 + // } + // + let symbol = undefined; + const responseMarket = this.safeValue (this.marketsById, order['symbol']); + if (responseMarket !== undefined) { + symbol = responseMarket['symbol']; + } else if (market !== undefined) { + symbol = market['symbol']; + } + const timestamp = this.safeInteger (order, 'create_time'); + // Limit Order Request Returns: Order Price + // Market Order Returns: cny amount of market order + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount', 0.0); + const filled = this.safeFloat (order, 'deal_amount', 0.0); + const av_price = this.safeFloat (order, 'avg_price'); + let cost = undefined; + if (av_price !== undefined) { + cost = filled * av_price; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString (order, 'order_id'); + const type = this.safeString (order, 'order_type'); + const side = this.safeString (order, 'type'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': undefined, + 'fee': undefined, + 'info': this.safeValue (order, 'info', order), + 'average': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let order = { + 'symbol': market['id'], + 'type': side, + 'amount': amount, + }; + if (type === 'market') { + order['type'] += '_market'; + } else { + order['price'] = price; + } + const response = await this.privatePostCreateOrder (this.extend (order, params)); + order = this.omit (order, 'type'); + order['order_id'] = response['order_id']; + order['type'] = side; + order['order_type'] = type; + order['create_time'] = this.milliseconds (); + order['info'] = response; + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'order_id': id, + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + // Id can be a list of ids delimited by a comma + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'order_id': id, + }; + const response = await this.privatePostOrdersInfo (this.extend (request, params)); + const data = this.safeValue (response, 'orders', []); + const orders = this.parseOrders (data, market); + const numOrders = orders.length; + if (numOrders === 1) { + return orders[0]; + } else { + return orders; + } + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (limit === undefined) { + limit = 100; + } + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'current_page': 1, + 'page_length': limit, + }; + const response = await this.privatePostOrdersInfoHistory (this.extend (request, params)); + const data = this.safeValue (response, 'orders', []); + return this.parseOrders (data, undefined, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const orders = await this.fetchOrders (symbol, since, limit, params); + const closed = this.filterBy (orders, 'status', 'closed'); + const canceled = this.filterBy (orders, 'status', 'cancelled'); // cancelled orders may be partially filled + const allOrders = this.arrayConcat (closed, canceled); + return this.filterBySymbolSinceLimit (allOrders, symbol, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // mark and fee are optional params, mark is a note and must be less than 255 characters + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'assetCode': currency['id'], + 'amount': amount, + 'account': address, + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const response = this.privatePostWithdraw (this.extend (request, params)); + return { + 'id': this.safeString (response, 'id'), + 'info': response, + }; + } + + convertSecretToPem (secret) { + const lineLength = 64; + const secretLength = secret.length - 0; + let numLines = parseInt (secretLength / lineLength); + numLines = this.sum (numLines, 1); + let pem = "-----BEGIN PRIVATE KEY-----\n"; // eslint-disable-line + for (let i = 0; i < numLines; i++) { + const start = i * lineLength; + const end = this.sum (start, lineLength); + pem += this.secret.slice (start, end) + "\n"; // eslint-disable-line + } + return pem + '-----END PRIVATE KEY-----'; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + // Every endpoint ends with ".do" + url += '.do'; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const query = this.keysort (this.extend ({ + 'api_key': this.apiKey, + }, params)); + const queryString = this.rawencode (query); + const message = this.hash (this.encode (queryString)).toUpperCase (); + const cacheSecretAsPem = this.safeValue (this.options, 'cacheSecretAsPem', true); + let pem = undefined; + if (cacheSecretAsPem) { + pem = this.safeValue (this.options, 'pem'); + if (pem === undefined) { + pem = this.convertSecretToPem (this.secret); + this.options['pem'] = pem; + } + } else { + pem = this.convertSecretToPem (this.secret); + } + const sign = this.binaryToBase64 (this.rsa (message, this.encode (pem), 'RS256')); + query['sign'] = sign; + body = this.urlencode (query); + headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + const success = this.safeString (response, 'result'); + if (success === 'false') { + const errorCode = this.safeString (response, 'error_code'); + const message = this.safeString ({ + '10000': 'Internal error', + '10001': 'The required parameters can not be empty', + '10002': 'verification failed', + '10003': 'Illegal parameters', + '10004': 'User requests are too frequent', + '10005': 'Key does not exist', + '10006': 'user does not exist', + '10007': 'Invalid signature', + '10008': 'This currency pair is not supported', + '10009': 'Limit orders can not be missing orders and the number of orders', + '10010': 'Order price or order quantity must be greater than 0', + '10011': 'Market orders can not be missing the amount of the order', + '10012': 'market sell orders can not be missing orders', + '10013': 'is less than the minimum trading position 0.001', + '10014': 'Account number is not enough', + '10015': 'The order type is wrong', + '10016': 'Account balance is not enough', + '10017': 'Abnormal server', + '10018': 'order inquiry can not be more than 50 less than one', + '10019': 'withdrawal orders can not be more than 3 less than one', + '10020': 'less than the minimum amount of the transaction limit of 0.001', + '10022': 'Insufficient key authority', + }, errorCode, this.json (response)); + const ErrorClass = this.safeValue ({ + '10002': AuthenticationError, + '10004': DDoSProtection, + '10005': AuthenticationError, + '10006': AuthenticationError, + '10007': AuthenticationError, + '10009': InvalidOrder, + '10010': InvalidOrder, + '10011': InvalidOrder, + '10012': InvalidOrder, + '10013': InvalidOrder, + '10014': InvalidOrder, + '10015': InvalidOrder, + '10016': InvalidOrder, + '10022': AuthenticationError, + }, errorCode, ExchangeError); + throw new ErrorClass (message); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/liquid.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/liquid.js new file mode 100644 index 000000000..ddb1e0acc --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/liquid.js @@ -0,0 +1,1134 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { TICK_SIZE } = require ('./base/functions/number'); +const { ExchangeError, ArgumentsRequired, InvalidNonce, OrderNotFound, InvalidOrder, InsufficientFunds, AuthenticationError, DDoSProtection, NotSupported } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class liquid extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'liquid', + 'name': 'Liquid', + 'countries': [ 'JP', 'CN', 'TW' ], + 'version': '2', + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/45798859-1a872600-bcb4-11e8-8746-69291ce87b04.jpg', + 'api': 'https://api.liquid.com', + 'www': 'https://www.liquid.com', + 'doc': [ + 'https://developers.liquid.com', + ], + 'fees': 'https://help.liquid.com/getting-started-with-liquid/the-platform/fee-structure', + 'referral': 'https://www.liquid.com/sign-up/?affiliate=SbzC62lt30976', + }, + 'api': { + 'public': { + 'get': [ + 'currencies', + 'products', + 'products/{id}', + 'products/{id}/price_levels', + 'executions', + 'ir_ladders/{currency}', + 'fees', // add fetchFees, fetchTradingFees, fetchFundingFees + ], + }, + 'private': { + 'get': [ + 'accounts', // undocumented https://github.com/ccxt/ccxt/pull/7493 + 'accounts/balance', + 'accounts/main_asset', + 'accounts/{id}', + 'accounts/{currency}/reserved_balance_details', + 'crypto_accounts', // add fetchAccounts + 'crypto_withdrawals', // add fetchWithdrawals + 'executions/me', + 'fiat_accounts', // add fetchAccounts + 'fund_infos', // add fetchDeposits + 'loan_bids', + 'loans', + 'orders', + 'orders/{id}', + 'orders/{id}/trades', // add fetchOrderTrades + 'trades', + 'trades/{id}/loans', + 'trading_accounts', + 'trading_accounts/{id}', + 'transactions', + 'withdrawals', // add fetchWithdrawals + ], + 'post': [ + 'crypto_withdrawals', + 'fund_infos', + 'fiat_accounts', + 'loan_bids', + 'orders', + 'withdrawals', + ], + 'put': [ + 'crypto_withdrawal/{id}/cancel', + 'loan_bids/{id}/close', + 'loans/{id}', + 'orders/{id}', // add editOrder + 'orders/{id}/cancel', + 'trades/{id}', + 'trades/{id}/adjust_margin', + 'trades/{id}/close', + 'trades/close_all', + 'trading_accounts/{id}', + 'withdrawals/{id}/cancel', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.0015, + 'maker': 0.0000, + 'tiers': { + 'perpetual': { + 'maker': [ + [ 0, 0.0000 ], + [ 25000, 0.0000 ], + [ 50000, -0.00025 ], + [ 100000, -0.00025 ], + [ 1000000, -0.00025 ], + [ 10000000, -0.00025 ], + [ 25000000, -0.00025 ], + [ 50000000, -0.00025 ], + [ 75000000, -0.00025 ], + [ 100000000, -0.00025 ], + [ 200000000, -0.00025 ], + [ 300000000, -0.00025 ], + ], + 'taker': [ + [ 0, 0.000600 ], + [ 25000, 0.000575 ], + [ 50000, 0.000550 ], + [ 100000, 0.000525 ], + [ 1000000, 0.000500 ], + [ 10000000, 0.000475 ], + [ 25000000, 0.000450 ], + [ 50000000, 0.000425 ], + [ 75000000, 0.000400 ], + [ 100000000, 0.000375 ], + [ 200000000, 0.000350 ], + [ 300000000, 0.000325 ], + ], + }, + 'spot': { + 'taker': [ + [ 0, 0.0015 ], + [ 10000, 0.0015 ], + [ 20000, 0.0014 ], + [ 50000, 0.0013 ], + [ 100000, 0.0010 ], + [ 1000000, 0.0008 ], + [ 5000000, 0.0006 ], + [ 10000000, 0.0005 ], + [ 25000000, 0.0005 ], + [ 50000000, 0.00045 ], + [ 100000000, 0.0004 ], + [ 200000000, 0.0003 ], + ], + 'maker': [ + [ 0, 0.0000 ], + [ 10000, 0.0015 ], + [ 20000, 0.1400 ], + [ 50000, 0.1300 ], + [ 100000, 0.0800 ], + [ 1000000, 0.0004 ], + [ 5000000, 0.00035 ], + [ 10000000, 0.00025 ], + [ 25000000, 0.0000 ], + [ 50000000, 0.0000 ], + [ 100000000, 0.0000 ], + [ 200000000, 0.0000 ], + ], + }, + }, + }, + }, + 'precisionMode': TICK_SIZE, + 'exceptions': { + 'API rate limit exceeded. Please retry after 300s': DDoSProtection, + 'API Authentication failed': AuthenticationError, + 'Nonce is too small': InvalidNonce, + 'Order not found': OrderNotFound, + 'Can not update partially filled order': InvalidOrder, + 'Can not update non-live order': OrderNotFound, + 'not_enough_free_balance': InsufficientFunds, + 'must_be_positive': InvalidOrder, + 'less_than_order_size': InvalidOrder, + 'price_too_high': InvalidOrder, + }, + 'commonCurrencies': { + 'WIN': 'WCOIN', + 'HOT': 'HOT Token', + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + }, + 'options': { + 'cancelOrderException': true, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // currency_type: 'fiat', + // currency: 'USD', + // symbol: '$', + // assets_precision: 2, + // quoting_precision: 5, + // minimum_withdrawal: '15.0', + // withdrawal_fee: 5, + // minimum_fee: null, + // minimum_order_quantity: null, + // display_precision: 2, + // depositable: true, + // withdrawable: true, + // discount_fee: 0.5, + // }, + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const active = currency['depositable'] && currency['withdrawable']; + const amountPrecision = this.safeInteger (currency, 'display_precision'); + const pricePrecision = this.safeInteger (currency, 'quoting_precision'); + const precision = Math.max (amountPrecision, pricePrecision); + const decimalPrecision = 1 / Math.pow (10, precision); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': code, + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawal_fee'), + 'precision': decimalPrecision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -amountPrecision), + 'max': Math.pow (10, amountPrecision), + }, + 'price': { + 'min': Math.pow (10, -pricePrecision), + 'max': Math.pow (10, pricePrecision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minimum_withdrawal'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const spot = await this.publicGetProducts (params); + // + // [ + // { + // "id":"637", + // "product_type":"CurrencyPair", + // "code":"CASH", + // "name":null, + // "market_ask":"0.00000797", + // "market_bid":"0.00000727", + // "indicator":null, + // "currency":"BTC", + // "currency_pair_code":"TFTBTC", + // "symbol":null, + // "btc_minimum_withdraw":null, + // "fiat_minimum_withdraw":null, + // "pusher_channel":"product_cash_tftbtc_637", + // "taker_fee":"0.0", + // "maker_fee":"0.0", + // "low_market_bid":"0.00000685", + // "high_market_ask":"0.00000885", + // "volume_24h":"3696.0755956", + // "last_price_24h":"0.00000716", + // "last_traded_price":"0.00000766", + // "last_traded_quantity":"1748.0377978", + // "average_price":null, + // "quoted_currency":"BTC", + // "base_currency":"TFT", + // "tick_size":"0.00000001", + // "disabled":false, + // "margin_enabled":false, + // "cfd_enabled":false, + // "perpetual_enabled":false, + // "last_event_timestamp":"1596962820.000797146", + // "timestamp":"1596962820.000797146", + // "multiplier_up":"9.0", + // "multiplier_down":"0.1", + // "average_time_interval":null + // }, + // ] + // + const perpetual = await this.publicGetProducts ({ 'perpetual': '1' }); + // + // [ + // { + // "id":"604", + // "product_type":"Perpetual", + // "code":"CASH", + // "name":null, + // "market_ask":"11721.5", + // "market_bid":"11719.0", + // "indicator":null, + // "currency":"USD", + // "currency_pair_code":"P-BTCUSD", + // "symbol":"$", + // "btc_minimum_withdraw":null, + // "fiat_minimum_withdraw":null, + // "pusher_channel":"product_cash_p-btcusd_604", + // "taker_fee":"0.0012", + // "maker_fee":"0.0", + // "low_market_bid":"11624.5", + // "high_market_ask":"11859.0", + // "volume_24h":"0.271", + // "last_price_24h":"11621.5", + // "last_traded_price":"11771.5", + // "last_traded_quantity":"0.09", + // "average_price":"11771.5", + // "quoted_currency":"USD", + // "base_currency":"P-BTC", + // "tick_size":"0.5", + // "disabled":false, + // "margin_enabled":false, + // "cfd_enabled":false, + // "perpetual_enabled":true, + // "last_event_timestamp":"1596963309.418853092", + // "timestamp":"1596963309.418853092", + // "multiplier_up":null, + // "multiplier_down":"0.1", + // "average_time_interval":300, + // "index_price":"11682.8124", + // "mark_price":"11719.96781", + // "funding_rate":"0.00273", + // "fair_price":"11720.2745" + // }, + // ] + // + const currencies = await this.fetchCurrencies (); + const currenciesByCode = this.indexBy (currencies, 'code'); + const result = []; + const markets = this.arrayConcat (spot, perpetual); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency'); + const quoteId = this.safeString (market, 'quoted_currency'); + const productType = this.safeString (market, 'product_type'); + let type = 'spot'; + let spot = true; + let swap = false; + if (productType === 'Perpetual') { + spot = false; + swap = true; + type = 'swap'; + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = undefined; + if (swap) { + symbol = this.safeString (market, 'currency_pair_code'); + } else { + symbol = base + '/' + quote; + } + let maker = this.fees['trading']['maker']; + let taker = this.fees['trading']['taker']; + if (type === 'swap') { + maker = this.safeFloat (market, 'maker_fee', this.fees['trading']['maker']); + taker = this.safeFloat (market, 'taker_fee', this.fees['trading']['taker']); + } + const disabled = this.safeValue (market, 'disabled', false); + const active = !disabled; + const baseCurrency = this.safeValue (currenciesByCode, base); + const precision = { + 'amount': 0.00000001, + 'price': this.safeFloat (market, 'tick_size'), + }; + let minAmount = undefined; + if (baseCurrency !== undefined) { + minAmount = this.safeFloat (baseCurrency['info'], 'minimum_order_quantity'); + } + const limits = { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': type, + 'spot': spot, + 'swap': swap, + 'maker': maker, + 'taker': taker, + 'limits': limits, + 'precision': precision, + 'active': active, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + // + // { + // crypto_accounts: [ + // { + // id: 2221179, + // currency: 'USDT', + // balance: '0.0', + // reserved_balance: '0.0', + // pusher_channel: 'user_xxxxx_account_usdt', + // lowest_offer_interest_rate: null, + // highest_offer_interest_rate: null, + // address: '0', + // currency_symbol: 'USDT', + // minimum_withdraw: null, + // currency_type: 'crypto' + // }, + // ], + // fiat_accounts: [ + // { + // id: 1112734, + // currency: 'USD', + // balance: '0.0', + // reserved_balance: '0.0', + // pusher_channel: 'user_xxxxx_account_usd', + // lowest_offer_interest_rate: null, + // highest_offer_interest_rate: null, + // currency_symbol: '$', + // send_to_btc_address: null, + // exchange_rate: '1.0', + // currency_type: 'fiat' + // } + // ] + // } + // + const result = { 'info': response }; + const crypto = this.safeValue (response, 'crypto_accounts', []); + const fiat = this.safeValue (response, 'fiat_accounts', []); + for (let i = 0; i < crypto.length; i++) { + const balance = crypto[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'reserved_balance'); + result[code] = account; + } + for (let i = 0; i < fiat.length; i++) { + const balance = fiat[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'reserved_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const response = await this.publicGetProductsIdPriceLevels (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'buy_price_levels', 'sell_price_levels'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let last = undefined; + if ('last_traded_price' in ticker) { + if (ticker['last_traded_price']) { + const length = ticker['last_traded_price'].length; + if (length > 0) { + last = this.safeFloat (ticker, 'last_traded_price'); + } + } + } + let symbol = undefined; + if (market === undefined) { + const marketId = this.safeString (ticker, 'id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const baseId = this.safeString (ticker, 'base_currency'); + const quoteId = this.safeString (ticker, 'quoted_currency'); + if (symbol in this.markets) { + market = this.markets[symbol]; + } else { + symbol = this.safeCurrencyCode (baseId) + '/' + this.safeCurrencyCode (quoteId); + } + } + } + if (market !== undefined) { + symbol = market['symbol']; + } + let change = undefined; + let percentage = undefined; + let average = undefined; + const open = this.safeFloat (ticker, 'last_price_24h'); + if (open !== undefined && last !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_market_ask'), + 'low': this.safeFloat (ticker, 'low_market_bid'), + 'bid': this.safeFloat (ticker, 'market_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'market_ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume_24h'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetProducts (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + const response = await this.publicGetProductsId (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + // { id: 12345, + // quantity: "6.789", + // price: "98765.4321", + // taker_side: "sell", + // created_at: 1512345678, + // my_side: "buy" } + const timestamp = this.safeTimestamp (trade, 'created_at'); + const orderId = this.safeString (trade, 'order_id'); + // 'taker_side' gets filled for both fetchTrades and fetchMyTrades + const takerSide = this.safeString (trade, 'taker_side'); + // 'my_side' gets filled for fetchMyTrades only and may differ from 'taker_side' + const mySide = this.safeString (trade, 'my_side'); + const side = (mySide !== undefined) ? mySide : takerSide; + let takerOrMaker = undefined; + if (mySide !== undefined) { + takerOrMaker = (takerSide === mySide) ? 'taker' : 'maker'; + } + let cost = undefined; + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const id = this.safeString (trade, 'id'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'product_id': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + // timestamp should be in seconds, whereas we use milliseconds in since and everywhere + request['timestamp'] = parseInt (since / 1000); + } + const response = await this.publicGetExecutions (this.extend (request, params)); + const result = (since !== undefined) ? response : response['models']; + return this.parseTrades (result, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + // the `with_details` param is undocumented - it adds the order_id to the results + const request = { + 'product_id': market['id'], + 'with_details': true, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetExecutionsMe (this.extend (request, params)); + return this.parseTrades (response['models'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_order_id'); + params = this.omit (params, [ 'clientOrderId', 'client_order_id' ]); + const request = { + 'order_type': type, + 'product_id': this.marketId (symbol), + 'side': side, + 'quantity': this.amountToPrecision (symbol, amount), + }; + if (clientOrderId !== undefined) { + request['client_order_id'] = clientOrderId; + } + if ((type === 'limit') || (type === 'limit_post_only') || (type === 'market_with_range') || (type === 'stop')) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // "id": 2157474, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.0", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "live", + // "leverage_level": 1, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0", + // "client_order_id": null, + // } + // + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePutOrdersIdCancel (this.extend (request, params)); + const order = this.parseOrder (response); + if (order['status'] === 'closed') { + if (this.options['cancelOrderException']) { + throw new OrderNotFound (this.id + ' order closed already: ' + this.json (response)); + } + } + return order; + } + + async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (price === undefined) { + throw new ArgumentsRequired (this.id + ' editOrder requires the price argument'); + } + const request = { + 'order': { + 'quantity': this.amountToPrecision (symbol, amount), + 'price': this.priceToPrecision (symbol, price), + }, + 'id': id, + }; + const response = await this.privatePutOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + 'live': 'open', + 'filled': 'closed', + 'cancelled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "id": 2157474, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.0", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "live", + // "leverage_level": 1, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0" + // "client_order_id": null, + // } + // + // fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "id": 2157479, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.01", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "filled", + // "leverage_level": 2, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0", + // "executions": [ + // { + // "id": 4566133, + // "quantity": "0.01", + // "price": "500.0", + // "taker_side": "buy", + // "my_side": "sell", + // "created_at": 1465396785 + // } + // ] + // } + // + const orderId = this.safeString (order, 'id'); + const timestamp = this.safeTimestamp (order, 'created_at'); + const marketId = this.safeString (order, 'product_id'); + market = this.safeValue (this.markets_by_id, marketId); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const amount = this.safeFloat (order, 'quantity'); + let filled = this.safeFloat (order, 'filled_quantity'); + const price = this.safeFloat (order, 'price'); + let symbol = undefined; + let feeCurrency = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } + const type = this.safeString (order, 'order_type'); + let tradeCost = 0; + let tradeFilled = 0; + let average = this.safeFloat (order, 'average_price'); + const trades = this.parseTrades (this.safeValue (order, 'executions', []), market, undefined, undefined, { + 'order': orderId, + 'type': type, + }); + const numTrades = trades.length; + for (let i = 0; i < numTrades; i++) { + // php copies values upon assignment, but not references them + // todo rewrite this (shortly) + const trade = trades[i]; + trade['order'] = orderId; + trade['type'] = type; + tradeFilled = this.sum (tradeFilled, trade['amount']); + tradeCost = this.sum (tradeCost, trade['cost']); + } + let cost = undefined; + let lastTradeTimestamp = undefined; + if (numTrades > 0) { + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + if (!average && (tradeFilled > 0)) { + average = tradeCost / tradeFilled; + } + if (cost === undefined) { + cost = tradeCost; + } + if (filled === undefined) { + filled = tradeFilled; + } + } + let remaining = undefined; + if (amount !== undefined && filled !== undefined) { + remaining = amount - filled; + } + const side = this.safeString (order, 'side'); + const clientOrderId = this.safeString (order, 'client_order_id'); + return { + 'id': orderId, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'type': type, + 'timeInForce': undefined, + 'status': status, + 'symbol': symbol, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'cost': cost, + 'remaining': remaining, + 'average': average, + 'trades': trades, + 'fee': { + 'currency': feeCurrency, + 'cost': this.safeFloat (order, 'order_fee'), + }, + 'info': order, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // 'funding_currency': market['quoteId'], // filter orders based on "funding" currency (quote currency) + // 'product_id': market['id'], + // 'status': 'live', // 'filled', 'cancelled' + // 'trading_type': 'spot', // 'margin', 'cfd' + 'with_details': 1, // return full order details including executions + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['product_id'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // "models": [ + // { + // "id": 2157474, + // "order_type": "limit", + // "quantity": "0.01", + // "disc_quantity": "0.0", + // "iceberg_total_quantity": "0.0", + // "side": "sell", + // "filled_quantity": "0.0", + // "price": "500.0", + // "created_at": 1462123639, + // "updated_at": 1462123639, + // "status": "live", + // "leverage_level": 1, + // "source_exchange": "QUOINE", + // "product_id": 1, + // "product_code": "CASH", + // "funding_currency": "USD", + // "currency_pair_code": "BTCUSD", + // "order_fee": "0.0", + // "executions": [], // optional + // } + // ], + // "current_page": 1, + // "total_pages": 1 + // } + // + const orders = this.safeValue (response, 'models', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'status': 'live' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'status': 'filled' }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + // 'auth_code': '', // optional 2fa code + 'currency': currency['id'], + 'address': address, + 'amount': this.currencyToPrecision (code, amount), + // 'payment_id': tag, // for XRP only + // 'memo_type': 'text', // 'text', 'id' or 'hash', for XLM only + // 'memo_value': tag, // for XLM only + }; + if (tag !== undefined) { + if (code === 'XRP') { + request['payment_id'] = tag; + } else if (code === 'XLM') { + request['memo_type'] = 'text'; // overrideable via params + request['memo_value'] = tag; + } else { + throw new NotSupported (this.id + ' withdraw() only supports a tag along the address for XRP or XLM'); + } + } + const response = await this.privatePostCryptoWithdrawals (this.extend (request, params)); + // + // { + // "id": 1353, + // "address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", + // "amount": 1.0, + // "state": "pending", + // "currency": "BTC", + // "withdrawal_fee": 0.0, + // "created_at": 1568016450, + // "updated_at": 1568016450, + // "payment_id": null + // } + // + return this.parseTransaction (response, currency); + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'cancelled': 'canceled', + 'approved': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "id": 1353, + // "address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", + // "amount": 1.0, + // "state": "pending", + // "currency": "BTC", + // "withdrawal_fee": 0.0, + // "created_at": 1568016450, + // "updated_at": 1568016450, + // "payment_id": null + // } + // + // fetchDeposits, fetchWithdrawals + // + // ... + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString2 (transaction, 'payment_id', 'memo_value'); + const txid = undefined; + const currencyId = this.safeString (transaction, 'asset'); + const code = this.safeCurrencyCode (currencyId, currency); + const timestamp = this.safeTimestamp (transaction, 'created_at'); + const updated = this.safeTimestamp (transaction, 'updated_at'); + const type = 'withdrawal'; + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const amount = this.safeFloat (transaction, 'amount'); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + headers = { + 'X-Quoine-API-Version': this.version, + 'Content-Type': 'application/json', + }; + if (api === 'private') { + this.checkRequiredCredentials (); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length) { + body = this.json (query); + } + const nonce = this.nonce (); + const request = { + 'path': url, + 'token_id': this.apiKey, + 'iat': Math.floor (nonce / 1000), // issued at + }; + if (!('client_order_id' in query)) { + request['nonce'] = nonce; + } + headers['X-Quoine-Auth'] = this.jwt (request, this.encode (this.secret)); + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + url = this.urls['api'] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (code >= 200 && code < 300) { + return; + } + if (code === 401) { + // expected non-json response + this.throwExactlyMatchedException (this.exceptions, body, body); + return; + } + if (code === 429) { + throw new DDoSProtection (this.id + ' ' + body); + } + if (response === undefined) { + return; + } + const feedback = this.id + ' ' + body; + const message = this.safeString (response, 'message'); + const errors = this.safeValue (response, 'errors'); + if (message !== undefined) { + // + // { "message": "Order not found" } + // + this.throwExactlyMatchedException (this.exceptions, message, feedback); + } else if (errors !== undefined) { + // + // { "errors": { "user": ["not_enough_free_balance"] }} + // { "errors": { "quantity": ["less_than_order_size"] }} + // { "errors": { "order": ["Can not update partially filled order"] }} + // + const types = Object.keys (errors); + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const errorMessages = errors[type]; + for (let j = 0; j < errorMessages.length; j++) { + const message = errorMessages[j]; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + } + } + } else { + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/livecoin.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/livecoin.js new file mode 100644 index 000000000..0a8aa1aa0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/livecoin.js @@ -0,0 +1,931 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, OnMaintenance, BadResponse, ArgumentsRequired, AuthenticationError, NotSupported, InvalidOrder, OrderNotFound, ExchangeNotAvailable, RateLimitExceeded, InsufficientFunds } = require ('./base/errors'); +const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class livecoin extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'livecoin', + 'name': 'LiveCoin', + 'countries': [ 'US', 'UK', 'RU' ], + 'rateLimit': 1000, + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27980768-f22fc424-638a-11e7-89c9-6010a54ff9be.jpg', + 'api': 'https://api.livecoin.net', + 'www': 'https://www.livecoin.net', + 'doc': 'https://www.livecoin.net/api?lang=en', + 'referral': 'https://livecoin.net/?from=Livecoin-CQ1hfx44', + }, + 'api': { + 'public': { + 'get': [ + 'exchange/all/order_book', + 'exchange/last_trades', + 'exchange/maxbid_minask', + 'exchange/order_book', + 'exchange/restrictions', + 'exchange/ticker', // omit params to get all tickers at once + 'info/coinInfo', + ], + }, + 'private': { + 'get': [ + 'exchange/client_orders', + 'exchange/order', + 'exchange/trades', + 'exchange/commission', + 'exchange/commissionCommonInfo', + 'payment/balances', + 'payment/balance', + 'payment/get/address', + 'payment/history/size', + 'payment/history/transactions', + ], + 'post': [ + 'exchange/buylimit', + 'exchange/buymarket', + 'exchange/cancellimit', + 'exchange/selllimit', + 'exchange/sellmarket', + 'payment/out/capitalist', + 'payment/out/card', + 'payment/out/coin', + 'payment/out/okpay', + 'payment/out/payeer', + 'payment/out/perfectmoney', + 'payment/voucher/amount', + 'payment/voucher/make', + 'payment/voucher/redeem', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.18 / 100, + 'taker': 0.18 / 100, + }, + }, + 'commonCurrencies': { + 'BIT': 'First Bitcoin', + 'BTCH': 'Bithash', + 'CPC': 'Capricoin', + 'CBC': 'CryptoBossCoin', // conflict with CBC (CashBet Coin) + 'CPT': 'Cryptos', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081 + 'EDR': 'E-Dinar Coin', // conflicts with EDR for Endor Protocol and EDRCoin + 'eETT': 'EETT', + 'FirstBlood': '1ST', + 'FORTYTWO': '42', + 'LEO': 'LeoCoin', + 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 + 'ORE': 'Orectic', + 'PLN': 'Plutaneum', // conflict with Polish Zloty + 'RUR': 'RUB', + 'SCT': 'SpaceCoin', + 'TPI': 'ThaneCoin', + 'UNUS': 'LEO', // https://github.com/ccxt/ccxt/issues/7496 + 'WAX': 'WAXP', + 'wETT': 'WETT', + 'XBT': 'Bricktox', + }, + 'exceptions': { + 'exact': { + '1': ExchangeError, + '10': AuthenticationError, + '100': ExchangeError, // invalid parameters + '101': AuthenticationError, + '102': AuthenticationError, + '103': InvalidOrder, // invalid currency + '104': InvalidOrder, // invalid amount + '105': InvalidOrder, // unable to block funds + '11': AuthenticationError, + '12': AuthenticationError, + '2': AuthenticationError, // "User not found" + '20': AuthenticationError, + '30': AuthenticationError, + '31': NotSupported, + '32': ExchangeError, + '429': RateLimitExceeded, + '503': ExchangeNotAvailable, + }, + 'broad': { + 'insufficient funds': InsufficientFunds, // https://github.com/ccxt/ccxt/issues/5749 + 'NOT FOUND': OrderNotFound, + 'Cannot find order': OrderNotFound, + 'Minimal amount is': InvalidOrder, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeTicker (params); + const restrictions = await this.publicGetExchangeRestrictions (); + const restrictionsById = this.indexBy (restrictions['restrictions'], 'currencyPair'); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'symbol'); + const [ baseId, quoteId ] = id.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const coinRestrictions = this.safeValue (restrictionsById, symbol); + const precision = { + 'price': 5, + 'amount': 8, + 'cost': 8, + }; + const limits = { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + }; + if (coinRestrictions) { + precision['price'] = this.safeInteger (coinRestrictions, 'priceScale', 5); + limits['amount']['min'] = this.safeFloat (coinRestrictions, 'minLimitQuantity', limits['amount']['min']); + } + limits['price'] = { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetInfoCoinInfo (params); + if (typeof response === 'string') { + if (response.indexOf ('site is under maintenance') >= 0) { + throw new OnMaintenance (this.id + ' fetchCurrencies() failed to fetch the currencies, the exchange is on maintenance'); + } else { + throw new BadResponse (this.id + ' fetchCurrencies() failed to fetch the currencies'); + } + } + const currencies = this.safeValue (response, 'info'); + let result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'symbol'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const code = this.safeCurrencyCode (id); + const precision = 8; // default precision, todo: fix "magic constants" + const walletStatus = this.safeString (currency, 'walletStatus'); + const active = (walletStatus === 'normal'); + const name = this.safeString (currency, 'name'); + const fee = this.safeFloat (currency, 'withdrawFee'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'minOrderAmount'), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': this.safeFloat (currency, 'minOrderAmount'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawAmount'), + 'max': Math.pow (10, precision), + }, + 'deposit': { + 'min': this.safeFloat (currency, 'minDepositAmount'), + 'max': undefined, + }, + }, + }; + } + result = this.appendFiatCurrencies (result); + return result; + } + + appendFiatCurrencies (result) { + const precision = 8; + const defaults = { + 'info': undefined, + 'active': true, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'withdraw': { 'min': undefined, 'max': undefined }, + 'deposit': { 'min': undefined, 'max': undefined }, + 'amount': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + }, + 'id': undefined, + 'code': undefined, + 'name': undefined, + }; + const currencies = [ + { 'id': 'USD', 'code': 'USD', 'name': 'US Dollar' }, + { 'id': 'EUR', 'code': 'EUR', 'name': 'Euro' }, + // { 'id': 'RUR', 'code': 'RUB', 'name': 'Russian ruble' }, + ]; + currencies.push ({ + 'id': 'RUR', + 'code': this.safeCurrencyCode ('RUR'), + 'name': 'Russian ruble', + }); + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const code = currency['code']; + result[code] = this.extend (defaults, currency); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetPaymentBalances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let account = undefined; + if (code in result) { + account = result[code]; + } else { + account = this.account (); + } + if (balance['type'] === 'total') { + account['total'] = this.safeFloat (balance, 'value'); + } + if (balance['type'] === 'available') { + account['free'] = this.safeFloat (balance, 'value'); + } + if (balance['type'] === 'trade') { + account['used'] = this.safeFloat (balance, 'value'); + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetExchangeCommissionCommonInfo (params); + const commission = this.safeFloat (response, 'commission'); + return { + 'info': response, + 'maker': commission, + 'taker': commission, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + 'groupByPrice': 'false', + }; + if (limit !== undefined) { + request['depth'] = limit; // 100 + } + const response = await this.publicGetExchangeOrderBook (this.extend (request, params)); + const timestamp = this.safeInteger (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': undefined, + 'vwap': this.safeFloat (ticker, 'vwap'), + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetExchangeTicker (params); + const tickers = this.indexBy (response, 'symbol'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const ticker = await this.publicGetExchangeTicker (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "time": 1409935047, + // "id": 99451, + // "price": 350, + // "quantity": 2.85714285, + // "type": "BUY" + // } + // + // fetchMyTrades (private) + // + // { + // "datetime": 1435844369, + // "id": 30651619, + // "type": "sell", + // "symbol": "BTC/EUR", + // "price": 230, + // "quantity": 0.1, + // "commission": 0, + // "clientorderid": 1472837650 + // } + const timestamp = this.safeTimestamp2 (trade, 'time', 'datetime'); + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrency = market ? market['quote'] : undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'clientorderid'); + const id = this.safeString (trade, 'id'); + const side = this.safeStringLower (trade, 'type'); + const amount = this.safeFloat (trade, 'quantity'); + const price = this.safeFloat (trade, 'price'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '/'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currencyPair': market['id'], + // 'orderDesc': 'true', // or 'false', if true then new orders will be first, otherwise old orders will be first. + // 'offset': 0, // page offset, position of the first item on the page + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['currencyPair'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetExchangeTrades (this.extend (request, params)); + // + // [ + // { + // "datetime": 1435844369, + // "id": 30651619, + // "type": "sell", + // "symbol": "BTC/EUR", + // "price": 230, + // "quantity": 0.1, + // "commission": 0, + // "clientorderid": 1472837650 + // }, + // { + // "datetime": 1435844356, + // "id": 30651618, + // "type": "sell", + // "symbol": "BTC/EUR", + // "price": 230, + // "quantity": 0.2, + // "commission": 0.092, + // "clientorderid": 1472837651 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const response = await this.publicGetExchangeLastTrades (this.extend (request, params)); + // + // [ + // { + // "time": 1409935047, + // "id": 99451, + // "price": 350, + // "quantity": 2.85714285, + // "type": "BUY" + // }, + // { + // "time": 1409934792, + // "id": 99450, + // "price": 350, + // "quantity": 0.57142857, + // "type": "SELL" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.privateGetExchangeOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + parseOrderStatus (status) { + const statuses = { + 'OPEN': 'open', + 'PARTIALLY_FILLED': 'open', + 'EXECUTED': 'closed', + 'CANCELLED': 'canceled', + 'PARTIALLY_FILLED_AND_CANCELLED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + let timestamp = undefined; + if ('lastModificationTime' in order) { + timestamp = this.safeString (order, 'lastModificationTime'); + if (timestamp !== undefined) { + if (timestamp.indexOf ('T') >= 0) { + timestamp = this.parse8601 (timestamp); + } else { + timestamp = this.safeInteger (order, 'lastModificationTime'); + } + } + } + // TODO currently not supported by livecoin + // let trades = this.parseTrades (order['trades'], market, since, limit); + const trades = undefined; + const status = this.parseOrderStatus (this.safeString2 (order, 'status', 'orderStatus')); + const marketId = this.safeString2 (order, 'symbol', 'currencyPair'); + const symbol = this.safeSymbol (marketId, market, '/'); + let type = this.safeStringLower (order, 'type'); + let side = undefined; + if (type !== undefined) { + const orderType = type.split ('_'); + type = orderType[0]; + side = orderType[1]; + } + const price = this.safeFloat (order, 'price'); + // of the next two lines the latter overrides the former, if present in the order structure + let remaining = this.safeFloat (order, 'remainingQuantity'); + remaining = this.safeFloat (order, 'remaining_quantity', remaining); + const amount = this.safeFloat (order, 'quantity', remaining); + let filled = undefined; + if (remaining !== undefined) { + filled = amount - remaining; + } + let cost = undefined; + if (filled !== undefined && price !== undefined) { + cost = filled * price; + } + const feeRate = this.safeFloat (order, 'commission_rate'); + let feeCost = undefined; + if (cost !== undefined && feeRate !== undefined) { + feeCost = cost * feeRate; + } + if ((market === undefined) && (symbol in this.markets)) { + market = this.markets[symbol]; + } + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = market['quote']; + } + return { + 'info': order, + 'id': order['id'], + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': { + 'cost': feeCost, + 'currency': feeCurrency, + 'rate': feeRate, + }, + 'average': undefined, + }; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = {}; + if (symbol !== undefined) { + market = this.market (symbol); + request['currencyPair'] = market['id']; + } + if (since !== undefined) { + request['issuedFrom'] = parseInt (since); + } + if (limit !== undefined) { + request['endRow'] = limit - 1; + } + const response = await this.privateGetExchangeClientOrders (this.extend (request, params)); + const result = []; + let rawOrders = []; + if (response['data']) { + rawOrders = response['data']; + } + for (let i = 0; i < rawOrders.length; i++) { + const order = rawOrders[i]; + result.push (this.parseOrder (order, market)); + } + return this.sortBy (result, 'timestamp'); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'openClosed': 'OPEN', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'openClosed': 'CLOSED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const method = 'privatePostExchange' + this.capitalize (side) + type; + const market = this.market (symbol); + const request = { + 'quantity': this.amountToPrecision (symbol, amount), + 'currencyPair': market['id'], + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this[method] (this.extend (request, params)); + const result = { + 'info': response, + 'id': response['orderId'].toString (), + }; + const success = this.safeValue (response, 'success'); + if (success) { + result['status'] = 'open'; + } + return result; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'orderId': id, + 'currencyPair': market['id'], + }; + const response = await this.privatePostExchangeCancellimit (this.extend (request, params)); + const message = this.safeString (response, 'message', this.json (response)); + if ('success' in response) { + if (!response['success']) { + throw new InvalidOrder (message); + } else if ('cancelled' in response) { + if (response['cancelled']) { + return { + 'status': 'canceled', + 'info': response, + }; + } else { + throw new OrderNotFound (message); + } + } + } + throw new ExchangeError (this.id + ' cancelOrder() failed: ' + this.json (response)); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // Sometimes the response with be { key: null } for all keys. + // An example is if you attempt to withdraw more than is allowed when withdrawal fees are considered. + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + let wallet = address; + if (tag !== undefined) { + wallet += '::' + tag; + } + const request = { + 'amount': this.decimalToPrecision (amount, TRUNCATE, currency['precision'], DECIMAL_PLACES), + 'currency': currency['id'], + 'wallet': wallet, + }; + const response = await this.privatePostPaymentOutCoin (this.extend (request, params)); + const id = this.safeInteger (response, 'id'); + if (id === undefined) { + throw new InsufficientFunds (this.id + ' insufficient funds to cover requested withdrawal amount post fees ' + this.json (response)); + } + return { + 'info': response, + 'id': id, + }; + } + + parseTransaction (transaction, currency = undefined) { + // { + // "id": "c853093d5aa06df1c92d79c2...", (tx on deposits, address on withdrawals) + // "type": "DEPOSIT", + // "date": 1553186482676, + // "amount": 712.61266, + // "fee": 0, + // "fixedCurrency": "XVG", + // "taxCurrency": "XVG", + // "variableAmount": null, + // "variableCurrency": null, + // "external": "Coin", + // "login": "USERNAME", + // "externalKey": "....87diPBy......3hTtuwUT78Yi", (address on deposits, tx on withdrawals) + // "documentId": 1110662453 + // }, + let txid = undefined; + let address = undefined; + const id = this.safeString (transaction, 'documentId'); + const amount = this.safeFloat (transaction, 'amount'); + const timestamp = this.safeInteger (transaction, 'date'); + const type = this.safeStringLower (transaction, 'type'); + const currencyId = this.safeString (transaction, 'fixedCurrency'); + const feeCost = this.safeFloat (transaction, 'fee'); + const code = this.safeCurrencyCode (currencyId, currency); + if (type === 'withdrawal') { + txid = this.safeString (transaction, 'externalKey'); + address = this.safeString (transaction, 'id'); + } else if (type === 'deposit') { + address = this.safeString (transaction, 'externalKey'); + txid = this.safeString (transaction, 'id'); + } + let status = undefined; + if (type === 'deposit') { + status = 'ok'; // Deposits is not registered until they are in account. Withdrawals are left as undefined, not entirely sure about theyre status. + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const endtime = 2505600000; // 29 days - exchange has maximum 30 days. + const now = this.milliseconds (); + const request = { + 'types': 'DEPOSIT', + 'end': now, + 'start': (since !== undefined) ? parseInt (since) : now - endtime, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + const response = await this.privateGetPaymentHistoryTransactions (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const endtime = 2505600000; // 29 days - exchange has maximum 30 days. + const now = this.milliseconds (); + const request = { + 'types': 'WITHDRAWAL', + 'end': now, + 'start': (since !== undefined) ? parseInt (since) : now - endtime, + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + if (since !== undefined) { + request['start'] = since; + } + const response = await this.privateGetPaymentHistoryTransactions (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit); + } + + async fetchDepositAddress (currency, params = {}) { + const request = { + 'currency': currency, + }; + const response = await this.privateGetPaymentGetAddress (this.extend (request, params)); + let address = this.safeString (response, 'wallet'); + let tag = undefined; + if (address.indexOf (':') >= 0) { + const parts = address.split (':'); + address = parts[0]; + tag = parts[2]; + } + this.checkAddress (address); + return { + 'currency': currency, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + path; + const query = this.urlencode (this.keysort (params)); + if (method === 'GET') { + if (Object.keys (params).length) { + url += '?' + query; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + if (method === 'POST') { + body = query; + } + const signature = this.hmac (this.encode (query), this.encode (this.secret), 'sha256'); + headers = { + 'Api-Key': this.apiKey, + 'Sign': signature.toUpperCase (), + 'Content-Type': 'application/x-www-form-urlencoded', + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (code >= 300) { + const feedback = this.id + ' ' + body; + const errorCode = this.safeString (response, 'errorCode'); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); + } + // returns status code 200 even if success === false + const success = this.safeValue (response, 'success', true); + if (!success) { + const feedback = this.id + ' ' + body; + const message = this.safeString2 (response, 'message', 'exception'); + if (message !== undefined) { + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); + } + } +}; + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/luno.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/luno.js new file mode 100644 index 000000000..068e1c184 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/luno.js @@ -0,0 +1,654 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class luno extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'luno', + 'name': 'luno', + 'countries': [ 'GB', 'SG', 'ZA' ], + 'rateLimit': 1000, + 'version': '1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + }, + 'urls': { + 'referral': 'https://www.luno.com/invite/44893A', + 'logo': 'https://user-images.githubusercontent.com/1294454/27766607-8c1a69d8-5ede-11e7-930c-540b5eb9be24.jpg', + 'api': 'https://api.luno.com/api', + 'www': 'https://www.luno.com', + 'doc': [ + 'https://www.luno.com/en/api', + 'https://npmjs.org/package/bitx', + 'https://github.com/bausmeier/node-bitx', + ], + }, + 'api': { + 'public': { + 'get': [ + 'orderbook', + 'orderbook_top', + 'ticker', + 'tickers', + 'trades', + ], + }, + 'private': { + 'get': [ + 'accounts/{id}/pending', + 'accounts/{id}/transactions', + 'balance', + 'beneficiaries', + 'fee_info', + 'funding_address', + 'listorders', + 'listtrades', + 'orders/{id}', + 'quotes/{id}', + 'withdrawals', + 'withdrawals/{id}', + ], + 'post': [ + 'accounts', + 'accounts/{id}/name', + 'postorder', + 'marketorder', + 'stoporder', + 'funding_address', + 'withdrawals', + 'send', + 'quotes', + 'oauth2/grant', + ], + 'put': [ + 'accounts/{id}/name', + 'quotes/{id}', + ], + 'delete': [ + 'quotes/{id}', + 'withdrawals/{id}', + ], + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetTickers (params); + const result = []; + for (let i = 0; i < response['tickers'].length; i++) { + const market = response['tickers'][i]; + const id = market['pair']; + const baseId = id.slice (0, 3); + const quoteId = id.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetBalance (params); + const wallets = this.safeValue (response, 'balance', []); + const result = []; + for (let i = 0; i < wallets.length; i++) { + const account = wallets[i]; + const accountId = this.safeString (account, 'account_id'); + const currencyId = this.safeString (account, 'asset'); + const code = this.safeCurrencyCode (currencyId); + result.push ({ + 'id': accountId, + 'type': undefined, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const wallets = this.safeValue (response, 'balance', []); + const result = { 'info': response }; + for (let i = 0; i < wallets.length; i++) { + const wallet = wallets[i]; + const currencyId = this.safeString (wallet, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const reserved = this.safeFloat (wallet, 'reserved'); + const unconfirmed = this.safeFloat (wallet, 'unconfirmed'); + const balance = this.safeFloat (wallet, 'balance'); + const account = this.account (); + account['used'] = this.sum (reserved, unconfirmed); + account['total'] = this.sum (balance, unconfirmed); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + let method = 'publicGetOrderbook'; + if (limit !== undefined) { + if (limit <= 100) { + method += 'Top'; // get just the top of the orderbook when limit is low + } + } + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this[method] (this.extend (request, params)); + const timestamp = this.safeInteger (response, 'timestamp'); + return this.parseOrderBook (response, timestamp, 'bids', 'asks', 'price', 'volume'); + } + + parseOrderStatus (status) { + const statuses = { + // todo add other statuses + 'PENDING': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "base": "string", + // "completed_timestamp": "string", + // "counter": "string", + // "creation_timestamp": "string", + // "expiration_timestamp": "string", + // "fee_base": "string", + // "fee_counter": "string", + // "limit_price": "string", + // "limit_volume": "string", + // "order_id": "string", + // "pair": "string", + // "state": "PENDING", + // "type": "BID" + // } + // + const timestamp = this.safeInteger (order, 'creation_timestamp'); + let status = this.parseOrderStatus (this.safeString (order, 'state')); + status = (status === 'open') ? status : status; + const side = (order['type'] === 'ASK') ? 'sell' : 'buy'; + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const price = this.safeFloat (order, 'limit_price'); + const amount = this.safeFloat (order, 'limit_volume'); + const quoteFee = this.safeFloat (order, 'fee_counter'); + const baseFee = this.safeFloat (order, 'fee_base'); + const filled = this.safeFloat (order, 'base'); + const cost = this.safeFloat (order, 'counter'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + const fee = { 'currency': undefined }; + if (quoteFee) { + fee['cost'] = quoteFee; + if (market !== undefined) { + fee['currency'] = market['quote']; + } + } else { + fee['cost'] = baseFee; + if (market !== undefined) { + fee['currency'] = market['base']; + } + } + const id = this.safeString (order, 'order_id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'cost': cost, + 'remaining': remaining, + 'trades': undefined, + 'fee': fee, + 'info': order, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrdersByState (state = undefined, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (state !== undefined) { + request['state'] = state; + } + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privateGetListorders (this.extend (request, params)); + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState (undefined, symbol, since, limit, params); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('PENDING', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('COMPLETE', symbol, since, limit, params); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeInteger (ticker, 'timestamp'); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last_trade'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'rolling_24_hour_volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const tickers = this.indexBy (response['tickers'], 'pair'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market) { + // For public trade data (is_buy === True) indicates 'buy' side but for private trade data + // is_buy indicates maker or taker. The value of "type" (ASK/BID) indicate sell/buy side. + // Private trade data includes ID field which public trade data does not. + const orderId = this.safeString (trade, 'order_id'); + let takerOrMaker = undefined; + let side = undefined; + if (orderId !== undefined) { + side = (trade['type'] === 'ASK') ? 'sell' : 'buy'; + if (side === 'sell' && trade['is_buy']) { + takerOrMaker = 'maker'; + } else if (side === 'buy' && !trade['is_buy']) { + takerOrMaker = 'maker'; + } else { + takerOrMaker = 'taker'; + } + } else { + side = trade['is_buy'] ? 'buy' : 'sell'; + } + const feeBase = this.safeFloat (trade, 'fee_base'); + const feeCounter = this.safeFloat (trade, 'fee_counter'); + let feeCurrency = undefined; + let feeCost = undefined; + if (feeBase !== undefined) { + if (feeBase !== 0.0) { + feeCurrency = market['base']; + feeCost = feeBase; + } + } else if (feeCounter !== undefined) { + if (feeCounter !== 0.0) { + feeCurrency = market['quote']; + feeCost = feeCounter; + } + } + const timestamp = this.safeInteger (trade, 'timestamp'); + return { + 'info': trade, + 'id': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': this.safeFloat (trade, 'price'), + 'amount': this.safeFloat (trade, 'volume'), + // Does not include potential fee costs + 'cost': this.safeFloat (trade, 'counter'), + 'fee': { + 'cost': feeCost, + 'currency': feeCurrency, + }, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (since !== undefined) { + request['since'] = since; + } + const response = await this.publicGetTrades (this.extend (request, params)); + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (since !== undefined) { + request['since'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetListtrades (this.extend (request, params)); + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetFeeInfo (params); + return { + 'info': response, + 'maker': this.safeFloat (response, 'maker_fee'), + 'taker': this.safeFloat (response, 'taker_fee'), + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + let method = 'privatePost'; + const request = { + 'pair': this.marketId (symbol), + }; + if (type === 'market') { + method += 'Marketorder'; + request['type'] = side.toUpperCase (); + if (side === 'buy') { + request['counter_volume'] = amount; + } else { + request['base_volume'] = amount; + } + } else { + method += 'Postorder'; + request['volume'] = amount; + request['price'] = price; + request['type'] = (side === 'buy') ? 'BID' : 'ASK'; + } + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['order_id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + return await this.privatePostStoporder (this.extend (request, params)); + } + + async fetchLedgerByEntries (code = undefined, entry = -1, limit = 1, params = {}) { + // by default without entry number or limit number, return most recent entry + const since = undefined; + const request = { + 'min_row': entry, + 'max_row': this.sum (entry, limit), + }; + return await this.fetchLedger (code, since, limit, this.extend (request, params)); + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + let currency = undefined; + let id = this.safeString (params, 'id'); // account id + let min_row = this.safeValue (params, 'min_row'); + let max_row = this.safeValue (params, 'max_row'); + if (id === undefined) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchLedger() requires a currency code argument if no account id specified in params'); + } + currency = this.currency (code); + const accountsByCurrencyCode = this.indexBy (this.accounts, 'currency'); + const account = this.safeValue (accountsByCurrencyCode, code); + if (account === undefined) { + throw new ExchangeError (this.id + ' fetchLedger() could not find account id for ' + code); + } + id = account['id']; + } + if (min_row === undefined && max_row === undefined) { + max_row = 0; // Default to most recent transactions + min_row = -1000; // Maximum number of records supported + } else if (min_row === undefined || max_row === undefined) { + throw new ExchangeError (this.id + " fetchLedger() require both params 'max_row' and 'min_row' or neither to be defined"); + } + if (limit !== undefined && max_row - min_row > limit) { + if (max_row <= 0) { + min_row = max_row - limit; + } else if (min_row > 0) { + max_row = min_row + limit; + } + } + if (max_row - min_row > 1000) { + throw new ExchangeError (this.id + " fetchLedger() requires the params 'max_row' - 'min_row' <= 1000"); + } + const request = { + 'id': id, + 'min_row': min_row, + 'max_row': max_row, + }; + const response = await this.privateGetAccountsIdTransactions (this.extend (params, request)); + const entries = this.safeValue (response, 'transactions', []); + return this.parseLedger (entries, currency, since, limit); + } + + parseLedgerComment (comment) { + const words = comment.split (' '); + const types = { + 'Withdrawal': 'fee', + 'Trading': 'fee', + 'Payment': 'transaction', + 'Sent': 'transaction', + 'Deposit': 'transaction', + 'Received': 'transaction', + 'Released': 'released', + 'Reserved': 'reserved', + 'Sold': 'trade', + 'Bought': 'trade', + 'Failure': 'failed', + }; + let referenceId = undefined; + const firstWord = this.safeString (words, 0); + const thirdWord = this.safeString (words, 2); + const fourthWord = this.safeString (words, 3); + let type = this.safeString (types, firstWord, undefined); + if ((type === undefined) && (thirdWord === 'fee')) { + type = 'fee'; + } + if ((type === 'reserved') && (fourthWord === 'order')) { + referenceId = this.safeString (words, 4); + } + return { + 'type': type, + 'referenceId': referenceId, + }; + } + + parseLedgerEntry (entry, currency = undefined) { + // const details = this.safeValue (entry, 'details', {}); + const id = this.safeString (entry, 'row_index'); + const account_id = this.safeString (entry, 'account_id'); + const timestamp = this.safeValue (entry, 'timestamp'); + const currencyId = this.safeString (entry, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const available_delta = this.safeFloat (entry, 'available_delta'); + const balance_delta = this.safeFloat (entry, 'balance_delta'); + const after = this.safeFloat (entry, 'balance'); + const comment = this.safeString (entry, 'description'); + let before = after; + let amount = 0.0; + const result = this.parseLedgerComment (comment); + const type = result['type']; + const referenceId = result['referenceId']; + let direction = undefined; + let status = undefined; + if (balance_delta !== 0.0) { + before = after - balance_delta; // TODO: float precision + status = 'ok'; + amount = Math.abs (balance_delta); + } else if (available_delta < 0.0) { + status = 'pending'; + amount = Math.abs (available_delta); + } else if (available_delta > 0.0) { + status = 'canceled'; + amount = Math.abs (available_delta); + } + if (balance_delta > 0 || available_delta > 0) { + direction = 'in'; + } else if (balance_delta < 0 || available_delta < 0) { + direction = 'out'; + } + return { + 'id': id, + 'direction': direction, + 'account': account_id, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'before': before, + 'after': after, + 'status': status, + 'fee': undefined, + 'info': entry, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + if (api === 'private') { + this.checkRequiredCredentials (); + const auth = this.stringToBase64 (this.apiKey + ':' + this.secret); + headers = { + 'Authorization': 'Basic ' + this.decode (auth), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('error' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lykke.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lykke.js new file mode 100644 index 000000000..c513b8d80 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/lykke.js @@ -0,0 +1,613 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class lykke extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'lykke', + 'name': 'Lykke', + 'countries': [ 'CH' ], + 'version': 'v1', + 'rateLimit': 200, + 'has': { + 'CORS': false, + 'fetchOHLCV': false, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchTrades': true, + 'fetchMyTrades': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelAllOrders': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + }, + 'timeframes': { + '1m': 'Minute', + '5m': 'Min5', + '15m': 'Min15', + '30m': 'Min30', + '1h': 'Hour', + '4h': 'Hour4', + '6h': 'Hour6', + '12h': 'Hour12', + '1d': 'Day', + '1w': 'Week', + '1M': 'Month', + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/34487620-3139a7b0-efe6-11e7-90f5-e520cef74451.jpg', + 'api': { + 'mobile': 'https://public-api.lykke.com/api', + 'public': 'https://hft-api.lykke.com/api', + 'private': 'https://hft-api.lykke.com/api', + }, + 'test': { + 'mobile': 'https://public-api.lykke.com/api', + 'public': 'https://hft-service-dev.lykkex.net/api', + 'private': 'https://hft-service-dev.lykkex.net/api', + }, + 'www': 'https://www.lykke.com', + 'doc': [ + 'https://hft-api.lykke.com/swagger/ui/', + 'https://www.lykke.com/lykke_api', + ], + 'fees': 'https://www.lykke.com/trading-conditions', + }, + 'api': { + 'mobile': { + 'get': [ + 'AssetPairs/rate', + 'AssetPairs/rate/{assetPairId}', + 'AssetPairs/dictionary/{market}', + 'Assets/dictionary', + 'Candles/history/{market}/available', + 'Candles/history/{market}/{assetPair}/{period}/{type}/{from}/{to}', + 'Company/ownershipStructure', + 'Company/registrationsCount', + 'IsAlive', + 'Market', + 'Market/{market}', + 'Market/capitalization/{market}', + 'OrderBook', + 'OrderBook/{assetPairId}', + 'Trades/{AssetPairId}', + 'Trades/Last/{assetPair}/{n}', + ], + 'post': [ + 'AssetPairs/rate/history', + 'AssetPairs/rate/history/{assetPairId}', + ], + }, + 'public': { + 'get': [ + 'AssetPairs', + 'AssetPairs/{id}', + 'IsAlive', + 'OrderBooks', + 'OrderBooks/{AssetPairId}', + ], + }, + 'private': { + 'get': [ + 'Orders', + 'Orders/{id}', + 'Wallets', + 'History/trades', + ], + 'post': [ + 'Orders/limit', + 'Orders/market', + 'Orders/{id}/Cancel', + 'Orders/v2/market', + 'Orders/v2/limit', + 'Orders/stoplimit', + 'Orders/bulk', + ], + 'delete': [ + 'Orders', + 'Orders/{id}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0, // as of 7 Feb 2018, see https://github.com/ccxt/ccxt/issues/1863 + 'taker': 0.0, // https://www.lykke.com/cp/wallet-fees-and-limits + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.001, + }, + 'deposit': { + 'BTC': 0, + }, + }, + }, + 'commonCurrencies': { + 'XPD': 'Lykke XPD', + }, + }); + } + + parseTrade (trade, market) { + // + // public fetchTrades + // + // { + // "id": "d5983ab8-e9ec-48c9-bdd0-1b18f8e80a71", + // "assetPairId": "BTCUSD", + // "dateTime": "2019-05-15T06:52:02.147Z", + // "volume": 0.00019681, + // "index": 0, + // "price": 8023.333, + // "action": "Buy" + // } + // + // private fetchMyTrades + // { + // Id: '3500b83c-9963-4349-b3ee-b3e503073cea', + // OrderId: '83b50feb-8615-4dc6-b606-8a4168ecd708', + // DateTime: '2020-05-19T11:17:39.31+00:00', + // Timestamp: '2020-05-19T11:17:39.31+00:00', + // State: null, + // Amount: -0.004, + // BaseVolume: -0.004, + // QuotingVolume: 39.3898, + // Asset: 'BTC', + // BaseAssetId: 'BTC', + // QuotingAssetId: 'USD', + // AssetPair: 'BTCUSD', + // AssetPairId: 'BTCUSD', + // Price: 9847.427, + // Fee: { Amount: null, Type: 'Unknown', FeeAssetId: null } + // }, + const marketId = this.safeString (trade, 'AssetPairId'); + const symbol = this.safeSymbol (marketId, market); + const id = this.safeString2 (trade, 'id', 'Id'); + const orderId = this.safeString (trade, 'OrderId'); + const timestamp = this.parse8601 (this.safeString2 (trade, 'dateTime', 'DateTime')); + const price = this.safeFloat2 (trade, 'price', 'Price'); + let amount = this.safeFloat2 (trade, 'volume', 'Amount'); + let side = this.safeStringLower (trade, 'action'); + if (side === undefined) { + if (amount < 0) { + side = 'sell'; + } else { + side = 'buy'; + } + } + amount = Math.abs (amount); + const cost = price * amount; + const fee = { + 'cost': 0, // There are no fees for trading. https://www.lykke.com/wallet-fees-and-limits/ + 'currency': market['quote'], + }; + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': orderId, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 100; + } + const request = { + 'AssetPairId': market['id'], + 'skip': 0, + 'take': limit, + }; + const response = await this.mobileGetTradesAssetPairId (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (limit !== undefined) { + request['take'] = limit; // How many maximum items have to be returned, max 1000 default 100. + } + if (symbol !== undefined) { + market = this.market (symbol); + request['assetPairId'] = market['id']; + } + const response = await this.privateGetHistoryTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetWallets (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'AssetId'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'Balance'); + account['used'] = this.safeFloat (balance, 'Reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { 'id': id }; + return await this.privateDeleteOrdersId (this.extend (request, params)); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['assetPairId'] = market['id']; + } + return await this.privateDeleteOrders (this.extend (request, params)); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const query = { + 'AssetPairId': market['id'], + 'OrderAction': this.capitalize (side), + 'Volume': amount, + 'Asset': market['baseId'], + }; + if (type === 'limit') { + query['Price'] = price; + } + const method = 'privatePostOrdersV2' + this.capitalize (type); + const result = await this[method] (this.extend (query, params)); + // + // market + // + // { + // "Price": 0 + // } + // + // limit + // + // { + // "Id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + // } + // + const id = this.safeString (result, 'Id'); + price = this.safeFloat (result, 'Price'); + return { + 'id': id, + 'info': result, + 'clientOrderId': undefined, + 'timestamp': undefined, + 'datetime': undefined, + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': undefined, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetAssetPairs (); + // + // [ { Id: "AEBTC", + // Name: "AE/BTC", + // Accuracy: 6, + // InvertedAccuracy: 8, + // BaseAssetId: "6f75280b-a005-4016-a3d8-03dc644e8912", + // QuotingAssetId: "BTC", + // MinVolume: 0.4, + // MinInvertedVolume: 0.0001 }, + // { Id: "AEETH", + // Name: "AE/ETH", + // Accuracy: 6, + // InvertedAccuracy: 8, + // BaseAssetId: "6f75280b-a005-4016-a3d8-03dc644e8912", + // QuotingAssetId: "ETH", + // MinVolume: 0.4, + // MinInvertedVolume: 0.001 } ] + // + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'Id'); + const name = this.safeString (market, 'Name'); + const [ baseId, quoteId ] = name.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'price': this.safeInteger (market, 'Accuracy'), + 'amount': this.safeInteger (market, 'InvertedAccuracy'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'active': true, + 'info': market, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['amount']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'baseId': undefined, + 'quoteId': undefined, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const close = this.safeFloat (ticker, 'lastPrice'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': close, + 'last': close, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': undefined, + 'quoteVolume': this.safeFloat (ticker, 'volume24H'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const ticker = await this.mobileGetMarketMarket (this.extend (request, params)); + return this.parseTicker (ticker, market); + } + + parseOrderStatus (status) { + const statuses = { + 'Open': 'open', + 'Pending': 'open', + 'InOrderBook': 'open', + 'Processing': 'open', + 'Matched': 'closed', + 'Cancelled': 'canceled', + 'Rejected': 'rejected', + 'Replaced': 'canceled', + 'Placed': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "Id": "string", + // "Status": "Unknown", + // "AssetPairId": "string", + // "Volume": 0, + // "Price": 0, + // "RemainingVolume": 0, + // "LastMatchTime": "2020-03-26T20:58:50.710Z", + // "CreatedAt": "2020-03-26T20:58:50.710Z", + // "Type": "Unknown", + // "LowerLimitPrice": 0, + // "LowerPrice": 0, + // "UpperLimitPrice": 0, + // "UpperPrice": 0 + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'Status')); + const marketId = this.safeString (order, 'AssetPairId'); + const symbol = this.safeSymbol (marketId, market); + const lastTradeTimestamp = this.parse8601 (this.safeString (order, 'LastMatchTime')); + let timestamp = undefined; + if (('Registered' in order) && (order['Registered'])) { + timestamp = this.parse8601 (order['Registered']); + } else if (('CreatedAt' in order) && (order['CreatedAt'])) { + timestamp = this.parse8601 (order['CreatedAt']); + } + const price = this.safeFloat (order, 'Price'); + let side = undefined; + let amount = this.safeFloat (order, 'Volume'); + if (amount < 0) { + side = 'sell'; + amount = Math.abs (amount); + } else { + side = 'buy'; + } + const remaining = Math.abs (this.safeFloat (order, 'RemainingVolume')); + const filled = amount - remaining; + const cost = filled * price; + const id = this.safeString (order, 'Id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': undefined, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetOrders (params); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'InOrderBook', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'Matched', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetOrderBooksAssetPairId (this.extend ({ + 'AssetPairId': this.marketId (symbol), + }, params)); + const orderbook = { + 'timestamp': undefined, + 'bids': [], + 'asks': [], + }; + let timestamp = undefined; + for (let i = 0; i < response.length; i++) { + const side = response[i]; + if (side['IsBuy']) { + orderbook['bids'] = this.arrayConcat (orderbook['bids'], side['Prices']); + } else { + orderbook['asks'] = this.arrayConcat (orderbook['asks'], side['Prices']); + } + const sideTimestamp = this.parse8601 (side['Timestamp']); + timestamp = (timestamp === undefined) ? sideTimestamp : Math.max (timestamp, sideTimestamp); + } + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'Price', 'Volume'); + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1) { + const price = this.safeFloat (bidask, priceKey); + let amount = this.safeFloat (bidask, amountKey); + if (amount < 0) { + amount = -amount; + } + return [ price, amount ]; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'mobile') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + if ((method === 'GET') || (method === 'DELETE')) { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + this.checkRequiredCredentials (); + headers = { + 'api-key': this.apiKey, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }; + if (method === 'POST') { + if (Object.keys (params).length) { + body = this.json (params); + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/mercado.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/mercado.js new file mode 100644 index 000000000..228e82f69 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/mercado.js @@ -0,0 +1,509 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class mercado extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'mercado', + 'name': 'Mercado Bitcoin', + 'countries': [ 'BR' ], // Brazil + 'rateLimit': 1000, + 'version': 'v3', + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': false, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '2w': '2w', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27837060-e7c58714-60ea-11e7-9192-f05e86adb83f.jpg', + 'api': { + 'public': 'https://www.mercadobitcoin.net/api', + 'private': 'https://www.mercadobitcoin.net/tapi', + 'v4Public': 'https://www.mercadobitcoin.com.br/v4', + }, + 'www': 'https://www.mercadobitcoin.com.br', + 'doc': [ + 'https://www.mercadobitcoin.com.br/api-doc', + 'https://www.mercadobitcoin.com.br/trade-api', + ], + }, + 'api': { + 'public': { + 'get': [ + '{coin}/orderbook/', // last slash critical + '{coin}/ticker/', + '{coin}/trades/', + '{coin}/trades/{from}/', + '{coin}/trades/{from}/{to}', + '{coin}/day-summary/{year}/{month}/{day}/', + ], + }, + 'private': { + 'post': [ + 'cancel_order', + 'get_account_info', + 'get_order', + 'get_withdrawal', + 'list_system_messages', + 'list_orders', + 'list_orderbook', + 'place_buy_order', + 'place_sell_order', + 'place_market_buy_order', + 'place_market_sell_order', + 'withdraw_coin', + ], + }, + 'v4Public': { + 'get': [ + '{coin}/candle/', + ], + }, + }, + 'markets': { + 'BTC/BRL': { 'id': 'BRLBTC', 'symbol': 'BTC/BRL', 'base': 'BTC', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Bitcoin' }, + 'LTC/BRL': { 'id': 'BRLLTC', 'symbol': 'LTC/BRL', 'base': 'LTC', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Litecoin' }, + 'BCH/BRL': { 'id': 'BRLBCH', 'symbol': 'BCH/BRL', 'base': 'BCH', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'BCash' }, + 'XRP/BRL': { 'id': 'BRLXRP', 'symbol': 'XRP/BRL', 'base': 'XRP', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Ripple' }, + 'ETH/BRL': { 'id': 'BRLETH', 'symbol': 'ETH/BRL', 'base': 'ETH', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'Ethereum' }, + 'USDC/BRL': { 'id': 'BRLUSDC', 'symbol': 'USDC/BRL', 'base': 'USDC', 'quote': 'BRL', 'precision': { 'amount': 8, 'price': 5 }, 'suffix': 'USDC' }, + }, + 'fees': { + 'trading': { + 'maker': 0.3 / 100, + 'taker': 0.7 / 100, + }, + }, + }); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['base'], + }; + const response = await this.publicGetCoinOrderbook (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['base'], + }; + const response = await this.publicGetCoinTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'ticker', {}); + const timestamp = this.safeTimestamp (ticker, 'date'); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'tid'); + const type = undefined; + const side = this.safeString (trade, 'type'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = 'publicGetCoinTrades'; + const request = { + 'coin': market['base'], + }; + if (since !== undefined) { + method += 'From'; + request['from'] = parseInt (since / 1000); + } + const to = this.safeInteger (params, 'to'); + if (to !== undefined) { + method += 'To'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetAccountInfo (params); + const data = this.safeValue (response, 'response_data', {}); + const balances = this.safeValue (data, 'balance', {}); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + if (currencyId in balances) { + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['total'] = this.safeFloat (balance, 'total'); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'coin_pair': this.marketId (symbol), + }; + let method = this.capitalize (side) + 'Order'; + if (type === 'limit') { + method = 'privatePostPlace' + method; + request['limit_price'] = this.priceToPrecision (symbol, price); + request['quantity'] = this.amountToPrecision (symbol, amount); + } else { + method = 'privatePostPlaceMarket' + method; + if (side === 'buy') { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount'); + } + request['cost'] = this.priceToPrecision (symbol, amount * price); + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this[method] (this.extend (request, params)); + // TODO: replace this with a call to parseOrder for unification + return { + 'info': response, + 'id': response['response_data']['order']['order_id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder () requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin_pair': market['id'], + 'order_id': id, + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + // + // { + // response_data: { + // order: { + // order_id: 2176769, + // coin_pair: 'BRLBCH', + // order_type: 2, + // status: 3, + // has_fills: false, + // quantity: '0.10000000', + // limit_price: '1996.15999', + // executed_quantity: '0.00000000', + // executed_price_avg: '0.00000', + // fee: '0.00000000', + // created_timestamp: '1536956488', + // updated_timestamp: '1536956499', + // operations: [] + // } + // }, + // status_code: 100, + // server_unix_timestamp: '1536956499' + // } + // + const responseData = this.safeValue (response, 'response_data', {}); + const order = this.safeValue (responseData, 'order', {}); + return this.parseOrder (order, market); + } + + parseOrderStatus (status) { + const statuses = { + '2': 'open', + '3': 'canceled', + '4': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "order_id": 4, + // "coin_pair": "BRLBTC", + // "order_type": 1, + // "status": 2, + // "has_fills": true, + // "quantity": "2.00000000", + // "limit_price": "900.00000", + // "executed_quantity": "1.00000000", + // "executed_price_avg": "900.00000", + // "fee": "0.00300000", + // "created_timestamp": "1453838494", + // "updated_timestamp": "1453838494", + // "operations": [ + // { + // "operation_id": 1, + // "quantity": "1.00000000", + // "price": "900.00000", + // "fee_rate": "0.30", + // "executed_timestamp": "1453838494", + // }, + // ], + // } + // + const id = this.safeString (order, 'order_id'); + let side = undefined; + if ('order_type' in order) { + side = (order['order_type'] === 1) ? 'buy' : 'sell'; + } + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'coin_pair'); + market = this.safeMarket (marketId, market); + const timestamp = this.safeTimestamp (order, 'created_timestamp'); + const fee = { + 'cost': this.safeFloat (order, 'fee'), + 'currency': market['quote'], + }; + const price = this.safeFloat (order, 'limit_price'); + // price = this.safeFloat (order, 'executed_price_avg', price); + const average = this.safeFloat (order, 'executed_price_avg'); + const amount = this.safeFloat (order, 'quantity'); + const filled = this.safeFloat (order, 'executed_quantity'); + const remaining = amount - filled; + const cost = filled * average; + const lastTradeTimestamp = this.safeTimestamp (order, 'updated_timestamp'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': market['symbol'], + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, // todo parse trades (operations) + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder () requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin_pair': market['id'], + 'order_id': parseInt (id), + }; + const response = await this.privatePostGetOrder (this.extend (request, params)); + const responseData = this.safeValue (response, 'response_data', {}); + const order = this.safeValue (responseData, 'order'); + return this.parseOrder (order, market); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coin': currency['id'], + 'quantity': amount.toFixed (10), + 'address': address, + }; + if (code === 'BRL') { + const account_ref = ('account_ref' in params); + if (!account_ref) { + throw new ArgumentsRequired (this.id + ' requires account_ref parameter to withdraw ' + code); + } + } else if (code !== 'LTC') { + const tx_fee = ('tx_fee' in params); + if (!tx_fee) { + throw new ArgumentsRequired (this.id + ' requires tx_fee parameter to withdraw ' + code); + } + if (code === 'XRP') { + if (tag === undefined) { + if (!('destination_tag' in params)) { + throw new ArgumentsRequired (this.id + ' requires a tag argument or destination_tag parameter to withdraw ' + code); + } + } else { + request['destination_tag'] = tag; + } + } + } + const response = await this.privatePostWithdrawCoin (this.extend (request, params)); + return { + 'info': response, + 'id': response['response_data']['withdrawal']['id'], + }; + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeTimestamp (ohlcv, 'timestamp'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'precision': this.timeframes[timeframe], + 'coin': market['id'].toLowerCase (), + }; + if (limit !== undefined && since !== undefined) { + request['from'] = parseInt (since / 1000); + request['to'] = this.sum (request['from'], limit * this.parseTimeframe (timeframe)); + } else if (since !== undefined) { + request['from'] = parseInt (since / 1000); + request['to'] = this.sum (this.seconds (), 1); + } else if (limit !== undefined) { + request['to'] = this.seconds (); + request['from'] = request['to'] - (limit * this.parseTimeframe (timeframe)); + } + const response = await this.v4PublicGetCoinCandle (this.extend (request, params)); + const candles = this.safeValue (response, 'candles', []); + return this.parseOHLCVs (candles, market, timeframe, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders () requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin_pair': market['id'], + }; + const response = await this.privatePostListOrders (this.extend (request, params)); + const responseData = this.safeValue (response, 'response_data', {}); + const orders = this.safeValue (responseData, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public' || (api === 'v4Public')) { + url += this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + url += this.version + '/'; + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'tapi_method': path, + 'tapi_nonce': nonce, + }, params)); + const auth = '/tapi/' + this.version + '/' + '?' + body; + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'TAPI-ID': this.apiKey, + 'TAPI-MAC': this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('error_message' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/mixcoins.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/mixcoins.js new file mode 100644 index 000000000..85138f138 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/mixcoins.js @@ -0,0 +1,229 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class mixcoins extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'mixcoins', + 'name': 'MixCoins', + 'countries': [ 'GB', 'HK' ], + 'rateLimit': 1500, + 'version': 'v1', + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460810-1dd06c00-c616-11ea-9276-956f400d6ffa.jpg', + 'api': 'https://mixcoins.com/api', + 'www': 'https://mixcoins.com', + 'doc': 'https://mixcoins.com/help/api/', + }, + 'api': { + 'public': { + 'get': [ + 'ticker/', + 'trades/', + 'depth/', + ], + }, + 'private': { + 'post': [ + 'cancel', + 'info', + 'orders', + 'order', + 'transactions', + 'trade', + ], + }, + }, + 'markets': { + 'BTC/USDT': { 'id': 'btc_usdt', 'symbol': 'BTC/USDT', 'base': 'BTC', 'quote': 'USDT', 'baseId': 'btc', 'quoteId': 'usdt', 'maker': 0.0015, 'taker': 0.0025 }, + 'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.001, 'taker': 0.0015 }, + 'BCH/BTC': { 'id': 'bch_btc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bch', 'quoteId': 'btc', 'maker': 0.001, 'taker': 0.0015 }, + 'LSK/BTC': { 'id': 'lsk_btc', 'symbol': 'LSK/BTC', 'base': 'LSK', 'quote': 'BTC', 'baseId': 'lsk', 'quoteId': 'btc', 'maker': 0.0015, 'taker': 0.0025 }, + 'BCH/USDT': { 'id': 'bch_usdt', 'symbol': 'BCH/USDT', 'base': 'BCH', 'quote': 'USDT', 'baseId': 'bch', 'quoteId': 'usdt', 'maker': 0.001, 'taker': 0.0015 }, + 'ETH/USDT': { 'id': 'eth_usdt', 'symbol': 'ETH/USDT', 'base': 'ETH', 'quote': 'USDT', 'baseId': 'eth', 'quoteId': 'usdt', 'maker': 0.001, 'taker': 0.0015 }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostInfo (params); + const balances = this.safeValue (response['result'], 'wallet'); + const result = { 'info': response }; + const currencyIds = Object.keys (balances); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (balances, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'avail'); + account['used'] = this.safeFloat (balance, 'lock'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response['result']); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + }; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = this.safeValue (response, 'result'); + const timestamp = this.milliseconds (); + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response['result'], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'op': side, + 'amount': amount, + }; + if (type === 'market') { + request['order_type'] = 1; + request['price'] = price; + } else { + request['order_type'] = 0; + } + const response = await this.privatePostTrade (this.extend (request, params)); + return { + 'info': response, + 'id': response['result']['id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + path; + if (api === 'public') { + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.secret, 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('status' in response) { + // + // todo add a unified standard handleErrors with this.exceptions in describe() + // + // {"status":503,"message":"Maintenancing, try again later","result":null} + // + if (response['status'] === 200) { + return response; + } + } + throw new ExchangeError (this.id + ' ' + this.json (response)); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/novadax.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/novadax.js new file mode 100644 index 000000000..c02d4f1a9 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/novadax.js @@ -0,0 +1,1188 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, PermissionDenied, BadRequest, CancelPending, OrderNotFound, InsufficientFunds, RateLimitExceeded, InvalidOrder, AccountSuspended, BadSymbol, OnMaintenance } = require ('./base/errors'); +const { TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class novadax extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'novadax', + 'name': 'NovaDAX', + 'countries': [ 'BR' ], // Brazil + 'rateLimit': 50, + 'version': 'v1', + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelOrder': true, + 'createOrder': true, + 'fetchAccounts': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOrderTrades': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'ONE_MIN', + '5m': 'FIVE_MIN', + '15m': 'FIFTEEN_MIN', + '30m': 'HALF_HOU', + '1h': 'ONE_HOU', + '1d': 'ONE_DAY', + '1w': 'ONE_WEE', + '1M': 'ONE_MON', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/92337550-2b085500-f0b3-11ea-98e7-5794fb07dd3b.jpg', + 'api': { + 'public': 'https://api.novadax.com', + 'private': 'https://api.novadax.com', + }, + 'www': 'https://www.novadax.com.br', + 'doc': [ + 'https://doc.novadax.com/pt-BR/', + ], + 'fees': 'https://www.novadax.com.br/fees-and-limits', + 'referral': 'https://www.novadax.com.br/?s=ccxt', + }, + 'api': { + 'public': { + 'get': [ + 'common/symbol', + 'common/symbols', + 'common/timestamp', + 'market/tickers', + 'market/ticker', + 'market/depth', + 'market/trades', + 'market/kline/history', + ], + }, + 'private': { + 'get': [ + 'orders/get', + 'orders/list', + 'orders/fill', + 'orders/fills', + 'account/getBalance', + 'account/subs', + 'account/subs/balance', + 'account/subs/transfer/record', + 'wallet/query/deposit-withdraw', + ], + 'post': [ + 'orders/create', + 'orders/cancel', + 'account/withdraw/coin', + 'account/subs/transfer', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.5 / 100, + 'maker': 0.3 / 100, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'exceptions': { + 'exact': { + 'A99999': ExchangeError, // 500 Failed Internal error + // 'A10000': ExchangeError, // 200 Success Successful request + 'A10001': BadRequest, // 400 Params error Parameter is invalid + 'A10002': ExchangeError, // 404 Api not found API used is irrelevant + 'A10003': AuthenticationError, // 403 Authentication failed Authentication is failed + 'A10004': RateLimitExceeded, // 429 Too many requests Too many requests are made + 'A10005': PermissionDenied, // 403 Kyc required Need to complete KYC firstly + 'A10006': AccountSuspended, // 403 Customer canceled Account is canceled + 'A10007': BadRequest, // 400 Account not exist Sub account does not exist + 'A10011': BadSymbol, // 400 Symbol not exist Trading symbol does not exist + 'A10012': BadSymbol, // 400 Symbol not trading Trading symbol is temporarily not available + 'A10013': OnMaintenance, // 503 Symbol maintain Trading symbol is in maintain + 'A30001': OrderNotFound, // 400 Order not found Queried order is not found + 'A30002': InvalidOrder, // 400 Order amount is too small Order amount is too small + 'A30003': InvalidOrder, // 400 Order amount is invalid Order amount is invalid + 'A30004': InvalidOrder, // 400 Order value is too small Order value is too small + 'A30005': InvalidOrder, // 400 Order value is invalid Order value is invalid + 'A30006': InvalidOrder, // 400 Order price is invalid Order price is invalid + 'A30007': InsufficientFunds, // 400 Insufficient balance The balance is insufficient + 'A30008': InvalidOrder, // 400 Order was closed The order has been executed + 'A30009': InvalidOrder, // 400 Order canceled The order has been cancelled + 'A30010': CancelPending, // 400 Order cancelling The order is being cancelled + 'A30011': InvalidOrder, // 400 Order price too high The order price is too high + 'A30012': InvalidOrder, // 400 Order price too low The order price is too low + }, + 'broad': { + }, + }, + 'options': { + 'fetchOHLCV': { + 'volume': 'amount', // 'amount' for base volume or 'vol' for quote volume + }, + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetCommonTimestamp (params); + // + // { + // "code":"A10000", + // "data":1599090512080, + // "message":"Success" + // } + // + return this.safeInteger (response, 'data'); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetCommonSymbols (params); + // + // { + // "code":"A10000", + // "data":[ + // { + // "amountPrecision":8, + // "baseCurrency":"BTC", + // "minOrderAmount":"0.001", + // "minOrderValue":"25", + // "pricePrecision":2, + // "quoteCurrency":"BRL", + // "status":"ONLINE", + // "symbol":"BTC_BRL", + // "valuePrecision":2 + // }, + // ], + // "message":"Success" + // } + // + const result = []; + const data = this.safeValue (response, 'data', []); + for (let i = 0; i < data.length; i++) { + const market = data[i]; + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const id = this.safeString (market, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amountPrecision'), + 'price': this.safeInteger (market, 'pricePrecision'), + 'cost': this.safeInteger (market, 'valuePrecision'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'minOrderAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minOrderValue'), + 'max': undefined, + }, + }; + const status = this.safeString (market, 'status'); + const active = (status === 'ONLINE'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "ask":"61946.1", + // "baseVolume24h":"164.41930186", + // "bid":"61815", + // "high24h":"64930.72", + // "lastPrice":"61928.41", + // "low24h":"61156.32", + // "open24h":"64512.46", + // "quoteVolume24h":"10308157.95", + // "symbol":"BTC_BRL", + // "timestamp":1599091115090 + // } + // + const timestamp = this.safeInteger (ticker, 'timestamp'); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const open = this.safeFloat (ticker, 'open24h'); + const last = this.safeFloat (ticker, 'lastPrice'); + let percentage = undefined; + let change = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + percentage = change / open * 100; + average = this.sum (last, open) / 2; + } + const baseVolume = this.safeFloat (ticker, 'baseVolume24h'); + const quoteVolume = this.safeFloat (ticker, 'quoteVolume24h'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24h'), + 'low': this.safeFloat (ticker, 'low24h'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetMarketTicker (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data":{ + // "ask":"61946.1", + // "baseVolume24h":"164.41930186", + // "bid":"61815", + // "high24h":"64930.72", + // "lastPrice":"61928.41", + // "low24h":"61156.32", + // "open24h":"64512.46", + // "quoteVolume24h":"10308157.95", + // "symbol":"BTC_BRL", + // "timestamp":1599091115090 + // }, + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetMarketTickers (params); + // + // { + // "code":"A10000", + // "data":[ + // { + // "ask":"61879.36", + // "baseVolume24h":"164.40955092", + // "bid":"61815", + // "high24h":"64930.72", + // "lastPrice":"61820.04", + // "low24h":"61156.32", + // "open24h":"64624.19", + // "quoteVolume24h":"10307493.92", + // "symbol":"BTC_BRL", + // "timestamp":1599091291083 + // }, + // ], + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < data.length; i++) { + const ticker = this.parseTicker (data[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['limit'] = limit; // default 10, max 20 + } + const response = await this.publicGetMarketDepth (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data":{ + // "asks":[ + // ["0.037159","0.3741"], + // ["0.037215","0.2706"], + // ["0.037222","1.8459"], + // ], + // "bids":[ + // ["0.037053","0.3857"], + // ["0.036969","0.8101"], + // ["0.036953","1.5226"], + // ], + // "timestamp":1599280414448 + // }, + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', {}); + const timestamp = this.safeInteger (data, 'timestamp'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks'); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "amount":"0.0632", + // "price":"0.037288", + // "side":"BUY", + // "timestamp":1599279694576 + // } + // + // private fetchOrderTrades + // + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // } + // + // private fetchMyTrades + // + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "feeAmount": "0.0000988", + // "feeCurrency": "BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // } + // + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'orderId'); + const timestamp = this.safeInteger (trade, 'timestamp'); + const side = this.safeStringLower (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = this.safeFloat (trade, 'volume'); + if ((cost === undefined) && (amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const takerOrMaker = this.safeStringLower (trade, 'role'); + const feeString = this.safeString (trade, 'fee'); + let fee = undefined; + if (feeString !== undefined) { + const parts = feeString.split (' '); + const feeCurrencyId = this.safeString (parts, 1); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': this.safeFloat (parts, 0), + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default 100 + } + const response = await this.publicGetMarketTrades (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data":[ + // {"amount":"0.0632","price":"0.037288","side":"BUY","timestamp":1599279694576}, + // {"amount":"0.0052","price":"0.03715","side":"SELL","timestamp":1599276606852}, + // {"amount":"0.0058","price":"0.037188","side":"SELL","timestamp":1599275187812}, + // ], + // "message":"Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'unit': this.timeframes[timeframe], + }; + const duration = this.parseTimeframe (timeframe); + const now = this.seconds (); + if (limit === undefined) { + limit = 3000; // max + } + if (since === undefined) { + request['from'] = now - limit * duration; + request['to'] = now; + } else { + const startFrom = parseInt (since / 1000); + request['from'] = startFrom; + request['to'] = this.sum (startFrom, limit * duration); + } + const response = await this.publicGetMarketKlineHistory (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "amount": 8.25709100, + // "closePrice": 62553.20, + // "count": 29, + // "highPrice": 62592.87, + // "lowPrice": 62553.20, + // "openPrice": 62554.23, + // "score": 1602501480, + // "symbol": "BTC_BRL", + // "vol": 516784.2504067500 + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "amount": 8.25709100, + // "closePrice": 62553.20, + // "count": 29, + // "highPrice": 62592.87, + // "lowPrice": 62553.20, + // "openPrice": 62554.23, + // "score": 1602501480, + // "symbol": "BTC_BRL", + // "vol": 516784.2504067500 + // } + // + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const volumeField = this.safeString (options, 'volume', 'amount'); // or vol + return [ + this.safeTimestamp (ohlcv, 'score'), + this.safeFloat (ohlcv, 'openPrice'), + this.safeFloat (ohlcv, 'highPrice'), + this.safeFloat (ohlcv, 'lowPrice'), + this.safeFloat (ohlcv, 'closePrice'), + this.safeFloat (ohlcv, volumeField), + ]; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccountGetBalance (params); + // + // { + // "code": "A10000", + // "data": [ + // { + // "available": "1.23", + // "balance": "0.23", + // "currency": "BTC", + // "hold": "1" + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + const result = { 'info': response }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'available'); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'hold'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const uppercaseSide = side.toUpperCase (); + const request = { + 'symbol': market['id'], + 'type': uppercaseType, // LIMIT, MARKET + 'side': uppercaseSide, // or SELL + // 'accountId': '...', // subaccount id, optional + // 'amount': this.amountToPrecision (symbol, amount), + // "price": "1234.5678", // required for LIMIT and STOP orders + }; + if (uppercaseType === 'LIMIT') { + request['price'] = this.priceToPrecision (symbol, price); + request['amount'] = this.amountToPrecision (symbol, amount); + } else if (uppercaseType === 'MARKET') { + if (uppercaseSide === 'SELL') { + request['amount'] = this.amountToPrecision (symbol, amount); + } else if (uppercaseSide === 'BUY') { + let value = this.safeFloat (params, 'value'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (value === undefined) { + value = amount * price; + } + } else if (value === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'value' extra parameter (the exchange-specific behaviour)"); + } + } else { + value = (value === undefined) ? amount : value; + } + const precision = market['precision']['price']; + request['value'] = this.decimalToPrecision (value, TRUNCATE, precision, this.precisionMode); + } + } + const response = await this.privatePostOrdersCreate (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": { + // "amount": "0.001", + // "averagePrice": null, + // "filledAmount": "0", + // "filledFee": "0", + // "filledValue": "0", + // "id": "633679992971251712", + // "price": "35000", + // "side": "BUY", + // "status": "PROCESSING", + // "symbol": "BTC_BRL", + // "timestamp": 1571122683535, + // "type": "LIMIT", + // "value": "35" + // }, + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privatePostOrdersCancel (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": { + // "result": true + // }, + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersGet (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": { + // "id": "608695623247466496", + // "symbol": "BTC_BRL", + // "type": "MARKET", + // "side": "SELL", + // "price": null, + // "averagePrice": "0", + // "amount": "0.123", + // "filledAmount": "0", + // "value": null, + // "filledValue": "0", + // "filledFee": "0", + // "status": "REJECTED", + // "timestamp": 1565165945588 + // }, + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': market['id'], + // 'status': 'SUBMITTED,PROCESSING', // SUBMITTED, PROCESSING, PARTIAL_FILLED, CANCELING, FILLED, CANCELED, REJECTED + // 'fromId': '...', // order id to begin with + // 'toId': '...', // order id to end up with + // 'fromTimestamp': since, + // 'toTimestamp': this.milliseconds (), + // 'limit': limit, // default 100, max 100 + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default 100, max 100 + } + if (since !== undefined) { + request['fromTimestamp'] = since; + } + const response = await this.privateGetOrdersList (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "608695678650028032", + // "symbol": "BTC_BRL", + // "type": "MARKET", + // "side": "SELL", + // "price": null, + // "averagePrice": "0", + // "amount": "0.123", + // "filledAmount": "0", + // "value": null, + // "filledValue": "0", + // "filledFee": "0", + // "status": "REJECTED", + // "timestamp": 1565165958796 + // }, + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'SUBMITTED,PROCESSING,PARTIAL_FILLED,CANCELING', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'FILLED,CANCELED,REJECTED', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const response = await this.privateGetOrdersFill (this.extend (request, params)); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const data = this.safeValue (response, 'data', []); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // }, + // ], + // "message": "Success" + // } + // + return this.parseTrades (data, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'SUBMITTED': 'open', + 'PROCESSING': 'open', + 'PARTIAL_FILLED': 'open', + 'CANCELING': 'open', + 'FILLED': 'closed', + 'CANCELED': 'canceled', + 'REJECTED': 'rejected', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder, fetchOrders, fetchOrder + // + // { + // "amount": "0.001", + // "averagePrice": null, + // "filledAmount": "0", + // "filledFee": "0", + // "filledValue": "0", + // "id": "633679992971251712", + // "price": "35000", + // "side": "BUY", + // "status": "PROCESSING", + // "symbol": "BTC_BRL", + // "timestamp": 1571122683535, + // "type": "LIMIT", + // "value": "35" + // } + // + // cancelOrder + // + // { + // "result": true + // } + // + const id = this.safeString (order, 'id'); + const amount = this.safeFloat (order, 'amount'); + const price = this.safeFloat (order, 'price'); + const cost = this.safeFloat (order, 'filledValue'); + const type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeInteger (order, 'timestamp'); + const average = this.safeFloat (order, 'averagePrice'); + const filled = this.safeFloat (order, 'filledAmount'); + let remaining = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = Math.max (0, amount - filled); + } + let fee = undefined; + const feeCost = this.safeFloat (order, 'filledFee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'code': currency['id'], + 'amount': this.currencyToPrecision (code, amount), + 'wallet': address, + }; + if (tag !== undefined) { + request['tag'] = tag; + } + const response = await this.privatePostAccountWithdrawCoin (this.extend (request, params)); + // + // { + // "code":"A10000", + // "data": "DR123", + // "message":"Success" + // } + // + return this.parseTransaction (response, currency); + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetAccountSubs (params); + // + // { + // "code": "A10000", + // "data": [ + // { + // "subId": "CA648856083527372800", + // "state": "Normal", + // "subAccount": "003", + // "subIdentify": "003" + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < data.length; i++) { + const account = data[i]; + const accountId = this.safeString (account, 'subId'); + const type = this.safeString (account, 'subAccount'); + result.push ({ + 'id': accountId, + 'type': type, + 'currency': undefined, + 'info': account, + }); + } + return result; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'coin_in', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'coin_out', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'currency': currency['id'], + // 'type': 'coin_in', // 'coin_out' + // 'direct': 'asc', // 'desc' + // 'size': limit, // default 100 + // 'start': id, // offset id + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.privateGetWalletQueryDepositWithdraw (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "DR562339304588709888", + // "type": "COIN_IN", + // "currency": "XLM", + // "chain": "XLM", + // "address": "GCUTK7KHPJC3ZQJ3OMWWFHAK2OXIBRD4LNZQRCCOVE7A2XOPP2K5PU5Q", + // "addressTag": "1000009", + // "amount": 1.0, + // "state": "SUCCESS", + // "txHash": "39210645748822f8d4ce673c7559aa6622e6e9cdd7073bc0fcae14b1edfda5f4", + // "createdAt": 1554113737000, + // "updatedAt": 1601371273000 + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + parseTransactionStatus (status) { + // Pending the record is wait broadcast to chain + // x/M confirming the comfirming state of tx, the M is total confirmings needed + // SUCCESS the record is success full + // FAIL the record failed + const parts = status.split (' '); + status = this.safeString (parts, 1, status); + const statuses = { + 'Pending': 'pending', + 'confirming': 'pending', + 'SUCCESS': 'ok', + 'FAIL': 'failed', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "code":"A10000", + // "data": "DR123", + // "message":"Success" + // } + // + // fetchTransactions + // + // { + // "id": "DR562339304588709888", + // "type": "COIN_IN", + // "currency": "XLM", + // "chain": "XLM", + // "address": "GCUTK7KHPJC3ZQJ3OMWWFHAK2OXIBRD4LNZQRCCOVE7A2XOPP2K5PU5Q", + // "addressTag": "1000009", + // "amount": 1.0, + // "state": "SUCCESS", + // "txHash": "39210645748822f8d4ce673c7559aa6622e6e9cdd7073bc0fcae14b1edfda5f4", + // "createdAt": 1554113737000, + // "updatedAt": 1601371273000 + // } + // + const id = this.safeString2 (transaction, 'id', 'data'); + let type = this.safeString (transaction, 'type'); + if (type === 'COIN_IN') { + type = 'deposit'; + } else if (type === 'COIN_OUT') { + type = 'withdraw'; + } + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'addressTag'); + const txid = this.safeString (transaction, 'txHash'); + const timestamp = this.safeInteger (transaction, 'createdAt'); + const updated = this.safeInteger (transaction, 'updatedAt'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'orderId': id, // Order ID, string + // 'symbol': market['id'], // The trading symbol, like BTC_BRL, string + // 'fromId': fromId, // Search fill id to begin with, string + // 'toId': toId, // Search fill id to end up with, string + // 'fromTimestamp': since, // Search order fill time to begin with, in milliseconds, string + // 'toTimestamp': this.milliseconds (), // Search order fill time to end up with, in milliseconds, string + // 'limit': limit, // The number of fills to return, default 100, max 100, string + // 'accountId': subaccountId, // Sub account ID, if not informed, the fills will be return under master account, string + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['fromTimestamp'] = since; + } + const response = await this.privateGetOrdersFills (this.extend (request, params)); + // + // { + // "code": "A10000", + // "data": [ + // { + // "id": "608717046691139584", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0988", + // "price": "45514.76", + // "fee": "0.0000988 BTC", + // "feeAmount": "0.0000988", + // "feeCurrency": "BTC", + // "role": "MAKER", + // "timestamp": 1565171053345 + // }, + // { + // "id": "608717065729085441", + // "orderId": "608716957545402368", + // "symbol": "BTC_BRL", + // "side": "BUY", + // "amount": "0.0242", + // "price": "45514.76", + // "fee": "0.0000242 BTC", + // "feeAmount": "0.0000988", + // "feeCurrency": "BTC", + // "role": "MAKER", + // "timestamp": 1565171057882 + // } + // ], + // "message": "Success" + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = '/' + this.version + '/' + this.implodeParams (path, params); + let url = this.urls['api'][api] + request; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + headers = { + 'X-Nova-Access-Key': this.apiKey, + 'X-Nova-Timestamp': timestamp, + }; + let queryString = undefined; + if (method === 'POST') { + body = this.json (query); + queryString = this.hash (body, 'md5'); + headers['Content-Type'] = 'application/json'; + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + queryString = this.urlencode (this.keysort (query)); + } + const auth = method + "\n" + request + "\n" + queryString + "\n" + timestamp; // eslint-disable-line quotes + headers['X-Nova-Signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"code":"A10003","data":[],"message":"Authentication failed, Invalid accessKey."} + // + const errorCode = this.safeString (response, 'code'); + if (errorCode !== 'A10000') { + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/oceanex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/oceanex.js new file mode 100644 index 000000000..b77c3dde7 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/oceanex.js @@ -0,0 +1,683 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, AuthenticationError, ArgumentsRequired, BadRequest, InvalidOrder, InsufficientFunds, OrderNotFound, PermissionDenied } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class oceanex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'oceanex', + 'name': 'OceanEx', + 'countries': [ 'US' ], + 'version': 'v1', + 'rateLimit': 3000, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/58385970-794e2d80-8001-11e9-889c-0567cd79b78e.jpg', + 'api': 'https://api.oceanex.pro', + 'www': 'https://www.oceanex.pro.com', + 'doc': 'https://api.oceanex.pro/doc/v1', + 'referral': 'https://oceanex.pro/signup?referral=VE24QX', + }, + 'has': { + 'fetchMarkets': true, + 'fetchCurrencies': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTrades': true, + 'fetchTradingLimits': false, + 'fetchTradingFees': false, + 'fetchAllTradingFees': true, + 'fetchFundingFees': false, + 'fetchTime': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchBalance': true, + 'createMarketOrder': true, + 'createOrder': true, + 'cancelOrder': true, + 'cancelOrders': true, + 'cancelAllOrders': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'tickers/{pair}', + 'tickers_multi', + 'order_book', + 'order_book/multi', + 'fees/trading', + 'trades', + 'timestamp', + ], + }, + 'private': { + 'get': [ + 'key', + 'members/me', + 'orders', + 'orders/filter', + ], + 'post': [ + 'orders', + 'orders/multi', + 'order/delete', + 'order/delete/multi', + 'orders/clear', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.1 / 100, + }, + }, + 'commonCurrencies': { + 'PLA': 'Plair', + }, + 'exceptions': { + 'codes': { + '-1': BadRequest, + '-2': BadRequest, + '1001': BadRequest, + '1004': ArgumentsRequired, + '1006': AuthenticationError, + '1008': AuthenticationError, + '1010': AuthenticationError, + '1011': PermissionDenied, + '2001': AuthenticationError, + '2002': InvalidOrder, + '2004': OrderNotFound, + '9003': PermissionDenied, + }, + 'exact': { + 'market does not have a valid value': BadRequest, + 'side does not have a valid value': BadRequest, + 'Account::AccountError: Cannot lock funds': InsufficientFunds, + 'The account does not exist': AuthenticationError, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const request = { 'show_details': true }; + const response = await this.publicGetMarkets (this.extend (request, params)); + const result = []; + const markets = this.safeValue (response, 'data'); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeValue (market, 'id'); + const name = this.safeValue (market, 'name'); + let [ baseId, quoteId ] = name.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + baseId = baseId.toLowerCase (); + quoteId = quoteId.toLowerCase (); + const symbol = base + '/' + quote; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'info': market, + 'precision': { + 'amount': this.safeValue (market, 'amount_precision'), + 'price': this.safeValue (market, 'price_precision'), + 'base': this.safeValue (market, 'ask_precision'), + 'quote': this.safeValue (market, 'bid_precision'), + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeValue (market, 'minimum_trading_amount'), + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTickersPair (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": { + // "at":1559431729, + // "ticker": { + // "buy":"0.0065", + // "sell":"0.00677", + // "low":"0.00677", + // "high":"0.00677", + // "last":"0.00677", + // "vol":"2000.0" + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const marketIds = this.marketIds (symbols); + const request = { 'markets': marketIds }; + const response = await this.publicGetTickersMulti (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": { + // "at":1559431729, + // "ticker": { + // "buy":"0.0065", + // "sell":"0.00677", + // "low":"0.00677", + // "high":"0.00677", + // "last":"0.00677", + // "vol":"2000.0" + // } + // } + // } + // + const data = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < data.length; i++) { + const ticker = data[i]; + const marketId = this.safeString (ticker, 'market'); + const market = this.safeMarket (marketId); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTicker (data, market = undefined) { + // + // { + // "at":1559431729, + // "ticker": { + // "buy":"0.0065", + // "sell":"0.00677", + // "low":"0.00677", + // "high":"0.00677", + // "last":"0.00677", + // "vol":"2000.0" + // } + // } + // + const ticker = this.safeValue (data, 'ticker', {}); + const timestamp = this.safeTimestamp (data, 'at'); + return { + 'symbol': market['symbol'], + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': this.safeFloat (ticker, 'last'), + 'last': this.safeFloat (ticker, 'last'), + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": { + // "timestamp":1559433057, + // "asks": [ + // ["100.0","20.0"], + // ["4.74","2000.0"], + // ["1.74","4000.0"], + // ], + // "bids":[ + // ["0.0065","5482873.4"], + // ["0.00649","4781956.2"], + // ["0.00648","2876006.8"], + // ], + // } + // } + // + const orderbook = this.safeValue (response, 'data', {}); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + return this.parseOrderBook (orderbook, timestamp); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + if (symbols === undefined) { + symbols = this.symbols; + } + const marketIds = this.marketIds (symbols); + const request = { + 'markets': marketIds, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderBookMulti (this.extend (request, params)); + // + // { + // "code":0, + // "message":"Operation successful", + // "data": [ + // { + // "timestamp":1559433057, + // "market": "bagvet", + // "asks": [ + // ["100.0","20.0"], + // ["4.74","2000.0"], + // ["1.74","4000.0"], + // ], + // "bids":[ + // ["0.0065","5482873.4"], + // ["0.00649","4781956.2"], + // ["0.00648","2876006.8"], + // ], + // }, + // ..., + // ], + // } + // + const data = this.safeValue (response, 'data', []); + const result = {}; + for (let i = 0; i < data.length; i++) { + const orderbook = data[i]; + const marketId = this.safeString (orderbook, 'market'); + const symbol = this.safeSymbol (marketId); + const timestamp = this.safeTimestamp (orderbook, 'timestamp'); + result[symbol] = this.parseOrderBook (orderbook, timestamp); + } + return result; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTrades (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + parseTrade (trade, market = undefined) { + let side = this.safeValue (trade, 'side'); + if (side === 'bid') { + side = 'buy'; + } else if (side === 'ask') { + side = 'sell'; + } + const marketId = this.safeValue (trade, 'market'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = this.safeTimestamp (trade, 'created_on'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': this.safeString (trade, 'id'), + 'order': undefined, + 'type': 'limit', + 'takerOrMaker': undefined, + 'side': side, + 'price': this.safeFloat (trade, 'price'), + 'amount': this.safeFloat (trade, 'volume'), + 'cost': undefined, + 'fee': undefined, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTimestamp (params); + // + // {"code":0,"message":"Operation successful","data":1559433420} + // + return this.safeTimestamp (response, 'data'); + } + + async fetchAllTradingFees (params = {}) { + const response = await this.publicGetFeesTrading (params); + const data = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < data.length; i++) { + const group = data[i]; + const maker = this.safeValue (group, 'ask_fee', {}); + const taker = this.safeValue (group, 'bid_fee', {}); + const marketId = this.safeString (group, 'market'); + const symbol = this.safeSymbol (marketId); + result[symbol] = { + 'info': group, + 'symbol': symbol, + 'maker': this.safeFloat (maker, 'value'), + 'taker': this.safeFloat (taker, 'value'), + }; + } + return result; + } + + async fetchKey (params = {}) { + const response = await this.privateGetKey (params); + return this.safeValue (response, 'data'); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetMembersMe (params); + const data = this.safeValue (response, 'data'); + const balances = this.safeValue (data, 'accounts'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeValue (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': side, + 'ord_type': type, + 'volume': this.amountToPrecision (symbol, amount), + }; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + let ids = id; + if (!Array.isArray (id)) { + ids = [ id ]; + } + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const request = { 'ids': ids }; + const response = await this.privateGetOrders (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + const dataLength = data.length; + if (data === undefined) { + throw new OrderNotFound (this.id + ' could not found matching order'); + } + if (Array.isArray (id)) { + return this.parseOrders (data, market); + } + if (dataLength === 0) { + throw new OrderNotFound (this.id + ' could not found matching order'); + } + return this.parseOrder (data[0], market); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'states': [ 'wait' ], + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'states': [ 'done', 'cancel' ], + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const states = this.safeValue (params, 'states', [ 'wait', 'done', 'cancel' ]); + const query = this.omit (params, 'states'); + const request = { + 'market': market['id'], + 'states': states, + 'need_price': 'True', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetOrdersFilter (this.extend (request, query)); + const data = this.safeValue (response, 'data', []); + let result = []; + for (let i = 0; i < data.length; i++) { + const orders = this.safeValue (data[i], 'orders', []); + const status = this.parseOrderStatus (this.safeValue (data[i], 'state')); + const parsedOrders = this.parseOrders (orders, market, since, limit, { 'status': status }); + result = this.arrayConcat (result, parsedOrders); + } + return result; + } + + parseOrder (order, market = undefined) { + // + // { + // "created_at": "2019-01-18T00:38:18Z", + // "trades_count": 0, + // "remaining_volume": "0.2", + // "price": "1001.0", + // "created_on": "1547771898", + // "side": "buy", + // "volume": "0.2", + // "state": "wait", + // "ord_type": "limit", + // "avg_price": "0.0", + // "executed_volume": "0.0", + // "id": 473797, + // "market": "veteth" + // } + // + const status = this.parseOrderStatus (this.safeValue (order, 'state')); + const marketId = this.safeString2 (order, 'market', 'market_id'); + const symbol = this.safeSymbol (marketId); + let timestamp = this.safeTimestamp (order, 'created_on'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'created_at')); + } + return { + 'info': order, + 'id': this.safeString (order, 'id'), + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': this.safeValue (order, 'ord_type'), + 'timeInForce': undefined, + 'side': this.safeValue (order, 'side'), + 'price': this.safeFloat (order, 'price'), + 'stopPrice': undefined, + 'average': this.safeFloat (order, 'avg_price'), + 'amount': this.safeFloat (order, 'volume'), + 'remaining': this.safeFloat (order, 'remaining_volume'), + 'filled': this.safeFloat (order, 'executed_volume'), + 'status': status, + 'cost': undefined, + 'trades': undefined, + 'fee': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'wait': 'open', + 'done': 'closed', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + async createOrders (symbol, orders, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'orders': orders, + }; + // orders: [{"side":"buy", "volume":.2, "price":1001}, {"side":"sell", "volume":0.2, "price":1002}] + const response = await this.privatePostOrdersMulti (this.extend (request, params)); + const data = response['data']; + return this.parseOrders (data); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrderDelete (this.extend ({ 'id': id }, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrderDeleteMulti (this.extend ({ 'ids': ids }, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostOrdersClear (params); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (path === 'tickers_multi' || path === 'order_book/multi') { + let request = '?'; + const markets = this.safeValue (params, 'markets'); + for (let i = 0; i < markets.length; i++) { + request += 'markets[]=' + markets[i] + '&'; + } + const limit = this.safeValue (params, 'limit'); + if (limit !== undefined) { + request += 'limit=' + limit; + } + url += request; + } else if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + const request = { + 'uid': this.apiKey, + 'data': query, + }; + // to set the private key: + // const fs = require ('fs') + // exchange.secret = fs.readFileSync ('oceanex.pem', 'utf8') + const jwt_token = this.jwt (request, this.encode (this.secret), 'RS256'); + url += '?user_jwt=' + jwt_token; + } + headers = { 'Content-Type': 'application/json' }; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + // + // {"code":1011,"message":"This IP '5.228.233.138' is not allowed","data":{}} + // + if (response === undefined) { + return; + } + const errorCode = this.safeString (response, 'code'); + const message = this.safeString (response, 'message'); + if ((errorCode !== undefined) && (errorCode !== '0')) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['codes'], errorCode, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (response); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/okcoin.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/okcoin.js new file mode 100644 index 000000000..73a624f67 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/okcoin.js @@ -0,0 +1,35 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const okex = require ('./okex.js'); + +// --------------------------------------------------------------------------- + +module.exports = class okcoin extends okex { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'okcoin', + 'name': 'OKCoin', + 'countries': [ 'CN', 'US' ], + 'hostname': 'okcoin.com', + 'pro': true, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87295551-102fbf00-c50e-11ea-90a9-462eebba5829.jpg', + 'www': 'https://www.okcoin.com', + 'doc': 'https://www.okcoin.com/docs/en/', + 'fees': 'https://www.okcoin.com/coin-fees', + 'referral': 'https://www.okcoin.com/account/register?flag=activity&channelId=600001513', + }, + 'fees': { + 'trading': { + 'taker': 0.002, + 'maker': 0.001, + }, + }, + 'options': { + 'fetchMarkets': [ 'spot' ], + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/okex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/okex.js new file mode 100644 index 000000000..a1d6e3c48 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/okex.js @@ -0,0 +1,3616 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, OnMaintenance, ArgumentsRequired, BadRequest, AccountSuspended, InvalidAddress, PermissionDenied, DDoSProtection, InsufficientFunds, InvalidNonce, CancelPending, InvalidOrder, OrderNotFound, AuthenticationError, RequestTimeout, NotSupported, BadSymbol } = require ('./base/errors'); +const { TICK_SIZE, TRUNCATE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class okex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'okex', + 'name': 'OKEX', + 'countries': [ 'CN', 'US' ], + 'version': 'v3', + 'rateLimit': 1000, // up to 3000 requests per 5 minutes ≈ 600 requests per minute ≈ 10 requests per second ≈ 100 ms + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': false, // see below + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchOrderTrades': true, + 'fetchTime': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'futures': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '604800', + '1M': '2678400', + '3M': '8035200', + '6M': '16070400', + '1y': '31536000', + }, + 'hostname': 'okex.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg', + 'api': { + 'rest': 'https://www.{hostname}', + }, + 'www': 'https://www.okex.com', + 'doc': 'https://www.okex.com/docs/en/', + 'fees': 'https://www.okex.com/pages/products/fees.html', + 'referral': 'https://www.okex.com/join/1888677', + 'test': { + 'rest': 'https://testnet.okex.com', + }, + }, + 'api': { + 'general': { + 'get': [ + 'time', + ], + }, + 'account': { + 'get': [ + 'wallet', + 'sub-account', + 'asset-valuation', + 'wallet/{currency}', + 'withdrawal/history', + 'withdrawal/history/{currency}', + 'ledger', + 'deposit/address', + 'deposit/history', + 'deposit/history/{currency}', + 'currencies', + 'withdrawal/fee', + ], + 'post': [ + 'transfer', + 'withdrawal', + ], + }, + 'spot': { + 'get': [ + 'accounts', + 'accounts/{currency}', + 'accounts/{currency}/ledger', + 'orders', + 'orders_pending', + 'orders/{order_id}', + 'orders/{client_oid}', + 'trade_fee', + 'fills', + 'algo', + // public + 'instruments', + 'instruments/{instrument_id}/book', + 'instruments/ticker', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/candles', + 'instruments/{instrument_id}/history/candles', + ], + 'post': [ + 'order_algo', + 'orders', + 'batch_orders', + 'cancel_orders/{order_id}', + 'cancel_orders/{client_oid}', + 'cancel_batch_algos', + 'cancel_batch_orders', + ], + }, + 'margin': { + 'get': [ + 'accounts', + 'accounts/{instrument_id}', + 'accounts/{instrument_id}/ledger', + 'accounts/availability', + 'accounts/{instrument_id}/availability', + 'accounts/borrowed', + 'accounts/{instrument_id}/borrowed', + 'orders', + 'accounts/{instrument_id}/leverage', + 'orders/{order_id}', + 'orders/{client_oid}', + 'orders_pending', + 'fills', + // public + 'instruments/{instrument_id}/mark_price', + ], + 'post': [ + 'accounts/borrow', + 'accounts/repayment', + 'orders', + 'batch_orders', + 'cancel_orders', + 'cancel_orders/{order_id}', + 'cancel_orders/{client_oid}', + 'cancel_batch_orders', + 'accounts/{instrument_id}/leverage', + ], + }, + 'futures': { + 'get': [ + 'position', + '{instrument_id}/position', + 'accounts', + 'accounts/{underlying}', + 'accounts/{underlying}/leverage', + 'accounts/{underlying}/ledger', + 'order_algo/{instrument_id}', + 'orders/{instrument_id}', + 'orders/{instrument_id}/{order_id}', + 'orders/{instrument_id}/{client_oid}', + 'fills', + 'trade_fee', + 'accounts/{instrument_id}/holds', + 'order_algo/{instrument_id}', + // public + 'instruments', + 'instruments/{instrument_id}/book', + 'instruments/ticker', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/candles', + 'instruments/{instrument_id}/history/candles', + 'instruments/{instrument_id}/index', + 'rate', + 'instruments/{instrument_id}/estimated_price', + 'instruments/{instrument_id}/open_interest', + 'instruments/{instrument_id}/price_limit', + 'instruments/{instrument_id}/mark_price', + 'instruments/{instrument_id}/liquidation', + ], + 'post': [ + 'accounts/{underlying}/leverage', + 'order', + 'orders', + 'cancel_order/{instrument_id}/{order_id}', + 'cancel_order/{instrument_id}/{client_oid}', + 'cancel_batch_orders/{instrument_id}', + 'accounts/margin_mode', + 'close_position', + 'cancel_all', + 'order_algo', + 'cancel_algos', + ], + }, + 'swap': { + 'get': [ + 'position', + '{instrument_id}/position', + 'accounts', + '{instrument_id}/accounts', + 'accounts/{instrument_id}/settings', + 'accounts/{instrument_id}/ledger', + 'orders/{instrument_id}', + 'orders/{instrument_id}/{order_id}', + 'orders/{instrument_id}/{client_oid}', + 'fills', + 'accounts/{instrument_id}/holds', + 'trade_fee', + 'order_algo/{instrument_id}', + // public + 'instruments', + 'instruments/{instrument_id}/depth', + 'instruments/ticker', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/candles', + 'instruments/{instrument_id}/history/candles', + 'instruments/{instrument_id}/index', + 'rate', + 'instruments/{instrument_id}/open_interest', + 'instruments/{instrument_id}/price_limit', + 'instruments/{instrument_id}/liquidation', + 'instruments/{instrument_id}/funding_time', + 'instruments/{instrument_id}/mark_price', + 'instruments/{instrument_id}/historical_funding_rate', + ], + 'post': [ + 'accounts/{instrument_id}/leverage', + 'order', + 'orders', + 'cancel_order/{instrument_id}/{order_id}', + 'cancel_order/{instrument_id}/{client_oid}', + 'cancel_batch_orders/{instrument_id}', + 'order_algo', + 'cancel_algos', + ], + }, + 'option': { + 'get': [ + 'accounts', + 'position', + '{underlying}/position', + 'accounts/{underlying}', + 'orders/{underlying}', + 'fills/{underlying}', + 'accounts/{underlying}/ledger', + 'trade_fee', + 'orders/{underlying}/{order_id}', + 'orders/{underlying}/{client_oid}', + // public + 'underlying', + 'instruments/{underlying}', + 'instruments/{underlying}/summary', + 'instruments/{underlying}/summary/{instrument_id}', + 'instruments/{instrument_id}/book', + 'instruments/{instrument_id}/trades', + 'instruments/{instrument_id}/ticker', + 'instruments/{instrument_id}/candles', + ], + 'post': [ + 'order', + 'orders', + 'cancel_order/{underlying}/{order_id}', + 'cancel_order/{underlying}/{client_oid}', + 'cancel_batch_orders/{underlying}', + 'amend_order/{underlying}', + 'amend_batch_orders/{underlying}', + ], + }, + 'index': { + 'get': [ + '{instrument_id}/constituents', + ], + }, + }, + 'fees': { + 'trading': { + 'taker': 0.0015, + 'maker': 0.0010, + }, + 'spot': { + 'taker': 0.0015, + 'maker': 0.0010, + }, + 'futures': { + 'taker': 0.0005, + 'maker': 0.0002, + }, + 'swap': { + 'taker': 0.00075, + 'maker': 0.00020, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'password': true, + }, + 'exceptions': { + // http error codes + // 400 Bad Request — Invalid request format + // 401 Unauthorized — Invalid API Key + // 403 Forbidden — You do not have access to the requested resource + // 404 Not Found + // 429 Client Error: Too Many Requests for url + // 500 Internal Server Error — We had a problem with our server + 'exact': { + '1': ExchangeError, // { "code": 1, "message": "System error" } + // undocumented + 'failure to get a peer from the ring-balancer': ExchangeNotAvailable, // { "message": "failure to get a peer from the ring-balancer" } + 'Server is busy, please try again.': ExchangeNotAvailable, // { "message": "Server is busy, please try again." } + 'An unexpected error occurred': ExchangeError, // { "message": "An unexpected error occurred" } + 'System error': ExchangeError, // {"error_message":"System error","message":"System error"} + '4010': PermissionDenied, // { "code": 4010, "message": "For the security of your funds, withdrawals are not permitted within 24 hours after changing fund password / mobile number / Google Authenticator settings " } + // common + // '0': ExchangeError, // 200 successful,when the order placement / cancellation / operation is successful + '4001': ExchangeError, // no data received in 30s + '4002': ExchangeError, // Buffer full. cannot write data + // -------------------------------------------------------- + '30001': AuthenticationError, // { "code": 30001, "message": 'request header "OK_ACCESS_KEY" cannot be blank'} + '30002': AuthenticationError, // { "code": 30002, "message": 'request header "OK_ACCESS_SIGN" cannot be blank'} + '30003': AuthenticationError, // { "code": 30003, "message": 'request header "OK_ACCESS_TIMESTAMP" cannot be blank'} + '30004': AuthenticationError, // { "code": 30004, "message": 'request header "OK_ACCESS_PASSPHRASE" cannot be blank'} + '30005': InvalidNonce, // { "code": 30005, "message": "invalid OK_ACCESS_TIMESTAMP" } + '30006': AuthenticationError, // { "code": 30006, "message": "invalid OK_ACCESS_KEY" } + '30007': BadRequest, // { "code": 30007, "message": 'invalid Content_Type, please use "application/json" format'} + '30008': RequestTimeout, // { "code": 30008, "message": "timestamp request expired" } + '30009': ExchangeError, // { "code": 30009, "message": "system error" } + '30010': AuthenticationError, // { "code": 30010, "message": "API validation failed" } + '30011': PermissionDenied, // { "code": 30011, "message": "invalid IP" } + '30012': AuthenticationError, // { "code": 30012, "message": "invalid authorization" } + '30013': AuthenticationError, // { "code": 30013, "message": "invalid sign" } + '30014': DDoSProtection, // { "code": 30014, "message": "request too frequent" } + '30015': AuthenticationError, // { "code": 30015, "message": 'request header "OK_ACCESS_PASSPHRASE" incorrect'} + '30016': ExchangeError, // { "code": 30015, "message": "you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey" } + '30017': ExchangeError, // { "code": 30017, "message": "apikey's broker id does not match" } + '30018': ExchangeError, // { "code": 30018, "message": "apikey's domain does not match" } + '30019': ExchangeNotAvailable, // { "code": 30019, "message": "Api is offline or unavailable" } + '30020': BadRequest, // { "code": 30020, "message": "body cannot be blank" } + '30021': BadRequest, // { "code": 30021, "message": "Json data format error" }, { "code": 30021, "message": "json data format error" } + '30022': PermissionDenied, // { "code": 30022, "message": "Api has been frozen" } + '30023': BadRequest, // { "code": 30023, "message": "{0} parameter cannot be blank" } + '30024': BadSymbol, // {"code":30024,"message":"\"instrument_id\" is an invalid parameter"} + '30025': BadRequest, // { "code": 30025, "message": "{0} parameter category error" } + '30026': DDoSProtection, // { "code": 30026, "message": "requested too frequent" } + '30027': AuthenticationError, // { "code": 30027, "message": "login failure" } + '30028': PermissionDenied, // { "code": 30028, "message": "unauthorized execution" } + '30029': AccountSuspended, // { "code": 30029, "message": "account suspended" } + '30030': ExchangeNotAvailable, // { "code": 30030, "message": "endpoint request failed. Please try again" } + '30031': BadRequest, // { "code": 30031, "message": "token does not exist" } + '30032': BadSymbol, // { "code": 30032, "message": "pair does not exist" } + '30033': BadRequest, // { "code": 30033, "message": "exchange domain does not exist" } + '30034': ExchangeError, // { "code": 30034, "message": "exchange ID does not exist" } + '30035': ExchangeError, // { "code": 30035, "message": "trading is not supported in this website" } + '30036': ExchangeError, // { "code": 30036, "message": "no relevant data" } + '30037': ExchangeNotAvailable, // { "code": 30037, "message": "endpoint is offline or unavailable" } + // '30038': AuthenticationError, // { "code": 30038, "message": "user does not exist" } + '30038': OnMaintenance, // {"client_oid":"","code":"30038","error_code":"30038","error_message":"Matching engine is being upgraded. Please try in about 1 minute.","message":"Matching engine is being upgraded. Please try in about 1 minute.","order_id":"-1","result":false} + '30044': RequestTimeout, // { "code":30044, "message":"Endpoint request timeout" } + // futures + '32001': AccountSuspended, // { "code": 32001, "message": "futures account suspended" } + '32002': PermissionDenied, // { "code": 32002, "message": "futures account does not exist" } + '32003': CancelPending, // { "code": 32003, "message": "canceling, please wait" } + '32004': ExchangeError, // { "code": 32004, "message": "you have no unfilled orders" } + '32005': InvalidOrder, // { "code": 32005, "message": "max order quantity" } + '32006': InvalidOrder, // { "code": 32006, "message": "the order price or trigger price exceeds USD 1 million" } + '32007': InvalidOrder, // { "code": 32007, "message": "leverage level must be the same for orders on the same side of the contract" } + '32008': InvalidOrder, // { "code": 32008, "message": "Max. positions to open (cross margin)" } + '32009': InvalidOrder, // { "code": 32009, "message": "Max. positions to open (fixed margin)" } + '32010': ExchangeError, // { "code": 32010, "message": "leverage cannot be changed with open positions" } + '32011': ExchangeError, // { "code": 32011, "message": "futures status error" } + '32012': ExchangeError, // { "code": 32012, "message": "futures order update error" } + '32013': ExchangeError, // { "code": 32013, "message": "token type is blank" } + '32014': ExchangeError, // { "code": 32014, "message": "your number of contracts closing is larger than the number of contracts available" } + '32015': ExchangeError, // { "code": 32015, "message": "margin ratio is lower than 100% before opening positions" } + '32016': ExchangeError, // { "code": 32016, "message": "margin ratio is lower than 100% after opening position" } + '32017': ExchangeError, // { "code": 32017, "message": "no BBO" } + '32018': ExchangeError, // { "code": 32018, "message": "the order quantity is less than 1, please try again" } + '32019': ExchangeError, // { "code": 32019, "message": "the order price deviates from the price of the previous minute by more than 3%" } + '32020': ExchangeError, // { "code": 32020, "message": "the price is not in the range of the price limit" } + '32021': ExchangeError, // { "code": 32021, "message": "leverage error" } + '32022': ExchangeError, // { "code": 32022, "message": "this function is not supported in your country or region according to the regulations" } + '32023': ExchangeError, // { "code": 32023, "message": "this account has outstanding loan" } + '32024': ExchangeError, // { "code": 32024, "message": "order cannot be placed during delivery" } + '32025': ExchangeError, // { "code": 32025, "message": "order cannot be placed during settlement" } + '32026': ExchangeError, // { "code": 32026, "message": "your account is restricted from opening positions" } + '32027': ExchangeError, // { "code": 32027, "message": "cancelled over 20 orders" } + '32028': ExchangeError, // { "code": 32028, "message": "account is suspended and liquidated" } + '32029': ExchangeError, // { "code": 32029, "message": "order info does not exist" } + '32030': InvalidOrder, // The order cannot be cancelled + '32031': ArgumentsRequired, // client_oid or order_id is required. + '32038': AuthenticationError, // User does not exist + '32040': ExchangeError, // User have open contract orders or position + '32044': ExchangeError, // { "code": 32044, "message": "The margin ratio after submitting this order is lower than the minimum requirement ({0}) for your tier." } + '32045': ExchangeError, // String of commission over 1 million + '32046': ExchangeError, // Each user can hold up to 10 trade plans at the same time + '32047': ExchangeError, // system error + '32048': InvalidOrder, // Order strategy track range error + '32049': ExchangeError, // Each user can hold up to 10 track plans at the same time + '32050': InvalidOrder, // Order strategy rang error + '32051': InvalidOrder, // Order strategy ice depth error + '32052': ExchangeError, // String of commission over 100 thousand + '32053': ExchangeError, // Each user can hold up to 6 ice plans at the same time + '32057': ExchangeError, // The order price is zero. Market-close-all function cannot be executed + '32054': ExchangeError, // Trade not allow + '32055': InvalidOrder, // cancel order error + '32056': ExchangeError, // iceberg per order average should between {0}-{1} contracts + '32058': ExchangeError, // Each user can hold up to 6 initiative plans at the same time + '32059': InvalidOrder, // Total amount should exceed per order amount + '32060': InvalidOrder, // Order strategy type error + '32061': InvalidOrder, // Order strategy initiative limit error + '32062': InvalidOrder, // Order strategy initiative range error + '32063': InvalidOrder, // Order strategy initiative rate error + '32064': ExchangeError, // Time Stringerval of orders should set between 5-120s + '32065': ExchangeError, // Close amount exceeds the limit of Market-close-all (999 for BTC, and 9999 for the rest tokens) + '32066': ExchangeError, // You have open orders. Please cancel all open orders before changing your leverage level. + '32067': ExchangeError, // Account equity < required margin in this setting. Please adjust your leverage level again. + '32068': ExchangeError, // The margin for this position will fall short of the required margin in this setting. Please adjust your leverage level or increase your margin to proceed. + '32069': ExchangeError, // Target leverage level too low. Your account balance is insufficient to cover the margin required. Please adjust the leverage level again. + '32070': ExchangeError, // Please check open position or unfilled order + '32071': ExchangeError, // Your current liquidation mode does not support this action. + '32072': ExchangeError, // The highest available margin for your order’s tier is {0}. Please edit your margin and place a new order. + '32073': ExchangeError, // The action does not apply to the token + '32074': ExchangeError, // The number of contracts of your position, open orders, and the current order has exceeded the maximum order limit of this asset. + '32075': ExchangeError, // Account risk rate breach + '32076': ExchangeError, // Liquidation of the holding position(s) at market price will require cancellation of all pending close orders of the contracts. + '32077': ExchangeError, // Your margin for this asset in futures account is insufficient and the position has been taken over for liquidation. (You will not be able to place orders, close positions, transfer funds, or add margin during this period of time. Your account will be restored after the liquidation is complete.) + '32078': ExchangeError, // Please cancel all open orders before switching the liquidation mode(Please cancel all open orders before switching the liquidation mode) + '32079': ExchangeError, // Your open positions are at high risk.(Please add margin or reduce positions before switching the mode) + '32080': ExchangeError, // Funds cannot be transferred out within 30 minutes after futures settlement + '32083': ExchangeError, // The number of contracts should be a positive multiple of %%. Please place your order again + // token and margin trading + '33001': PermissionDenied, // { "code": 33001, "message": "margin account for this pair is not enabled yet" } + '33002': AccountSuspended, // { "code": 33002, "message": "margin account for this pair is suspended" } + '33003': InsufficientFunds, // { "code": 33003, "message": "no loan balance" } + '33004': ExchangeError, // { "code": 33004, "message": "loan amount cannot be smaller than the minimum limit" } + '33005': ExchangeError, // { "code": 33005, "message": "repayment amount must exceed 0" } + '33006': ExchangeError, // { "code": 33006, "message": "loan order not found" } + '33007': ExchangeError, // { "code": 33007, "message": "status not found" } + '33008': InsufficientFunds, // { "code": 33008, "message": "loan amount cannot exceed the maximum limit" } + '33009': ExchangeError, // { "code": 33009, "message": "user ID is blank" } + '33010': ExchangeError, // { "code": 33010, "message": "you cannot cancel an order during session 2 of call auction" } + '33011': ExchangeError, // { "code": 33011, "message": "no new market data" } + '33012': ExchangeError, // { "code": 33012, "message": "order cancellation failed" } + '33013': InvalidOrder, // { "code": 33013, "message": "order placement failed" } + '33014': OrderNotFound, // { "code": 33014, "message": "order does not exist" } + '33015': InvalidOrder, // { "code": 33015, "message": "exceeded maximum limit" } + '33016': ExchangeError, // { "code": 33016, "message": "margin trading is not open for this token" } + '33017': InsufficientFunds, // { "code": 33017, "message": "insufficient balance" } + '33018': ExchangeError, // { "code": 33018, "message": "this parameter must be smaller than 1" } + '33020': ExchangeError, // { "code": 33020, "message": "request not supported" } + '33021': BadRequest, // { "code": 33021, "message": "token and the pair do not match" } + '33022': InvalidOrder, // { "code": 33022, "message": "pair and the order do not match" } + '33023': ExchangeError, // { "code": 33023, "message": "you can only place market orders during call auction" } + '33024': InvalidOrder, // { "code": 33024, "message": "trading amount too small" } + '33025': InvalidOrder, // { "code": 33025, "message": "base token amount is blank" } + '33026': ExchangeError, // { "code": 33026, "message": "transaction completed" } + '33027': InvalidOrder, // { "code": 33027, "message": "cancelled order or order cancelling" } + '33028': InvalidOrder, // { "code": 33028, "message": "the decimal places of the trading price exceeded the limit" } + '33029': InvalidOrder, // { "code": 33029, "message": "the decimal places of the trading size exceeded the limit" } + '33034': ExchangeError, // { "code": 33034, "message": "You can only place limit order after Call Auction has started" } + '33035': ExchangeError, // This type of order cannot be canceled(This type of order cannot be canceled) + '33036': ExchangeError, // Exceeding the limit of entrust order + '33037': ExchangeError, // The buy order price should be lower than 130% of the trigger price + '33038': ExchangeError, // The sell order price should be higher than 70% of the trigger price + '33039': ExchangeError, // The limit of callback rate is 0 < x <= 5% + '33040': ExchangeError, // The trigger price of a buy order should be lower than the latest transaction price + '33041': ExchangeError, // The trigger price of a sell order should be higher than the latest transaction price + '33042': ExchangeError, // The limit of price variance is 0 < x <= 1% + '33043': ExchangeError, // The total amount must be larger than 0 + '33044': ExchangeError, // The average amount should be 1/1000 * total amount <= x <= total amount + '33045': ExchangeError, // The price should not be 0, including trigger price, order price, and price limit + '33046': ExchangeError, // Price variance should be 0 < x <= 1% + '33047': ExchangeError, // Sweep ratio should be 0 < x <= 100% + '33048': ExchangeError, // Per order limit: Total amount/1000 < x <= Total amount + '33049': ExchangeError, // Total amount should be X > 0 + '33050': ExchangeError, // Time interval should be 5 <= x <= 120s + '33051': ExchangeError, // cancel order number not higher limit: plan and track entrust no more than 10, ice and time entrust no more than 6 + '33059': BadRequest, // { "code": 33059, "message": "client_oid or order_id is required" } + '33060': BadRequest, // { "code": 33060, "message": "Only fill in either parameter client_oid or order_id" } + '33061': ExchangeError, // Value of a single market price order cannot exceed 100,000 USD + '33062': ExchangeError, // The leverage ratio is too high. The borrowed position has exceeded the maximum position of this leverage ratio. Please readjust the leverage ratio + '33063': ExchangeError, // Leverage multiple is too low, there is insufficient margin in the account, please readjust the leverage ratio + '33064': ExchangeError, // The setting of the leverage ratio cannot be less than 2, please readjust the leverage ratio + '33065': ExchangeError, // Leverage ratio exceeds maximum leverage ratio, please readjust leverage ratio + '33085': InvalidOrder, // The value of the position and buying order has reached the position limit, and no further buying is allowed. + // account + '21009': ExchangeError, // Funds cannot be transferred out within 30 minutes after swap settlement(Funds cannot be transferred out within 30 minutes after swap settlement) + '34001': PermissionDenied, // { "code": 34001, "message": "withdrawal suspended" } + '34002': InvalidAddress, // { "code": 34002, "message": "please add a withdrawal address" } + '34003': ExchangeError, // { "code": 34003, "message": "sorry, this token cannot be withdrawn to xx at the moment" } + '34004': ExchangeError, // { "code": 34004, "message": "withdrawal fee is smaller than minimum limit" } + '34005': ExchangeError, // { "code": 34005, "message": "withdrawal fee exceeds the maximum limit" } + '34006': ExchangeError, // { "code": 34006, "message": "withdrawal amount is lower than the minimum limit" } + '34007': ExchangeError, // { "code": 34007, "message": "withdrawal amount exceeds the maximum limit" } + '34008': InsufficientFunds, // { "code": 34008, "message": "insufficient balance" } + '34009': ExchangeError, // { "code": 34009, "message": "your withdrawal amount exceeds the daily limit" } + '34010': ExchangeError, // { "code": 34010, "message": "transfer amount must be larger than 0" } + '34011': ExchangeError, // { "code": 34011, "message": "conditions not met" } + '34012': ExchangeError, // { "code": 34012, "message": "the minimum withdrawal amount for NEO is 1, and the amount must be an integer" } + '34013': ExchangeError, // { "code": 34013, "message": "please transfer" } + '34014': ExchangeError, // { "code": 34014, "message": "transfer limited" } + '34015': ExchangeError, // { "code": 34015, "message": "subaccount does not exist" } + '34016': PermissionDenied, // { "code": 34016, "message": "transfer suspended" } + '34017': AccountSuspended, // { "code": 34017, "message": "account suspended" } + '34018': AuthenticationError, // { "code": 34018, "message": "incorrect trades password" } + '34019': PermissionDenied, // { "code": 34019, "message": "please bind your email before withdrawal" } + '34020': PermissionDenied, // { "code": 34020, "message": "please bind your funds password before withdrawal" } + '34021': InvalidAddress, // { "code": 34021, "message": "Not verified address" } + '34022': ExchangeError, // { "code": 34022, "message": "Withdrawals are not available for sub accounts" } + '34023': PermissionDenied, // { "code": 34023, "message": "Please enable futures trading before transferring your funds" } + '34026': ExchangeError, // transfer too frequently(transfer too frequently) + '34036': ExchangeError, // Parameter is incorrect, please refer to API documentation + '34037': ExchangeError, // Get the sub-account balance interface, account type is not supported + '34038': ExchangeError, // Since your C2C transaction is unusual, you are restricted from fund transfer. Please contact our customer support to cancel the restriction + '34039': ExchangeError, // You are now restricted from transferring out your funds due to abnormal trades on C2C Market. Please transfer your fund on our website or app instead to verify your identity + // swap + '35001': ExchangeError, // { "code": 35001, "message": "Contract does not exist" } + '35002': ExchangeError, // { "code": 35002, "message": "Contract settling" } + '35003': ExchangeError, // { "code": 35003, "message": "Contract paused" } + '35004': ExchangeError, // { "code": 35004, "message": "Contract pending settlement" } + '35005': AuthenticationError, // { "code": 35005, "message": "User does not exist" } + '35008': InvalidOrder, // { "code": 35008, "message": "Risk ratio too high" } + '35010': InvalidOrder, // { "code": 35010, "message": "Position closing too large" } + '35012': InvalidOrder, // { "code": 35012, "message": "Incorrect order size" } + '35014': InvalidOrder, // { "code": 35014, "message": "Order price is not within limit" } + '35015': InvalidOrder, // { "code": 35015, "message": "Invalid leverage level" } + '35017': ExchangeError, // { "code": 35017, "message": "Open orders exist" } + '35019': InvalidOrder, // { "code": 35019, "message": "Order size too large" } + '35020': InvalidOrder, // { "code": 35020, "message": "Order price too high" } + '35021': InvalidOrder, // { "code": 35021, "message": "Order size exceeded current tier limit" } + '35022': BadRequest, // { "code": 35022, "message": "Contract status error" } + '35024': BadRequest, // { "code": 35024, "message": "Contract not initialized" } + '35025': InsufficientFunds, // { "code": 35025, "message": "No account balance" } + '35026': BadRequest, // { "code": 35026, "message": "Contract settings not initialized" } + '35029': OrderNotFound, // { "code": 35029, "message": "Order does not exist" } + '35030': InvalidOrder, // { "code": 35030, "message": "Order size too large" } + '35031': InvalidOrder, // { "code": 35031, "message": "Cancel order size too large" } + '35032': ExchangeError, // { "code": 35032, "message": "Invalid user status" } + '35037': ExchangeError, // No last traded price in cache + '35039': ExchangeError, // { "code": 35039, "message": "Open order quantity exceeds limit" } + '35040': InvalidOrder, // {"error_message":"Invalid order type","result":"true","error_code":"35040","order_id":"-1"} + '35044': ExchangeError, // { "code": 35044, "message": "Invalid order status" } + '35046': InsufficientFunds, // { "code": 35046, "message": "Negative account balance" } + '35047': InsufficientFunds, // { "code": 35047, "message": "Insufficient account balance" } + '35048': ExchangeError, // { "code": 35048, "message": "User contract is frozen and liquidating" } + '35049': InvalidOrder, // { "code": 35049, "message": "Invalid order type" } + '35050': InvalidOrder, // { "code": 35050, "message": "Position settings are blank" } + '35052': InsufficientFunds, // { "code": 35052, "message": "Insufficient cross margin" } + '35053': ExchangeError, // { "code": 35053, "message": "Account risk too high" } + '35055': InsufficientFunds, // { "code": 35055, "message": "Insufficient account balance" } + '35057': ExchangeError, // { "code": 35057, "message": "No last traded price" } + '35058': ExchangeError, // { "code": 35058, "message": "No limit" } + '35059': BadRequest, // { "code": 35059, "message": "client_oid or order_id is required" } + '35060': BadRequest, // { "code": 35060, "message": "Only fill in either parameter client_oid or order_id" } + '35061': BadRequest, // { "code": 35061, "message": "Invalid instrument_id" } + '35062': InvalidOrder, // { "code": 35062, "message": "Invalid match_price" } + '35063': InvalidOrder, // { "code": 35063, "message": "Invalid order_size" } + '35064': InvalidOrder, // { "code": 35064, "message": "Invalid client_oid" } + '35066': InvalidOrder, // Order interval error + '35067': InvalidOrder, // Time-weighted order ratio error + '35068': InvalidOrder, // Time-weighted order range error + '35069': InvalidOrder, // Time-weighted single transaction limit error + '35070': InvalidOrder, // Algo order type error + '35071': InvalidOrder, // Order total must be larger than single order limit + '35072': InvalidOrder, // Maximum 6 unfulfilled time-weighted orders can be held at the same time + '35073': InvalidOrder, // Order price is 0. Market-close-all not available + '35074': InvalidOrder, // Iceberg order single transaction average error + '35075': InvalidOrder, // Failed to cancel order + '35076': InvalidOrder, // LTC 20x leverage. Not allowed to open position + '35077': InvalidOrder, // Maximum 6 unfulfilled iceberg orders can be held at the same time + '35078': InvalidOrder, // Order amount exceeded 100,000 + '35079': InvalidOrder, // Iceberg order price variance error + '35080': InvalidOrder, // Callback rate error + '35081': InvalidOrder, // Maximum 10 unfulfilled trail orders can be held at the same time + '35082': InvalidOrder, // Trail order callback rate error + '35083': InvalidOrder, // Each user can only hold a maximum of 10 unfulfilled stop-limit orders at the same time + '35084': InvalidOrder, // Order amount exceeded 1 million + '35085': InvalidOrder, // Order amount is not in the correct range + '35086': InvalidOrder, // Price exceeds 100 thousand + '35087': InvalidOrder, // Price exceeds 100 thousand + '35088': InvalidOrder, // Average amount error + '35089': InvalidOrder, // Price exceeds 100 thousand + '35090': ExchangeError, // No stop-limit orders available for cancelation + '35091': ExchangeError, // No trail orders available for cancellation + '35092': ExchangeError, // No iceberg orders available for cancellation + '35093': ExchangeError, // No trail orders available for cancellation + '35094': ExchangeError, // Stop-limit order last traded price error + '35095': BadRequest, // Instrument_id error + '35096': ExchangeError, // Algo order status error + '35097': ExchangeError, // Order status and order ID cannot exist at the same time + '35098': ExchangeError, // An order status or order ID must exist + '35099': ExchangeError, // Algo order ID error + // option + '36001': BadRequest, // Invalid underlying index. + '36002': BadRequest, // Instrument does not exist. + '36005': ExchangeError, // Instrument status is invalid. + '36101': AuthenticationError, // Account does not exist. + '36102': PermissionDenied, // Account status is invalid. + '36103': PermissionDenied, // Account is suspended due to ongoing liquidation. + '36104': PermissionDenied, // Account is not enabled for options trading. + '36105': PermissionDenied, // Please enable the account for option contract. + '36106': PermissionDenied, // Funds cannot be transferred in or out, as account is suspended. + '36107': PermissionDenied, // Funds cannot be transferred out within 30 minutes after option exercising or settlement. + '36108': InsufficientFunds, // Funds cannot be transferred in or out, as equity of the account is less than zero. + '36109': PermissionDenied, // Funds cannot be transferred in or out during option exercising or settlement. + '36201': PermissionDenied, // New order function is blocked. + '36202': PermissionDenied, // Account does not have permission to short option. + '36203': InvalidOrder, // Invalid format for client_oid. + '36204': ExchangeError, // Invalid format for request_id. + '36205': BadRequest, // Instrument id does not match underlying index. + '36206': BadRequest, // Order_id and client_oid can not be used at the same time. + '36207': InvalidOrder, // Either order price or fartouch price must be present. + '36208': InvalidOrder, // Either order price or size must be present. + '36209': InvalidOrder, // Either order_id or client_oid must be present. + '36210': InvalidOrder, // Either order_ids or client_oids must be present. + '36211': InvalidOrder, // Exceeding max batch size for order submission. + '36212': InvalidOrder, // Exceeding max batch size for oder cancellation. + '36213': InvalidOrder, // Exceeding max batch size for order amendment. + '36214': ExchangeError, // Instrument does not have valid bid/ask quote. + '36216': OrderNotFound, // Order does not exist. + '36217': InvalidOrder, // Order submission failed. + '36218': InvalidOrder, // Order cancellation failed. + '36219': InvalidOrder, // Order amendment failed. + '36220': InvalidOrder, // Order is pending cancel. + '36221': InvalidOrder, // Order qty is not valid multiple of lot size. + '36222': InvalidOrder, // Order price is breaching highest buy limit. + '36223': InvalidOrder, // Order price is breaching lowest sell limit. + '36224': InvalidOrder, // Exceeding max order size. + '36225': InvalidOrder, // Exceeding max open order count for instrument. + '36226': InvalidOrder, // Exceeding max open order count for underlying. + '36227': InvalidOrder, // Exceeding max open size across all orders for underlying + '36228': InvalidOrder, // Exceeding max available qty for instrument. + '36229': InvalidOrder, // Exceeding max available qty for underlying. + '36230': InvalidOrder, // Exceeding max position limit for underlying. + }, + 'broad': { + }, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'fetchOHLCV': { + 'type': 'Candles', // Candles or HistoryCandles + }, + 'createMarketBuyOrderRequiresPrice': true, + 'fetchMarkets': [ 'spot', 'futures', 'swap', 'option' ], + 'defaultType': 'spot', // 'account', 'spot', 'margin', 'futures', 'swap', 'option' + 'auth': { + 'time': 'public', + 'currencies': 'private', + 'instruments': 'public', + 'rate': 'public', + '{instrument_id}/constituents': 'public', + }, + }, + 'commonCurrencies': { + // OKEX refers to ERC20 version of Aeternity (AEToken) + 'AE': 'AET', // https://github.com/ccxt/ccxt/issues/4981 + 'BOX': 'DefiBox', + 'HOT': 'Hydro Protocol', + 'HSR': 'HC', + 'MAG': 'Maggie', + 'YOYO': 'YOYOW', + 'WIN': 'WinToken', // https://github.com/ccxt/ccxt/issues/5701 + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.generalGetTime (params); + // + // { + // "iso": "2015-01-07T23:47:25.201Z", + // "epoch": 1420674445.201 + // } + // + return this.parse8601 (this.safeString (response, 'iso')); + } + + async fetchMarkets (params = {}) { + const types = this.safeValue (this.options, 'fetchMarkets'); + let result = []; + for (let i = 0; i < types.length; i++) { + const markets = await this.fetchMarketsByType (types[i], params); + result = this.arrayConcat (result, markets); + } + return result; + } + + parseMarkets (markets) { + const result = []; + for (let i = 0; i < markets.length; i++) { + result.push (this.parseMarket (markets[i])); + } + return result; + } + + parseMarket (market) { + // + // spot markets + // + // { + // base_currency: "EOS", + // instrument_id: "EOS-OKB", + // min_size: "0.01", + // quote_currency: "OKB", + // size_increment: "0.000001", + // tick_size: "0.0001" + // } + // + // futures markets + // + // { + // instrument_id: "XRP-USD-200320", + // underlying_index: "XRP", + // quote_currency: "USD", + // tick_size: "0.0001", + // contract_val: "10", + // listing: "2020-03-06", + // delivery: "2020-03-20", + // trade_increment: "1", + // alias: "this_week", + // underlying: "XRP-USD", + // base_currency: "XRP", + // settlement_currency: "XRP", + // is_inverse: "true", + // contract_val_currency: "USD", + // } + // + // swap markets + // + // { + // instrument_id: "BSV-USD-SWAP", + // underlying_index: "BSV", + // quote_currency: "USD", + // coin: "BSV", + // contract_val: "10", + // listing: "2018-12-21T07:53:47.000Z", + // delivery: "2020-03-14T08:00:00.000Z", + // size_increment: "1", + // tick_size: "0.01", + // base_currency: "BSV", + // underlying: "BSV-USD", + // settlement_currency: "BSV", + // is_inverse: "true", + // contract_val_currency: "USD" + // } + // + // options markets + // + // { + // instrument_id: 'BTC-USD-200327-4000-C', + // underlying: 'BTC-USD', + // settlement_currency: 'BTC', + // contract_val: '0.1000', + // option_type: 'C', + // strike: '4000', + // tick_size: '0.0005', + // lot_size: '1.0000', + // listing: '2019-12-25T08:30:36.302Z', + // delivery: '2020-03-27T08:00:00.000Z', + // state: '2', + // trading_start_time: '2019-12-25T08:30:36.302Z', + // timestamp: '2020-03-13T08:05:09.456Z', + // } + // + const id = this.safeString (market, 'instrument_id'); + let marketType = 'spot'; + let spot = true; + let future = false; + let swap = false; + let option = false; + let baseId = this.safeString (market, 'base_currency'); + let quoteId = this.safeString (market, 'quote_currency'); + const contractVal = this.safeFloat (market, 'contract_val'); + if (contractVal !== undefined) { + if ('option_type' in market) { + marketType = 'option'; + spot = false; + option = true; + const underlying = this.safeString (market, 'underlying'); + const parts = underlying.split ('-'); + baseId = this.safeString (parts, 0); + quoteId = this.safeString (parts, 1); + } else { + marketType = 'swap'; + spot = false; + swap = true; + const futuresAlias = this.safeString (market, 'alias'); + if (futuresAlias !== undefined) { + swap = false; + future = true; + marketType = 'futures'; + baseId = this.safeString (market, 'underlying_index'); + } + } + } + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = spot ? (base + '/' + quote) : id; + const lotSize = this.safeFloat2 (market, 'lot_size', 'trade_increment'); + const precision = { + 'amount': this.safeFloat (market, 'size_increment', lotSize), + 'price': this.safeFloat (market, 'tick_size'), + }; + const minAmount = this.safeFloat2 (market, 'min_size', 'base_min_size'); + const active = true; + const fees = this.safeValue2 (this.fees, marketType, 'trading', {}); + return this.extend (fees, { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': marketType, + 'spot': spot, + 'futures': future, + 'swap': swap, + 'option': option, + 'active': active, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': undefined, + }, + 'price': { + 'min': precision['price'], + 'max': undefined, + }, + 'cost': { + 'min': precision['price'], + 'max': undefined, + }, + }, + }); + } + + async fetchMarketsByType (type, params = {}) { + if (type === 'option') { + const underlying = await this.optionGetUnderlying (params); + let result = []; + for (let i = 0; i < underlying.length; i++) { + const response = await this.optionGetInstrumentsUnderlying ({ + 'underlying': underlying[i], + }); + // + // options markets + // + // [ + // { + // instrument_id: 'BTC-USD-200327-4000-C', + // underlying: 'BTC-USD', + // settlement_currency: 'BTC', + // contract_val: '0.1000', + // option_type: 'C', + // strike: '4000', + // tick_size: '0.0005', + // lot_size: '1.0000', + // listing: '2019-12-25T08:30:36.302Z', + // delivery: '2020-03-27T08:00:00.000Z', + // state: '2', + // trading_start_time: '2019-12-25T08:30:36.302Z', + // timestamp: '2020-03-13T08:05:09.456Z', + // }, + // ] + // + result = this.arrayConcat (result, response); + } + return this.parseMarkets (result); + } else if ((type === 'spot') || (type === 'futures') || (type === 'swap')) { + const method = type + 'GetInstruments'; + const response = await this[method] (params); + // + // spot markets + // + // [ + // { + // base_currency: "EOS", + // instrument_id: "EOS-OKB", + // min_size: "0.01", + // quote_currency: "OKB", + // size_increment: "0.000001", + // tick_size: "0.0001" + // } + // ] + // + // futures markets + // + // [ + // { + // instrument_id: "XRP-USD-200320", + // underlying_index: "XRP", + // quote_currency: "USD", + // tick_size: "0.0001", + // contract_val: "10", + // listing: "2020-03-06", + // delivery: "2020-03-20", + // trade_increment: "1", + // alias: "this_week", + // underlying: "XRP-USD", + // base_currency: "XRP", + // settlement_currency: "XRP", + // is_inverse: "true", + // contract_val_currency: "USD", + // } + // ] + // + // swap markets + // + // [ + // { + // instrument_id: "BSV-USD-SWAP", + // underlying_index: "BSV", + // quote_currency: "USD", + // coin: "BSV", + // contract_val: "10", + // listing: "2018-12-21T07:53:47.000Z", + // delivery: "2020-03-14T08:00:00.000Z", + // size_increment: "1", + // tick_size: "0.01", + // base_currency: "BSV", + // underlying: "BSV-USD", + // settlement_currency: "BSV", + // is_inverse: "true", + // contract_val_currency: "USD" + // } + // ] + // + return this.parseMarkets (response); + } else { + throw new NotSupported (this.id + ' fetchMarketsByType does not support market type ' + type); + } + } + + async fetchCurrencies (params = {}) { + // has['fetchCurrencies'] is currently set to false + // despite that their docs say these endpoints are public: + // https://www.okex.com/api/account/v3/withdrawal/fee + // https://www.okex.com/api/account/v3/currencies + // it will still reply with { "code":30001, "message": "OK-ACCESS-KEY header is required" } + // if you attempt to access it without authentication + const response = await this.accountGetCurrencies (params); + // + // [ + // { + // name: '', + // currency: 'BTC', + // can_withdraw: '1', + // can_deposit: '1', + // min_withdrawal: '0.0100000000000000' + // }, + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const precision = 8; // default precision, todo: fix "magic constants" + const name = this.safeString (currency, 'name'); + const canDeposit = this.safeInteger (currency, 'can_deposit'); + const canWithdraw = this.safeInteger (currency, 'can_withdraw'); + const active = (canDeposit && canWithdraw) ? true : false; + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': name, + 'active': active, + 'fee': undefined, // todo: redesign + 'precision': precision, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { + 'min': this.safeFloat (currency, 'min_withdrawal'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = market['type'] + 'GetInstrumentsInstrumentId'; + method += (market['type'] === 'swap') ? 'Depth' : 'Book'; + const request = { + 'instrument_id': market['id'], + }; + if (limit !== undefined) { + request['size'] = limit; // max 200 + } + const response = await this[method] (this.extend (request, params)); + // + // { asks: [ ["0.02685268", "0.242571", "1"], + // ["0.02685493", "0.164085", "1"], + // ... + // ["0.02779", "1.039", "1"], + // ["0.027813", "0.0876", "1"] ], + // bids: [ ["0.02684052", "10.371849", "1"], + // ["0.02684051", "3.707", "4"], + // ... + // ["0.02634963", "0.132934", "1"], + // ["0.02634962", "0.264838", "2"] ], + // timestamp: "2018-12-17T20:24:16.159Z" } + // + const timestamp = this.parse8601 (this.safeString (response, 'timestamp')); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + // + // { best_ask: "0.02665472", + // best_bid: "0.02665221", + // instrument_id: "ETH-BTC", + // product_id: "ETH-BTC", + // last: "0.02665472", + // ask: "0.02665472", // missing in the docs + // bid: "0.02665221", // not mentioned in the docs + // open_24h: "0.02645482", + // high_24h: "0.02714633", + // low_24h: "0.02614109", + // base_volume_24h: "572298.901923", + // timestamp: "2018-12-17T21:20:07.856Z", + // quote_volume_24h: "15094.86831261" } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + let symbol = undefined; + const marketId = this.safeString (ticker, 'instrument_id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else if (marketId !== undefined) { + const parts = marketId.split ('-'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const open = this.safeFloat (ticker, 'open_24h'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_24h'), + 'low': this.safeFloat (ticker, 'low_24h'), + 'bid': this.safeFloat (ticker, 'best_bid'), + 'bidVolume': this.safeFloat (ticker, 'best_bid_size'), + 'ask': this.safeFloat (ticker, 'best_ask'), + 'askVolume': this.safeFloat (ticker, 'best_ask_size'), + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'base_volume_24h'), + 'quoteVolume': this.safeFloat (ticker, 'quote_volume_24h'), + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['type'] + 'GetInstrumentsInstrumentIdTicker'; + const request = { + 'instrument_id': market['id'], + }; + const response = await this[method] (this.extend (request, params)); + // + // { best_ask: "0.02665472", + // best_bid: "0.02665221", + // instrument_id: "ETH-BTC", + // product_id: "ETH-BTC", + // last: "0.02665472", + // ask: "0.02665472", + // bid: "0.02665221", + // open_24h: "0.02645482", + // high_24h: "0.02714633", + // low_24h: "0.02614109", + // base_volume_24h: "572298.901923", + // timestamp: "2018-12-17T21:20:07.856Z", + // quote_volume_24h: "15094.86831261" } + // + return this.parseTicker (response); + } + + async fetchTickersByType (type, symbols = undefined, params = {}) { + await this.loadMarkets (); + const method = type + 'GetInstrumentsTicker'; + const response = await this[method] (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + return await this.fetchTickersByType (type, symbols, this.omit (params, 'type')); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // spot trades + // + // { + // time: "2018-12-17T23:31:08.268Z", + // timestamp: "2018-12-17T23:31:08.268Z", + // trade_id: "409687906", + // price: "0.02677805", + // size: "0.923467", + // side: "sell" + // } + // + // futures trades, swap trades + // + // { + // trade_id: "1989230840021013", + // side: "buy", + // price: "92.42", + // qty: "184", // missing in swap markets + // size: "5", // missing in futures markets + // timestamp: "2018-12-17T23:26:04.613Z" + // } + // + // fetchOrderTrades (private) + // + // spot trades, margin trades + // + // { + // "created_at":"2019-03-15T02:52:56.000Z", + // "exec_type":"T", // whether the order is taker or maker + // "fee":"0.00000082", + // "instrument_id":"BTC-USDT", + // "ledger_id":"3963052721", + // "liquidity":"T", // whether the order is taker or maker + // "order_id":"2482659399697408", + // "price":"3888.6", + // "product_id":"BTC-USDT", + // "side":"buy", + // "size":"0.00055306", + // "timestamp":"2019-03-15T02:52:56.000Z" + // }, + // + // futures trades, swap trades + // + // { + // "trade_id":"197429674631450625", + // "instrument_id":"EOS-USD-SWAP", + // "order_id":"6a-7-54d663a28-0", + // "price":"3.633", + // "order_qty":"1.0000", + // "fee":"-0.000551", + // "created_at":"2019-03-21T04:41:58.0Z", // missing in swap trades + // "timestamp":"2019-03-25T05:56:31.287Z", // missing in futures trades + // "exec_type":"M", // whether the order is taker or maker + // "side":"short", // "buy" in futures trades + // } + // + let symbol = undefined; + const marketId = this.safeString (trade, 'instrument_id'); + let base = undefined; + let quote = undefined; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } else if (marketId !== undefined) { + const parts = marketId.split ('-'); + const numParts = parts.length; + if (numParts === 2) { + const [ baseId, quoteId ] = parts; + base = this.safeCurrencyCode (baseId); + quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = marketId; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + base = market['base']; + quote = market['quote']; + } + const timestamp = this.parse8601 (this.safeString2 (trade, 'timestamp', 'created_at')); + const price = this.safeFloat (trade, 'price'); + let amount = this.safeFloat2 (trade, 'size', 'qty'); + amount = this.safeFloat (trade, 'order_qty', amount); + let takerOrMaker = this.safeString2 (trade, 'exec_type', 'liquidity'); + if (takerOrMaker === 'M') { + takerOrMaker = 'maker'; + } else if (takerOrMaker === 'T') { + takerOrMaker = 'taker'; + } + const side = this.safeString (trade, 'side'); + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = (side === 'buy') ? base : quote; + fee = { + // fee is either a positive number (invitation rebate) + // or a negative number (transaction fee deduction) + // therefore we need to invert the fee + // more about it https://github.com/ccxt/ccxt/issues/5909 + 'cost': -feeCost, + 'currency': feeCurrency, + }; + } + const orderId = this.safeString (trade, 'order_id'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': this.safeString2 (trade, 'trade_id', 'ledger_id'), + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['type'] + 'GetInstrumentsInstrumentIdTrades'; + if ((limit === undefined) || (limit > 100)) { + limit = 100; // maximum = default = 100 + } + const request = { + 'instrument_id': market['id'], + 'limit': limit, + // from: 'id', + // to: 'id', + }; + const response = await this[method] (this.extend (request, params)); + // + // spot markets + // + // [ + // { + // time: "2018-12-17T23:31:08.268Z", + // timestamp: "2018-12-17T23:31:08.268Z", + // trade_id: "409687906", + // price: "0.02677805", + // size: "0.923467", + // side: "sell" + // } + // ] + // + // futures markets, swap markets + // + // [ + // { + // trade_id: "1989230840021013", + // side: "buy", + // price: "92.42", + // qty: "184", // missing in swap markets + // size: "5", // missing in futures markets + // timestamp: "2018-12-17T23:26:04.613Z" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // spot markets + // + // { + // close: "0.02684545", + // high: "0.02685084", + // low: "0.02683312", + // open: "0.02683894", + // time: "2018-12-17T20:28:00.000Z", + // volume: "101.457222" + // } + // + // futures markets + // + // [ + // 1545072720000, + // 0.3159, + // 0.3161, + // 0.3144, + // 0.3149, + // 22886, + // 725179.26172331, + // ] + // + if (Array.isArray (ohlcv)) { + const numElements = ohlcv.length; + const volumeIndex = (numElements > 6) ? 6 : 5; + let timestamp = this.safeValue (ohlcv, 0); + if (typeof timestamp === 'string') { + timestamp = this.parse8601 (timestamp); + } + return [ + timestamp, // timestamp + this.safeFloat (ohlcv, 1), // Open + this.safeFloat (ohlcv, 2), // High + this.safeFloat (ohlcv, 3), // Low + this.safeFloat (ohlcv, 4), // Close + // this.safeFloat (ohlcv, 5), // Quote Volume + // this.safeFloat (ohlcv, 6), // Base Volume + this.safeFloat (ohlcv, volumeIndex), // Volume, okex will return base volume in the 7th element for future markets + ]; + } else { + return [ + this.parse8601 (this.safeString (ohlcv, 'time')), + this.safeFloat (ohlcv, 'open'), // Open + this.safeFloat (ohlcv, 'high'), // High + this.safeFloat (ohlcv, 'low'), // Low + this.safeFloat (ohlcv, 'close'), // Close + this.safeFloat (ohlcv, 'volume'), // Base Volume + ]; + } + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const duration = this.parseTimeframe (timeframe); + const request = { + 'instrument_id': market['id'], + 'granularity': this.timeframes[timeframe], + }; + const options = this.safeValue (this.options, 'fetchOHLCV', {}); + const defaultType = this.safeString (options, 'type', 'Candles'); // Candles or HistoryCandles + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + const method = market['type'] + 'GetInstrumentsInstrumentId' + type; + if (type === 'Candles') { + if (since !== undefined) { + if (limit !== undefined) { + request['end'] = this.iso8601 (this.sum (since, limit * duration * 1000)); + } + request['start'] = this.iso8601 (since); + } else { + if (limit !== undefined) { + const now = this.milliseconds (); + request['start'] = this.iso8601 (now - limit * duration * 1000); + request['end'] = this.iso8601 (now); + } + } + } else if (type === 'HistoryCandles') { + if (market['option']) { + throw new NotSupported (this.id + ' fetchOHLCV does not have ' + type + ' for ' + market['type'] + ' markets'); + } + if (since !== undefined) { + if (limit === undefined) { + limit = 300; // default + } + request['start'] = this.iso8601 (this.sum (since, limit * duration * 1000)); + request['end'] = this.iso8601 (since); + } else { + if (limit !== undefined) { + const now = this.milliseconds (); + request['end'] = this.iso8601 (now - limit * duration * 1000); + request['start'] = this.iso8601 (now); + } + } + } + const response = await this[method] (this.extend (request, params)); + // + // spot markets + // + // [ + // { + // close: "0.02683401", + // high: "0.02683401", + // low: "0.02683401", + // open: "0.02683401", + // time: "2018-12-17T23:47:00.000Z", + // volume: "0" + // }, + // { + // close: "0.02684545", + // high: "0.02685084", + // low: "0.02683312", + // open: "0.02683894", + // time: "2018-12-17T20:28:00.000Z", + // volume: "101.457222" + // } + // ] + // + // futures + // + // [ + // [ + // 1545090660000, + // 0.3171, + // 0.3174, + // 0.3171, + // 0.3173, + // 1648, + // 51930.38579450868 + // ], + // [ + // 1545072720000, + // 0.3159, + // 0.3161, + // 0.3144, + // 0.3149, + // 22886, + // 725179.26172331 + // ] + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseAccountBalance (response) { + // + // account + // + // [ + // { + // balance: 0, + // available: 0, + // currency: "BTC", + // hold: 0 + // }, + // { + // balance: 0, + // available: 0, + // currency: "ETH", + // hold: 0 + // } + // ] + // + // spot + // + // [ + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "BTC", + // balance: "0.0000000497717339", + // available: "0.0000000497717339", + // holds: "0" + // }, + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "ICN", + // balance: "0.00000000925", + // available: "0.00000000925", + // holds: "0" + // } + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'hold'); + account['free'] = this.safeFloat (balance, 'available'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseMarginBalance (response) { + // + // [ + // { + // "currency:BTC": { + // "available":"0", + // "balance":"0", + // "borrowed":"0", + // "can_withdraw":"0", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "currency:USDT": { + // "available":"100", + // "balance":"100", + // "borrowed":"0", + // "can_withdraw":"100", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "instrument_id":"BTC-USDT", + // "liquidation_price":"0", + // "product_id":"BTC-USDT", + // "risk_rate":"" + // }, + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const marketId = this.safeString (balance, 'instrument_id'); + const market = this.safeValue (this.markets_by_id, marketId); + let symbol = undefined; + if (market === undefined) { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } else { + symbol = market['symbol']; + } + const omittedBalance = this.omit (balance, [ + 'instrument_id', + 'liquidation_price', + 'product_id', + 'risk_rate', + 'margin_ratio', + 'maint_margin_ratio', + 'tiers', + ]); + const keys = Object.keys (omittedBalance); + const accounts = {}; + for (let k = 0; k < keys.length; k++) { + const key = keys[k]; + const marketBalance = balance[key]; + if (key.indexOf (':') >= 0) { + const parts = key.split (':'); + const currencyId = parts[1]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (marketBalance, 'balance'); + account['used'] = this.safeFloat (marketBalance, 'hold'); + account['free'] = this.safeFloat (marketBalance, 'available'); + accounts[code] = account; + } else { + throw new NotSupported (this.id + ' margin balance response format has changed!'); + } + } + result[symbol] = this.parseBalance (accounts); + } + return result; + } + + parseFuturesBalance (response) { + // + // { + // "info":{ + // "eos":{ + // "auto_margin":"0", + // "contracts": [ + // { + // "available_qty":"40.37069445", + // "fixed_balance":"0", + // "instrument_id":"EOS-USD-190329", + // "margin_for_unfilled":"0", + // "margin_frozen":"0", + // "realized_pnl":"0", + // "unrealized_pnl":"0" + // }, + // { + // "available_qty":"40.37069445", + // "fixed_balance":"14.54895721", + // "instrument_id":"EOS-USD-190628", + // "margin_for_unfilled":"0", + // "margin_frozen":"10.64042157", + // "realized_pnl":"-3.90853564", + // "unrealized_pnl":"-0.259" + // }, + // ], + // "equity":"50.75220665", + // "margin_mode":"fixed", + // "total_avail_balance":"40.37069445" + // }, + // } + // } + // + // their root field name is "info", so our info will contain their info + const result = { 'info': response }; + const info = this.safeValue (response, 'info', {}); + const ids = Object.keys (info); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const code = this.safeCurrencyCode (id); + const balance = this.safeValue (info, id, {}); + const account = this.account (); + const totalAvailBalance = this.safeFloat (balance, 'total_avail_balance'); + if (this.safeString (balance, 'margin_mode') === 'fixed') { + const contracts = this.safeValue (balance, 'contracts', []); + let free = totalAvailBalance; + for (let i = 0; i < contracts.length; i++) { + const contract = contracts[i]; + const fixedBalance = this.safeFloat (contract, 'fixed_balance'); + const realizedPnl = this.safeFloat (contract, 'realized_pnl'); + const marginFrozen = this.safeFloat (contract, 'margin_frozen'); + const marginForUnfilled = this.safeFloat (contract, 'margin_for_unfilled'); + const margin = this.sum (fixedBalance, realizedPnl) - marginFrozen - marginForUnfilled; + free = this.sum (free, margin); + } + account['free'] = free; + } else { + const realizedPnl = this.safeFloat (balance, 'realized_pnl'); + const unrealizedPnl = this.safeFloat (balance, 'unrealized_pnl'); + const marginFrozen = this.safeFloat (balance, 'margin_frozen'); + const marginForUnfilled = this.safeFloat (balance, 'margin_for_unfilled'); + account['free'] = this.sum (totalAvailBalance, realizedPnl, unrealizedPnl) - marginFrozen - marginForUnfilled; + } + // it may be incorrect to use total, free and used for swap accounts + account['total'] = this.safeFloat (balance, 'equity'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseSwapBalance (response) { + // + // { + // "info": [ + // { + // "equity":"3.0139", + // "fixed_balance":"0.0000", + // "instrument_id":"EOS-USD-SWAP", + // "margin":"0.5523", + // "margin_frozen":"0.0000", + // "margin_mode":"crossed", + // "margin_ratio":"1.0913", + // "realized_pnl":"-0.0006", + // "timestamp":"2019-03-25T03:46:10.336Z", + // "total_avail_balance":"3.0000", + // "unrealized_pnl":"0.0145" + // } + // ] + // } + // + // their root field name is "info", so our info will contain their info + const result = { 'info': response }; + const info = this.safeValue (response, 'info', []); + for (let i = 0; i < info.length; i++) { + const balance = info[i]; + const marketId = this.safeString (balance, 'instrument_id'); + let symbol = marketId; + if (marketId in this.markets_by_id) { + symbol = this.markets_by_id[marketId]['symbol']; + } + const account = this.account (); + // it may be incorrect to use total, free and used for swap accounts + account['total'] = this.safeFloat (balance, 'equity'); + account['free'] = this.safeFloat (balance, 'total_avail_balance'); + result[symbol] = account; + } + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchBalance requires a type parameter (one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + await this.loadMarkets (); + const suffix = (type === 'account') ? 'Wallet' : 'Accounts'; + const method = type + 'Get' + suffix; + const query = this.omit (params, 'type'); + const response = await this[method] (query); + // + // account + // + // [ + // { + // balance: 0, + // available: 0, + // currency: "BTC", + // hold: 0 + // }, + // { + // balance: 0, + // available: 0, + // currency: "ETH", + // hold: 0 + // } + // ] + // + // spot + // + // [ + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "BTC", + // balance: "0.0000000497717339", + // available: "0.0000000497717339", + // holds: "0" + // }, + // { + // frozen: "0", + // hold: "0", + // id: "2149632", + // currency: "ICN", + // balance: "0.00000000925", + // available: "0.00000000925", + // holds: "0" + // } + // ] + // + // margin + // + // [ + // { + // "currency:BTC": { + // "available":"0", + // "balance":"0", + // "borrowed":"0", + // "can_withdraw":"0", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "currency:USDT": { + // "available":"100", + // "balance":"100", + // "borrowed":"0", + // "can_withdraw":"100", + // "frozen":"0", + // "hold":"0", + // "holds":"0", + // "lending_fee":"0" + // }, + // "instrument_id":"BTC-USDT", + // "liquidation_price":"0", + // "product_id":"BTC-USDT", + // "risk_rate":"" + // }, + // ] + // + // futures + // + // { + // "info":{ + // "eos":{ + // "auto_margin":"0", + // "contracts": [ + // { + // "available_qty":"40.37069445", + // "fixed_balance":"0", + // "instrument_id":"EOS-USD-190329", + // "margin_for_unfilled":"0", + // "margin_frozen":"0", + // "realized_pnl":"0", + // "unrealized_pnl":"0" + // }, + // { + // "available_qty":"40.37069445", + // "fixed_balance":"14.54895721", + // "instrument_id":"EOS-USD-190628", + // "margin_for_unfilled":"0", + // "margin_frozen":"10.64042157", + // "realized_pnl":"-3.90853564", + // "unrealized_pnl":"-0.259" + // }, + // ], + // "equity":"50.75220665", + // "margin_mode":"fixed", + // "total_avail_balance":"40.37069445" + // }, + // } + // } + // + // swap + // + // { + // "info": [ + // { + // "equity":"3.0139", + // "fixed_balance":"0.0000", + // "instrument_id":"EOS-USD-SWAP", + // "margin":"0.5523", + // "margin_frozen":"0.0000", + // "margin_mode":"crossed", + // "margin_ratio":"1.0913", + // "realized_pnl":"-0.0006", + // "timestamp":"2019-03-25T03:46:10.336Z", + // "total_avail_balance":"3.0000", + // "unrealized_pnl":"0.0145" + // } + // ] + // } + // + return this.parseBalanceByType (type, response); + } + + parseBalanceByType (type, response) { + if ((type === 'account') || (type === 'spot')) { + return this.parseAccountBalance (response); + } else if (type === 'margin') { + return this.parseMarginBalance (response); + } else if (type === 'futures') { + return this.parseFuturesBalance (response); + } else if (type === 'swap') { + return this.parseSwapBalance (response); + } + throw new NotSupported (this.id + " fetchBalance does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let request = { + 'instrument_id': market['id'], + // 'client_oid': 'abcdef1234567890', // [a-z0-9]{1,32} + // 'order_type': '0', // 0 = Normal limit order, 1 = Post only, 2 = Fill Or Kill, 3 = Immediatel Or Cancel, 4 = Market for futures only + }; + const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId'); + if (clientOrderId !== undefined) { + request['client_oid'] = clientOrderId; + params = this.omit (params, [ 'client_oid', 'clientOrderId' ]); + } + let method = undefined; + if (market['futures'] || market['swap']) { + const size = market['futures'] ? this.numberToString (amount) : this.amountToPrecision (symbol, amount); + request = this.extend (request, { + 'type': type, // 1:open long 2:open short 3:close long 4:close short for futures + 'size': size, + // 'match_price': '0', // Order at best counter party price? (0:no 1:yes). The default is 0. If it is set as 1, the price parameter will be ignored. When posting orders at best bid price, order_type can only be 0 (regular order). + }); + const orderType = this.safeString (params, 'order_type'); + // order_type === '4' means a market order + const isMarketOrder = (type === 'market') || (orderType === '4'); + if (isMarketOrder) { + request['order_type'] = '4'; + } else { + request['price'] = this.priceToPrecision (symbol, price); + } + if (market['futures']) { + request['leverage'] = '10'; // or '20' + } + method = market['type'] + 'PostOrder'; + } else { + const marginTrading = this.safeString (params, 'margin_trading', '1'); // 1 = spot, 2 = margin + request = this.extend (request, { + 'side': side, + 'type': type, // limit/market + 'margin_trading': marginTrading, // 1 = spot, 2 = margin + }); + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + request['size'] = this.amountToPrecision (symbol, amount); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let notional = this.safeFloat (params, 'notional'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (notional === undefined) { + notional = amount * price; + } + } else if (notional === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'notional' extra parameter (the exchange-specific behaviour)"); + } + } else { + notional = (notional === undefined) ? amount : notional; + } + const precision = market['precision']['price']; + request['notional'] = this.decimalToPrecision (notional, TRUNCATE, precision, this.precisionMode); + } else { + request['size'] = this.amountToPrecision (symbol, amount); + } + } + method = (marginTrading === '2') ? 'marginPostOrders' : 'spotPostOrders'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "client_oid":"oktspot79", + // "error_code":"", + // "error_message":"", + // "order_id":"2510789768709120", + // "result":true + // } + // + const order = this.parseOrder (response, market); + return this.extend (order, { + 'type': type, + 'side': side, + }); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let type = undefined; + if (market['futures'] || market['swap']) { + type = market['type']; + } else { + const defaultType = this.safeString2 (this.options, 'cancelOrder', 'defaultType', market['type']); + type = this.safeString (params, 'type', defaultType); + } + if (type === undefined) { + throw new ArgumentsRequired (this.id + " cancelOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); + } + let method = type + 'PostCancelOrder'; + const request = { + 'instrument_id': market['id'], + }; + if (market['futures'] || market['swap']) { + method += 'InstrumentId'; + } else { + method += 's'; + } + const clientOrderId = this.safeString2 (params, 'client_oid', 'clientOrderId'); + if (clientOrderId !== undefined) { + method += 'ClientOid'; + request['client_oid'] = clientOrderId; + } else { + method += 'OrderId'; + request['order_id'] = id; + } + const query = this.omit (params, [ 'type', 'client_oid', 'clientOrderId' ]); + const response = await this[method] (this.extend (request, query)); + const result = ('result' in response) ? response : this.safeValue (response, market['id'], {}); + // + // spot, margin + // + // { + // "btc-usdt": [ + // { + // "result":true, + // "client_oid":"a123", + // "order_id": "2510832677225473" + // } + // ] + // } + // + // futures, swap + // + // { + // "result": true, + // "client_oid": "oktfuture10", // missing if requested by order_id + // "order_id": "2517535534836736", + // "instrument_id": "EOS-USD-190628" + // } + // + return this.parseOrder (result, market); + } + + parseOrderStatus (status) { + const statuses = { + '-2': 'failed', + '-1': 'canceled', + '0': 'open', + '1': 'open', + '2': 'closed', + '3': 'open', + '4': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrderSide (side) { + const sides = { + '1': 'buy', // open long + '2': 'sell', // open short + '3': 'sell', // close long + '4': 'buy', // close short + }; + return this.safeString (sides, side, side); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "client_oid":"oktspot79", + // "error_code":"", + // "error_message":"", + // "order_id":"2510789768709120", + // "result":true + // } + // + // cancelOrder + // + // { + // "result": true, + // "client_oid": "oktfuture10", // missing if requested by order_id + // "order_id": "2517535534836736", + // // instrument_id is missing for spot/margin orders + // // available in futures and swap orders only + // "instrument_id": "EOS-USD-190628", + // } + // + // fetchOrder, fetchOrdersByState, fetchOpenOrders, fetchClosedOrders + // + // // spot and margin orders + // + // { + // "client_oid":"oktspot76", + // "created_at":"2019-03-18T07:26:49.000Z", + // "filled_notional":"3.9734", + // "filled_size":"0.001", // filled_qty in futures and swap orders + // "funds":"", // this is most likely the same as notional + // "instrument_id":"BTC-USDT", + // "notional":"", + // "order_id":"2500723297813504", + // "order_type":"0", + // "price":"4013", + // "product_id":"BTC-USDT", // missing in futures and swap orders + // "side":"buy", + // "size":"0.001", + // "status":"filled", + // "state": "2", + // "timestamp":"2019-03-18T07:26:49.000Z", + // "type":"limit" + // } + // + // // futures and swap orders + // + // { + // "instrument_id":"EOS-USD-190628", + // "size":"10", + // "timestamp":"2019-03-20T10:04:55.000Z", + // "filled_qty":"10", // filled_size in spot and margin orders + // "fee":"-0.00841043", + // "order_id":"2512669605501952", + // "price":"3.668", + // "price_avg":"3.567", // missing in spot and margin orders + // "status":"2", + // "state": "2", + // "type":"4", + // "contract_val":"10", + // "leverage":"10", // missing in swap, spot and margin orders + // "client_oid":"", + // "pnl":"1.09510794", // missing in swap, spo and margin orders + // "order_type":"0" + // } + // + const id = this.safeString (order, 'order_id'); + const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); + let side = this.safeString (order, 'side'); + const type = this.safeString (order, 'type'); + if ((side !== 'buy') && (side !== 'sell')) { + side = this.parseOrderSide (type); + } + let symbol = undefined; + const marketId = this.safeString (order, 'instrument_id'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } else { + symbol = marketId; + } + if (market !== undefined) { + if (symbol === undefined) { + symbol = market['symbol']; + } + } + let amount = this.safeFloat (order, 'size'); + const filled = this.safeFloat2 (order, 'filled_size', 'filled_qty'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + amount = Math.max (amount, filled); + remaining = Math.max (0, amount - filled); + } + } + if (type === 'market') { + remaining = 0; + } + let cost = this.safeFloat2 (order, 'filled_notional', 'funds'); + const price = this.safeFloat (order, 'price'); + let average = this.safeFloat (order, 'price_avg'); + if (cost === undefined) { + if (filled !== undefined && average !== undefined) { + cost = average * filled; + } + } else { + if ((average === undefined) && (filled !== undefined) && (filled > 0)) { + average = cost / filled; + } + } + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const feeCost = this.safeFloat (order, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrency = undefined; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + let clientOrderId = this.safeString (order, 'client_oid'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; // fix empty clientOrderId string + } + const stopPrice = this.safeFloat (order, 'trigger_price'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); + const type = this.safeString (params, 'type', defaultType); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); + } + const instrumentId = (market['futures'] || market['swap']) ? 'InstrumentId' : ''; + let method = type + 'GetOrders' + instrumentId; + const request = { + 'instrument_id': market['id'], + // 'client_oid': 'abcdef12345', // optional, [a-z0-9]{1,32} + // 'order_id': id, + }; + const clientOid = this.safeString (params, 'client_oid'); + if (clientOid !== undefined) { + method += 'ClientOid'; + request['client_oid'] = clientOid; + } else { + method += 'OrderId'; + request['order_id'] = id; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot, margin + // + // { + // "client_oid":"oktspot70", + // "created_at":"2019-03-15T02:52:56.000Z", + // "filled_notional":"3.8886", + // "filled_size":"0.001", + // "funds":"", + // "instrument_id":"BTC-USDT", + // "notional":"", + // "order_id":"2482659399697408", + // "order_type":"0", + // "price":"3927.3", + // "product_id":"BTC-USDT", + // "side":"buy", + // "size":"0.001", + // "status":"filled", + // "state": "2", + // "timestamp":"2019-03-15T02:52:56.000Z", + // "type":"limit" + // } + // + // futures, swap + // + // { + // "instrument_id":"EOS-USD-190628", + // "size":"10", + // "timestamp":"2019-03-20T02:46:38.000Z", + // "filled_qty":"10", + // "fee":"-0.0080819", + // "order_id":"2510946213248000", + // "price":"3.712", + // "price_avg":"3.712", + // "status":"2", + // "state": "2", + // "type":"2", + // "contract_val":"10", + // "leverage":"10", + // "client_oid":"", // missing in swap orders + // "pnl":"0", // missing in swap orders + // "order_type":"0" + // } + // + return this.parseOrder (response); + } + + async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrdersByState requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + let type = undefined; + if (market['futures'] || market['swap']) { + type = market['type']; + } else { + const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); + type = this.safeString (params, 'type', defaultType); + } + if (type === undefined) { + throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); + } + const request = { + 'instrument_id': market['id'], + // '-2': failed, + // '-1': cancelled, + // '0': open , + // '1': partially filled, + // '2': fully filled, + // '3': submitting, + // '4': cancelling, + // '6': incomplete(open+partially filled), + // '7': complete(cancelled+fully filled), + 'state': state, + }; + let method = type + 'GetOrders'; + if (market['futures'] || market['swap']) { + method += 'InstrumentId'; + } + const query = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, query)); + // + // spot, margin + // + // [ + // // in fact, this documented API response does not correspond + // // to their actual API response for spot markets + // // OKEX v3 API returns a plain array of orders (see below) + // [ + // { + // "client_oid":"oktspot76", + // "created_at":"2019-03-18T07:26:49.000Z", + // "filled_notional":"3.9734", + // "filled_size":"0.001", + // "funds":"", + // "instrument_id":"BTC-USDT", + // "notional":"", + // "order_id":"2500723297813504", + // "order_type":"0", + // "price":"4013", + // "product_id":"BTC-USDT", + // "side":"buy", + // "size":"0.001", + // "status":"filled", + // "state": "2", + // "timestamp":"2019-03-18T07:26:49.000Z", + // "type":"limit" + // }, + // ], + // { + // "before":"2500723297813504", + // "after":"2500650881647616" + // } + // ] + // + // futures, swap + // + // { + // "result":true, // missing in swap orders + // "order_info": [ + // { + // "instrument_id":"EOS-USD-190628", + // "size":"10", + // "timestamp":"2019-03-20T10:04:55.000Z", + // "filled_qty":"10", + // "fee":"-0.00841043", + // "order_id":"2512669605501952", + // "price":"3.668", + // "price_avg":"3.567", + // "status":"2", + // "state": "2", + // "type":"4", + // "contract_val":"10", + // "leverage":"10", // missing in swap orders + // "client_oid":"", + // "pnl":"1.09510794", // missing in swap orders + // "order_type":"0" + // }, + // ] + // } + // + let orders = undefined; + if (market['swap'] || market['futures']) { + orders = this.safeValue (response, 'order_info', []); + } else { + orders = response; + const responseLength = response.length; + if (responseLength < 1) { + return []; + } + // in fact, this documented API response does not correspond + // to their actual API response for spot markets + // OKEX v3 API returns a plain array of orders + if (responseLength > 1) { + const before = this.safeValue (response[1], 'before'); + if (before !== undefined) { + orders = response[0]; + } + } + } + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // '-2': failed, + // '-1': cancelled, + // '0': open , + // '1': partially filled, + // '2': fully filled, + // '3': submitting, + // '4': cancelling, + // '6': incomplete(open+partially filled), + // '7': complete(cancelled+fully filled), + return await this.fetchOrdersByState ('6', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // '-2': failed, + // '-1': cancelled, + // '0': open , + // '1': partially filled, + // '2': fully filled, + // '3': submitting, + // '4': cancelling, + // '6': incomplete(open+partially filled), + // '7': complete(cancelled+fully filled), + return await this.fetchOrdersByState ('7', symbol, since, limit, params); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // address: '0x696abb81974a8793352cbd33aadcf78eda3cfdfa', + // currency: 'eth' + // tag: 'abcde12345', // will be missing if the token does not require a deposit tag + // payment_id: 'abcde12345', // will not be returned if the token does not require a payment_id + // // can_deposit: 1, // 0 or 1, documented but missing + // // can_withdraw: 1, // 0 or 1, documented but missing + // } + // + const address = this.safeString (depositAddress, 'address'); + let tag = this.safeString2 (depositAddress, 'tag', 'payment_id'); + tag = this.safeString (depositAddress, 'memo', tag); + const currencyId = this.safeString (depositAddress, 'currency'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.accountGetDepositAddress (this.extend (request, params)); + // + // [ + // { + // address: '0x696abb81974a8793352cbd33aadcf78eda3cfdfa', + // currency: 'eth' + // } + // ] + // + const addresses = this.parseDepositAddresses (response); + const address = this.safeValue (addresses, code); + if (address === undefined) { + throw new InvalidAddress (this.id + ' fetchDepositAddress cannot return nonexistent addresses, you should create withdrawal addresses with the exchange website first'); + } + return address; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag) { + address = address + ':' + tag; + } + const fee = this.safeString (params, 'fee'); + if (fee === undefined) { + throw new ArgumentsRequired (this.id + " withdraw() requires a `fee` string parameter, network transaction fee must be ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set '0'. Withdrawing to external digital asset address requires network transaction fee."); + } + const request = { + 'currency': currency['id'], + 'to_address': address, + 'destination': '4', // 2 = OKCoin International, 3 = OKEx 4 = others + 'amount': this.numberToString (amount), + 'fee': fee, // String. Network transaction fee ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set as 0. Withdrawal to external digital asset address requires network transaction fee. + }; + if ('password' in params) { + request['trade_pwd'] = params['password']; + } else if ('trade_pwd' in params) { + request['trade_pwd'] = params['trade_pwd']; + } else if (this.password) { + request['trade_pwd'] = this.password; + } + const query = this.omit (params, [ 'fee', 'password', 'trade_pwd' ]); + if (!('trade_pwd' in request)) { + throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a password / trade_pwd parameter'); + } + const response = await this.accountPostWithdrawal (this.extend (request, query)); + // + // { + // "amount":"0.1", + // "withdrawal_id":"67485", + // "currency":"btc", + // "result":true + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'withdrawal_id'), + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'accountGetDepositHistory'; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + method += 'Currency'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit, params); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'accountGetWithdrawalHistory'; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + method += 'Currency'; + } + const response = await this[method] (this.extend (request, params)); + return this.parseTransactions (response, currency, since, limit, params); + } + + parseTransactionStatus (status) { + // + // deposit statuses + // + // { + // '0': 'waiting for confirmation', + // '1': 'confirmation account', + // '2': 'recharge success' + // } + // + // withdrawal statues + // + // { + // '-3': 'pending cancel', + // '-2': 'cancelled', + // '-1': 'failed', + // '0': 'pending', + // '1': 'sending', + // '2': 'sent', + // '3': 'email confirmation', + // '4': 'manual confirmation', + // '5': 'awaiting identity confirmation' + // } + // + const statuses = { + '-3': 'pending', + '-2': 'canceled', + '-1': 'failed', + '0': 'pending', + '1': 'pending', + '2': 'ok', + '3': 'pending', + '4': 'pending', + '5': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // { + // "amount":"0.1", + // "withdrawal_id":"67485", + // "currency":"btc", + // "result":true + // } + // + // fetchWithdrawals + // + // { + // amount: "4.72100000", + // withdrawal_id: "1729116", + // fee: "0.01000000eth", + // txid: "0xf653125bbf090bcfe4b5e8e7b8f586a9d87aa7de94598702758c0802b…", + // currency: "ETH", + // from: "7147338839", + // to: "0x26a3CB49578F07000575405a57888681249c35Fd", + // timestamp: "2018-08-17T07:03:42.000Z", + // status: "2" + // } + // + // fetchDeposits + // + // { + // "amount": "4.19511659", + // "txid": "14c9a8c925647cdb7e5b2937ea9aefe2b29b2c273150ad3f44b3b8a4635ed437", + // "currency": "XMR", + // "from": "", + // "to": "48PjH3ksv1fiXniKvKvyH5UtFs5WhfS2Vf7U3TwzdRJtCc7HJWvCQe56dRahyhQyTAViXZ8Nzk4gQg6o4BJBMUoxNy8y8g7", + // "deposit_id": 11571659, <-- we can use this + // "timestamp": "2019-10-01T14:54:19.000Z", + // "status": "2" + // } + // + let type = undefined; + let id = undefined; + let address = undefined; + const withdrawalId = this.safeString (transaction, 'withdrawal_id'); + const addressFrom = this.safeString (transaction, 'from'); + const addressTo = this.safeString (transaction, 'to'); + if (withdrawalId !== undefined) { + type = 'withdrawal'; + id = withdrawalId; + address = addressTo; + } else { + // the payment_id will appear on new deposits but appears to be removed from the response after 2 months + id = this.safeString2 (transaction, 'payment_id', 'deposit_id'); + type = 'deposit'; + address = addressTo; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const txid = this.safeString (transaction, 'txid'); + const timestamp = this.parse8601 (this.safeString (transaction, 'timestamp')); + let feeCost = undefined; + if (type === 'deposit') { + feeCost = 0; + } else { + if (currencyId !== undefined) { + const feeWithCurrencyId = this.safeString (transaction, 'fee'); + if (feeWithCurrencyId !== undefined) { + // https://github.com/ccxt/ccxt/pull/5748 + const lowercaseCurrencyId = currencyId.toLowerCase (); + const feeWithoutCurrencyId = feeWithCurrencyId.replace (lowercaseCurrencyId, ''); + feeCost = parseFloat (feeWithoutCurrencyId); + } + } + } + // todo parse tags + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': undefined, + 'tagTo': undefined, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseMyTrade (pair, market = undefined) { + // check that trading symbols match in both entries + const userTrade = this.safeValue (pair, 1); + const otherTrade = this.safeValue (pair, 0); + const firstMarketId = this.safeString (otherTrade, 'instrument_id'); + const secondMarketId = this.safeString (userTrade, 'instrument_id'); + if (firstMarketId !== secondMarketId) { + throw new NotSupported (this.id + ' parseMyTrade() received unrecognized response format, differing instrument_ids in one fill, the exchange API might have changed, paste your verbose output: https://github.com/ccxt/ccxt/wiki/FAQ#what-is-required-to-get-help'); + } + const marketId = firstMarketId; + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + const quoteId = market['quoteId']; + let side = undefined; + let amount = undefined; + let cost = undefined; + const receivedCurrencyId = this.safeString (userTrade, 'currency'); + let feeCurrencyId = undefined; + if (receivedCurrencyId === quoteId) { + side = this.safeString (otherTrade, 'side'); + amount = this.safeFloat (otherTrade, 'size'); + cost = this.safeFloat (userTrade, 'size'); + feeCurrencyId = this.safeString (otherTrade, 'currency'); + } else { + side = this.safeString (userTrade, 'side'); + amount = this.safeFloat (userTrade, 'size'); + cost = this.safeFloat (otherTrade, 'size'); + feeCurrencyId = this.safeString (userTrade, 'currency'); + } + const id = this.safeString (userTrade, 'trade_id'); + const price = this.safeFloat (userTrade, 'price'); + const feeCostFirst = this.safeFloat (otherTrade, 'fee'); + const feeCostSecond = this.safeFloat (userTrade, 'fee'); + const feeCurrencyCodeFirst = this.safeCurrencyCode (this.safeString (otherTrade, 'currency')); + const feeCurrencyCodeSecond = this.safeCurrencyCode (this.safeString (userTrade, 'currency')); + let fee = undefined; + let fees = undefined; + // fee is either a positive number (invitation rebate) + // or a negative number (transaction fee deduction) + // therefore we need to invert the fee + // more about it https://github.com/ccxt/ccxt/issues/5909 + if ((feeCostFirst !== undefined) && (feeCostFirst !== 0)) { + if ((feeCostSecond !== undefined) && (feeCostSecond !== 0)) { + fees = [ + { + 'cost': -feeCostFirst, + 'currency': feeCurrencyCodeFirst, + }, + { + 'cost': -feeCostSecond, + 'currency': feeCurrencyCodeSecond, + }, + ]; + } else { + fee = { + 'cost': -feeCostFirst, + 'currency': feeCurrencyCodeFirst, + }; + } + } else if ((feeCostSecond !== undefined) && (feeCostSecond !== 0)) { + fee = { + 'cost': -feeCostSecond, + 'currency': feeCurrencyCodeSecond, + }; + } else { + fee = { + 'cost': 0, + 'currency': this.safeCurrencyCode (feeCurrencyId), + }; + } + // + // simplified structures to show the underlying semantics + // + // // market/limit sell + // + // { + // "currency":"USDT", + // "fee":"-0.04647925", // ←--- fee in received quote currency + // "price":"129.13", // ←------ price + // "size":"30.98616393", // ←-- cost + // }, + // { + // "currency":"ETH", + // "fee":"0", + // "price":"129.13", + // "size":"0.23996099", // ←--- amount + // }, + // + // // market/limit buy + // + // { + // "currency":"ETH", + // "fee":"-0.00036049", // ←--- fee in received base currency + // "price":"129.16", // ←------ price + // "size":"0.240322", // ←----- amount + // }, + // { + // "currency":"USDT", + // "fee":"0", + // "price":"129.16", + // "size":"31.03998952", // ←-- cost + // } + // + const timestamp = this.parse8601 (this.safeString2 (userTrade, 'timestamp', 'created_at')); + let takerOrMaker = this.safeString2 (userTrade, 'exec_type', 'liquidity'); + if (takerOrMaker === 'M') { + takerOrMaker = 'maker'; + } else if (takerOrMaker === 'T') { + takerOrMaker = 'taker'; + } + const orderId = this.safeString (userTrade, 'order_id'); + const result = { + 'info': pair, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'takerOrMaker': takerOrMaker, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + if (fees !== undefined) { + result['fees'] = fees; + } + return result; + } + + parseMyTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { + const grouped = this.groupBy (trades, 'trade_id'); + const tradeIds = Object.keys (grouped); + const result = []; + for (let i = 0; i < tradeIds.length; i++) { + const tradeId = tradeIds[i]; + const pair = grouped[tradeId]; + // make sure it has exactly 2 trades, no more, no less + const numTradesInPair = pair.length; + if (numTradesInPair === 2) { + const trade = this.parseMyTrade (pair); + result.push (trade); + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + // okex actually returns ledger entries instead of fills here, so each fill in the order + // is represented by two trades with opposite buy/sell sides, not one :\ + // this aspect renders the 'fills' endpoint unusable for fetchOrderTrades + // until either OKEX fixes the API or we workaround this on our side somehow + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if ((limit !== undefined) && (limit > 100)) { + limit = 100; + } + const request = { + 'instrument_id': market['id'], + // 'order_id': id, // string + // 'after': '1', // pagination of data to return records earlier than the requested ledger_id + // 'before': '1', // P=pagination of data to return records newer than the requested ledger_id + // 'limit': limit, // optional, number of results per request, default = maximum = 100 + }; + const defaultType = this.safeString2 (this.options, 'fetchMyTrades', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + const method = type + 'GetFills'; + const response = await this[method] (this.extend (request, query)); + // + // [ + // // sell + // { + // "created_at":"2020-03-29T11:55:25.000Z", + // "currency":"USDT", + // "exec_type":"T", + // "fee":"-0.04647925", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562924353", + // "liquidity":"T", + // "order_id":"4636470489136128", + // "price":"129.13", + // "product_id":"ETH-USDT", + // "side":"buy", + // "size":"30.98616393", + // "timestamp":"2020-03-29T11:55:25.000Z", + // "trade_id":"18551601" + // }, + // { + // "created_at":"2020-03-29T11:55:25.000Z", + // "currency":"ETH", + // "exec_type":"T", + // "fee":"0", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562924352", + // "liquidity":"T", + // "order_id":"4636470489136128", + // "price":"129.13", + // "product_id":"ETH-USDT", + // "side":"sell", + // "size":"0.23996099", + // "timestamp":"2020-03-29T11:55:25.000Z", + // "trade_id":"18551601" + // }, + // // buy + // { + // "created_at":"2020-03-29T11:55:16.000Z", + // "currency":"ETH", + // "exec_type":"T", + // "fee":"-0.00036049", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562922669", + // "liquidity":"T", + // "order_id": "4636469894136832", + // "price":"129.16", + // "product_id":"ETH-USDT", + // "side":"buy", + // "size":"0.240322", + // "timestamp":"2020-03-29T11:55:16.000Z", + // "trade_id":"18551600" + // }, + // { + // "created_at":"2020-03-29T11:55:16.000Z", + // "currency":"USDT", + // "exec_type":"T", + // "fee":"0", + // "instrument_id":"ETH-USDT", + // "ledger_id":"10562922668", + // "liquidity":"T", + // "order_id":"4636469894136832", + // "price":"129.16", + // "product_id":"ETH-USDT", + // "side":"sell", + // "size":"31.03998952", + // "timestamp":"2020-03-29T11:55:16.000Z", + // "trade_id":"18551600" + // } + // ] + // + return this.parseMyTrades (response, market, since, limit, params); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + // 'instrument_id': market['id'], + 'order_id': id, + // 'after': '1', // return the page after the specified page number + // 'before': '1', // return the page before the specified page number + // 'limit': limit, // optional, number of results per request, default = maximum = 100 + }; + return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params)); + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + let method = undefined; + const request = { + 'instrument_id': market['id'], + // 'order_id': id, // string + // 'after': '1', // pagination of data to return records earlier than the requested ledger_id + // 'before': '1', // P=pagination of data to return records newer than the requested ledger_id + // 'limit': limit, // optional, number of results per request, default = maximum = 100 + }; + const type = market['type']; + if ((type === 'futures') || (type === 'swap')) { + method = type + 'GetInstrumentIdPosition'; + } else if (type === 'option') { + const underlying = this.safeString (params, 'underlying'); + if (underlying === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPosition() requires an underlying parameter for ' + type + ' market ' + symbol); + } + method = type + 'GetUnderlyingPosition'; + } else { + throw new NotSupported (this.id + ' fetchPosition() does not support ' + type + ' market ' + symbol + ', supported market types are futures, swap or option'); + } + const response = await this[method] (this.extend (request, params)); + // + // futures + // + // crossed margin mode + // + // { + // "result": true, + // "holding": [ + // { + // "long_qty": "2", + // "long_avail_qty": "2", + // "long_avg_cost": "8260", + // "long_settlement_price": "8260", + // "realised_pnl": "0.00020928", + // "short_qty": "2", + // "short_avail_qty": "2", + // "short_avg_cost": "8259.99", + // "short_settlement_price": "8259.99", + // "liquidation_price": "113.81", + // "instrument_id": "BTC-USD-191227", + // "leverage": "10", + // "created_at": "2019-09-25T07:58:42.129Z", + // "updated_at": "2019-10-08T14:02:51.029Z", + // "margin_mode": "crossed", + // "short_margin": "0.00242197", + // "short_pnl": "6.63E-6", + // "short_pnl_ratio": "0.002477997", + // "short_unrealised_pnl": "6.63E-6", + // "long_margin": "0.00242197", + // "long_pnl": "-6.65E-6", + // "long_pnl_ratio": "-0.002478", + // "long_unrealised_pnl": "-6.65E-6", + // "long_settled_pnl": "0", + // "short_settled_pnl": "0", + // "last": "8257.57" + // } + // ], + // "margin_mode": "crossed" + // } + // + // fixed margin mode + // + // { + // "result": true, + // "holding": [ + // { + // "long_qty": "4", + // "long_avail_qty": "4", + // "long_margin": "0.00323844", + // "long_liqui_price": "7762.09", + // "long_pnl_ratio": "0.06052306", + // "long_avg_cost": "8234.43", + // "long_settlement_price": "8234.43", + // "realised_pnl": "-0.00000296", + // "short_qty": "2", + // "short_avail_qty": "2", + // "short_margin": "0.00241105", + // "short_liqui_price": "9166.74", + // "short_pnl_ratio": "0.03318052", + // "short_avg_cost": "8295.13", + // "short_settlement_price": "8295.13", + // "instrument_id": "BTC-USD-191227", + // "long_leverage": "15", + // "short_leverage": "10", + // "created_at": "2019-09-25T07:58:42.129Z", + // "updated_at": "2019-10-08T13:12:09.438Z", + // "margin_mode": "fixed", + // "short_margin_ratio": "0.10292507", + // "short_maint_margin_ratio": "0.005", + // "short_pnl": "7.853E-5", + // "short_unrealised_pnl": "7.853E-5", + // "long_margin_ratio": "0.07103743", + // "long_maint_margin_ratio": "0.005", + // "long_pnl": "1.9841E-4", + // "long_unrealised_pnl": "1.9841E-4", + // "long_settled_pnl": "0", + // "short_settled_pnl": "0", + // "last": "8266.99" + // } + // ], + // "margin_mode": "fixed" + // } + // + // swap + // + // crossed margin mode + // + // { + // "margin_mode": "crossed", + // "timestamp": "2019-09-27T03:49:02.018Z", + // "holding": [ + // { + // "avail_position": "3", + // "avg_cost": "59.49", + // "instrument_id": "LTC-USD-SWAP", + // "last": "55.98", + // "leverage": "10.00", + // "liquidation_price": "4.37", + // "maint_margin_ratio": "0.0100", + // "margin": "0.0536", + // "position": "3", + // "realized_pnl": "0.0000", + // "unrealized_pnl": "0", + // "settled_pnl": "-0.0330", + // "settlement_price": "55.84", + // "side": "long", + // "timestamp": "2019-09-27T03:49:02.018Z" + // }, + // ] + // } + // + // fixed margin mode + // + // { + // "margin_mode": "fixed", + // "timestamp": "2019-09-27T03:47:37.230Z", + // "holding": [ + // { + // "avail_position": "20", + // "avg_cost": "8025.0", + // "instrument_id": "BTC-USD-SWAP", + // "last": "8113.1", + // "leverage": "15.00", + // "liquidation_price": "7002.6", + // "maint_margin_ratio": "0.0050", + // "margin": "0.0454", + // "position": "20", + // "realized_pnl": "-0.0001", + // "unrealized_pnl": "0", + // "settled_pnl": "0.0076", + // "settlement_price": "8279.2", + // "side": "long", + // "timestamp": "2019-09-27T03:47:37.230Z" + // } + // ] + // } + // + // option + // + // { + // "holding":[ + // { + // "instrument_id":"BTC-USD-190927-12500-C", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.017", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // }, + // { + // "instrument_id":"BTC-USD-190927-12500-P", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.019", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // } + // ] + // } + // + // todo unify parsePosition/parsePositions + return response; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let method = undefined; + const defaultType = this.safeString2 (this.options, 'fetchPositions', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + if ((type === 'futures') || (type === 'swap')) { + method = type + 'GetPosition'; + } else if (type === 'option') { + const underlying = this.safeString (params, 'underlying'); + if (underlying === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPositions() requires an underlying parameter for ' + type + ' markets'); + } + method = type + 'GetUnderlyingPosition'; + } else { + throw new NotSupported (this.id + ' fetchPositions() does not support ' + type + ' markets, supported market types are futures, swap or option'); + } + params = this.omit (params, 'type'); + const response = await this[method] (params); + // + // futures + // + // ... + // + // + // swap + // + // ... + // + // option + // + // { + // "holding":[ + // { + // "instrument_id":"BTC-USD-190927-12500-C", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.017", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // }, + // { + // "instrument_id":"BTC-USD-190927-12500-P", + // "position":"20", + // "avg_cost":"3.26", + // "avail_position":"20", + // "settlement_price":"0.019", + // "total_pnl":"50", + // "pnl_ratio":"0.3", + // "realized_pnl":"40", + // "unrealized_pnl":"10", + // "pos_margin":"100", + // "option_value":"70", + // "created_at":"2019-08-30T03:09:20.315Z", + // "updated_at":"2019-08-30T03:40:18.318Z" + // } + // ] + // } + // + // todo unify parsePosition/parsePositions + return response; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'fetchLedger', 'defaultType'); + const type = this.safeString (params, 'type', defaultType); + const query = this.omit (params, 'type'); + const suffix = (type === 'account') ? '' : 'Accounts'; + let argument = ''; + const request = { + // 'from': 'id', + // 'to': 'id', + }; + if (limit !== undefined) { + request['limit'] = limit; + } + let currency = undefined; + if ((type === 'spot') || (type === 'futures')) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + " fetchLedger requires a currency code argument for '" + type + "' markets"); + } + argument = 'Currency'; + currency = this.currency (code); + request['currency'] = currency['id']; + } else if ((type === 'margin') || (type === 'swap')) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + " fetchLedger requires a code argument (a market symbol) for '" + type + "' markets"); + } + argument = 'InstrumentId'; + const market = this.market (code); // we intentionally put a market inside here for the margin and swap ledgers + currency = this.currency (market['base']); + request['instrument_id'] = market['id']; + // + // if (type === 'margin') { + // // + // // 3. Borrow + // // 4. Repayment + // // 5. Interest + // // 7. Buy + // // 8. Sell + // // 9. From capital account + // // 10. From C2C + // // 11. From Futures + // // 12. From Spot + // // 13. From ETT + // // 14. To capital account + // // 15. To C2C + // // 16. To Spot + // // 17. To Futures + // // 18. To ETT + // // 19. Mandatory Repayment + // // 20. From Piggybank + // // 21. To Piggybank + // // 22. From Perpetual + // // 23. To Perpetual + // // 24. Liquidation Fee + // // 54. Clawback + // // 59. Airdrop Return. + // // + // request['type'] = 'number'; // All types will be returned if this filed is left blank + // } + // + } else if (type === 'account') { + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + // + // // + // // 1. deposit + // // 2. withdrawal + // // 13. cancel withdrawal + // // 18. into futures account + // // 19. out of futures account + // // 20. into sub account + // // 21. out of sub account + // // 28. claim + // // 29. into ETT account + // // 30. out of ETT account + // // 31. into C2C account + // // 32. out of C2C account + // // 33. into margin account + // // 34. out of margin account + // // 37. into spot account + // // 38. out of spot account + // // + // request['type'] = 'number'; + // + } else { + throw new NotSupported (this.id + " fetchLedger does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); + } + const method = type + 'Get' + suffix + argument + 'Ledger'; + const response = await this[method] (this.extend (request, query)); + // + // transfer funds transfer in/out + // trade funds moved as a result of a trade, spot and margin accounts only + // rebate fee rebate as per fee schedule, spot and margin accounts only + // match open long/open short/close long/close short (futures) or a change in the amount because of trades (swap) + // fee fee, futures only + // settlement settlement/clawback/settle long/settle short + // liquidation force close long/force close short/deliver close long/deliver close short + // funding funding fee, swap only + // margin a change in the amount after adjusting margin, swap only + // + // account + // + // [ + // { + // "amount":0.00051843, + // "balance":0.00100941, + // "currency":"BTC", + // "fee":0, + // "ledger_id":8987285, + // "timestamp":"2018-10-12T11:01:14.000Z", + // "typename":"Get from activity" + // } + // ] + // + // spot + // + // [ + // { + // "timestamp":"2019-03-18T07:08:25.000Z", + // "ledger_id":"3995334780", + // "created_at":"2019-03-18T07:08:25.000Z", + // "currency":"BTC", + // "amount":"0.0009985", + // "balance":"0.0029955", + // "type":"trade", + // "details":{ + // "instrument_id":"BTC-USDT", + // "order_id":"2500650881647616", + // "product_id":"BTC-USDT" + // } + // } + // ] + // + // margin + // + // [ + // [ + // { + // "created_at":"2019-03-20T03:45:05.000Z", + // "ledger_id":"78918186", + // "timestamp":"2019-03-20T03:45:05.000Z", + // "currency":"EOS", + // "amount":"0", // ? + // "balance":"0.59957711", + // "type":"transfer", + // "details":{ + // "instrument_id":"EOS-USDT", + // "order_id":"787057", + // "product_id":"EOS-USDT" + // } + // } + // ], + // { + // "before":"78965766", + // "after":"78918186" + // } + // ] + // + // futures + // + // [ + // { + // "ledger_id":"2508090544914461", + // "timestamp":"2019-03-19T14:40:24.000Z", + // "amount":"-0.00529521", + // "balance":"0", + // "currency":"EOS", + // "type":"fee", + // "details":{ + // "order_id":"2506982456445952", + // "instrument_id":"EOS-USD-190628" + // } + // } + // ] + // + // swap + // + // [ + // { + // "amount":"0.004742", + // "fee":"-0.000551", + // "type":"match", + // "instrument_id":"EOS-USD-SWAP", + // "ledger_id":"197429674941902848", + // "timestamp":"2019-03-25T05:56:31.286Z" + // }, + // ] + // + const responseLength = response.length; + if (responseLength < 1) { + return []; + } + const isArray = Array.isArray (response[0]); + const isMargin = (type === 'margin'); + const entries = (isMargin && isArray) ? response[0] : response; + if (type === 'swap') { + const ledgerEntries = this.parseLedger (entries); + return this.filterBySymbolSinceLimit (ledgerEntries, code, since, limit); + } + return this.parseLedger (entries, currency, since, limit); + } + + parseLedgerEntryType (type) { + const types = { + 'transfer': 'transfer', // // funds transfer in/out + 'trade': 'trade', // funds moved as a result of a trade, spot and margin accounts only + 'rebate': 'rebate', // fee rebate as per fee schedule, spot and margin accounts only + 'match': 'trade', // open long/open short/close long/close short (futures) or a change in the amount because of trades (swap) + 'fee': 'fee', // fee, futures only + 'settlement': 'trade', // settlement/clawback/settle long/settle short + 'liquidation': 'trade', // force close long/force close short/deliver close long/deliver close short + 'funding': 'fee', // funding fee, swap only + 'margin': 'margin', // a change in the amount after adjusting margin, swap only + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // + // account + // + // { + // "amount":0.00051843, + // "balance":0.00100941, + // "currency":"BTC", + // "fee":0, + // "ledger_id":8987285, + // "timestamp":"2018-10-12T11:01:14.000Z", + // "typename":"Get from activity" + // } + // + // spot + // + // { + // "timestamp":"2019-03-18T07:08:25.000Z", + // "ledger_id":"3995334780", + // "created_at":"2019-03-18T07:08:25.000Z", + // "currency":"BTC", + // "amount":"0.0009985", + // "balance":"0.0029955", + // "type":"trade", + // "details":{ + // "instrument_id":"BTC-USDT", + // "order_id":"2500650881647616", + // "product_id":"BTC-USDT" + // } + // } + // + // margin + // + // { + // "created_at":"2019-03-20T03:45:05.000Z", + // "ledger_id":"78918186", + // "timestamp":"2019-03-20T03:45:05.000Z", + // "currency":"EOS", + // "amount":"0", // ? + // "balance":"0.59957711", + // "type":"transfer", + // "details":{ + // "instrument_id":"EOS-USDT", + // "order_id":"787057", + // "product_id":"EOS-USDT" + // } + // } + // + // futures + // + // { + // "ledger_id":"2508090544914461", + // "timestamp":"2019-03-19T14:40:24.000Z", + // "amount":"-0.00529521", + // "balance":"0", + // "currency":"EOS", + // "type":"fee", + // "details":{ + // "order_id":"2506982456445952", + // "instrument_id":"EOS-USD-190628" + // } + // } + // + // swap + // + // { + // "amount":"0.004742", + // "fee":"-0.000551", + // "type":"match", + // "instrument_id":"EOS-USD-SWAP", + // "ledger_id":"197429674941902848", + // "timestamp":"2019-03-25T05:56:31.286Z" + // }, + // + const id = this.safeString (item, 'ledger_id'); + const account = undefined; + const details = this.safeValue (item, 'details', {}); + const referenceId = this.safeString (details, 'order_id'); + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'type')); + const code = this.safeCurrencyCode (this.safeString (item, 'currency'), currency); + const amount = this.safeFloat (item, 'amount'); + const timestamp = this.parse8601 (this.safeString (item, 'timestamp')); + const fee = { + 'cost': this.safeFloat (item, 'fee'), + 'currency': code, + }; + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + const marketId = this.safeString (item, 'instrument_id'); + let symbol = undefined; + if (marketId in this.markets_by_id) { + const market = this.markets_by_id[marketId]; + symbol = market['symbol']; + } + return { + 'info': item, + 'id': id, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'symbol': symbol, + 'amount': amount, + 'before': before, // balance before + 'after': after, // balance after + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const isArray = Array.isArray (params); + let request = '/api/' + api + '/' + this.version + '/'; + request += isArray ? path : this.implodeParams (path, params); + const query = isArray ? params : this.omit (params, this.extractParams (path)); + let url = this.implodeParams (this.urls['api']['rest'], { 'hostname': this.hostname }) + request; + const type = this.getPathAuthenticationType (path); + if (type === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (type === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.iso8601 (this.milliseconds ()); + headers = { + 'OK-ACCESS-KEY': this.apiKey, + 'OK-ACCESS-PASSPHRASE': this.password, + 'OK-ACCESS-TIMESTAMP': timestamp, + // 'OK-FROM': '', + // 'OK-TO': '', + // 'OK-LIMIT': '', + }; + let auth = timestamp + method + request; + if (method === 'GET') { + if (Object.keys (query).length) { + const urlencodedQuery = '?' + this.urlencode (query); + url += urlencodedQuery; + auth += urlencodedQuery; + } + } else { + if (isArray || Object.keys (query).length) { + body = this.json (query); + auth += body; + } + headers['Content-Type'] = 'application/json'; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); + headers['OK-ACCESS-SIGN'] = signature; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + getPathAuthenticationType (path) { + // https://github.com/ccxt/ccxt/issues/6651 + // a special case to handle the optionGetUnderlying interefering with + // other endpoints containing this keyword + if (path === 'underlying') { + return 'public'; + } + const auth = this.safeValue (this.options, 'auth', {}); + const key = this.findBroadlyMatchedKey (auth, path); + return this.safeString (auth, key, 'private'); + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (!response) { + return; // fallback to default error handler + } + const feedback = this.id + ' ' + body; + if (code === 503) { + // {"message":"name resolution failed"} + throw new ExchangeNotAvailable (feedback); + } + // + // {"error_message":"Order does not exist","result":"true","error_code":"35029","order_id":"-1"} + // + const message = this.safeString (response, 'message'); + const errorCode = this.safeString2 (response, 'code', 'error_code'); + const nonEmptyMessage = ((message !== undefined) && (message !== '')); + const nonZeroErrorCode = (errorCode !== undefined) && (errorCode !== '0'); + if (nonEmptyMessage) { + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + if (nonZeroErrorCode) { + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + if (nonZeroErrorCode || nonEmptyMessage) { + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/paymium.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/paymium.js new file mode 100644 index 000000000..1af0c5355 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/paymium.js @@ -0,0 +1,263 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class paymium extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'paymium', + 'name': 'Paymium', + 'countries': [ 'FR', 'EU' ], + 'rateLimit': 2000, + 'version': 'v1', + 'has': { + 'CORS': true, + 'fetchBalance': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchOrderBook': true, + 'createOrder': true, + 'cancelOrder': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87153930-f0f02200-c2c0-11ea-9c0a-40337375ae89.jpg', + 'api': 'https://paymium.com/api', + 'www': 'https://www.paymium.com', + 'fees': 'https://www.paymium.com/page/help/fees', + 'doc': [ + 'https://github.com/Paymium/api-documentation', + 'https://www.paymium.com/page/developers', + ], + 'referral': 'https://www.paymium.com/page/sign-up?referral=eDAzPoRQFMvaAB8sf-qj', + }, + 'api': { + 'public': { + 'get': [ + 'countries', + 'data/{currency}/ticker', + 'data/{currency}/trades', + 'data/{currency}/depth', + 'bitcoin_charts/{id}/trades', + 'bitcoin_charts/{id}/depth', + ], + }, + 'private': { + 'get': [ + 'user', + 'user/addresses', + 'user/addresses/{address}', + 'user/orders', + 'user/orders/{uuid}', + 'user/price_alerts', + 'merchant/get_payment/{uuid}', + ], + 'post': [ + 'user/addresses', + 'user/orders', + 'user/withdrawals', + 'user/email_transfers', + 'user/payment_requests', + 'user/price_alerts', + 'merchant/create_payment', + ], + 'delete': [ + 'user/orders/{uuid}', + 'user/orders/{uuid}/cancel', + 'user/price_alerts/{id}', + ], + }, + }, + 'markets': { + 'BTC/EUR': { 'id': 'eur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR', 'baseId': 'btc', 'quoteId': 'eur' }, + }, + 'fees': { + 'trading': { + 'maker': 0.002, + 'taker': 0.002, + }, + }, + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetUser (params); + const result = { 'info': response }; + const currencies = Object.keys (this.currencies); + for (let i = 0; i < currencies.length; i++) { + const code = currencies[i]; + const currencyId = this.currencyId (code); + const free = 'balance_' + currencyId; + if (free in response) { + const account = this.account (); + const used = 'locked_' + currencyId; + account['free'] = this.safeFloat (response, free); + account['used'] = this.safeFloat (response, used); + result[code] = account; + } + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': this.marketId (symbol), + }; + const response = await this.publicGetDataCurrencyDepth (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'bids', 'asks', 'price', 'amount'); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'currency': this.marketId (symbol), + }; + const ticker = await this.publicGetDataCurrencyTicker (this.extend (request, params)); + const timestamp = this.safeTimestamp (ticker, 'at'); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'price'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'variation'), + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market) { + const timestamp = this.safeTimestamp (trade, 'created_at_int'); + const id = this.safeString (trade, 'uuid'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amountField = 'traded_' + market['base'].toLowerCase (); + const amount = this.safeFloat (trade, amountField); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + }; + const response = await this.publicGetDataCurrencyTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'type': this.capitalize (type) + 'Order', + 'currency': this.marketId (symbol), + 'direction': side, + 'amount': amount, + }; + if (type !== 'market') { + request['price'] = price; + } + const response = await this.privatePostUserOrders (this.extend (request, params)); + return { + 'info': response, + 'id': response['uuid'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'uuid': id, + }; + return await this.privateDeleteUserOrdersUuidCancel (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + let auth = nonce + url; + headers = { + 'Api-Key': this.apiKey, + 'Api-Nonce': nonce, + }; + if (method === 'POST') { + if (Object.keys (query).length) { + body = this.json (query); + auth += body; + headers['Content-Type'] = 'application/json'; + } + } else { + if (Object.keys (query).length) { + const queryString = this.urlencode (query); + auth += queryString; + url += '?' + queryString; + } + } + headers['Api-Signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const response = await this.fetch2 (path, api, method, params, headers, body); + if ('errors' in response) { + throw new ExchangeError (this.id + ' ' + this.json (response)); + } + return response; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/phemex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/phemex.js new file mode 100644 index 000000000..5fa317455 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/phemex.js @@ -0,0 +1,2505 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, BadRequest, PermissionDenied, AccountSuspended, CancelPending, DDoSProtection, DuplicateOrderId, NotSupported } = require ('./base/errors'); +const { TICK_SIZE, ROUND, DECIMAL_PLACES } = require ('./base/functions/number'); + +// ---------------------------------------------------------------------------- + +module.exports = class phemex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'phemex', + 'name': 'Phemex', + 'countries': [ 'CN' ], // China + 'rateLimit': 100, + 'version': 'v1', + 'certified': false, + 'pro': true, + 'has': { + 'cancelAllOrders': true, // swap contracts only + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg', + 'test': { + 'v1': 'https://testnet-api.phemex.com/v1', + 'public': 'https://testnet-api.phemex.com/exchange/public', + 'private': 'https://testnet-api.phemex.com', + }, + 'api': { + 'v1': 'https://api.phemex.com/v1', + 'public': 'https://api.phemex.com/exchange/public', + 'private': 'https://api.phemex.com', + }, + 'www': 'https://phemex.com', + 'doc': 'https://github.com/phemex/phemex-api-docs', + 'fees': 'https://phemex.com/fees-conditions', + 'referral': 'https://phemex.com/register?referralCode=EDNVJ', + }, + 'timeframes': { + '1m': '60', + '3m': '180', + '5m': '300', + '15m': '900', + '30m': '1800', + '1h': '3600', + '2h': '7200', + '3h': '10800', + '4h': '14400', + '6h': '21600', + '12h': '43200', + '1d': '86400', + '1w': '604800', + '1M': '2592000', + }, + 'api': { + 'public': { + 'get': [ + 'cfg/v2/products', // spot + contracts + 'products', // contracts only + 'nomics/trades', // ?market=&since= + 'md/kline', // ?from=1589811875&resolution=1800&symbol=sBTCUSDT&to=1592457935 + ], + }, + 'v1': { + 'get': [ + 'md/orderbook', // ?symbol=&id= + 'md/trade', // ?symbol=&id= + 'md/ticker/24hr', // ?symbol=&id= + 'md/ticker/24hr/all', // ?id= + 'md/spot/ticker/24hr', // ?symbol=&id= + 'md/spot/ticker/24hr/all', // ?symbol=&id= + 'exchange/public/products', // contracts only + ], + }, + 'private': { + 'get': [ + // spot + 'spot/orders/active', // ?symbol=&orderID= + // 'spot/orders/active', // ?symbol=&clOrDID= + 'spot/orders', // ?symbol= + 'spot/wallets', // ?currency= + 'exchange/spot/order', // ?symbol=&ordStatus=ordType=&start=&end=&limit=&offset= + 'exchange/spot/order/trades', // ?symbol=&start=&end=&limit=&offset= + // swap + 'accounts/accountPositions', // ?currency= + 'orders/activeList', // ?symbol= + 'exchange/order/list', // ?symbol=&start=&end=&offset=&limit=&ordStatus=&withCount= + 'exchange/order', // ?symbol=&orderID= + // 'exchange/order', // ?symbol=&clOrdID= + 'exchange/order/trade', // ?symbol=&start=&end=&limit=&offset=&withCount= + 'phemex-user/users/children', // ?offset=&limit=&withCount= + 'phemex-user/wallets/v2/depositAddress', // ?_t=1592722635531¤cy=USDT + 'exchange/margins/transfer', // ?start=&end=&offset=&limit=&withCount= + 'exchange/wallets/confirm/withdraw', // ?code= + 'exchange/wallets/withdrawList', // ?currency=&limit=&offset=&withCount= + 'exchange/wallets/depositList', // ?currency=&offset=&limit= + 'exchange/wallets/v2/depositAddress', // ?currency= + ], + 'post': [ + // spot + 'spot/orders', + // swap + 'orders', + 'positions/assign', // ?symbol=&posBalance=&posBalanceEv= + 'exchange/wallets/transferOut', + 'exchange/wallets/transferIn', + 'exchange/margins', + 'exchange/wallets/createWithdraw', // ?otpCode= + 'exchange/wallets/cancelWithdraw', + 'exchange/wallets/createWithdrawAddress', // ?otpCode={optCode} + ], + 'put': [ + // spot + 'spot/orders', // ?symbol=&orderID=&origClOrdID=&clOrdID=&priceEp=&baseQtyEV="eQtyEv=&stopPxEp= + // swap + 'orders/replace', // ?symbol=&orderID=&origClOrdID=&clOrdID=&price=&priceEp=&orderQty=&stopPx=&stopPxEp=&takeProfit=&takeProfitEp=&stopLoss=&stopLossEp=&pegOffsetValueEp=&pegPriceType= + 'positions/leverage', // ?symbol=&leverage=&leverageEr= + 'positions/riskLimit', // ?symbol=&riskLimit=&riskLimitEv= + ], + 'delete': [ + // spot + 'spot/orders', // ?symbol=&orderID= + // 'spot/orders', // ?symbol=&clOrdID= + // swap + 'orders/cancel', // ?symbol=&orderID= + 'orders', // ?symbol=&orderID=,, + 'orders/all', // ?symbol=&untriggered=&text= + ], + }, + }, + 'precisionMode': TICK_SIZE, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.1 / 100, + 'maker': 0.1 / 100, + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'exceptions': { + 'exact': { + // not documented + '412': BadRequest, // {"code":412,"msg":"Missing parameter - resolution","data":null} + '6001': BadRequest, // {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null} + // documented + '19999': BadRequest, // REQUEST_IS_DUPLICATED Duplicated request ID + '10001': DuplicateOrderId, // OM_DUPLICATE_ORDERID Duplicated order ID + '10002': OrderNotFound, // OM_ORDER_NOT_FOUND Cannot find order ID + '10003': CancelPending, // OM_ORDER_PENDING_CANCEL Cannot cancel while order is already in pending cancel status + '10004': CancelPending, // OM_ORDER_PENDING_REPLACE Cannot cancel while order is already in pending cancel status + '10005': CancelPending, // OM_ORDER_PENDING Cannot cancel while order is already in pending cancel status + '11001': InsufficientFunds, // TE_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance + '11002': InvalidOrder, // TE_INVALID_RISK_LIMIT Invalid risk limit value + '11003': InsufficientFunds, // TE_NO_ENOUGH_BALANCE_FOR_NEW_RISK_LIMIT Insufficient available balance + '11004': InvalidOrder, // TE_INVALID_LEVERAGE invalid input or new leverage is over maximum allowed leverage + '11005': InsufficientFunds, // TE_NO_ENOUGH_BALANCE_FOR_NEW_LEVERAGE Insufficient available balance + '11006': ExchangeError, // TE_CANNOT_CHANGE_POSITION_MARGIN_WITHOUT_POSITION Position size is zero. Cannot change margin + '11007': ExchangeError, // TE_CANNOT_CHANGE_POSITION_MARGIN_FOR_CROSS_MARGIN Cannot change margin under CrossMargin + '11008': ExchangeError, // TE_CANNOT_REMOVE_POSITION_MARGIN_MORE_THAN_ADDED exceeds the maximum removable Margin + '11009': ExchangeError, // TE_CANNOT_REMOVE_POSITION_MARGIN_DUE_TO_UNREALIZED_PNL exceeds the maximum removable Margin + '11010': InsufficientFunds, // TE_CANNOT_ADD_POSITION_MARGIN_DUE_TO_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance + '11011': InvalidOrder, // TE_REDUCE_ONLY_ABORT Cannot accept reduce only order + '11012': InvalidOrder, // TE_REPLACE_TO_INVALID_QTY Order quantity Error + '11013': InvalidOrder, // TE_CONDITIONAL_NO_POSITION Position size is zero. Cannot determine conditional order's quantity + '11014': InvalidOrder, // TE_CONDITIONAL_CLOSE_POSITION_WRONG_SIDE Close position conditional order has the same side + '11015': InvalidOrder, // TE_CONDITIONAL_TRIGGERED_OR_CANCELED + '11016': BadRequest, // TE_ADL_NOT_TRADING_REQUESTED_ACCOUNT Request is routed to the wrong trading engine + '11017': ExchangeError, // TE_ADL_CANNOT_FIND_POSITION Cannot find requested position on current account + '11018': ExchangeError, // TE_NO_NEED_TO_SETTLE_FUNDING The current account does not need to pay a funding fee + '11019': ExchangeError, // TE_FUNDING_ALREADY_SETTLED The current account already pays the funding fee + '11020': ExchangeError, // TE_CANNOT_TRANSFER_OUT_DUE_TO_BONUS Withdraw to wallet needs to remove all remaining bonus. However if bonus is used by position or order cost, withdraw fails. + '11021': ExchangeError, // TE_INVALID_BONOUS_AMOUNT // Grpc command cannot be negative number Invalid bonus amount + '11022': AccountSuspended, // TE_REJECT_DUE_TO_BANNED Account is banned + '11023': ExchangeError, // TE_REJECT_DUE_TO_IN_PROCESS_OF_LIQ Account is in the process of liquidation + '11024': ExchangeError, // TE_REJECT_DUE_TO_IN_PROCESS_OF_ADL Account is in the process of auto-deleverage + '11025': BadRequest, // TE_ROUTE_ERROR Request is routed to the wrong trading engine + '11026': ExchangeError, // TE_UID_ACCOUNT_MISMATCH + '11027': BadSymbol, // TE_SYMBOL_INVALID Invalid number ID or name + '11028': BadSymbol, // TE_CURRENCY_INVALID Invalid currency ID or name + '11029': ExchangeError, // TE_ACTION_INVALID Unrecognized request type + '11030': ExchangeError, // TE_ACTION_BY_INVALID + '11031': DDoSProtection, // TE_SO_NUM_EXCEEDS Number of total conditional orders exceeds the max limit + '11032': DDoSProtection, // TE_AO_NUM_EXCEEDS Number of total active orders exceeds the max limit + '11033': DuplicateOrderId, // TE_ORDER_ID_DUPLICATE Duplicated order ID + '11034': InvalidOrder, // TE_SIDE_INVALID Invalid side + '11035': InvalidOrder, // TE_ORD_TYPE_INVALID Invalid OrderType + '11036': InvalidOrder, // TE_TIME_IN_FORCE_INVALID Invalid TimeInForce + '11037': InvalidOrder, // TE_EXEC_INST_INVALID Invalid ExecType + '11038': InvalidOrder, // TE_TRIGGER_INVALID Invalid trigger type + '11039': InvalidOrder, // TE_STOP_DIRECTION_INVALID Invalid stop direction type + '11040': InvalidOrder, // TE_NO_MARK_PRICE Cannot get valid mark price to create conditional order + '11041': InvalidOrder, // TE_NO_INDEX_PRICE Cannot get valid index price to create conditional order + '11042': InvalidOrder, // TE_NO_LAST_PRICE Cannot get valid last market price to create conditional order + '11043': InvalidOrder, // TE_RISING_TRIGGER_DIRECTLY Conditional order would be triggered immediately + '11044': InvalidOrder, // TE_FALLING_TRIGGER_DIRECTLY Conditional order would be triggered immediately + '11045': InvalidOrder, // TE_TRIGGER_PRICE_TOO_LARGE Conditional order trigger price is too high + '11046': InvalidOrder, // TE_TRIGGER_PRICE_TOO_SMALL Conditional order trigger price is too low + '11047': InvalidOrder, // TE_BUY_TP_SHOULD_GT_BASE TakeProfile BUY conditional order trigger price needs to be greater than reference price + '11048': InvalidOrder, // TE_BUY_SL_SHOULD_LT_BASE StopLoss BUY condition order price needs to be less than the reference price + '11049': InvalidOrder, // TE_BUY_SL_SHOULD_GT_LIQ StopLoss BUY condition order price needs to be greater than liquidation price or it will not trigger + '11050': InvalidOrder, // TE_SELL_TP_SHOULD_LT_BASE TakeProfile SELL conditional order trigger price needs to be less than reference price + '11051': InvalidOrder, // TE_SELL_SL_SHOULD_LT_LIQ StopLoss SELL condition order price needs to be less than liquidation price or it will not trigger + '11052': InvalidOrder, // TE_SELL_SL_SHOULD_GT_BASE StopLoss SELL condition order price needs to be greater than the reference price + '11053': InvalidOrder, // TE_PRICE_TOO_LARGE + '11054': InvalidOrder, // TE_PRICE_WORSE_THAN_BANKRUPT Order price cannot be more aggressive than bankrupt price if this order has instruction to close a position + '11055': InvalidOrder, // TE_PRICE_TOO_SMALL Order price is too low + '11056': InvalidOrder, // TE_QTY_TOO_LARGE Order quantity is too large + '11057': InvalidOrder, // TE_QTY_NOT_MATCH_REDUCE_ONLY Does not allow ReduceOnly order without position + '11058': InvalidOrder, // TE_QTY_TOO_SMALL Order quantity is too small + '11059': InvalidOrder, // TE_TP_SL_QTY_NOT_MATCH_POS Position size is zero. Cannot accept any TakeProfit or StopLoss order + '11060': InvalidOrder, // TE_SIDE_NOT_CLOSE_POS TakeProfit or StopLoss order has wrong side. Cannot close position + '11061': CancelPending, // TE_ORD_ALREADY_PENDING_CANCEL Repeated cancel request + '11062': InvalidOrder, // TE_ORD_ALREADY_CANCELED Order is already canceled + '11063': InvalidOrder, // TE_ORD_STATUS_CANNOT_CANCEL Order is not able to be canceled under current status + '11064': InvalidOrder, // TE_ORD_ALREADY_PENDING_REPLACE Replace request is rejected because order is already in pending replace status + '11065': InvalidOrder, // TE_ORD_REPLACE_NOT_MODIFIED Replace request does not modify any parameters of the order + '11066': InvalidOrder, // TE_ORD_STATUS_CANNOT_REPLACE Order is not able to be replaced under current status + '11067': InvalidOrder, // TE_CANNOT_REPLACE_PRICE Market conditional order cannot change price + '11068': InvalidOrder, // TE_CANNOT_REPLACE_QTY Condtional order for closing position cannot change order quantity, since the order quantity is determined by position size already + '11069': ExchangeError, // TE_ACCOUNT_NOT_IN_RANGE The account ID in the request is not valid or is not in the range of the current process + '11070': BadSymbol, // TE_SYMBOL_NOT_IN_RANGE The symbol is invalid + '11071': InvalidOrder, // TE_ORD_STATUS_CANNOT_TRIGGER + '11072': InvalidOrder, // TE_TKFR_NOT_IN_RANGE The fee value is not valid + '11073': InvalidOrder, // TE_MKFR_NOT_IN_RANGE The fee value is not valid + '11074': InvalidOrder, // TE_CANNOT_ATTACH_TP_SL Order request cannot contain TP/SL parameters when the account already has positions + '11075': InvalidOrder, // TE_TP_TOO_LARGE TakeProfit price is too large + '11076': InvalidOrder, // TE_TP_TOO_SMALL TakeProfit price is too small + '11077': InvalidOrder, // TE_TP_TRIGGER_INVALID Invalid trigger type + '11078': InvalidOrder, // TE_SL_TOO_LARGE StopLoss price is too large + '11079': InvalidOrder, // TE_SL_TOO_SMALL StopLoss price is too small + '11080': InvalidOrder, // TE_SL_TRIGGER_INVALID Invalid trigger type + '11081': InvalidOrder, // TE_RISK_LIMIT_EXCEEDS Total potential position breaches current risk limit + '11082': InsufficientFunds, // TE_CANNOT_COVER_ESTIMATE_ORDER_LOSS The remaining balance cannot cover the potential unrealized PnL for this new order + '11083': InvalidOrder, // TE_TAKE_PROFIT_ORDER_DUPLICATED TakeProfit order already exists + '11084': InvalidOrder, // TE_STOP_LOSS_ORDER_DUPLICATED StopLoss order already exists + '11085': DuplicateOrderId, // TE_CL_ORD_ID_DUPLICATE ClOrdId is duplicated + '11086': InvalidOrder, // TE_PEG_PRICE_TYPE_INVALID PegPriceType is invalid + '11087': InvalidOrder, // TE_BUY_TS_SHOULD_LT_BASE The trailing order's StopPrice should be less than the current last price + '11088': InvalidOrder, // TE_BUY_TS_SHOULD_GT_LIQ The traling order's StopPrice should be greater than the current liquidation price + '11089': InvalidOrder, // TE_SELL_TS_SHOULD_LT_LIQ The traling order's StopPrice should be greater than the current last price + '11090': InvalidOrder, // TE_SELL_TS_SHOULD_GT_BASE The traling order's StopPrice should be less than the current liquidation price + '11091': InvalidOrder, // TE_BUY_REVERT_VALUE_SHOULD_LT_ZERO The PegOffset should be less than zero + '11092': InvalidOrder, // TE_SELL_REVERT_VALUE_SHOULD_GT_ZERO The PegOffset should be greater than zero + '11093': InvalidOrder, // TE_BUY_TTP_SHOULD_ACTIVATE_ABOVE_BASE The activation price should be greater than the current last price + '11094': InvalidOrder, // TE_SELL_TTP_SHOULD_ACTIVATE_BELOW_BASE The activation price should be less than the current last price + '11095': InvalidOrder, // TE_TRAILING_ORDER_DUPLICATED A trailing order exists already + '11096': InvalidOrder, // TE_CLOSE_ORDER_CANNOT_ATTACH_TP_SL An order to close position cannot have trailing instruction + '11097': BadRequest, // TE_CANNOT_FIND_WALLET_OF_THIS_CURRENCY This crypto is not supported + '11098': BadRequest, // TE_WALLET_INVALID_ACTION Invalid action on wallet + '11099': ExchangeError, // TE_WALLET_VID_UNMATCHED Wallet operation request has a wrong wallet vid + '11100': InsufficientFunds, // TE_WALLET_INSUFFICIENT_BALANCE Wallet has insufficient balance + '11101': InsufficientFunds, // TE_WALLET_INSUFFICIENT_LOCKED_BALANCE Locked balance in wallet is not enough for unlock/withdraw request + '11102': BadRequest, // TE_WALLET_INVALID_DEPOSIT_AMOUNT Deposit amount must be greater than zero + '11103': BadRequest, // TE_WALLET_INVALID_WITHDRAW_AMOUNT Withdraw amount must be less than zero + '11104': BadRequest, // TE_WALLET_REACHED_MAX_AMOUNT Deposit makes wallet exceed max amount allowed + '11105': InsufficientFunds, // TE_PLACE_ORDER_INSUFFICIENT_BASE_BALANCE Insufficient funds in base wallet + '11106': InsufficientFunds, // TE_PLACE_ORDER_INSUFFICIENT_QUOTE_BALANCE Insufficient funds in quote wallet + '11107': ExchangeError, // TE_CANNOT_CONNECT_TO_REQUEST_SEQ TradingEngine failed to connect with CrossEngine + '11108': InvalidOrder, // TE_CANNOT_REPLACE_OR_CANCEL_MARKET_ORDER Cannot replace/amend market order + '11109': InvalidOrder, // TE_CANNOT_REPLACE_OR_CANCEL_IOC_ORDER Cannot replace/amend ImmediateOrCancel order + '11110': InvalidOrder, // TE_CANNOT_REPLACE_OR_CANCEL_FOK_ORDER Cannot replace/amend FillOrKill order + '11111': InvalidOrder, // TE_MISSING_ORDER_ID OrderId is missing + '11112': InvalidOrder, // TE_QTY_TYPE_INVALID QtyType is invalid + '11113': BadRequest, // TE_USER_ID_INVALID UserId is invalid + '11114': InvalidOrder, // TE_ORDER_VALUE_TOO_LARGE Order value is too large + '11115': InvalidOrder, // TE_ORDER_VALUE_TOO_SMALL Order value is too small + // not documented + '30018': BadRequest, // {"code":30018,"msg":"phemex.data.size.uplimt","data":null} + '39996': PermissionDenied, // {"code": "39996","msg": "Access denied."} + }, + 'broad': { + 'Failed to find api-key': AuthenticationError, // {"msg":"Failed to find api-key 1c5ec63fd-660d-43ea-847a-0d3ba69e106e","code":10500} + 'Missing required parameter': BadRequest, // {"msg":"Missing required parameter","code":10500} + 'API Signature verification failed': AuthenticationError, // {"msg":"API Signature verification failed.","code":10500} + }, + }, + 'options': { + 'x-phemex-request-expiry': 60, // in seconds + 'createOrderByQuoteRequiresPrice': true, + }, + }); + } + + parseSafeFloat (value = undefined) { + if (value === undefined) { + return value; + } + value = value.replace (',', ''); + const parts = value.split (' '); + return this.safeFloat (parts, 0); + } + + parseSwapMarket (market) { + // + // { + // "symbol":"BTCUSD", + // "displaySymbol":"BTC / USD", + // "indexSymbol":".BTC", + // "markSymbol":".MBTC", + // "fundingRateSymbol":".BTCFR", + // "fundingRate8hSymbol":".BTCFR8H", + // "contractUnderlyingAssets":"USD", + // "settleCurrency":"BTC", + // "quoteCurrency":"USD", + // "contractSize":"1 USD", + // "lotSize":1, + // "tickSize":0.5, + // "priceScale":4, + // "ratioScale":8, + // "pricePrecision":1, + // "minPriceEp":5000, + // "maxPriceEp":10000000000, + // "maxOrderQty":1000000, + // "type":"Perpetual" + // "steps":"50", + // "riskLimits":[ + // {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000}, + // {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000}, + // {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000}, + // ], + // "underlyingSymbol":".BTC", + // "baseCurrency":"BTC", + // "settlementCurrency":"BTC", + // "valueScale":8, + // "defaultLeverage":0, + // "maxLeverage":100, + // "initMarginEr":"1000000", + // "maintMarginEr":"500000", + // "defaultRiskLimitEv":10000000000, + // "deleverage":true, + // "makerFeeRateEr":-250000, + // "takerFeeRateEr":750000, + // "fundingInterval":8, + // "marketUrl":"https://phemex.com/trade/BTCUSD", + // "description":"BTCUSD is a BTC/USD perpetual contract priced on the .BTC Index. Each contract is worth 1 USD of Bitcoin. Funding is paid and received every 8 hours. At UTC time: 00:00, 08:00, 16:00.", + // } + // + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseCurrency', 'contractUnderlyingAssets'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const type = this.safeStringLower (market, 'type'); + let taker = undefined; + let maker = undefined; + let inverse = false; + const spot = false; + const swap = true; + const settlementCurrencyId = this.safeString (market, 'settlementCurrency'); + if (settlementCurrencyId !== quoteId) { + inverse = true; + } + const linear = !inverse; + const precision = { + 'amount': this.safeFloat (market, 'lotSize'), + 'price': this.safeFloat (market, 'tickSize'), + }; + const priceScale = this.safeInteger (market, 'priceScale'); + const ratioScale = this.safeInteger (market, 'ratioScale'); + const valueScale = this.safeInteger (market, 'valueScale'); + const minPriceEp = this.safeFloat (market, 'minPriceEp'); + const maxPriceEp = this.safeFloat (market, 'maxPriceEp'); + const makerFeeRateEr = this.safeFloat (market, 'makerFeeRateEr'); + const takerFeeRateEr = this.safeFloat (market, 'takerFeeRateEr'); + if (makerFeeRateEr !== undefined) { + maker = this.fromEn (makerFeeRateEr, ratioScale, 0.00000001); + } + if (takerFeeRateEr !== undefined) { + taker = this.fromEn (takerFeeRateEr, ratioScale, 0.00000001); + } + const limits = { + 'amount': { + 'min': precision['amount'], + 'max': undefined, + }, + 'price': { + 'min': this.fromEn (minPriceEp, priceScale, precision['price']), + 'max': this.fromEn (maxPriceEp, priceScale, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': this.parseSafeFloat (this.safeString (market, 'maxOrderQty')), + }, + }; + const active = undefined; + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'swap': swap, + 'linear': linear, + 'inverse': inverse, + 'active': active, + 'taker': taker, + 'maker': maker, + 'priceScale': priceScale, + 'valueScale': valueScale, + 'ratioScale': ratioScale, + 'precision': precision, + 'limits': limits, + }; + } + + parseSpotMarket (market) { + // + // { + // "symbol":"sBTCUSDT", + // "displaySymbol":"BTC / USDT", + // "quoteCurrency":"USDT", + // "pricePrecision":2, + // "type":"Spot", + // "baseCurrency":"BTC", + // "baseTickSize":"0.000001 BTC", + // "baseTickSizeEv":100, + // "quoteTickSize":"0.01 USDT", + // "quoteTickSizeEv":1000000, + // "minOrderValue":"10 USDT", + // "minOrderValueEv":1000000000, + // "maxBaseOrderSize":"1000 BTC", + // "maxBaseOrderSizeEv":100000000000, + // "maxOrderValue":"5,000,000 USDT", + // "maxOrderValueEv":500000000000000, + // "defaultTakerFee":"0.001", + // "defaultTakerFeeEr":100000, + // "defaultMakerFee":"0.001", + // "defaultMakerFeeEr":100000, + // "baseQtyPrecision":6, + // "quoteQtyPrecision":2 + // } + // + const type = this.safeStringLower (market, 'type'); + const id = this.safeString (market, 'symbol'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const baseId = this.safeString (market, 'baseCurrency'); + const linear = undefined; + const inverse = undefined; + const spot = true; + const swap = false; + const taker = this.safeFloat (market, 'defaultTakerFee'); + const maker = this.safeFloat (market, 'defaultMakerFee'); + const precision = { + 'amount': this.parseSafeFloat (this.safeString (market, 'baseTickSize')), + 'price': this.parseSafeFloat (this.safeString (market, 'quoteTickSize')), + }; + const limits = { + 'amount': { + 'min': precision['amount'], + 'max': this.parseSafeFloat (this.safeString (market, 'maxBaseOrderSize')), + }, + 'price': { + 'min': precision['price'], + 'max': undefined, + }, + 'cost': { + 'min': this.parseSafeFloat (this.safeString (market, 'minOrderValue')), + 'max': this.parseSafeFloat (this.safeString (market, 'maxOrderValue')), + }, + }; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = undefined; + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'type': type, + 'spot': spot, + 'swap': swap, + 'linear': linear, + 'inverse': inverse, + 'active': active, + 'taker': taker, + 'maker': maker, + 'precision': precision, + 'priceScale': 8, + 'valueScale': 8, + 'ratioScale': 8, + 'limits': limits, + }; + } + + async fetchMarkets (params = {}) { + const v2Products = await this.publicGetCfgV2Products (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "ratioScale":8, + // "currencies":[ + // {"currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"}, + // {"currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"}, + // {"currency":"USDT","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"TetherUS"}, + // ], + // "products":[ + // { + // "symbol":"BTCUSD", + // "displaySymbol":"BTC / USD", + // "indexSymbol":".BTC", + // "markSymbol":".MBTC", + // "fundingRateSymbol":".BTCFR", + // "fundingRate8hSymbol":".BTCFR8H", + // "contractUnderlyingAssets":"USD", + // "settleCurrency":"BTC", + // "quoteCurrency":"USD", + // "contractSize":1.0, + // "lotSize":1, + // "tickSize":0.5, + // "priceScale":4, + // "ratioScale":8, + // "pricePrecision":1, + // "minPriceEp":5000, + // "maxPriceEp":10000000000, + // "maxOrderQty":1000000, + // "type":"Perpetual" + // }, + // { + // "symbol":"sBTCUSDT", + // "displaySymbol":"BTC / USDT", + // "quoteCurrency":"USDT", + // "pricePrecision":2, + // "type":"Spot", + // "baseCurrency":"BTC", + // "baseTickSize":"0.000001 BTC", + // "baseTickSizeEv":100, + // "quoteTickSize":"0.01 USDT", + // "quoteTickSizeEv":1000000, + // "minOrderValue":"10 USDT", + // "minOrderValueEv":1000000000, + // "maxBaseOrderSize":"1000 BTC", + // "maxBaseOrderSizeEv":100000000000, + // "maxOrderValue":"5,000,000 USDT", + // "maxOrderValueEv":500000000000000, + // "defaultTakerFee":"0.001", + // "defaultTakerFeeEr":100000, + // "defaultMakerFee":"0.001", + // "defaultMakerFeeEr":100000, + // "baseQtyPrecision":6, + // "quoteQtyPrecision":2 + // }, + // ], + // "riskLimits":[ + // { + // "symbol":"BTCUSD", + // "steps":"50", + // "riskLimits":[ + // {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000}, + // {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000}, + // {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000}, + // ] + // }, + // ], + // "leverages":[ + // {"initialMargin":"1.0%","initialMarginEr":1000000,"options":[1,2,3,5,10,25,50,100]}, + // {"initialMargin":"1.5%","initialMarginEr":1500000,"options":[1,2,3,5,10,25,50,66]}, + // {"initialMargin":"2.0%","initialMarginEr":2000000,"options":[1,2,3,5,10,25,33,50]}, + // ] + // } + // } + // + const v1Products = await this.v1GetExchangePublicProducts (params); + const v1ProductsData = this.safeValue (v1Products, 'data', []); + // + // { + // "code":0, + // "msg":"OK", + // "data":[ + // { + // "symbol":"BTCUSD", + // "underlyingSymbol":".BTC", + // "quoteCurrency":"USD", + // "baseCurrency":"BTC", + // "settlementCurrency":"BTC", + // "maxOrderQty":1000000, + // "maxPriceEp":100000000000000, + // "lotSize":1, + // "tickSize":"0.5", + // "contractSize":"1 USD", + // "priceScale":4, + // "ratioScale":8, + // "valueScale":8, + // "defaultLeverage":0, + // "maxLeverage":100, + // "initMarginEr":"1000000", + // "maintMarginEr":"500000", + // "defaultRiskLimitEv":10000000000, + // "deleverage":true, + // "makerFeeRateEr":-250000, + // "takerFeeRateEr":750000, + // "fundingInterval":8, + // "marketUrl":"https://phemex.com/trade/BTCUSD", + // "description":"BTCUSD is a BTC/USD perpetual contract priced on the .BTC Index. Each contract is worth 1 USD of Bitcoin. Funding is paid and received every 8 hours. At UTC time: 00:00, 08:00, 16:00.", + // "type":"Perpetual" + // }, + // ] + // } + // + const v2ProductsData = this.safeValue (v2Products, 'data', {}); + const products = this.safeValue (v2ProductsData, 'products', []); + const riskLimits = this.safeValue (v2ProductsData, 'riskLimits', []); + const riskLimitsById = this.indexBy (riskLimits, 'symbol'); + const v1ProductsById = this.indexBy (v1ProductsData, 'symbol'); + const result = []; + for (let i = 0; i < products.length; i++) { + let market = products[i]; + const type = this.safeStringLower (market, 'type'); + if (type === 'perpetual') { + const id = this.safeString (market, 'symbol'); + const riskLimitValues = this.safeValue (riskLimitsById, id, {}); + market = this.extend (market, riskLimitValues); + const v1ProductsValues = this.safeValue (v1ProductsById, id, {}); + market = this.extend (market, v1ProductsValues); + market = this.parseSwapMarket (market); + } else { + market = this.parseSpotMarket (market); + } + result.push (market); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCfgV2Products (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // ..., + // "currencies":[ + // {"currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"}, + // {"currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"}, + // {"currency":"USDT","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"TetherUS"}, + // ], + // ... + // } + // } + const data = this.safeValue (response, 'data', {}); + const currencies = this.safeValue (data, 'currencies', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'currency'); + const name = this.safeString (currency, 'name'); + const code = this.safeCurrencyCode (id); + const valueScale = this.safeInteger (currency, 'valueScale'); + const minValueEv = this.safeFloat (currency, 'minValueEv'); + const maxValueEv = this.safeFloat (currency, 'maxValueEv'); + let minAmount = undefined; + let maxAmount = undefined; + let precision = undefined; + if (valueScale !== undefined) { + precision = Math.pow (10, -valueScale); + precision = parseFloat (this.decimalToPrecision (precision, ROUND, 0.00000001, this.precisionMode)); + if (minValueEv !== undefined) { + minAmount = parseFloat (this.decimalToPrecision (minValueEv * precision, ROUND, 0.00000001, this.precisionMode)); + } + if (maxValueEv !== undefined) { + maxAmount = parseFloat (this.decimalToPrecision (maxValueEv * precision, ROUND, 0.00000001, this.precisionMode)); + } + } + result[code] = { + 'id': id, + 'info': currency, + 'code': code, + 'name': name, + 'active': undefined, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { + 'min': minAmount, + 'max': maxAmount, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + 'valueScale': valueScale, + }; + } + return result; + } + + parseBidAsk (bidask, priceKey = 0, amountKey = 1, market = undefined) { + if (market === undefined) { + throw new ArgumentsRequired (this.id + ' parseBidAsk requires a market argument'); + } + let amount = this.safeFloat (bidask, amountKey); + if (market['spot']) { + amount = this.fromEv (amount, market); + } + return [ + this.fromEp (this.safeFloat (bidask, priceKey), market), + amount, + ]; + } + + parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) { + const result = { + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + const sides = [ bidsKey, asksKey ]; + for (let i = 0; i < sides.length; i++) { + const side = sides[i]; + const orders = []; + const bidasks = this.safeValue (orderbook, side); + for (let k = 0; k < bidasks.length; k++) { + orders.push (this.parseBidAsk (bidasks[k], priceKey, amountKey, market)); + } + result[side] = orders; + } + result[bidsKey] = this.sortBy (result[bidsKey], 0, true); + result[asksKey] = this.sortBy (result[asksKey], 0); + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'id': 123456789, // optional request id + }; + const response = await this.v1GetMdOrderbook (this.extend (request, params)); + // + // { + // "error": null, + // "id": 0, + // "result": { + // "book": { + // "asks": [ + // [ 23415000000, 105262000 ], + // [ 23416000000, 147914000 ], + // [ 23419000000, 160914000 ], + // ], + // "bids": [ + // [ 23360000000, 32995000 ], + // [ 23359000000, 221887000 ], + // [ 23356000000, 284599000 ], + // ], + // }, + // "depth": 30, + // "sequence": 1592059928, + // "symbol": "sETHUSDT", + // "timestamp": 1592387340020000955, + // "type": "snapshot" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const book = this.safeValue (result, 'book', {}); + const timestamp = this.safeIntegerProduct (result, 'timestamp', 0.000001); + const orderbook = this.parseOrderBook (book, timestamp, 'bids', 'asks', 0, 1, market); + orderbook['nonce'] = this.safeInteger (result, 'sequence'); + return orderbook; + } + + toEn (n, scale, precision) { + return parseInt (this.decimalToPrecision (n * Math.pow (10, scale), ROUND, precision, DECIMAL_PLACES)); + } + + toEv (amount, market = undefined) { + if ((amount === undefined) || (market === undefined)) { + return amount; + } + return this.toEn (amount, market['valueScale'], 0); + } + + toEp (price, market = undefined) { + if ((price === undefined) || (market === undefined)) { + return price; + } + return this.toEn (price, market['priceScale'], 0); + } + + fromEn (en, scale, precision, precisionMode = undefined) { + precisionMode = (precisionMode === undefined) ? this.precisionMode : precisionMode; + return parseFloat (this.decimalToPrecision (en * Math.pow (10, -scale), ROUND, precision, precisionMode)); + } + + fromEp (ep, market = undefined) { + if ((ep === undefined) || (market === undefined)) { + return ep; + } + return this.fromEn (ep, market['priceScale'], market['precision']['price']); + } + + fromEv (ev, market = undefined) { + if ((ev === undefined) || (market === undefined)) { + return ev; + } + if (market['spot']) { + return this.fromEn (ev, market['valueScale'], market['precision']['amount']); + } else { + return this.fromEn (ev, market['valueScale'], 1 / Math.pow (10, market['valueScale'])); + } + } + + fromEr (er, market = undefined) { + if ((er === undefined) || (market === undefined)) { + return er; + } + return this.fromEn (er, market['ratioScale'], 0.00000001); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1592467200, // timestamp + // 300, // interval + // 23376000000, // last + // 23322000000, // open + // 23381000000, // high + // 23315000000, // low + // 23367000000, // close + // 208671000, // base volume + // 48759063370, // quote volume + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.fromEp (this.safeFloat (ohlcv, 3), market), + this.fromEp (this.safeFloat (ohlcv, 4), market), + this.fromEp (this.safeFloat (ohlcv, 5), market), + this.fromEp (this.safeFloat (ohlcv, 6), market), + this.fromEv (this.safeFloat (ohlcv, 7), market), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + const request = { + // 'symbol': market['id'], + 'resolution': this.timeframes[timeframe], + // 'from': 1588830682, // seconds + // 'to': this.seconds (), + }; + const duration = this.parseTimeframe (timeframe); + const now = this.seconds (); + if (since !== undefined) { + if (limit === undefined) { + limit = 2000; // max 2000 + } + since = parseInt (since / 1000); + request['from'] = since; + // time ranges ending in the future are not accepted + // https://github.com/ccxt/ccxt/issues/8050 + request['to'] = Math.min (now, this.sum (since, duration * limit)); + } else if (limit !== undefined) { + limit = Math.min (limit, 2000); + request['from'] = now - duration * this.sum (limit, 1); + request['to'] = now; + } else { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument, or a limit argument, or both'); + } + await this.loadMarkets (); + const market = this.market (symbol); + request['symbol'] = market['id']; + const response = await this.publicGetMdKline (this.extend (request, params)); + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "total":-1, + // "rows":[ + // [1592467200,300,23376000000,23322000000,23381000000,23315000000,23367000000,208671000,48759063370], + // [1592467500,300,23367000000,23314000000,23390000000,23311000000,23331000000,234820000,54848948710], + // [1592467800,300,23331000000,23385000000,23391000000,23326000000,23387000000,152931000,35747882250], + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const rows = this.safeValue (data, 'rows', []); + return this.parseOHLCVs (rows, market, timeframe, since, limit); + } + + parseTicker (ticker, market = undefined) { + // + // spot + // + // { + // "askEp": 943836000000, + // "bidEp": 943601000000, + // "highEp": 955946000000, + // "lastEp": 943803000000, + // "lowEp": 924973000000, + // "openEp": 948693000000, + // "symbol": "sBTCUSDT", + // "timestamp": 1592471203505728630, + // "turnoverEv": 111822826123103, + // "volumeEv": 11880532281 + // } + // + // swap + // + // { + // "askEp": 2332500, + // "bidEp": 2331000, + // "fundingRateEr": 10000, + // "highEp": 2380000, + // "indexEp": 2329057, + // "lastEp": 2331500, + // "lowEp": 2274000, + // "markEp": 2329232, + // "openEp": 2337500, + // "openInterest": 1298050, + // "predFundingRateEr": 19921, + // "symbol": "ETHUSD", + // "timestamp": 1592474241582701416, + // "turnoverEv": 47228362330, + // "volume": 4053863 + // } + // + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.safeIntegerProduct (ticker, 'timestamp', 0.000001); + const last = this.fromEp (this.safeFloat (ticker, 'lastEp'), market); + const quoteVolume = this.fromEp (this.safeFloat (ticker, 'turnoverEv'), market); + let baseVolume = this.safeFloat (ticker, 'volume'); + if (baseVolume === undefined) { + baseVolume = this.fromEv (this.safeFloat (ticker, 'volumeEv')); + } + let vwap = undefined; + if ((market !== undefined) && (market['spot'])) { + vwap = this.vwap (baseVolume, quoteVolume); + } + let change = undefined; + let percentage = undefined; + let average = undefined; + const open = this.fromEp (this.safeFloat (ticker, 'openEp'), market); + if ((open !== undefined) && (last !== undefined)) { + change = last - open; + if (open > 0) { + percentage = change / open * 100; + } + average = this.sum (open, last) / 2; + } + const result = { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.fromEp (this.safeFloat (ticker, 'highEp'), market), + 'low': this.fromEp (this.safeFloat (ticker, 'lowEp'), market), + 'bid': this.fromEp (this.safeFloat (ticker, 'bidEp'), market), + 'bidVolume': undefined, + 'ask': this.fromEp (this.safeFloat (ticker, 'askEp'), market), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'id': 123456789, // optional request id + }; + const method = market['spot'] ? 'v1GetMdSpotTicker24hr' : 'v1GetMdTicker24hr'; + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "error": null, + // "id": 0, + // "result": { + // "askEp": 943836000000, + // "bidEp": 943601000000, + // "highEp": 955946000000, + // "lastEp": 943803000000, + // "lowEp": 924973000000, + // "openEp": 948693000000, + // "symbol": "sBTCUSDT", + // "timestamp": 1592471203505728630, + // "turnoverEv": 111822826123103, + // "volumeEv": 11880532281 + // } + // } + // + // swap + // + // { + // "error": null, + // "id": 0, + // "result": { + // "askEp": 2332500, + // "bidEp": 2331000, + // "fundingRateEr": 10000, + // "highEp": 2380000, + // "indexEp": 2329057, + // "lastEp": 2331500, + // "lowEp": 2274000, + // "markEp": 2329232, + // "openEp": 2337500, + // "openInterest": 1298050, + // "predFundingRateEr": 19921, + // "symbol": "ETHUSD", + // "timestamp": 1592474241582701416, + // "turnoverEv": 47228362330, + // "volume": 4053863 + // } + // } + // + const result = this.safeValue (response, 'result', {}); + return this.parseTicker (result, market); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'id': 123456789, // optional request id + }; + const response = await this.v1GetMdTrade (this.extend (request, params)); + // + // { + // "error": null, + // "id": 0, + // "result": { + // "sequence": 1315644947, + // "symbol": "BTCUSD", + // "trades": [ + // [ 1592541746712239749, 13156448570000, "Buy", 93070000, 40173 ], + // [ 1592541740434625085, 13156447110000, "Sell", 93065000, 5000 ], + // [ 1592541732958241616, 13156441390000, "Buy", 93070000, 3460 ], + // ], + // "type": "snapshot" + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const trades = this.safeValue (result, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // [ + // 1592541746712239749, + // 13156448570000, + // "Buy", + // 93070000, + // 40173 + // ] + // + // fetchMyTrades (private) + // + // spot + // + // { + // "qtyType": "ByQuote", + // "transactTimeNs": 1589450974800550100, + // "clOrdID": "8ba59d40-df25-d4b0-14cf-0703f44e9690", + // "orderID": "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83", + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "priceEP": 970056000000, + // "baseQtyEv": 0, + // "quoteQtyEv": 1000000000, + // "action": "New", + // "execStatus": "MakerFill", + // "ordStatus": "Filled", + // "ordType": "Limit", + // "execInst": "None", + // "timeInForce": "GoodTillCancel", + // "stopDirection": "UNSPECIFIED", + // "tradeType": "Trade", + // "stopPxEp": 0, + // "execId": "c6bd8979-07ba-5946-b07e-f8b65135dbb1", + // "execPriceEp": 970056000000, + // "execBaseQtyEv": 103000, + // "execQuoteQtyEv": 999157680, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "execFeeEv": 0, + // "feeRateEr": 0 + // } + // + // swap + // + // { + // "transactTimeNs": 1578026629824704800, + // "symbol": "BTCUSD", + // "currency": "BTC", + // "action": "Replace", + // "side": "Sell", + // "tradeType": "Trade", + // "execQty": 700, + // "execPriceEp": 71500000, + // "orderQty": 700, + // "priceEp": 71500000, + // "execValueEv": 9790209, + // "feeRateEr": -25000, + // "execFeeEv": -2447, + // "ordType": "Limit", + // "execID": "b01671a1-5ddc-5def-b80a-5311522fd4bf", + // "orderID": "b63bc982-be3a-45e0-8974-43d6375fb626", + // "clOrdID": "uuid-1577463487504", + // "execStatus": "MakerFill" + // } + // + let price = undefined; + let amount = undefined; + let timestamp = undefined; + let id = undefined; + let side = undefined; + let cost = undefined; + let type = undefined; + let fee = undefined; + const marketId = this.safeString (trade, 'symbol'); + market = this.safeMarket (marketId, market); + const symbol = market['symbol']; + let orderId = undefined; + let takerOrMaker = undefined; + if (Array.isArray (trade)) { + const tradeLength = trade.length; + timestamp = this.safeIntegerProduct (trade, 0, 0.000001); + if (tradeLength > 4) { + id = this.safeString (trade, tradeLength - 4); + } + side = this.safeStringLower (trade, tradeLength - 3); + price = this.fromEp (this.safeFloat (trade, tradeLength - 2), market); + amount = this.fromEv (this.safeFloat (trade, tradeLength - 1), market); + if (market['spot']) { + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + } + } else { + timestamp = this.safeIntegerProduct (trade, 'transactTimeNs', 0.000001); + id = this.safeString2 (trade, 'execId', 'execID'); + orderId = this.safeString (trade, 'orderID'); + side = this.safeStringLower (trade, 'side'); + type = this.parseOrderType (this.safeString (trade, 'ordType')); + const execStatus = this.safeString (trade, 'execStatus'); + if (execStatus === 'MakerFill') { + takerOrMaker = 'maker'; + } + price = this.fromEp (this.safeFloat (trade, 'execPriceEp'), market); + amount = this.fromEv (this.safeFloat (trade, 'execBaseQtyEv'), market); + amount = this.safeFloat (trade, 'execQty', amount); + cost = this.fromEv (this.safeFloat2 (trade, 'execQuoteQtyEv', 'execValueEv'), market); + const feeCost = this.fromEv (this.safeFloat (trade, 'execFeeEv'), market); + if (feeCost !== undefined) { + let feeRate = undefined; + let feeRateEr = this.safeFloat (trade, 'feeRateEr'); + if (feeRateEr < 0) { + feeRateEr = Math.abs (feeRateEr); + feeRate = this.fromEr (feeRateEr, market); + feeRate = -feeRate; + } else { + feeRate = this.fromEr (feeRateEr, market); + } + let feeCurrencyCode = undefined; + if (market['spot']) { + feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + } else { + const info = this.safeValue (market, 'info'); + if (info !== undefined) { + const settlementCurrencyId = this.safeString (info, 'settlementCurrency'); + feeCurrencyCode = this.safeCurrencyCode (settlementCurrencyId); + } + } + fee = { + 'cost': feeCost, + 'rate': feeRate, + 'currency': feeCurrencyCode, + }; + } + } + return { + 'info': trade, + 'id': id, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'order': orderId, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseSpotBalance (response) { + // + // { + // "code":0, + // "msg":"", + // "data":[ + // { + // "currency":"USDT", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // }, + // { + // "currency":"ETH", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // } + // ] + // } + // + const result = { 'info': response }; + const data = this.safeValue (response, 'data', []); + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const currency = this.safeValue (this.currencies, code, {}); + const scale = this.safeInteger (currency, 'valueScale', 8); + const account = this.account (); + const balanceEv = this.safeFloat (balance, 'balanceEv'); + const lockedTradingBalanceEv = this.safeFloat (balance, 'lockedTradingBalanceEv'); + const lockedWithdrawEv = this.safeFloat (balance, 'lockedWithdrawEv'); + const total = this.fromEn (balanceEv, scale, scale, DECIMAL_PLACES); + const lockedTradingBalance = this.fromEn (lockedTradingBalanceEv, scale, scale, DECIMAL_PLACES); + const lockedWithdraw = this.fromEn (lockedWithdrawEv, scale, scale, DECIMAL_PLACES); + const used = this.sum (lockedTradingBalance, lockedWithdraw); + account['total'] = total; + account['used'] = used; + result[code] = account; + } + return this.parseBalance (result); + } + + parseSwapBalance (response) { + // + // { + // "code":0, + // "msg":"", + // "data":{ + // "account":{ + // "accountId":6192120001, + // "currency":"BTC", + // "accountBalanceEv":1254744, + // "totalUsedBalanceEv":0, + // "bonusBalanceEv":1254744 + // }, + // "positions":[ + // { + // "accountID":6192120001, + // "symbol":"BTCUSD", + // "currency":"BTC", + // "side":"None", + // "positionStatus":"Normal", + // "crossMargin":false, + // "leverageEr":0, + // "leverage":0E-8, + // "initMarginReqEr":1000000, + // "initMarginReq":0.01000000, + // "maintMarginReqEr":500000, + // "maintMarginReq":0.00500000, + // "riskLimitEv":10000000000, + // "riskLimit":100.00000000, + // "size":0, + // "value":0E-8, + // "valueEv":0, + // "avgEntryPriceEp":0, + // "avgEntryPrice":0E-8, + // "posCostEv":0, + // "posCost":0E-8, + // "assignedPosBalanceEv":0, + // "assignedPosBalance":0E-8, + // "bankruptCommEv":0, + // "bankruptComm":0E-8, + // "bankruptPriceEp":0, + // "bankruptPrice":0E-8, + // "positionMarginEv":0, + // "positionMargin":0E-8, + // "liquidationPriceEp":0, + // "liquidationPrice":0E-8, + // "deleveragePercentileEr":0, + // "deleveragePercentile":0E-8, + // "buyValueToCostEr":1150750, + // "buyValueToCost":0.01150750, + // "sellValueToCostEr":1149250, + // "sellValueToCost":0.01149250, + // "markPriceEp":96359083, + // "markPrice":9635.90830000, + // "markValueEv":0, + // "markValue":null, + // "unRealisedPosLossEv":0, + // "unRealisedPosLoss":null, + // "estimatedOrdLossEv":0, + // "estimatedOrdLoss":0E-8, + // "usedBalanceEv":0, + // "usedBalance":0E-8, + // "takeProfitEp":0, + // "takeProfit":null, + // "stopLossEp":0, + // "stopLoss":null, + // "realisedPnlEv":0, + // "realisedPnl":null, + // "cumRealisedPnlEv":0, + // "cumRealisedPnl":null + // } + // ] + // } + // } + // + const result = { 'info': response }; + const data = this.safeValue (response, 'data', {}); + const balance = this.safeValue (data, 'account', {}); + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const currency = this.currency (code); + const account = this.account (); + const accountBalanceEv = this.safeFloat (balance, 'accountBalanceEv'); + const totalUsedBalanceEv = this.safeFloat (balance, 'totalUsedBalanceEv'); + const valueScale = this.safeInteger (currency, 'valueScale', 8); + account['total'] = this.fromEn (accountBalanceEv, valueScale, valueScale, DECIMAL_PLACES); + account['used'] = this.fromEn (totalUsedBalanceEv, valueScale, valueScale, DECIMAL_PLACES); + result[code] = account; + return this.parseBalance (result); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const defaultType = this.safeString2 (this.options, 'defaultType', 'fetchBalance', 'spot'); + const type = this.safeString (params, 'type', defaultType); + let method = 'privateGetSpotWallets'; + const request = {}; + if (type === 'swap') { + const code = this.safeString (params, 'code'); + if (code !== undefined) { + const currency = this.currency (code); + request['currency'] = currency['id']; + params = this.omit (params, 'code'); + } else { + const currency = this.safeString (params, 'currency'); + if (currency === undefined) { + throw new ArgumentsRequired (this.id + ' fetchBalance requires a code parameter or a currency parameter for ' + type + ' type'); + } + } + method = 'privateGetAccountsAccountPositions'; + } + params = this.omit (params, 'type'); + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code":0, + // "msg":"", + // "data":[ + // { + // "currency":"USDT", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // }, + // { + // "currency":"ETH", + // "balanceEv":0, + // "lockedTradingBalanceEv":0, + // "lockedWithdrawEv":0, + // "lastUpdateTimeNs":1592065834511322514, + // "walletVid":0 + // } + // ] + // } + // + // swap + // + // { + // "code":0, + // "msg":"", + // "data":{ + // "account":{ + // "accountId":6192120001, + // "currency":"BTC", + // "accountBalanceEv":1254744, + // "totalUsedBalanceEv":0, + // "bonusBalanceEv":1254744 + // }, + // "positions":[ + // { + // "accountID":6192120001, + // "symbol":"BTCUSD", + // "currency":"BTC", + // "side":"None", + // "positionStatus":"Normal", + // "crossMargin":false, + // "leverageEr":0, + // "leverage":0E-8, + // "initMarginReqEr":1000000, + // "initMarginReq":0.01000000, + // "maintMarginReqEr":500000, + // "maintMarginReq":0.00500000, + // "riskLimitEv":10000000000, + // "riskLimit":100.00000000, + // "size":0, + // "value":0E-8, + // "valueEv":0, + // "avgEntryPriceEp":0, + // "avgEntryPrice":0E-8, + // "posCostEv":0, + // "posCost":0E-8, + // "assignedPosBalanceEv":0, + // "assignedPosBalance":0E-8, + // "bankruptCommEv":0, + // "bankruptComm":0E-8, + // "bankruptPriceEp":0, + // "bankruptPrice":0E-8, + // "positionMarginEv":0, + // "positionMargin":0E-8, + // "liquidationPriceEp":0, + // "liquidationPrice":0E-8, + // "deleveragePercentileEr":0, + // "deleveragePercentile":0E-8, + // "buyValueToCostEr":1150750, + // "buyValueToCost":0.01150750, + // "sellValueToCostEr":1149250, + // "sellValueToCost":0.01149250, + // "markPriceEp":96359083, + // "markPrice":9635.90830000, + // "markValueEv":0, + // "markValue":null, + // "unRealisedPosLossEv":0, + // "unRealisedPosLoss":null, + // "estimatedOrdLossEv":0, + // "estimatedOrdLoss":0E-8, + // "usedBalanceEv":0, + // "usedBalance":0E-8, + // "takeProfitEp":0, + // "takeProfit":null, + // "stopLossEp":0, + // "stopLoss":null, + // "realisedPnlEv":0, + // "realisedPnl":null, + // "cumRealisedPnlEv":0, + // "cumRealisedPnl":null + // } + // ] + // } + // } + // + const result = (type === 'swap') ? this.parseSwapBalance (response) : this.parseSpotBalance (response); + return result; + } + + parseOrderStatus (status) { + const statuses = { + 'Created': 'open', + 'Untriggered': 'open', + 'Deactivated': 'closed', + 'Triggered': 'open', + 'Rejected': 'rejected', + 'New': 'open', + 'PartiallyFilled': 'open', + 'Filled': 'closed', + 'Canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrderType (type) { + const types = { + 'Limit': 'limit', + 'Market': 'market', + }; + return this.safeString (types, type, type); + } + + parseTimeInForce (timeInForce) { + const timeInForces = { + 'GoodTillCancel': 'GTC', + 'PostOnly': 'PO', + 'ImmediateOrCancel': 'IOC', + 'FillOrKill': 'FOK', + }; + return this.safeString (timeInForces, timeInForce, timeInForce); + } + + parseSpotOrder (order, market = undefined) { + // + // spot + // + // { + // "orderID": "d1d09454-cabc-4a23-89a7-59d43363f16d", + // "clOrdID": "309bcd5c-9f6e-4a68-b775-4494542eb5cb", + // "priceEp": 0, + // "action": "New", + // "trigger": "UNSPECIFIED", + // "pegPriceType": "UNSPECIFIED", + // "stopDirection": "UNSPECIFIED", + // "bizError": 0, + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "baseQtyEv": 0, + // "ordType": "Limit", + // "timeInForce": "GoodTillCancel", + // "ordStatus": "Created", + // "cumFeeEv": 0, + // "cumBaseQtyEv": 0, + // "cumQuoteQtyEv": 0, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "avgPriceEp": 0, + // "cumBaseAmountEv": 0, + // "cumQuoteAmountEv": 0, + // "quoteQtyEv": 0, + // "qtyType": "ByBase", + // "stopPxEp": 0, + // "pegOffsetValueEp": 0 + // } + // + // { + // "orderID":"99232c3e-3d6a-455f-98cc-2061cdfe91bc", + // "stopPxEp":0, + // "avgPriceEp":0, + // "qtyType":"ByBase", + // "leavesBaseQtyEv":0, + // "leavesQuoteQtyEv":0, + // "baseQtyEv":"1000000000", + // "feeCurrency":"4", + // "stopDirection":"UNSPECIFIED", + // "symbol":"sETHUSDT", + // "side":"Buy", + // "quoteQtyEv":250000000000, + // "priceEp":25000000000, + // "ordType":"Limit", + // "timeInForce":"GoodTillCancel", + // "ordStatus":"Rejected", + // "execStatus":"NewRejected", + // "createTimeNs":1592675305266037130, + // "cumFeeEv":0, + // "cumBaseValueEv":0, + // "cumQuoteValueEv":0 + // } + // + const id = this.safeString (order, 'orderID'); + let clientOrderId = this.safeString (order, 'clOrdID'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + let price = this.fromEp (this.safeFloat (order, 'priceEp'), market); + if (price === 0) { + price = undefined; + } + const amount = this.fromEv (this.safeFloat (order, 'baseQtyEv'), market); + const remaining = this.fromEv (this.safeFloat (order, 'leavesBaseQtyEv'), market); + let filled = this.fromEv (this.safeFloat (order, 'cumBaseQtyEv'), market); + const cost = this.fromEv (this.safeFloat (order, 'quoteQtyEv'), market); + const average = this.fromEp (this.safeFloat (order, 'avgPriceEp'), market); + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const side = this.safeStringLower (order, 'side'); + const type = this.parseOrderType (this.safeString (order, 'ordType')); + const timestamp = this.safeIntegerProduct2 (order, 'actionTimeNs', 'createTimeNs', 0.000001); + let fee = undefined; + const feeCost = this.fromEv (this.safeFloat (order, 'cumFeeEv'), market); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': undefined, + }; + } + if (filled === undefined) { + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.min (0, amount - remaining); + } + } + const timeInForce = this.parseTimeInForce (this.safeStirng (order, 'timeInForce')); + const stopPrice = this.fromEp (this.safeFloat (order, 'stopPxEp', market)); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + parseSwapOrder (order, market = undefined) { + // + // { + // "bizError":0, + // "orderID":"7a1ad384-44a3-4e54-a102-de4195a29e32", + // "clOrdID":"", + // "symbol":"ETHUSD", + // "side":"Buy", + // "actionTimeNs":1592668973945065381, + // "transactTimeNs":0, + // "orderType":"Market", + // "priceEp":2267500, + // "price":226.75000000, + // "orderQty":1, + // "displayQty":0, + // "timeInForce":"ImmediateOrCancel", + // "reduceOnly":false, + // "closedPnlEv":0, + // "closedPnl":0E-8, + // "closedSize":0, + // "cumQty":0, + // "cumValueEv":0, + // "cumValue":0E-8, + // "leavesQty":1, + // "leavesValueEv":11337, + // "leavesValue":1.13370000, + // "stopDirection":"UNSPECIFIED", + // "stopPxEp":0, + // "stopPx":0E-8, + // "trigger":"UNSPECIFIED", + // "pegOffsetValueEp":0, + // "execStatus":"PendingNew", + // "pegPriceType":"UNSPECIFIED", + // "ordStatus":"Created" + // } + // + const id = this.safeString (order, 'orderID'); + let clientOrderId = this.safeString (order, 'clOrdID'); + if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { + clientOrderId = undefined; + } + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const side = this.safeStringLower (order, 'side'); + const type = this.parseOrderType (this.safeString (order, 'orderType')); + const price = this.fromEp (this.safeFloat (order, 'priceEp'), market); + const amount = this.safeFloat (order, 'orderQty'); + const filled = this.safeFloat (order, 'cumQty'); + const remaining = this.safeFloat (order, 'leavesQty'); + const timestamp = this.safeIntegerProduct (order, 'actionTimeNs', 0.000001); + const cost = this.safeFloat (order, 'cumValue'); + let lastTradeTimestamp = this.safeIntegerProduct (order, 'transactTimeNs', 0.000001); + if (lastTradeTimestamp === 0) { + lastTradeTimestamp = undefined; + } + const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce')); + const stopPrice = this.safeFloat (order, 'stopPx'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'datetime': this.iso8601 (timestamp), + 'timestamp': timestamp, + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'average': undefined, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrder (order, market = undefined) { + if ('closedPnl' in order) { + return this.parseSwapOrder (order, market); + } + return this.parseSpotOrder (order, market); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + side = this.capitalize (side); + type = this.capitalize (type); + const request = { + // common + 'symbol': market['id'], + 'side': side, // Sell, Buy + 'ordType': type, // Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched or Pegged for swap orders + // 'stopPxEp': this.toEp (stopPx, market), // for conditional orders + // 'priceEp': this.toEp (price, market), // required for limit orders + // 'timeInForce': 'GoodTillCancel', // GoodTillCancel, PostOnly, ImmediateOrCancel, FillOrKill + // ---------------------------------------------------------------- + // spot + // 'qtyType': 'ByBase', // ByBase, ByQuote + // 'quoteQtyEv': this.toEp (cost, market), + // 'baseQtyEv': this.toEv (amount, market), + // 'trigger': 'ByLastPrice', // required for conditional orders + // ---------------------------------------------------------------- + // swap + // 'clOrdID': this.uuid (), // max length 40 + // 'orderQty': this.amountToPrecision (amount, symbol), + // 'reduceOnly': false, + // 'closeOnTrigger': false, // implicit reduceOnly and cancel other orders in the same direction + // 'takeProfitEp': this.toEp (takeProfit, market), + // 'stopLossEp': this.toEp (stopLossEp, market), + // 'triggerType': 'ByMarkPrice', // ByMarkPrice, ByLastPrice + // 'pegOffsetValueEp': integer, // Trailing offset from current price. Negative value when position is long, positive when position is short + // 'pegPriceType': 'TrailingStopPeg', // TrailingTakeProfitPeg + // 'text': 'comment', + }; + if (market['spot']) { + let qtyType = this.safeValue (params, 'qtyType', 'ByBase'); + if (price !== undefined) { + qtyType = 'ByQuote'; + } + request['qtyType'] = qtyType; + if (qtyType === 'ByQuote') { + let cost = this.safeFloat (params, 'cost'); + params = this.omit (params, 'cost'); + if (this.options['createOrderByQuoteRequiresPrice']) { + if (price !== undefined) { + cost = amount * price; + } else if (cost === undefined) { + throw new ArgumentsRequired (this.id + ' createOrder ' + qtyType + ' requires a price argument or a cost parameter'); + } + } + cost = (cost === undefined) ? amount : cost; + request['quoteQtyEv'] = this.toEp (cost, market); + } else { + request['baseQtyEv'] = this.toEv (amount, market); + } + } else if (market['swap']) { + request['orderQty'] = parseInt (amount); + } + if (type === 'Limit') { + request['priceEp'] = this.toEp (price, market); + } + const stopPrice = this.safeFloat2 (params, 'stopPx', 'stopPrice'); + if (stopPrice !== undefined) { + request['stopPxEp'] = this.toEp (stopPrice, market); + } + params = this.omit (params, [ 'stopPx', 'stopPrice' ]); + const method = market['spot'] ? 'privatePostSpotOrders' : 'privatePostOrders'; + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code": 0, + // "msg": "", + // "data": { + // "orderID": "d1d09454-cabc-4a23-89a7-59d43363f16d", + // "clOrdID": "309bcd5c-9f6e-4a68-b775-4494542eb5cb", + // "priceEp": 0, + // "action": "New", + // "trigger": "UNSPECIFIED", + // "pegPriceType": "UNSPECIFIED", + // "stopDirection": "UNSPECIFIED", + // "bizError": 0, + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "baseQtyEv": 0, + // "ordType": "Limit", + // "timeInForce": "GoodTillCancel", + // "ordStatus": "Created", + // "cumFeeEv": 0, + // "cumBaseQtyEv": 0, + // "cumQuoteQtyEv": 0, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "avgPriceEp": 0, + // "cumBaseAmountEv": 0, + // "cumQuoteAmountEv": 0, + // "quoteQtyEv": 0, + // "qtyType": "ByBase", + // "stopPxEp": 0, + // "pegOffsetValueEp": 0 + // } + // } + // + // swap + // + // { + // "code":0, + // "msg":"", + // "data":{ + // "bizError":0, + // "orderID":"7a1ad384-44a3-4e54-a102-de4195a29e32", + // "clOrdID":"", + // "symbol":"ETHUSD", + // "side":"Buy", + // "actionTimeNs":1592668973945065381, + // "transactTimeNs":0, + // "orderType":"Market", + // "priceEp":2267500, + // "price":226.75000000, + // "orderQty":1, + // "displayQty":0, + // "timeInForce":"ImmediateOrCancel", + // "reduceOnly":false, + // "closedPnlEv":0, + // "closedPnl":0E-8, + // "closedSize":0, + // "cumQty":0, + // "cumValueEv":0, + // "cumValue":0E-8, + // "leavesQty":1, + // "leavesValueEv":11337, + // "leavesValue":1.13370000, + // "stopDirection":"UNSPECIFIED", + // "stopPxEp":0, + // "stopPx":0E-8, + // "trigger":"UNSPECIFIED", + // "pegOffsetValueEp":0, + // "execStatus":"PendingNew", + // "pegPriceType":"UNSPECIFIED", + // "ordStatus":"Created" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdID'); + params = this.omit (params, [ 'clientOrderId', 'clOrdID' ]); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + } else { + request['orderID'] = id; + } + const method = market['spot'] ? 'privateDeleteSpotOrders' : 'privateDeleteOrdersCancel'; + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'symbol': market['id'], + // 'untriggerred': false, // false to cancel non-conditional orders, true to cancel conditional orders + // 'text': 'up to 40 characters max', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + if (!market['swap']) { + throw new NotSupported (this.id + ' cancelAllOrders() supports swap market type orders only'); + } + request['symbol'] = market['id']; + } + return await this.privateDeleteOrdersAll (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetSpotOrdersActive' : 'privateGetExchangeOrder'; + const request = { + 'symbol': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdID'); + params = this.omit (params, [ 'clientOrderId', 'clOrdID' ]); + if (clientOrderId !== undefined) { + request['clOrdID'] = clientOrderId; + } else { + request['orderID'] = id; + } + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + let order = data; + if (Array.isArray (data)) { + const numOrders = data.length; + if (numOrders < 1) { + if (clientOrderId !== undefined) { + throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with clientOrderId ' + clientOrderId + ' not found'); + } else { + throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with id ' + id + ' not found'); + } + } + order = this.safeValue (data, 0, {}); + } + return this.parseOrder (order, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetExchangeOrderList'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + const rows = this.safeValue (data, 'rows', []); + return this.parseOrders (rows, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetOrdersActiveList'; + const request = { + 'symbol': market['id'], + }; + try { + const response = await this[method] (this.extend (request, params)); + const data = this.safeValue (response, 'data', {}); + if (Array.isArray (data)) { + return this.parseOrders (data, market, since, limit); + } else { + const rows = this.safeValue (data, 'rows', []); + return this.parseOrders (rows, market, since, limit); + } + } catch (e) { + return []; + } + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetExchangeSpotOrder' : 'privateGetExchangeOrderList'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "total":8, + // "rows":[ + // { + // "orderID":"99232c3e-3d6a-455f-98cc-2061cdfe91bc", + // "stopPxEp":0, + // "avgPriceEp":0, + // "qtyType":"ByBase", + // "leavesBaseQtyEv":0, + // "leavesQuoteQtyEv":0, + // "baseQtyEv":"1000000000", + // "feeCurrency":"4", + // "stopDirection":"UNSPECIFIED", + // "symbol":"sETHUSDT", + // "side":"Buy", + // "quoteQtyEv":250000000000, + // "priceEp":25000000000, + // "ordType":"Limit", + // "timeInForce":"GoodTillCancel", + // "ordStatus":"Rejected", + // "execStatus":"NewRejected", + // "createTimeNs":1592675305266037130, + // "cumFeeEv":0, + // "cumBaseValueEv":0, + // "cumQuoteValueEv":0 + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + if (Array.isArray (data)) { + return this.parseOrders (data, market, since, limit); + } else { + const rows = this.safeValue (data, 'rows', []); + return this.parseOrders (rows, market, since, limit); + } + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchClosedOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const method = market['spot'] ? 'privateGetExchangeSpotOrderTrades' : 'privateGetExchangeOrderTrade'; + const request = { + 'symbol': market['id'], + }; + if (since !== undefined) { + request['start'] = since; + } + if (market['swap'] && (limit !== undefined)) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // spot + // + // { + // "code": 0, + // "msg": "OK", + // "data": { + // "total": 1, + // "rows": [ + // { + // "qtyType": "ByQuote", + // "transactTimeNs": 1589450974800550100, + // "clOrdID": "8ba59d40-df25-d4b0-14cf-0703f44e9690", + // "orderID": "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83", + // "symbol": "sBTCUSDT", + // "side": "Buy", + // "priceEP": 970056000000, + // "baseQtyEv": 0, + // "quoteQtyEv": 1000000000, + // "action": "New", + // "execStatus": "MakerFill", + // "ordStatus": "Filled", + // "ordType": "Limit", + // "execInst": "None", + // "timeInForce": "GoodTillCancel", + // "stopDirection": "UNSPECIFIED", + // "tradeType": "Trade", + // "stopPxEp": 0, + // "execId": "c6bd8979-07ba-5946-b07e-f8b65135dbb1", + // "execPriceEp": 970056000000, + // "execBaseQtyEv": 103000, + // "execQuoteQtyEv": 999157680, + // "leavesBaseQtyEv": 0, + // "leavesQuoteQtyEv": 0, + // "execFeeEv": 0, + // "feeRateEr": 0 + // } + // ] + // } + // } + // + // + // swap + // + // { + // "code": 0, + // "msg": "OK", + // "data": { + // "total": 79, + // "rows": [ + // { + // "transactTimeNs": 1606054879331565300, + // "symbol": "BTCUSD", + // "currency": "BTC", + // "action": "New", + // "side": "Buy", + // "tradeType": "Trade", + // "execQty": 5, + // "execPriceEp": 182990000, + // "orderQty": 5, + // "priceEp": 183870000, + // "execValueEv": 27323, + // "feeRateEr": 75000, + // "execFeeEv": 21, + // "ordType": "Market", + // "execID": "5eee56a4-04a9-5677-8eb0-c2fe22ae3645", + // "orderID": "ee0acb82-f712-4543-a11d-d23efca73197", + // "clOrdID": "", + // "execStatus": "TakerFill" + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const rows = this.safeValue (data, 'rows', []); + return this.parseTrades (rows, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetPhemexUserWalletsV2DepositAddress (this.extend (request, params)); + // { + // "code":0, + // "msg":"OK", + // "data":{ + // "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad", + // "tag":null + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const address = this.safeString (data, 'address'); + const tag = this.safeString (data, 'tag'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetExchangeWalletsDepositList (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":[ + // { + // "id":29200, + // "currency":"USDT", + // "currencyCode":3, + // "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d", + // "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad", + // "amountEv":3000000000, + // "confirmations":13, + // "type":"Deposit", + // "status":"Success", + // "createdAt":1592722565000 + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransactions (data, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetExchangeWalletsWithdrawList (params); + // + // { + // "code":0, + // "msg":"OK", + // "data":[ + // { + // "address": "1Lxxxxxxxxxxx" + // "amountEv": 200000 + // "currency": "BTC" + // "currencyCode": 1 + // "expiredTime": 0 + // "feeEv": 50000 + // "rejectReason": null + // "status": "Succeed" + // "txHash": "44exxxxxxxxxxxxxxxxxxxxxx" + // "withdrawStatus: "" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransactions (data, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'Success': 'ok', + 'Succeed': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw + // + // ... + // + // fetchDeposits + // + // { + // "id":29200, + // "currency":"USDT", + // "currencyCode":3, + // "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d", + // "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad", + // "amountEv":3000000000, + // "confirmations":13, + // "type":"Deposit", + // "status":"Success", + // "createdAt":1592722565000 + // } + // + // fetchWithdrawals + // + // { + // "address": "1Lxxxxxxxxxxx" + // "amountEv": 200000 + // "currency": "BTC" + // "currencyCode": 1 + // "expiredTime": 0 + // "feeEv": 50000 + // "rejectReason": null + // "status": "Succeed" + // "txHash": "44exxxxxxxxxxxxxxxxxxxxxx" + // "withdrawStatus: "" + // } + // + const id = this.safeString (transaction, 'id'); + const address = this.safeString (transaction, 'address'); + const tag = undefined; + const txid = this.safeString (transaction, 'txHash'); + const currencyId = this.safeString (transaction, 'currency'); + currency = this.safeCurrency (currencyId, currency); + const code = currency['code']; + const timestamp = this.safeInteger (transaction, 'createdAt'); + let type = this.safeStringLower (transaction, 'type'); + const feeCost = this.fromEn (this.safeFloat (transaction, 'feeEv'), currency['valueScale'], currency['precision']); + let fee = undefined; + if (feeCost !== undefined) { + type = 'withdrawal'; + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const amount = this.fromEn (this.safeFloat (transaction, 'amountEv'), currency['valueScale'], currency['precision']); + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + async fetchPositions (symbols = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const code = this.safeString (params, 'code'); + const request = {}; + if (code === undefined) { + const currencyId = this.safeString (params, 'currency'); + if (currencyId === undefined) { + throw new ArgumentsRequired (this.id + ' fetchPositions() requires a currency parameter or a code parameter'); + } + } else { + const currency = this.currency (code); + params = this.omit (params, 'code'); + request['currency'] = currency['id']; + } + const response = await this.privateGetAccountsAccountPositions (this.extend (request, params)); + // + // { + // "code":0,"msg":"", + // "data":{ + // "account":{ + // "accountId":6192120001, + // "currency":"BTC", + // "accountBalanceEv":1254744, + // "totalUsedBalanceEv":0, + // "bonusBalanceEv":1254744 + // }, + // "positions":[ + // { + // "accountID":6192120001, + // "symbol":"BTCUSD", + // "currency":"BTC", + // "side":"None", + // "positionStatus":"Normal", + // "crossMargin":false, + // "leverageEr":100000000, + // "leverage":1.00000000, + // "initMarginReqEr":100000000, + // "initMarginReq":1.00000000, + // "maintMarginReqEr":500000, + // "maintMarginReq":0.00500000, + // "riskLimitEv":10000000000, + // "riskLimit":100.00000000, + // "size":0, + // "value":0E-8, + // "valueEv":0, + // "avgEntryPriceEp":0, + // "avgEntryPrice":0E-8, + // "posCostEv":0, + // "posCost":0E-8, + // "assignedPosBalanceEv":0, + // "assignedPosBalance":0E-8, + // "bankruptCommEv":0, + // "bankruptComm":0E-8, + // "bankruptPriceEp":0, + // "bankruptPrice":0E-8, + // "positionMarginEv":0, + // "positionMargin":0E-8, + // "liquidationPriceEp":0, + // "liquidationPrice":0E-8, + // "deleveragePercentileEr":0, + // "deleveragePercentile":0E-8, + // "buyValueToCostEr":100225000, + // "buyValueToCost":1.00225000, + // "sellValueToCostEr":100075000, + // "sellValueToCost":1.00075000, + // "markPriceEp":135736070, + // "markPrice":13573.60700000, + // "markValueEv":0, + // "markValue":null, + // "unRealisedPosLossEv":0, + // "unRealisedPosLoss":null, + // "estimatedOrdLossEv":0, + // "estimatedOrdLoss":0E-8, + // "usedBalanceEv":0, + // "usedBalance":0E-8, + // "takeProfitEp":0, + // "takeProfit":null, + // "stopLossEp":0, + // "stopLoss":null, + // "cumClosedPnlEv":0, + // "cumFundingFeeEv":0, + // "cumTransactFeeEv":0, + // "realisedPnlEv":0, + // "realisedPnl":null, + // "cumRealisedPnlEv":0, + // "cumRealisedPnl":null + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const positions = this.safeValue (data, 'positions', []); + // todo unify parsePosition/parsePositions + return positions; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + const requestPath = '/' + this.implodeParams (path, params); + let url = requestPath; + let queryString = ''; + if ((method === 'GET') || (method === 'DELETE') || (method === 'PUT')) { + if (Object.keys (query).length) { + queryString = this.urlencodeWithArrayRepeat (query); + url += '?' + queryString; + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.seconds (); + const xPhemexRequestExpiry = this.safeInteger (this.options, 'x-phemex-request-expiry', 60); + const expiry = this.sum (timestamp, xPhemexRequestExpiry); + const expiryString = expiry.toString (); + headers = { + 'x-phemex-access-token': this.apiKey, + 'x-phemex-request-expiry': expiryString, + }; + let payload = ''; + if (method === 'POST') { + payload = this.json (params); + body = payload; + headers['Content-Type'] = 'application/json'; + } + const auth = requestPath + queryString + expiryString + payload; + headers['x-phemex-request-signature'] = this.hmac (this.encode (auth), this.encode (this.secret)); + } + url = this.urls['api'][api] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"code":30018,"msg":"phemex.data.size.uplimt","data":null} + // {"code":412,"msg":"Missing parameter - resolution","data":null} + // {"code":412,"msg":"Missing parameter - to","data":null} + // {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null} + // + const error = this.safeValue (response, 'error', response); + const errorCode = this.safeString (error, 'code'); + const message = this.safeString (error, 'msg'); + if ((errorCode !== undefined) && (errorCode !== '0')) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/poloniex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/poloniex.js new file mode 100644 index 000000000..f0b874031 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/poloniex.js @@ -0,0 +1,1485 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, RequestTimeout, AuthenticationError, PermissionDenied, DDoSProtection, InsufficientFunds, OrderNotFound, InvalidOrder, AccountSuspended, CancelPending, InvalidNonce, OnMaintenance, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class poloniex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'poloniex', + 'name': 'Poloniex', + 'countries': [ 'US' ], + 'rateLimit': 1000, // up to 6 calls per second + 'certified': false, + 'pro': true, + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrder': true, // true endpoint for a single open order + 'fetchOpenOrders': true, // true endpoint for open orders + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrderTrades': true, // true endpoint for trades of a single open or closed order + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, + 'fetchTradingFees': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'cancelAllOrders': true, + 'withdraw': true, + }, + 'timeframes': { + '5m': 300, + '15m': 900, + '30m': 1800, + '2h': 7200, + '4h': 14400, + '1d': 86400, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766817-e9456312-5ee6-11e7-9b3c-b628ca5626a5.jpg', + 'api': { + 'public': 'https://poloniex.com/public', + 'private': 'https://poloniex.com/tradingApi', + }, + 'www': 'https://www.poloniex.com', + 'doc': 'https://docs.poloniex.com', + 'fees': 'https://poloniex.com/fees', + 'referral': 'https://poloniex.com/signup?c=UBFZJRPJ', + }, + 'api': { + 'public': { + 'get': [ + 'return24hVolume', + 'returnChartData', + 'returnCurrencies', + 'returnLoanOrders', + 'returnOrderBook', + 'returnTicker', + 'returnTradeHistory', + ], + }, + 'private': { + 'post': [ + 'buy', + 'cancelLoanOffer', + 'cancelOrder', + 'cancelAllOrders', + 'closeMarginPosition', + 'createLoanOffer', + 'generateNewAddress', + 'getMarginPosition', + 'marginBuy', + 'marginSell', + 'moveOrder', + 'returnActiveLoans', + 'returnAvailableAccountBalances', + 'returnBalances', + 'returnCompleteBalances', + 'returnDepositAddresses', + 'returnDepositsWithdrawals', + 'returnFeeInfo', + 'returnLendingHistory', + 'returnMarginAccountSummary', + 'returnOpenLoanOffers', + 'returnOpenOrders', + 'returnOrderTrades', + 'returnOrderStatus', + 'returnTradableBalances', + 'returnTradeHistory', + 'sell', + 'toggleAutoRenew', + 'transferBalance', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + // starting from Jan 8 2020 + 'maker': 0.0009, + 'taker': 0.0009, + }, + 'funding': {}, + }, + 'limits': { + 'amount': { + 'min': 0.000001, + 'max': 1000000000, + }, + 'price': { + 'min': 0.00000001, + 'max': 1000000000, + }, + 'cost': { + 'min': 0.00000000, + 'max': 1000000000, + }, + }, + 'precision': { + 'amount': 8, + 'price': 8, + }, + 'commonCurrencies': { + 'AIR': 'AirCoin', + 'APH': 'AphroditeCoin', + 'BCC': 'BTCtalkcoin', + 'BDG': 'Badgercoin', + 'BTM': 'Bitmark', + 'CON': 'Coino', + 'GOLD': 'GoldEagles', + 'GPUC': 'GPU', + 'HOT': 'Hotcoin', + 'ITC': 'Information Coin', + 'KEY': 'KEYCoin', + 'PLX': 'ParallaxCoin', + 'REPV2': 'REP', + 'STR': 'XLM', + 'SOC': 'SOCC', + 'XAP': 'API Coin', + // this is not documented in the API docs for Poloniex + // https://github.com/ccxt/ccxt/issues/7084 + // when the user calls withdraw ('USDT', amount, address, tag, params) + // with params = { 'currencyToWithdrawAs': 'USDTTRON' } + // or params = { 'currencyToWithdrawAs': 'USDTETH' } + // fetchWithdrawals ('USDT') returns the corresponding withdrawals + // with a USDTTRON or a USDTETH currency id, respectfully + // therefore we have map them back to the original code USDT + // otherwise the returned withdrawals are filtered out + 'USDTTRON': 'USDT', + 'USDTETH': 'USDT', + }, + 'options': { + 'limits': { + 'cost': { + 'min': { + 'BTC': 0.0001, + 'ETH': 0.0001, + 'XMR': 0.0001, + 'USDT': 1.0, + }, + }, + }, + }, + 'exceptions': { + 'exact': { + 'You may only place orders that reduce your position.': InvalidOrder, + 'Invalid order number, or you are not the person who placed the order.': OrderNotFound, + 'Permission denied': PermissionDenied, + 'Connection timed out. Please try again.': RequestTimeout, + 'Internal error. Please try again.': ExchangeNotAvailable, + 'Currently in maintenance mode.': OnMaintenance, + 'Order not found, or you are not the person who placed it.': OrderNotFound, + 'Invalid API key/secret pair.': AuthenticationError, + 'Please do not make more than 8 API calls per second.': DDoSProtection, + 'Rate must be greater than zero.': InvalidOrder, // {"error":"Rate must be greater than zero."} + 'Invalid currency pair.': BadSymbol, // {"error":"Invalid currency pair."} + 'Invalid currencyPair parameter.': BadSymbol, // {"error":"Invalid currencyPair parameter."} + }, + 'broad': { + 'Total must be at least': InvalidOrder, // {"error":"Total must be at least 0.0001."} + 'This account is frozen.': AccountSuspended, + 'Not enough': InsufficientFunds, + 'Nonce must be greater': InvalidNonce, + 'You have already called cancelOrder or moveOrder on this order.': CancelPending, + 'Amount must be at least': InvalidOrder, // {"error":"Amount must be at least 0.000001."} + 'is either completed or does not exist': InvalidOrder, // {"error":"Order 587957810791 is either completed or does not exist."} + 'Error pulling ': ExchangeError, // {"error":"Error pulling order book"} + }, + }, + 'orders': {}, // orders cache / emulation + }); + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': parseFloat (this.feeToPrecision (symbol, cost)), + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "date":1590913773, + // "high":0.02491611, + // "low":0.02491611, + // "open":0.02491611, + // "close":0.02491611, + // "volume":0, + // "quoteVolume":0, + // "weightedAverage":0.02491611 + // } + // + return [ + this.safeTimestamp (ohlcv, 'date'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'quoteVolume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + 'period': this.timeframes[timeframe], + }; + if (since === undefined) { + request['end'] = this.seconds (); + if (limit === undefined) { + request['start'] = request['end'] - this.parseTimeframe ('1w'); // max range = 1 week + } else { + request['start'] = request['end'] - limit * this.parseTimeframe (timeframe); + } + } else { + request['start'] = parseInt (since / 1000); + if (limit !== undefined) { + const end = this.sum (request['start'], limit * this.parseTimeframe (timeframe)); + request['end'] = end; + } + } + const response = await this.publicGetReturnChartData (this.extend (request, params)); + // + // [ + // {"date":1590913773,"high":0.02491611,"low":0.02491611,"open":0.02491611,"close":0.02491611,"volume":0,"quoteVolume":0,"weightedAverage":0.02491611}, + // {"date":1590913800,"high":0.02495324,"low":0.02489501,"open":0.02491797,"close":0.02493693,"volume":0.0927415,"quoteVolume":3.7227869,"weightedAverage":0.02491185}, + // {"date":1590914100,"high":0.02498596,"low":0.02488503,"open":0.02493033,"close":0.02497896,"volume":0.21196348,"quoteVolume":8.50291888,"weightedAverage":0.02492832}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async loadMarkets (reload = false, params = {}) { + const markets = await super.loadMarkets (reload, params); + const currenciesByNumericId = this.safeValue (this.options, 'currenciesByNumericId'); + if ((currenciesByNumericId === undefined) || reload) { + this.options['currenciesByNumericId'] = this.indexBy (this.currencies, 'numericId'); + } + return markets; + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetReturnTicker (params); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ quoteId, baseId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const limits = this.extend (this.limits, { + 'cost': { + 'min': this.safeValue (this.options['limits']['cost']['min'], quote), + }, + }); + const isFrozen = this.safeString (market, 'isFrozen'); + const active = (isFrozen !== '1'); + const numericId = this.safeInteger (market, 'id'); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': active, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const request = { + 'account': 'all', + }; + const response = await this.privatePostReturnCompleteBalances (this.extend (request, params)); + const result = { 'info': response }; + const currencyIds = Object.keys (response); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const balance = this.safeValue (response, currencyId, {}); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'onOrders'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchTradingFees (params = {}) { + await this.loadMarkets (); + const fees = await this.privatePostReturnFeeInfo (params); + // + // { + // makerFee: '0.00100000', + // takerFee: '0.00200000', + // marginMakerFee: '0.00100000', + // marginTakerFee: '0.00200000', + // thirtyDayVolume: '106.08463302', + // nextTier: 500000, + // } + // + return { + 'info': fees, + 'maker': this.safeFloat (fees, 'makerFee'), + 'taker': this.safeFloat (fees, 'takerFee'), + 'withdraw': {}, + 'deposit': {}, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; // 100 + } + const response = await this.publicGetReturnOrderBook (this.extend (request, params)); + const orderbook = this.parseOrderBook (response); + orderbook['nonce'] = this.safeInteger (response, 'seq'); + return orderbook; + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'currencyPair': 'all', + }; + if (limit !== undefined) { + request['depth'] = limit; // 100 + } + const response = await this.publicGetReturnOrderBook (this.extend (request, params)); + const marketIds = Object.keys (response); + const result = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + let symbol = undefined; + if (marketId in this.markets_by_id) { + symbol = this.markets_by_id[marketId]['symbol']; + } else { + const [ quoteId, baseId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const orderbook = this.parseOrderBook (response[marketId]); + orderbook['nonce'] = this.safeInteger (response[marketId], 'seq'); + result[symbol] = orderbook; + } + return result; + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + let open = undefined; + let change = undefined; + let average = undefined; + const last = this.safeFloat (ticker, 'last'); + const relativeChange = this.safeFloat (ticker, 'percentChange'); + if (relativeChange !== -1) { + open = last / this.sum (1, relativeChange); + change = last - open; + average = this.sum (last, open) / 2; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high24hr'), + 'low': this.safeFloat (ticker, 'low24hr'), + 'bid': this.safeFloat (ticker, 'highestBid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'lowestAsk'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': relativeChange * 100, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'quoteVolume'), + 'quoteVolume': this.safeFloat (ticker, 'baseVolume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetReturnTicker (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let symbol = undefined; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } else { + const [ quoteId, baseId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + market = { 'symbol': symbol }; + } + const ticker = response[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetReturnCurrencies (params); + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = response[id]; + const precision = 8; // default precision, todo: fix "magic constants" + const code = this.safeCurrencyCode (id); + const active = (currency['delisted'] === 0) && !currency['disabled']; + const numericId = this.safeInteger (currency, 'id'); + const fee = this.safeFloat (currency, 'txFee'); + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'name': currency['name'], + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': fee, + 'max': Math.pow (10, precision), + }, + }, + }; + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.publicGetReturnTicker (params); + const ticker = response[market['id']]; + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchMyTrades + // + // { + // globalTradeID: 471030550, + // tradeID: '42582', + // date: '2020-06-16 09:47:50', + // rate: '0.000079980000', + // amount: '75215.00000000', + // total: '6.01569570', + // fee: '0.00095000', + // feeDisplay: '0.26636100 TRX (0.07125%)', + // orderNumber: '5963454848', + // type: 'sell', + // category: 'exchange' + // } + // + // createOrder (taker trades) + // + // { + // 'amount': '200.00000000', + // 'date': '2019-12-15 16:04:10', + // 'rate': '0.00000355', + // 'total': '0.00071000', + // 'tradeID': '119871', + // 'type': 'buy', + // 'takerAdjustment': '200.00000000' + // } + // + const id = this.safeString2 (trade, 'globalTradeID', 'tradeID'); + const orderId = this.safeString (trade, 'orderNumber'); + const timestamp = this.parse8601 (this.safeString (trade, 'date')); + let symbol = undefined; + if ((!market) && ('currencyPair' in trade)) { + const marketId = this.safeString (trade, 'currencyPair'); + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ quoteId, baseId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeString (trade, 'type'); + let fee = undefined; + const price = this.safeFloat (trade, 'rate'); + const cost = this.safeFloat (trade, 'total'); + const amount = this.safeFloat (trade, 'amount'); + const feeDisplay = this.safeString (trade, 'feeDisplay'); + if (feeDisplay !== undefined) { + const parts = feeDisplay.split (' '); + const feeCost = this.safeFloat (parts, 0); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (parts, 1); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + let feeRate = this.safeString (parts, 2); + if (feeRate !== undefined) { + feeRate = feeRate.replace ('(', ''); + const feeRateParts = feeRate.split ('%'); + feeRate = this.safeString (feeRateParts, 0); + feeRate = parseFloat (feeRate) / 100; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + 'rate': feeRate, + }; + } + } + let takerOrMaker = undefined; + const takerAdjustment = this.safeFloat (trade, 'takerAdjustment'); + if (takerAdjustment !== undefined) { + takerOrMaker = 'taker'; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + request['end'] = this.seconds (); // last 50000 trades by default + } + const trades = await this.publicGetReturnTradeHistory (this.extend (request, params)); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const pair = market ? market['id'] : 'all'; + const request = { 'currencyPair': pair }; + if (since !== undefined) { + request['start'] = parseInt (since / 1000); + request['end'] = this.sum (this.seconds (), 1); // adding 1 is a fix for #3411 + } + // limit is disabled (does not really work as expected) + if (limit !== undefined) { + request['limit'] = parseInt (limit); + } + const response = await this.privatePostReturnTradeHistory (this.extend (request, params)); + // + // specific market (symbol defined) + // + // [ + // { + // globalTradeID: 470912587, + // tradeID: '42543', + // date: '2020-06-15 17:31:22', + // rate: '0.000083840000', + // amount: '95237.60321429', + // total: '7.98472065', + // fee: '0.00095000', + // feeDisplay: '0.36137761 TRX (0.07125%)', + // orderNumber: '5926344995', + // type: 'sell', + // category: 'exchange' + // }, + // { + // globalTradeID: 470974497, + // tradeID: '42560', + // date: '2020-06-16 00:41:23', + // rate: '0.000078220000', + // amount: '1000000.00000000', + // total: '78.22000000', + // fee: '0.00095000', + // feeDisplay: '3.48189819 TRX (0.07125%)', + // orderNumber: '5945490830', + // type: 'sell', + // category: 'exchange' + // } + // ] + // + // all markets (symbol undefined) + // + // { + // BTC_GNT: [{ + // globalTradeID: 470839947, + // tradeID: '4322347', + // date: '2020-06-15 12:25:24', + // rate: '0.000005810000', + // amount: '1702.04429303', + // total: '0.00988887', + // fee: '0.00095000', + // feeDisplay: '4.18235294 TRX (0.07125%)', + // orderNumber: '102290272520', + // type: 'buy', + // category: 'exchange' + // }, { + // globalTradeID: 470895902, + // tradeID: '4322413', + // date: '2020-06-15 16:19:00', + // rate: '0.000005980000', + // amount: '18.66879219', + // total: '0.00011163', + // fee: '0.00095000', + // feeDisplay: '0.04733727 TRX (0.07125%)', + // orderNumber: '102298304480', + // type: 'buy', + // category: 'exchange' + // }], + // } + // + let result = []; + if (market !== undefined) { + result = this.parseTrades (response, market); + } else { + if (response) { + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let market = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + const trades = this.parseTrades (response[id], market); + for (let j = 0; j < trades.length; j++) { + result.push (trades[j]); + } + } else { + const [ quoteId, baseId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const trades = response[id]; + for (let j = 0; j < trades.length; j++) { + const market = { + 'symbol': symbol, + 'base': base, + 'quote': quote, + }; + result.push (this.parseTrade (trades[j], market)); + } + } + } + } + } + return this.filterBySinceLimit (result, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'Open': 'open', + 'Partially filled': 'open', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOpenOrder + // + // { + // status: 'Open', + // rate: '0.40000000', + // amount: '1.00000000', + // currencyPair: 'BTC_ETH', + // date: '2018-10-17 17:04:50', + // total: '0.40000000', + // type: 'buy', + // startingAmount: '1.00000', + // } + // + // fetchOpenOrders + // + // { + // orderNumber: '514514894224', + // type: 'buy', + // rate: '0.00001000', + // startingAmount: '100.00000000', + // amount: '100.00000000', + // total: '0.00100000', + // date: '2018-10-23 17:38:53', + // margin: 0, + // } + // + // createOrder + // + // { + // 'orderNumber': '9805453960', + // 'resultingTrades': [ + // { + // 'amount': '200.00000000', + // 'date': '2019-12-15 16:04:10', + // 'rate': '0.00000355', + // 'total': '0.00071000', + // 'tradeID': '119871', + // 'type': 'buy', + // 'takerAdjustment': '200.00000000', + // }, + // ], + // 'fee': '0.00000000', + // 'clientOrderId': '12345', + // 'currencyPair': 'BTC_MANA', + // // --------------------------------------------------------- + // // the following fields are injected by createOrder + // 'timestamp': timestamp, + // 'status': 'open', + // 'type': type, + // 'side': side, + // 'price': price, + // 'amount': amount, + // // --------------------------------------------------------- + // // 'resultingTrades' in editOrder + // 'resultingTrades': { + // 'BTC_MANA': [], + // } + // } + // + let timestamp = this.safeInteger (order, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'date')); + } + let symbol = undefined; + const marketId = this.safeString (order, 'currencyPair'); + if (marketId !== undefined) { + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const [ quoteId, baseId ] = marketId.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + let trades = undefined; + let resultingTrades = this.safeValue (order, 'resultingTrades'); + if (!Array.isArray (resultingTrades)) { + resultingTrades = this.safeValue (resultingTrades, this.safeString (market, 'id', marketId)); + } + if (resultingTrades !== undefined) { + trades = this.parseTrades (resultingTrades, market); + } + const price = this.safeFloat2 (order, 'price', 'rate'); + let remaining = this.safeFloat (order, 'amount'); + let amount = this.safeFloat (order, 'startingAmount'); + let filled = undefined; + let cost = 0; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + if (price !== undefined) { + cost = filled * price; + } + } + } else { + amount = remaining; + } + let status = this.parseOrderStatus (this.safeString (order, 'status')); + let average = undefined; + let lastTradeTimestamp = undefined; + if (filled === undefined) { + if (trades !== undefined) { + filled = 0; + cost = 0; + const tradesLength = trades.length; + if (tradesLength > 0) { + lastTradeTimestamp = trades[0]['timestamp']; + for (let i = 0; i < tradesLength; i++) { + const trade = trades[i]; + const tradeAmount = trade['amount']; + const tradePrice = trade['price']; + filled = this.sum (filled, tradeAmount); + cost = this.sum (cost, tradePrice * tradeAmount); + lastTradeTimestamp = Math.max (lastTradeTimestamp, trade['timestamp']); + } + } + if (amount !== undefined) { + remaining = Math.max (amount - filled, 0); + if (filled >= amount) { + status = 'closed'; + } + } + } + } + if ((filled !== undefined) && (cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + let type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side', type); + if (type === side) { + type = undefined; + } + const id = this.safeString (order, 'orderNumber'); + let fee = undefined; + const feeCost = this.safeFloat (order, 'fee'); + if (feeCost !== undefined) { + let feeCurrencyCode = undefined; + if (market !== undefined) { + feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; + } + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const clientOrderId = this.safeString (order, 'clientOrderId'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'trades': trades, + 'fee': fee, + }; + } + + parseOpenOrders (orders, market, result) { + for (let i = 0; i < orders.length; i++) { + const order = orders[i]; + const extended = this.extend (order, { + 'status': 'open', + 'type': 'limit', + 'side': order['type'], + 'price': order['rate'], + }); + result.push (this.parseOrder (extended, market)); + } + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const pair = market ? market['id'] : 'all'; + const request = { + 'currencyPair': pair, + }; + const response = await this.privatePostReturnOpenOrders (this.extend (request, params)); + const extension = { 'status': 'open' }; + if (market === undefined) { + const marketIds = Object.keys (response); + let openOrders = []; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const orders = response[marketId]; + const m = this.markets_by_id[marketId]; + openOrders = this.arrayConcat (openOrders, this.parseOrders (orders, m, undefined, undefined, extension)); + } + return this.filterBySinceLimit (openOrders, since, limit); + } else { + return this.parseOrders (response, market, since, limit, extension); + } + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' createOrder() does not accept market orders'); + } + await this.loadMarkets (); + const method = 'privatePost' + this.capitalize (side); + const market = this.market (symbol); + amount = this.amountToPrecision (symbol, amount); + const request = { + 'currencyPair': market['id'], + 'rate': this.priceToPrecision (symbol, price), + 'amount': amount, + }; + const clientOrderId = this.safeString (params, 'clientOrderId'); + if (clientOrderId !== undefined) { + request['clientOrderId'] = clientOrderId; + params = this.omit (params, 'clientOrderId'); + } + // remember the timestamp before issuing the request + const timestamp = this.milliseconds (); + const response = await this[method] (this.extend (request, params)); + // + // { + // 'orderNumber': '9805453960', + // 'resultingTrades': [ + // { + // 'amount': '200.00000000', + // 'date': '2019-12-15 16:04:10', + // 'rate': '0.00000355', + // 'total': '0.00071000', + // 'tradeID': '119871', + // 'type': 'buy', + // 'takerAdjustment': '200.00000000', + // }, + // ], + // 'fee': '0.00000000', + // 'currencyPair': 'BTC_MANA', + // } + // + return this.parseOrder (this.extend ({ + 'timestamp': timestamp, + 'status': 'open', + 'type': type, + 'side': side, + 'price': price, + 'amount': amount, + }, response), market); + } + + async editOrder (id, symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + price = parseFloat (price); + const request = { + 'orderNumber': id, + 'rate': this.priceToPrecision (symbol, price), + }; + if (amount !== undefined) { + request['amount'] = this.amountToPrecision (symbol, amount); + } + const response = await this.privatePostMoveOrder (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + const clientOrderId = this.safeValue (params, 'clientOrderId'); + if (clientOrderId === undefined) { + request['orderNumber'] = id; + } else { + request['clientOrderId'] = clientOrderId; + } + params = this.omit (params, 'clientOrderId'); + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['currencyPair'] = market['id']; + } + const response = await this.privatePostCancelAllOrders (this.extend (request, params)); + // + // { + // "success": 1, + // "message": "Orders canceled", + // "orderNumbers": [ + // 503749, + // 888321, + // 7315825, + // 7316824 + // ] + // } + // + return response; + } + + async fetchOpenOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + id = id.toString (); + const request = { + 'orderNumber': id, + }; + const response = await this.privatePostReturnOrderStatus (this.extend (request, params)); + // + // { + // success: 1, + // result: { + // '6071071': { + // status: 'Open', + // rate: '0.40000000', + // amount: '1.00000000', + // currencyPair: 'BTC_ETH', + // date: '2018-10-17 17:04:50', + // total: '0.40000000', + // type: 'buy', + // startingAmount: '1.00000', + // }, + // }, + // } + // + const result = this.safeValue (response['result'], id); + if (result === undefined) { + throw new OrderNotFound (this.id + ' order id ' + id + ' not found'); + } + return this.parseOrder (result); + } + + async fetchOrderStatus (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const orders = await this.fetchOpenOrders (symbol, undefined, undefined, params); + const indexed = this.indexBy (orders, 'id'); + return (id in indexed) ? 'open' : 'closed'; + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderNumber': id, + }; + const trades = await this.privatePostReturnOrderTrades (this.extend (request, params)); + return this.parseTrades (trades); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + // USDT, USDTETH, USDTTRON + let currencyId = undefined; + let currency = undefined; + if (code in this.currencies) { + currency = this.currency (code); + currencyId = currency['id']; + } else { + currencyId = code; + } + const request = { + 'currency': currencyId, + }; + const response = await this.privatePostGenerateNewAddress (this.extend (request, params)); + let address = undefined; + let tag = undefined; + if (response['success'] === 1) { + address = this.safeString (response, 'response'); + } + this.checkAddress (address); + if (currency !== undefined) { + const depositAddress = this.safeString (currency['info'], 'depositAddress'); + if (depositAddress !== undefined) { + tag = address; + address = depositAddress; + } + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const response = await this.privatePostReturnDepositAddresses (params); + // USDT, USDTETH, USDTTRON + let currencyId = undefined; + let currency = undefined; + if (code in this.currencies) { + currency = this.currency (code); + currencyId = currency['id']; + } else { + currencyId = code; + } + let address = this.safeString (response, currencyId); + let tag = undefined; + this.checkAddress (address); + if (currency !== undefined) { + const depositAddress = this.safeString (currency['info'], 'depositAddress'); + if (depositAddress !== undefined) { + tag = address; + address = depositAddress; + } + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + }; + if (tag !== undefined) { + request['paymentId'] = tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + // + // { + // response: 'Withdrew 1.00000000 USDT.', + // email2FA: false, + // withdrawalNumber: 13449869 + // } + // + return { + 'info': response, + 'id': this.safeString (response, 'withdrawalNumber'), + }; + } + + async fetchTransactionsHelper (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const year = 31104000; // 60 * 60 * 24 * 30 * 12 = one year of history, why not + const now = this.seconds (); + const start = (since !== undefined) ? parseInt (since / 1000) : now - 10 * year; + const request = { + 'start': start, // UNIX timestamp, required + 'end': now, // UNIX timestamp, required + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privatePostReturnDepositsWithdrawals (this.extend (request, params)); + // + // { + // "adjustments":[], + // "deposits":[ + // { + // currency: "BTC", + // address: "1MEtiqJWru53FhhHrfJPPvd2tC3TPDVcmW", + // amount: "0.01063000", + // confirmations: 1, + // txid: "952b0e1888d6d491591facc0d37b5ebec540ac1efb241fdbc22bcc20d1822fb6", + // timestamp: 1507916888, + // status: "COMPLETE" + // }, + // { + // currency: "ETH", + // address: "0x20108ba20b65c04d82909e91df06618107460197", + // amount: "4.00000000", + // confirmations: 38, + // txid: "0x4be260073491fe63935e9e0da42bd71138fdeb803732f41501015a2d46eb479d", + // timestamp: 1525060430, + // status: "COMPLETE" + // } + // ], + // "withdrawals":[ + // { + // "withdrawalNumber":13449869, + // "currency":"USDTTRON", // not documented in API docs, see commonCurrencies in describe() + // "address":"TXGaqPW23JdRWhsVwS2mRsGsegbdnAd3Rw", + // "amount":"1.00000000", + // "fee":"0.00000000", + // "timestamp":1591573420, + // "status":"COMPLETE: dadf427224b3d44b38a2c13caa4395e4666152556ca0b2f67dbd86a95655150f", + // "ipAddress":"74.116.3.247", + // "canCancel":0, + // "canResendEmail":0, + // "paymentID":null, + // "scope":"crypto" + // }, + // { + // withdrawalNumber: 8224394, + // currency: "EMC2", + // address: "EYEKyCrqTNmVCpdDV8w49XvSKRP9N3EUyF", + // amount: "63.10796020", + // fee: "0.01000000", + // timestamp: 1510819838, + // status: "COMPLETE: d37354f9d02cb24d98c8c4fc17aa42f475530b5727effdf668ee5a43ce667fd6", + // ipAddress: "5.220.220.200" + // }, + // { + // withdrawalNumber: 9290444, + // currency: "ETH", + // address: "0x191015ff2e75261d50433fbd05bd57e942336149", + // amount: "0.15500000", + // fee: "0.00500000", + // timestamp: 1514099289, + // status: "COMPLETE: 0x12d444493b4bca668992021fd9e54b5292b8e71d9927af1f076f554e4bea5b2d", + // ipAddress: "5.228.227.214" + // }, + // { + // withdrawalNumber: 11518260, + // currency: "BTC", + // address: "8JoDXAmE1GY2LRK8jD1gmAmgRPq54kXJ4t", + // amount: "0.20000000", + // fee: "0.00050000", + // timestamp: 1527918155, + // status: "COMPLETE: 1864f4ebb277d90b0b1ff53259b36b97fa1990edc7ad2be47c5e0ab41916b5ff", + // ipAddress: "211.8.195.26" + // } + // ] + // } + // + return response; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.fetchTransactionsHelper (code, since, limit, params); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const withdrawals = this.safeValue (response, 'withdrawals', []); + const deposits = this.safeValue (response, 'deposits', []); + const withdrawalTransactions = this.parseTransactions (withdrawals, currency, since, limit); + const depositTransactions = this.parseTransactions (deposits, currency, since, limit); + const transactions = this.arrayConcat (depositTransactions, withdrawalTransactions); + return this.filterByCurrencySinceLimit (this.sortBy (transactions, 'timestamp'), code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.fetchTransactionsHelper (code, since, limit, params); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const withdrawals = this.safeValue (response, 'withdrawals', []); + const transactions = this.parseTransactions (withdrawals, currency, since, limit); + return this.filterByCurrencySinceLimit (transactions, code, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const response = await this.fetchTransactionsHelper (code, since, limit, params); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const deposits = this.safeValue (response, 'deposits', []); + const transactions = this.parseTransactions (deposits, currency, since, limit); + return this.filterByCurrencySinceLimit (transactions, code, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'COMPLETE': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // deposits + // + // { + // "txid": "f49d489616911db44b740612d19464521179c76ebe9021af85b6de1e2f8d68cd", + // "type": "deposit", + // "amount": "49798.01987021", + // "status": "COMPLETE", + // "address": "DJVJZ58tJC8UeUv9Tqcdtn6uhWobouxFLT", + // "currency": "DOGE", + // "timestamp": 1524321838, + // "confirmations": 3371, + // "depositNumber": 134587098 + // } + // + // withdrawals + // + // { + // "fee": "0.00050000", + // "type": "withdrawal", + // "amount": "0.40234387", + // "status": "COMPLETE: fbabb2bf7d81c076f396f3441166d5f60f6cea5fdfe69e02adcc3b27af8c2746", + // "address": "1EdAqY4cqHoJGAgNfUFER7yZpg1Jc9DUa3", + // "currency": "BTC", + // "canCancel": 0, + // "ipAddress": "185.230.101.31", + // "paymentID": null, + // "timestamp": 1523834337, + // "canResendEmail": 0, + // "withdrawalNumber": 11162900 + // } + // + const timestamp = this.safeTimestamp (transaction, 'timestamp'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let status = this.safeString (transaction, 'status', 'pending'); + let txid = this.safeString (transaction, 'txid'); + if (status !== undefined) { + const parts = status.split (': '); + const numParts = parts.length; + status = parts[0]; + if ((numParts > 1) && (txid === undefined)) { + txid = parts[1]; + } + status = this.parseTransactionStatus (status); + } + const defaultType = ('withdrawalNumber' in transaction) ? 'withdrawal' : 'deposit'; + const type = this.safeString (transaction, 'type', defaultType); + const id = this.safeString2 (transaction, 'withdrawalNumber', 'depositNumber'); + let amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'paymentID'); + // according to https://poloniex.com/fees/ + const feeCost = this.safeFloat (transaction, 'fee', 0); + if (type === 'withdrawal') { + // poloniex withdrawal amount includes the fee + amount = amount - feeCost; + } + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': tag, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + async fetchPosition (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPair': market['id'], + }; + const response = await this.privatePostGetMarginPosition (this.extend (request, params)); + // + // { + // type: "none", + // amount: "0.00000000", + // total: "0.00000000", + // basePrice: "0.00000000", + // liquidationPrice: -1, + // pl: "0.00000000", + // lendingFees: "0.00000000" + // } + // + // todo unify parsePosition/parsePositions + return response; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.extend ({ 'command': path }, params); + if (api === 'public') { + url += '?' + this.urlencode (query); + } else { + this.checkRequiredCredentials (); + query['nonce'] = this.nonce (); + body = this.urlencode (query); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // {"error":"Permission denied."} + if ('error' in response) { + const message = response['error']; + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/probit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/probit.js new file mode 100644 index 000000000..9fb49b9d1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/probit.js @@ -0,0 +1,1271 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ExchangeNotAvailable, BadResponse, BadRequest, InvalidOrder, InsufficientFunds, AuthenticationError, ArgumentsRequired, InvalidAddress, RateLimitExceeded, DDoSProtection, BadSymbol } = require ('./base/errors'); +const { TRUNCATE, TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class probit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'probit', + 'name': 'ProBit', + 'countries': [ 'SC', 'KR' ], // Seychelles, South Korea + 'rateLimit': 250, // ms + 'has': { + 'CORS': true, + 'fetchTime': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchTickers': true, + 'fetchTicker': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchTrades': true, + 'fetchBalance': true, + 'createOrder': true, + 'createMarketOrder': true, + 'cancelOrder': true, + 'fetchOrder': true, + 'fetchOpenOrders': true, + 'fetchClosedOrders': true, + 'fetchMyTrades': true, + 'fetchDepositAddress': true, + 'withdraw': true, + 'signIn': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '10m': '10m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '6h': '6h', + '12h': '12h', + '1d': '1D', + '1w': '1W', + '1M': '1M', + }, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/79268032-c4379480-7ea2-11ea-80b3-dd96bb29fd0d.jpg', + 'api': { + 'accounts': 'https://accounts.probit.com', + 'public': 'https://api.probit.com/api/exchange', + 'private': 'https://api.probit.com/api/exchange', + }, + 'www': 'https://www.probit.com', + 'doc': [ + 'https://docs-en.probit.com', + 'https://docs-ko.probit.com', + ], + 'fees': 'https://support.probit.com/hc/en-us/articles/360020968611-Trading-Fees', + 'referral': 'https://www.probit.com/r/34608773', + }, + 'api': { + 'public': { + 'get': [ + 'market', + 'currency', + 'currency_with_platform', + 'time', + 'ticker', + 'order_book', + 'trade', + 'candle', + ], + }, + 'private': { + 'post': [ + 'new_order', + 'cancel_order', + 'withdrawal', + ], + 'get': [ + 'balance', + 'order', + 'open_order', + 'order_history', + 'trade_history', + 'deposit_address', + ], + }, + 'accounts': { + 'post': [ + 'token', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + }, + 'exceptions': { + 'exact': { + 'UNAUTHORIZED': AuthenticationError, + 'INVALID_ARGUMENT': BadRequest, // Parameters are not a valid format, parameters are empty, or out of range, or a parameter was sent when not required. + 'TRADING_UNAVAILABLE': ExchangeNotAvailable, + 'NOT_ENOUGH_BALANCE': InsufficientFunds, + 'NOT_ALLOWED_COMBINATION': BadRequest, + 'INVALID_ORDER': InvalidOrder, // Requested order does not exist, or it is not your order + 'RATE_LIMIT_EXCEEDED': RateLimitExceeded, // You are sending requests too frequently. Please try it later. + 'MARKET_UNAVAILABLE': ExchangeNotAvailable, // Market is closed today + 'INVALID_MARKET': BadSymbol, // Requested market is not exist + 'INVALID_CURRENCY': BadRequest, // Requested currency is not exist on ProBit system + 'TOO_MANY_OPEN_ORDERS': DDoSProtection, // Too many open orders + 'DUPLICATE_ADDRESS': InvalidAddress, // Address already exists in withdrawal address list + }, + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + }, + 'precisionMode': TICK_SIZE, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'timeInForce': { + 'limit': 'gtc', + 'market': 'ioc', + }, + }, + 'commonCurrencies': { + 'BTCBEAR': 'BEAR', + 'BTCBULL': 'BULL', + 'CBC': 'CryptoBharatCoin', + 'UNI': 'UNICORN Token', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarket (params); + // + // { + // "data":[ + // { + // "id":"MONA-USDT", + // "base_currency_id":"MONA", + // "quote_currency_id":"USDT", + // "min_price":"0.001", + // "max_price":"9999999999999999", + // "price_increment":"0.001", + // "min_quantity":"0.0001", + // "max_quantity":"9999999999999999", + // "quantity_precision":4, + // "min_cost":"1", + // "max_cost":"9999999999999999", + // "cost_precision":8, + // "taker_fee_rate":"0.2", + // "maker_fee_rate":"0.2", + // "show_in_ui":true, + // "closed":false + // }, + // ] + // } + // + const markets = this.safeValue (response, 'data', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'base_currency_id'); + const quoteId = this.safeString (market, 'quote_currency_id'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const closed = this.safeValue (market, 'closed', false); + const active = !closed; + const amountPrecision = this.safeInteger (market, 'quantity_precision'); + const costPrecision = this.safeInteger (market, 'cost_precision'); + const precision = { + 'amount': 1 / Math.pow (10, amountPrecision), + 'price': this.safeFloat (market, 'price_increment'), + 'cost': 1 / Math.pow (10, costPrecision), + }; + const takerFeeRate = this.safeFloat (market, 'taker_fee_rate'); + const makerFeeRate = this.safeFloat (market, 'maker_fee_rate'); + result.push ({ + 'id': id, + 'info': market, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'taker': takerFeeRate / 100, + 'maker': makerFeeRate / 100, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_quantity'), + 'max': this.safeFloat (market, 'max_quantity'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'min_cost'), + 'max': this.safeFloat (market, 'max_cost'), + }, + }, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencyWithPlatform (params); + // + // { + // "data":[ + // { + // "id":"USDT", + // "display_name":{"ko-kr":"테더","en-us":"Tether"}, + // "show_in_ui":true, + // "platform":[ + // { + // "id":"ETH", + // "priority":1, + // "deposit":true, + // "withdrawal":true, + // "currency_id":"USDT", + // "precision":6, + // "min_confirmation_count":15, + // "require_destination_tag":false, + // "display_name":{"name":{"ko-kr":"ERC-20","en-us":"ERC-20"}}, + // "min_deposit_amount":"0", + // "min_withdrawal_amount":"1", + // "withdrawal_fee":[ + // {"amount":"0.01","priority":2,"currency_id":"ETH"}, + // {"amount":"1.5","priority":1,"currency_id":"USDT"}, + // ], + // "deposit_fee":{}, + // "suspended_reason":"", + // "deposit_suspended":false, + // "withdrawal_suspended":false + // }, + // { + // "id":"OMNI", + // "priority":2, + // "deposit":true, + // "withdrawal":true, + // "currency_id":"USDT", + // "precision":6, + // "min_confirmation_count":3, + // "require_destination_tag":false, + // "display_name":{"name":{"ko-kr":"OMNI","en-us":"OMNI"}}, + // "min_deposit_amount":"0", + // "min_withdrawal_amount":"5", + // "withdrawal_fee":[{"amount":"5","priority":1,"currency_id":"USDT"}], + // "deposit_fee":{}, + // "suspended_reason":"wallet_maintenance", + // "deposit_suspended":false, + // "withdrawal_suspended":false + // } + // ], + // "stakeable":false, + // "unstakeable":false, + // "auto_stake":false, + // "auto_stake_amount":"0" + // } + // ] + // } + // + const currencies = this.safeValue (response, 'data'); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'id'); + const code = this.safeCurrencyCode (id); + const displayName = this.safeValue (currency, 'display_name'); + const name = this.safeString (displayName, 'en-us'); + const platforms = this.safeValue (currency, 'platform', []); + const platformsByPriority = this.sortBy (platforms, 'priority'); + const platform = this.safeValue (platformsByPriority, 0, {}); + const precision = this.safeInteger (platform, 'precision'); + const depositSuspended = this.safeValue (platform, 'deposit_suspended'); + const withdrawalSuspended = this.safeValue (platform, 'withdrawal_suspended'); + const active = !(depositSuspended && withdrawalSuspended); + const withdrawalFees = this.safeValue (platform, 'withdrawal_fee', {}); + const withdrawalFeesByPriority = this.sortBy (withdrawalFees, 'priority'); + const withdrawalFee = this.safeValue (withdrawalFeesByPriority, 0, {}); + const fee = this.safeFloat (withdrawalFee, 'amount'); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'deposit': { + 'min': this.safeFloat (platform, 'min_deposit_amount'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (platform, 'min_withdrawal_amount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + // + // { + // data: [ + // { + // "currency_id":"XRP", + // "total":"100", + // "available":"0", + // } + // ] + // } + // + const data = this.safeValue (response, 'data'); + const result = { 'info': data }; + for (let i = 0; i < data.length; i++) { + const balance = data[i]; + const currencyId = this.safeString (balance, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'total'); + account['free'] = this.safeFloat (balance, 'available'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + }; + const response = await this.publicGetOrderBook (this.extend (request, params)); + // + // { + // data: [ + // { side: 'buy', price: '0.000031', quantity: '10' }, + // { side: 'buy', price: '0.00356007', quantity: '4.92156877' }, + // { side: 'sell', price: '0.1857', quantity: '0.17' }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const dataBySide = this.groupBy (data, 'side'); + return this.parseOrderBook (dataBySide, undefined, 'buy', 'sell', 'price', 'quantity'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (symbols !== undefined) { + const marketIds = this.marketIds (symbols); + request['market_ids'] = marketIds.join (','); + } + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "data":[ + // { + // "last":"0.022902", + // "low":"0.021693", + // "high":"0.024093", + // "change":"-0.000047", + // "base_volume":"15681.986", + // "quote_volume":"360.514403624", + // "market_id":"ETH-BTC", + // "time":"2020-04-12T18:43:38.000Z" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTickers (data, symbols); + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_ids': market['id'], + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // { + // "data":[ + // { + // "last":"0.022902", + // "low":"0.021693", + // "high":"0.024093", + // "change":"-0.000047", + // "base_volume":"15681.986", + // "quote_volume":"360.514403624", + // "market_id":"ETH-BTC", + // "time":"2020-04-12T18:43:38.000Z" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const ticker = this.safeValue (data, 0); + if (ticker === undefined) { + throw new BadResponse (this.id + ' fetchTicker() returned an empty response'); + } + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "last":"0.022902", + // "low":"0.021693", + // "high":"0.024093", + // "change":"-0.000047", + // "base_volume":"15681.986", + // "quote_volume":"360.514403624", + // "market_id":"ETH-BTC", + // "time":"2020-04-12T18:43:38.000Z" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'time')); + const marketId = this.safeString (ticker, 'market_id'); + const symbol = this.safeSymbol (marketId, market, '-'); + const close = this.safeFloat (ticker, 'last'); + const change = this.safeFloat (ticker, 'change'); + let percentage = undefined; + let open = undefined; + if (change !== undefined) { + if (close !== undefined) { + open = close - change; + percentage = (change / open) * 100; + } + } + const baseVolume = this.safeFloat (ticker, 'base_volume'); + const quoteVolume = this.safeFloat (ticker, 'quote_volume'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': close, + 'last': close, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + 'limit': 100, + 'start_time': this.iso8601 (0), + 'end_time': this.iso8601 (this.milliseconds ()), + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['market_id'] = market['id']; + } + if (since !== undefined) { + request['start_time'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradeHistory (this.extend (request, params)); + // + // { + // data: [ + // { + // "id":"BTC-USDT:183566", + // "order_id":"17209376", + // "side":"sell", + // "fee_amount":"0.657396569175", + // "fee_currency_id":"USDT", + // "status":"settled", + // "price":"6573.96569175", + // "quantity":"0.1", + // "cost":"657.396569175", + // "time":"2018-08-10T06:06:46.000Z", + // "market_id":"BTC-USDT" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + 'limit': 100, + 'start_time': '1970-01-01T00:00:00.000Z', + 'end_time': this.iso8601 (this.milliseconds ()), + }; + if (since !== undefined) { + request['start_time'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTrade (this.extend (request, params)); + // + // { + // "data":[ + // { + // "id":"ETH-BTC:3331886", + // "price":"0.022981", + // "quantity":"12.337", + // "time":"2020-04-12T20:55:42.371Z", + // "side":"sell", + // "tick_direction":"down" + // }, + // { + // "id":"ETH-BTC:3331885", + // "price":"0.022982", + // "quantity":"6.472", + // "time":"2020-04-12T20:55:39.652Z", + // "side":"sell", + // "tick_direction":"down" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":"ETH-BTC:3331886", + // "price":"0.022981", + // "quantity":"12.337", + // "time":"2020-04-12T20:55:42.371Z", + // "side":"sell", + // "tick_direction":"down" + // } + // + // fetchMyTrades (private) + // + // { + // "id":"BTC-USDT:183566", + // "order_id":"17209376", + // "side":"sell", + // "fee_amount":"0.657396569175", + // "fee_currency_id":"USDT", + // "status":"settled", + // "price":"6573.96569175", + // "quantity":"0.1", + // "cost":"657.396569175", + // "time":"2018-08-10T06:06:46.000Z", + // "market_id":"BTC-USDT" + // } + // + const timestamp = this.parse8601 (this.safeString (trade, 'time')); + const id = this.safeString (trade, 'id'); + let marketId = undefined; + if (id !== undefined) { + const parts = id.split (':'); + marketId = this.safeString (parts, 0); + } + marketId = this.safeString (trade, 'market_id', marketId); + const symbol = this.safeSymbol (marketId, market, '-'); + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const orderId = this.safeString (trade, 'order_id'); + const feeCost = this.safeFloat (trade, 'fee_amount'); + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'fee_currency_id'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTime (params = {}) { + const response = await this.publicGetTime (params); + // + // { "data":"2020-04-12T18:54:25.390Z" } + // + const timestamp = this.parse8601 (this.safeString (response, 'data')); + return timestamp; + } + + normalizeOHLCVTimestamp (timestamp, timeframe, after = false) { + const duration = this.parseTimeframe (timeframe); + if (timeframe === '1M') { + const iso8601 = this.iso8601 (timestamp); + const parts = iso8601.split ('-'); + const year = this.safeString (parts, 0); + let month = this.safeInteger (parts, 1); + if (after) { + month = this.sum (month, 1); + } + if (month < 10) { + month = '0' + month.toString (); + } else { + month = month.toString (); + } + return year + '-' + month + '-01T00:00:00.000Z'; + } else if (timeframe === '1w') { + timestamp = parseInt (timestamp / 1000); + const firstSunday = 259200; // 1970-01-04T00:00:00.000Z + const difference = timestamp - firstSunday; + const numWeeks = this.integerDivide (difference, duration); + let previousSunday = this.sum (firstSunday, numWeeks * duration); + if (after) { + previousSunday = this.sum (previousSunday, duration); + } + return this.iso8601 (previousSunday * 1000); + } else { + timestamp = parseInt (timestamp / 1000); + timestamp = duration * parseInt (timestamp / duration); + if (after) { + timestamp = this.sum (timestamp, duration); + } + return this.iso8601 (timestamp * 1000); + } + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const interval = this.timeframes[timeframe]; + limit = (limit === undefined) ? 100 : limit; + let requestLimit = this.sum (limit, 1); + requestLimit = Math.min (1000, requestLimit); // max 1000 + const request = { + 'market_ids': market['id'], + 'interval': interval, + 'sort': 'asc', // 'asc' will always include the start_time, 'desc' will always include end_time + 'limit': requestLimit, // max 1000 + }; + const now = this.milliseconds (); + const duration = this.parseTimeframe (timeframe); + let startTime = since; + let endTime = now; + if (since === undefined) { + if (limit === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOHLCV requires either a since argument or a limit argument'); + } else { + startTime = now - limit * duration * 1000; + } + } else { + if (limit === undefined) { + endTime = now; + } else { + endTime = this.sum (since, this.sum (limit, 1) * duration * 1000); + } + } + const startTimeNormalized = this.normalizeOHLCVTimestamp (startTime, timeframe); + const endTimeNormalized = this.normalizeOHLCVTimestamp (endTime, timeframe, true); + request['start_time'] = startTimeNormalized; + request['end_time'] = endTimeNormalized; + const response = await this.publicGetCandle (this.extend (request, params)); + // + // { + // "data":[ + // { + // "market_id":"ETH-BTC", + // "open":"0.02811", + // "close":"0.02811", + // "low":"0.02811", + // "high":"0.02811", + // "base_volume":"0.0005", + // "quote_volume":"0.000014055", + // "start_time":"2018-11-30T18:19:00.000Z", + // "end_time":"2018-11-30T18:20:00.000Z" + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "market_id":"ETH-BTC", + // "open":"0.02811", + // "close":"0.02811", + // "low":"0.02811", + // "high":"0.02811", + // "base_volume":"0.0005", + // "quote_volume":"0.000014055", + // "start_time":"2018-11-30T18:19:00.000Z", + // "end_time":"2018-11-30T18:20:00.000Z" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'start_time')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'base_volume'), + ]; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + since = this.parse8601 (since); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market_id'] = market['id']; + } + const response = await this.privateGetOpenOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'start_time': this.iso8601 (0), + 'end_time': this.iso8601 (this.milliseconds ()), + 'limit': 100, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market_id'] = market['id']; + } + if (since) { + request['start_time'] = this.iso8601 (since); + } + if (limit) { + request['limit'] = limit; + } + const response = await this.privateGetOrderHistory (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrders (data, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_order_id'); + if (clientOrderId !== undefined) { + request['client_order_id'] = clientOrderId; + } else { + request['order_id'] = id; + } + const query = this.omit (params, [ 'clientOrderId', 'client_order_id' ]); + const response = await this.privateGetOrder (this.extend (request, query)); + const data = this.safeValue (response, 'data', []); + const order = this.safeValue (data, 0); + return this.parseOrder (order, market); + } + + parseOrderStatus (status) { + const statuses = { + 'open': 'open', + 'cancelled': 'canceled', + 'filled': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // id: string, + // user_id: string, + // market_id: string, + // type: 'orderType', + // side: 'side', + // quantity: string, + // limit_price: string, + // time_in_force: 'timeInForce', + // filled_cost: string, + // filled_quantity: string, + // open_quantity: string, + // cancelled_quantity: string, + // status: 'orderStatus', + // time: 'date', + // client_order_id: string, + // } + // + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const marketId = this.safeString (order, 'market_id'); + const symbol = this.safeSymbol (marketId, market, '-'); + const timestamp = this.parse8601 (this.safeString (order, 'time')); + let price = this.safeFloat (order, 'limit_price'); + const filled = this.safeFloat (order, 'filled_quantity'); + let remaining = this.safeFloat (order, 'open_quantity'); + const canceledAmount = this.safeFloat (order, 'cancelled_quantity'); + if (canceledAmount !== undefined) { + remaining = this.sum (remaining, canceledAmount); + } + const amount = this.safeFloat (order, 'quantity', this.sum (filled, remaining)); + let cost = this.safeFloat2 (order, 'filled_cost', 'cost'); + if (type === 'market') { + price = undefined; + } + let average = undefined; + if (filled !== undefined) { + if (cost === undefined) { + if (price !== undefined) { + cost = price * filled; + } + } + if (cost !== undefined) { + if (filled > 0) { + average = cost / filled; + } + } + } + let clientOrderId = this.safeString (order, 'client_order_id'); + if (clientOrderId === '') { + clientOrderId = undefined; + } + const timeInForce = this.safeStringUpper (order, 'time_in_force'); + return { + 'id': id, + 'info': order, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': timeInForce, + 'side': side, + 'status': status, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'average': average, + 'cost': cost, + 'fee': undefined, + 'trades': undefined, + }; + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['cost'], this.precisionMode); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const options = this.safeValue (this.options, 'timeInForce'); + const defaultTimeInForce = this.safeValue (options, type); + const timeInForce = this.safeString2 (params, 'timeInForce', 'time_in_force', defaultTimeInForce); + const request = { + 'market_id': market['id'], + 'type': type, + 'side': side, + 'time_in_force': timeInForce, + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'client_order_id'); + if (clientOrderId !== undefined) { + request['client_order_id'] = clientOrderId; + } + let costToPrecision = undefined; + if (type === 'limit') { + request['limit_price'] = this.priceToPrecision (symbol, price); + request['quantity'] = this.amountToPrecision (symbol, amount); + } else if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + let cost = this.safeFloat (params, 'cost'); + const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); + if (createMarketBuyOrderRequiresPrice) { + if (price !== undefined) { + if (cost === undefined) { + cost = amount * price; + } + } else if (cost === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument for market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument or in the 'cost' extra parameter (the exchange-specific behaviour)"); + } + } else { + cost = (cost === undefined) ? amount : cost; + } + costToPrecision = this.costToPrecision (symbol, cost); + request['cost'] = costToPrecision; + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + } + const query = this.omit (params, [ 'timeInForce', 'time_in_force', 'clientOrderId', 'client_order_id' ]); + const response = await this.privatePostNewOrder (this.extend (request, query)); + // + // { + // data: { + // id: string, + // user_id: string, + // market_id: string, + // type: 'orderType', + // side: 'side', + // quantity: string, + // limit_price: string, + // time_in_force: 'timeInForce', + // filled_cost: string, + // filled_quantity: string, + // open_quantity: string, + // cancelled_quantity: string, + // status: 'orderStatus', + // time: 'date', + // client_order_id: string, + // } + // } + // + const data = this.safeValue (response, 'data'); + const order = this.parseOrder (data, market); + // a workaround for incorrect huge amounts + // returned by the exchange on market buys + if ((type === 'market') && (side === 'buy')) { + order['amount'] = undefined; + order['cost'] = parseFloat (costToPrecision); + order['remaining'] = undefined; + } + return order; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_id': market['id'], + 'order_id': id, + }; + const response = await this.privatePostCancelOrder (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseOrder (data); + } + + parseDepositAddress (depositAddress, currency = undefined) { + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'destination_tag'); + const currencyId = this.safeString (depositAddress, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_id': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + // + // { + // "data":[ + // { + // "currency_id":"ETH", + // "address":"0x12e2caf3c4051ba1146e612f532901a423a9898a", + // "destination_tag":null + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const firstAddress = this.safeValue (data, 0); + if (firstAddress === undefined) { + throw new InvalidAddress (this.id + ' fetchDepositAddress returned an empty response'); + } + return this.parseDepositAddress (firstAddress, currency); + } + + async fetchDepositAddresses (codes = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + if (codes) { + const currencyIds = []; + for (let i = 0; i < codes.length; i++) { + const currency = this.currency (codes[i]); + currencyIds.push (currency['id']); + } + request['currency_id'] = codes.join (','); + } + const response = await this.privateGetDepositAddress (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseDepositAddresses (data); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // In order to use this method + // you need to allow API withdrawal from the API Settings Page, and + // and register the list of withdrawal addresses and destination tags on the API Settings page + // you can only withdraw to the registered addresses using the API + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (tag === undefined) { + tag = ''; + } + const request = { + 'currency_id': currency['id'], + // 'platform_id': 'ETH', // if omitted it will use the default platform for the currency + 'address': address, + 'destination_tag': tag, + 'amount': this.currencyToPrecision (code, amount), + // which currency to pay the withdrawal fees + // only applicable for currencies that accepts multiple withdrawal fee options + // 'fee_currency_id': 'ETH', // if omitted it will use the default fee policy for each currency + // whether the amount field includes fees + // 'include_fee': false, // makes sense only when fee_currency_id is equal to currency_id + }; + const response = await this.privatePostWithdrawal (this.extend (request, params)); + const data = this.safeValue (response, 'data'); + return this.parseTransaction (data, currency); + } + + parseTransaction (transaction, currency = undefined) { + const id = this.safeString (transaction, 'id'); + const amount = this.safeFloat (transaction, 'amount'); + const address = this.safeString (transaction, 'address'); + const tag = this.safeString (transaction, 'destination_tag'); + const txid = this.safeString (transaction, 'hash'); + const timestamp = this.parse8601 (this.safeString (transaction, 'time')); + const type = this.safeString (transaction, 'type'); + const currencyId = this.safeString (transaction, 'currency_id'); + const code = this.safeCurrencyCode (currencyId); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const feeCost = this.safeFloat (transaction, 'fee'); + let fee = undefined; + if (feeCost !== undefined && feeCost !== 0) { + fee = { + 'currency': code, + 'cost': feeCost, + }; + } + return { + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'status': status, + 'type': type, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + 'info': transaction, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'requested': 'pending', + 'pending': 'pending', + 'confirming': 'pending', + 'confirmed': 'pending', + 'applying': 'pending', + 'done': 'ok', + 'cancelled': 'canceled', + 'cancelling': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/'; + const query = this.omit (params, this.extractParams (path)); + if (api === 'accounts') { + this.checkRequiredCredentials (); + url += this.implodeParams (path, params); + const auth = this.apiKey + ':' + this.secret; + const auth64 = this.stringToBase64 (auth); + headers = { + 'Authorization': 'Basic ' + this.decode (auth64), + 'Content-Type': 'application/json', + }; + if (Object.keys (query).length) { + body = this.json (query); + } + } else { + url += this.version + '/'; + if (api === 'public') { + url += this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + const now = this.milliseconds (); + this.checkRequiredCredentials (); + const expires = this.safeInteger (this.options, 'expires'); + if ((expires === undefined) || (expires < now)) { + throw new AuthenticationError (this.id + ' access token expired, call signIn() method'); + } + const accessToken = this.safeString (this.options, 'accessToken'); + headers = { + 'Authorization': 'Bearer ' + accessToken, + }; + url += this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (Object.keys (query).length) { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async signIn (params = {}) { + this.checkRequiredCredentials (); + const request = { + 'grant_type': 'client_credentials', // the only supported value + }; + const response = await this.accountsPostToken (this.extend (request, params)); + // + // { + // access_token: '0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=', + // token_type: 'bearer', + // expires_in: 900 + // } + // + const expiresIn = this.safeInteger (response, 'expires_in'); + const accessToken = this.safeString (response, 'access_token'); + this.options['accessToken'] = accessToken; + this.options['expires'] = this.sum (this.milliseconds (), expiresIn * 1000); + return response; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('errorCode' in response) { + const errorCode = this.safeString (response, 'errorCode'); + const message = this.safeString (response, 'message'); + if (errorCode !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['exact'], errorCode, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/qtrade.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/qtrade.js new file mode 100644 index 000000000..b1a3ab46e --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/qtrade.js @@ -0,0 +1,1540 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InvalidOrder, InsufficientFunds, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class qtrade extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'qtrade', + 'name': 'qTrade', + 'countries': [ 'US' ], + 'rateLimit': 1000, + 'version': 'v1', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg', + 'api': 'https://api.qtrade.io', + 'www': 'https://qtrade.io', + 'doc': 'https://qtrade-exchange.github.io/qtrade-docs', + 'referral': 'https://qtrade.io/?ref=BKOQWVFGRH2C', + }, + 'has': { + 'CORS': false, + 'fetchTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchMarkets': true, + 'fetchCurrencies': true, + 'fetchBalance': true, + 'fetchOrderBook': true, + 'fetchOrder': true, + 'fetchOrders': true, + 'fetchMyTrades': true, + 'fetchClosedOrders': true, + 'fetchOpenOrders': true, + 'fetchOHLCV': true, + 'createOrder': true, + 'cancelOrder': true, + 'createMarketOrder': false, + 'withdraw': true, + 'fetchDepositAddress': true, + 'fetchTransactions': false, + 'fetchDeposits': true, + 'fetchWithdrawals': true, + 'fetchDeposit': true, + 'fetchWithdrawal': true, + }, + 'timeframes': { + '5m': 'fivemin', + '15m': 'fifteenmin', + '30m': 'thirtymin', + '1h': 'onehour', + '2h': 'twohour', + '4h': 'fourhour', + '1d': 'oneday', + }, + 'api': { + 'public': { + 'get': [ + 'ticker/{market_string}', + 'tickers', + 'currency/{code}', + 'currencies', + 'common', + 'market/{market_string}', + 'markets', + 'market/{market_string}/trades', + 'orderbook/{market_string}', + 'market/{market_string}/ohlcv/{interval}', + ], + }, + 'private': { + 'get': [ + 'me', + 'balances', + 'balances_all', // undocumented + 'market/{market_string}', + 'orders', + 'order/{order_id}', + 'trades', + 'withdraw/{withdraw_id}', + 'withdraws', + 'deposit/{deposit_id}', + 'deposits', + 'transfers', + ], + 'post': [ + 'cancel_order', + 'withdraw', + 'deposit_address/{currency}', + 'sell_limit', + 'buy_limit', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.0025, + 'maker': 0.0, + }, + 'funding': { + 'withdraw': {}, + }, + }, + 'exceptions': { + 'exact': { + 'invalid_auth': AuthenticationError, + 'insuff_funds': InsufficientFunds, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // { + // "data":{ + // "markets":[ + // { + // "id":5, + // "market_currency":"BAC", + // "base_currency":"BTC", + // "maker_fee":"0.0025", + // "taker_fee":"0.0025", + // "metadata":{ + // "delisting_date":"7/15/2018", + // "market_notices":[ + // { + // "message":"Delisting Notice: This market has been delisted due to low volume. Please cancel your orders and withdraw your funds by 7/15/2018.", + // "type":"warning" + // } + // ] + // }, + // "can_trade":false, + // "can_cancel":true, + // "can_view":false, + // "market_string":"BAC_BTC", + // "minimum_sell_amount":"0.0001", + // "minimum_buy_value":"0.0001", + // "market_precision":8, + // "base_precision":8 + // }, + // ], + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const markets = this.safeValue (data, 'markets', []); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const marketId = this.safeString (market, 'market_string'); + const numericId = this.safeInteger (market, 'id'); + const baseId = this.safeString (market, 'market_currency'); + const quoteId = this.safeString (market, 'base_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'market_precision'), + 'price': this.safeInteger (market, 'base_precision'), + }; + const canView = this.safeValue (market, 'can_view', false); + const canTrade = this.safeValue (market, 'can_trade', false); + const active = canTrade && canView; + result.push ({ + 'symbol': symbol, + 'id': marketId, + 'numericId': numericId, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': active, + 'precision': precision, + 'taker': this.safeFloat (market, 'taker_fee'), + 'maker': this.safeFloat (market, 'maker_fee'), + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minimum_buy_value'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "data":{ + // "currencies":[ + // { + // "code":"DGB", + // "long_name":"Digibyte", + // "type":"bitcoin_like", + // "precision":8, + // "config":{ + // "price":0.0035, + // "withdraw_fee":"10", + // "deposit_types":[ + // { + // "label":"Address", + // "lookup_mode":"address", + // "render_type":"address", + // "deposit_type":"address", + // "lookup_config":{} + // } + // ], + // "default_signer":103, + // "address_version":30, + // "satoshi_per_byte":300, + // "required_confirmations":200, + // "required_generate_confirmations":300 + // }, + // "metadata":{}, + // "minimum_order":"0.0001", + // "status":"ok", + // "can_withdraw":true, + // "delisted":false, + // "deposit_disabled":false, + // "withdraw_disabled":false, + // "deposit_warn_codes":[], + // "withdraw_warn_codes":[] + // }, + // ], + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const currencies = this.safeValue (data, 'currencies', []); + const result = {}; + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'code'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'long_name'); + const type = this.safeString (currency, 'type'); + const canWithdraw = this.safeValue (currency, 'can_withdraw', true); + const depositDisabled = this.safeValue (currency, 'deposit_disabled', false); + const config = this.safeValue (currency, 'config', {}); + const status = this.safeString (currency, 'status'); + const active = canWithdraw && (status === 'ok') && !depositDisabled; + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'type': type, + 'name': name, + 'fee': this.safeFloat (config, 'withdraw_fee'), + 'precision': this.safeInteger (currency, 'precision'), + 'active': active, + 'limits': { + 'amount': { + 'min': this.safeFloat (currency, 'minimum_order'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': undefined, + 'max': undefined, + }, + }, + }; + } + return result; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time":"2019-12-07T22:55:00Z", + // "open":"0.00197", + // "high":"0.00197", + // "low":"0.00197", + // "close":"0.00197", + // "volume":"0.00016676", + // "market_volume":"0.08465047" + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'time')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'market_volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_string': market['id'], + 'interval': this.timeframes[timeframe], + }; + const response = await this.publicGetMarketMarketStringOhlcvInterval (this.extend (request, params)); + // + // { + // "data":{ + // "slices":[ + // {"time":"2019-12-07T22:55:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0.00016676","market_volume":"0.08465047"}, + // {"time":"2019-12-07T23:00:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"}, + // {"time":"2019-12-07T23:05:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"}, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const ohlcvs = this.safeValue (data, 'slices', []); + return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const marketId = this.marketId (symbol); + const request = { 'market_string': marketId }; + const response = await this.publicGetOrderbookMarketString (this.extend (request, params)); + // + // { + // "data":{ + // "buy":{ + // "0.00700015":"4.76196367", + // "0.00700017":"1.89755391", + // "0.00700018":"2.13214088", + // }, + // "last_change":1588539869958811, + // "sell":{ + // "0.02418662":"0.19513696", + // "0.02465627":"0.2439212", + // "0.02530277":"0.663475931274359255", + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orderbook = {}; + const sides = { 'buy': 'bids', 'sell': 'asks' }; + const keys = Object.keys (sides); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const side = sides[key]; + const bidasks = this.safeValue (data, key, {}); + const prices = Object.keys (bidasks); + const result = []; + for (let j = 0; j < prices.length; j++) { + const priceAsString = prices[j]; + const price = this.safeFloat (prices, j); + const amount = this.safeFloat (bidasks, priceAsString); + result.push ([ price, amount ]); + } + orderbook[side] = result; + } + const timestamp = this.safeIntegerProduct (data, 'last_change', 0.001); + return this.parseOrderBook (orderbook, timestamp); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "ask":"0.02423119", + // "bid":"0.0230939", + // "day_avg_price":"0.0247031874349301", + // "day_change":"-0.0237543162270376", + // "day_high":"0.02470552", + // "day_low":"0.02470172", + // "day_open":"0.02530277", + // "day_volume_base":"0.00268074", + // "day_volume_market":"0.10851798", + // "id":41, + // "id_hr":"ETH_BTC", + // "last":"0.02470172", + // "last_change":1588533365354609 + // } + // + const marketId = this.safeString (ticker, 'id_hr'); + const symbol = this.safeSymbol (marketId, market, '_'); + const timestamp = this.safeIntegerProduct (ticker, 'last_change', 0.001); + const previous = this.safeFloat (ticker, 'day_open'); + const last = this.safeFloat (ticker, 'last'); + const day_change = this.safeFloat (ticker, 'day_change'); + let percentage = undefined; + let change = undefined; + let average = this.safeFloat (ticker, 'day_avg_price'); + if (day_change !== undefined) { + percentage = day_change * 100; + if (previous !== undefined) { + change = day_change * previous; + } + } + if ((average === undefined) && (last !== undefined) && (previous !== undefined)) { + average = this.sum (last, previous) / 2; + } + const baseVolume = this.safeFloat (ticker, 'day_volume_market'); + const quoteVolume = this.safeFloat (ticker, 'day_volume_base'); + const vwap = this.vwap (baseVolume, quoteVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'day_high'), + 'low': this.safeFloat (ticker, 'day_low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': previous, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + // + // { + // "data":{ + // "markets":[ + // { + // "ask":"0.0000003", + // "bid":"0.00000029", + // "day_avg_price":"0.0000002999979728", + // "day_change":"0.0344827586206897", + // "day_high":"0.0000003", + // "day_low":"0.0000003", + // "day_open":"0.00000029", + // "day_volume_base":"0.00591958", + // "day_volume_market":"19732.06666665", + // "id":36, + // "id_hr":"DOGE_BTC", + // "last":"0.0000003", + // "last_change":1588534202130778 + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const tickers = this.safeValue (data, 'markets', []); + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_string': market['id'], + }; + const response = await this.publicGetTickerMarketString (this.extend (request, params)); + // + // { + // "data":{ + // "ask":"0.02423119", + // "bid":"0.0230939", + // "day_avg_price":"0.0247031874349301", + // "day_change":"-0.0237543162270376", + // "day_high":"0.02470552", + // "day_low":"0.02470172", + // "day_open":"0.02530277", + // "day_volume_base":"0.00268074", + // "day_volume_market":"0.10851798", + // "id":41, + // "id_hr":"ETH_BTC", + // "last":"0.02470172", + // "last_change":1588533365354609 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTicker (data, market); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_string': market['id'], + // 'older_than': 123, // returns trades with id < older_than + // 'newer_than': 123, // returns trades with id > newer_than + }; + const response = await this.publicGetMarketMarketStringTrades (this.extend (request, params)); + // + // { + // "data":{ + // "trades":[ + // { + // "id":85507, + // "amount":"0.09390502", + // "price":"0.02556325", + // "base_volume":"0.00240051", + // "seller_taker":true, + // "side":"sell", + // "created_at":"0001-01-01T00:00:00Z", + // "created_at_ts":1581560391338718 + // }, + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'desc': true, // Returns newest trades first when true + // 'older_than': 123, // returns trades with id < older_than + // 'newer_than': 123, // returns trades with id > newer_than + }; + let market = undefined; + const numericId = this.safeValue (params, 'market_id'); + if (numericId !== undefined) { + request['market_id'] = numericId; // mutually exclusive with market_string + } else if (symbol !== undefined) { + market = this.market (symbol); + request['market_string'] = market['id']; + } + const response = await this.privateGetTrades (this.extend (request, params)); + // + // { + // "data":{ + // "trades":[ + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "order_id":13790596, + // "market_id":41, + // "market_string":"ETH_BTC", + // "taker":true, + // "base_fee":"0.00001249", + // "side":"sell", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const trades = this.safeValue (data, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":85507, + // "amount":"0.09390502", + // "price":"0.02556325", + // "base_volume":"0.00240051", + // "seller_taker":true, + // "side":"sell", + // "created_at":"0001-01-01T00:00:00Z", + // "created_at_ts":1581560391338718 + // } + // + // fetchMyTrades (private) + // + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "order_id":13790596, + // "market_id":41, + // "market_string":"ETH_BTC", + // "taker":true, + // "base_fee":"0.00001249", + // "side":"sell", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // + // createOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "base_amount": "9.58970687", + // "base_fee": "0.02397426", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.97179355", + // "price": "9.86804952", + // "taker": true + // } + // + const id = this.safeString (trade, 'id'); + let timestamp = this.safeIntegerProduct (trade, 'created_at_ts', 0.001); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + const side = this.safeString (trade, 'side'); + const marketId = this.safeString (trade, 'market_string'); + const symbol = this.safeSymbol (marketId, market, '_'); + let cost = this.safeFloat2 (trade, 'base_volume', 'base_amount'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'market_amount', 'amount'); + if ((cost === undefined) && (amount !== undefined) && (price !== undefined)) { + if (price !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'base_fee'); + if (feeCost !== undefined) { + const feeCurrencyCode = (market === undefined) ? undefined : market['quote']; + fee = { + 'currency': feeCurrencyCode, + 'cost': feeCost, + }; + } + const taker = this.safeValue (trade, 'taker', true); + const takerOrMaker = taker ? 'taker' : 'maker'; + const orderId = this.safeString (trade, 'order_id'); + const result = { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalancesAll (params); + // + // { + // "data":{ + // "balances": [ + // { "balance": "100000000", "currency": "BCH" }, + // { "balance": "99992435.78253015", "currency": "LTC" }, + // { "balance": "99927153.76074182", "currency": "BTC" }, + // ], + // "order_balances":[], + // "limit_used":0, + // "limit_remaining":4000, + // "limit":4000 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let balances = this.safeValue (data, 'balances', []); + const result = { + 'info': response, + }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = (code in result) ? result[code] : this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = 0; + result[code] = account; + } + balances = this.safeValue (data, 'order_balances', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = (code in result) ? result[code] : this.account (); + account['used'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new InvalidOrder (this.id + ' createOrder() allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amount': this.amountToPrecision (symbol, amount), + 'market_id': market['numericId'], + 'price': this.priceToPrecision (symbol, price), + }; + const method = (side === 'sell') ? 'privatePostSellLimit' : 'privatePostBuyLimit'; + const response = await this[method] (this.extend (request, params)); + // + // { + // "data": { + // "order": { + // "created_at": "2018-04-06T20:46:52.899248Z", + // "id": 13253, + // "market_amount": "1", + // "market_amount_remaining": "0", + // "market_id": 1, + // "open": false, + // "order_type": "sell_limit", + // "price": "0.01", + // "trades": [ + // { + // "base_amount": "0.27834267", + // "base_fee": "0.00069585", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.02820645", + // "price": "9.86805058", + // "taker": true + // }, + // { + // "base_amount": "9.58970687", + // "base_fee": "0.02397426", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.97179355", + // "price": "9.86804952", + // "taker": true + // } + // ] + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const order = this.safeValue (data, 'order', {}); + return this.parseOrder (order, market); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "created_at": "2018-04-06T20:46:52.899248Z", + // "id": 13253, + // "market_amount": "1", + // "market_amount_remaining": "0", + // "market_id": 1, + // "open": false, + // "order_type": "sell_limit", + // "price": "0.01", + // "trades": [ + // { + // "base_amount": "0.27834267", + // "base_fee": "0.00069585", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.02820645", + // "price": "9.86805058", + // "taker": true + // }, + // { + // "base_amount": "9.58970687", + // "base_fee": "0.02397426", + // "created_at": "0001-01-01T00:00:00Z", + // "id": 0, + // "market_amount": "0.97179355", + // "price": "9.86804952", + // "taker": true + // } + // ] + // } + // + // fetchOrder + // + // { + // id: 13790596, + // market_amount: "0.15", + // market_amount_remaining: "0", + // created_at: "2020-05-04T06:08:18.513413Z", + // price: "0.0230939", + // base_amount: "0", + // order_type: "sell_limit", + // market_id: 41, + // market_string: "ETH_BTC", + // open: false, + // trades: [ + // { + // id: 107331, + // market_amount: "0.1082536946986", + // price: "0.0230939", + // base_amount: "0.00249999", + // taker: true, + // base_fee: "0.00001249", + // created_at: "2020-05-04T06:08:18.513413Z", + // } + // ], + // close_reason: "canceled" + // } + // + const id = this.safeString (order, 'id'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const sideType = this.safeString (order, 'order_type'); + let orderType = undefined; + let side = undefined; + if (sideType !== undefined) { + const parts = sideType.split ('_'); + side = this.safeString (parts, 0); + orderType = this.safeString (parts, 1); + } + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'market_amount'); + let remaining = this.safeFloat (order, 'market_amount_remaining'); + let filled = undefined; + const open = this.safeValue (order, 'open', false); + const closeReason = this.safeString (order, 'close_reason'); + let status = undefined; + if (open) { + status = 'open'; + } else if (closeReason === 'canceled') { + status = 'canceled'; + } else { + status = 'closed'; + } + const marketId = this.safeString (order, 'market_string'); + const symbol = this.safeSymbol (marketId, market, '_'); + const rawTrades = this.safeValue (order, 'trades', []); + const parsedTrades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'order': id, + 'side': side, + 'type': orderType, + }); + const numTrades = parsedTrades.length; + let lastTradeTimestamp = undefined; + let feeCost = undefined; + let cost = undefined; + if (numTrades > 0) { + feeCost = 0; + cost = 0; + filled = 0; + remaining = amount; + for (let i = 0; i < parsedTrades.length; i++) { + const trade = parsedTrades[i]; + feeCost = this.sum (trade['fee']['cost'], feeCost); + lastTradeTimestamp = this.safeInteger (trade, 'timestamp'); + cost = this.sum (trade['cost'], cost); + filled = this.sum (trade['amount'], filled); + remaining = Math.max (0, remaining - trade['amount']); + } + } + let fee = undefined; + if (feeCost !== undefined) { + const feeCurrencyCode = (market === undefined) ? undefined : market['quote']; + fee = { + 'currency': feeCurrencyCode, + 'cost': feeCost, + }; + } + if ((amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + let average = undefined; + if (filled !== undefined) { + if ((price !== undefined) && (cost === undefined)) { + cost = filled * price; + } + if ((cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': orderType, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'cost': cost, + 'trades': parsedTrades, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'id': parseInt (id), + }; + // successful cancellation returns 200 with no payload + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { 'order_id': id }; + const response = await this.privateGetOrderOrderId (this.extend (request, params)); + // + // { + // "data":{ + // "order":{ + // "id":13790596, + // "market_amount":"0.15", + // "market_amount_remaining":"0.0417463053014", + // "created_at":"2020-05-04T06:08:18.513413Z", + // "price":"0.0230939", + // "order_type":"sell_limit", + // "market_id":41, + // "market_string":"ETH_BTC", + // "open":true, + // "trades":[ + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "taker":true, + // "base_fee":"0.00001249", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // ] + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const order = this.safeValue (data, 'order', {}); + return this.parseOrder (order); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'open': true, + // 'older_than': 123, // returns orders with id < older_than + // 'newer_than': 123, // returns orders with id > newer_than + }; + let market = undefined; + const numericId = this.safeValue (params, 'market_id'); + if (numericId !== undefined) { + request['market_id'] = numericId; // mutually exclusive with market_string + } else if (symbol !== undefined) { + market = this.market (symbol); + request['market_string'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // { + // "data":{ + // "orders":[ + // { + // "id":13790596, + // "market_amount":"0.15", + // "market_amount_remaining":"0.0417463053014", + // "created_at":"2020-05-04T06:08:18.513413Z", + // "price":"0.0230939", + // "order_type":"sell_limit", + // "market_id":41, + // "market_string":"ETH_BTC", + // "open":true, + // "trades":[ + // { + // "id":107331, + // "market_amount":"0.1082536946986", + // "price":"0.0230939", + // "base_amount":"0.00249999", + // "taker":true, + // "base_fee":"0.00001249", + // "created_at":"2020-05-04T06:08:18.513413Z" + // } + // ] + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const orders = this.safeValue (data, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'open': true }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'open': false }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "currency_status":"ok", + // "deposit_methods":{ + // "address":{ + // "deposit_type":"address", + // "render_type":"address", + // "label":"Address", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // }, + // }, + // } + // + const code = (currency === undefined) ? undefined : currency['code']; + let address = this.safeString (depositAddress, 'address'); + let tag = undefined; + if (address !== undefined) { + const parts = address.split (':'); + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostDepositAddressCurrency (this.extend (request, params)); + // + // { + // "data":{ + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "currency_status":"ok", + // "deposit_methods":{ + // "address":{ + // "deposit_type":"address", + // "render_type":"address", + // "label":"Address", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // }, + // }, + // }, + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseDepositAddress (data, currency); + } + + async fetchDeposit (id, code = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'deposit_id': id, + }; + const response = await this.privateGetDepositDepositId (this.extend (request, params)); + // + // { + // "data":{ + // "deposit":{ + // "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "amount":"0.13", + // "currency":"ETH", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"credited", + // "relay_status":"", + // "network_data":{ + // "confirms":87, + // "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "sweep_balance":"0.150000000000000000", + // "confirms_required":80, + // "unsigned_sweep_tx":{ + // "chainId":1, + // "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "gas":"0x5208", + // "gasPrice":"0x19b45a500", + // "nonce":"0x0", + // "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "value":"0x214646b6347d800" + // }, + // "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "tx_index":"0x6f", + // "tx_value":"0.130000000000000000", + // "key_index":311, + // "blockheight":9877869, + // "signed_sweep_tx":{ + // "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9" + // }, + // "estimated_sweep_tx_fee":144900000000000 + // }, + // "created_at":"2020-05-04T05:38:42.145162Z" + // } + // } + // } + const data = this.safeValue (response, 'data', {}); + const deposit = this.safeValue (data, 'deposit', {}); + return this.parseTransaction (deposit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetDeposits (params); + // + // { + // "data":{ + // "deposits":[ + // { + // "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "amount":"0.13", + // "currency":"ETH", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"credited", + // "relay_status":"", + // "network_data":{ + // "confirms":87, + // "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "sweep_balance":"0.150000000000000000", + // "confirms_required":80, + // "unsigned_sweep_tx":{ + // "chainId":1, + // "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "gas":"0x5208", + // "gasPrice":"0x19b45a500", + // "nonce":"0x0", + // "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "value":"0x214646b6347d800" + // }, + // "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "tx_index":"0x6f", + // "tx_value":"0.130000000000000000", + // "key_index":311, + // "blockheight":9877869, + // "signed_sweep_tx":{ + // "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9" + // }, + // "estimated_sweep_tx_fee":144900000000000 + // }, + // "created_at":"2020-05-04T05:38:42.145162Z" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const deposits = this.safeValue (data, 'deposits', []); + return this.parseTransactions (deposits, currency, since, limit); + } + + async fetchWithdrawal (id, code = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'withdraw_id': id, + }; + const response = await this.privateGetWithdrawWithdrawId (this.extend (request, params)); + // + // { + // data: { + // withdraw: { + // "id":25524, + // "amount":"0.0417463053014", + // "user_id":0, + // "currency":"ETH", + // "network_data":{ + // "unsigned_tx":{ + // "chainId":1, + // "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "gas":"0x5208", + // "gasPrice":"0x20c8558e9", + // "nonce":"0xf3", + // "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "value":"0x71712bcd113308" + // }, + // "estimated_tx_fee":184800004893000, + // "confirms_required":80, + // "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "confirms":83, + // "signed_tx":{ + // "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0" + // } + // }, + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"confirmed", + // "relay_status":"", + // "created_at":"2020-05-05T06:32:19.907061Z", + // "cancel_requested":false + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const withdrawal = this.safeValue (data, 'withdraw', {}); + return this.parseTransaction (withdrawal); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + } + const response = await this.privateGetWithdraws (params); + // { + // "data":{ + // "withdraws":[ + // { + // "id":25524, + // "amount":"0.0417463053014", + // "user_id":0, + // "currency":"ETH", + // "network_data":{ + // "unsigned_tx":{ + // "chainId":1, + // "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "gas":"0x5208", + // "gasPrice":"0x20c8558e9", + // "nonce":"0xf3", + // "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "value":"0x71712bcd113308" + // }, + // "estimated_tx_fee":184800004893000, + // "confirms_required":80, + // "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "confirms":83, + // "signed_tx":{ + // "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0" + // } + // }, + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"confirmed", + // "relay_status":"", + // "created_at":"2020-05-05T06:32:19.907061Z", + // "cancel_requested":false + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const withdrawals = this.safeValue (data, 'withdraws', []); + return this.parseTransactions (withdrawals, currency, since, limit); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits, fetchDeposit + // + // { + // "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "amount":"0.13", + // "currency":"ETH", + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"credited", + // "relay_status":"", + // "network_data":{ + // "confirms":87, + // "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "sweep_balance":"0.150000000000000000", + // "confirms_required":80, + // "unsigned_sweep_tx":{ + // "chainId":1, + // "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "gas":"0x5208", + // "gasPrice":"0x19b45a500", + // "nonce":"0x0", + // "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "value":"0x214646b6347d800" + // }, + // "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0", + // "tx_index":"0x6f", + // "tx_value":"0.130000000000000000", + // "key_index":311, + // "blockheight":9877869, + // "signed_sweep_tx":{ + // "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0", + // "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9" + // }, + // "estimated_sweep_tx_fee":144900000000000 + // }, + // "created_at":"2020-05-04T05:38:42.145162Z" + // } + // + // fetchWithdrawals, fetchWithdrawal + // + // { + // "id":25524, + // "amount":"0.0417463053014", + // "user_id":0, + // "currency":"ETH", + // "network_data":{ + // "unsigned_tx":{ + // "chainId":1, + // "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93", + // "gas":"0x5208", + // "gasPrice":"0x20c8558e9", + // "nonce":"0xf3", + // "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "value":"0x71712bcd113308" + // }, + // "estimated_tx_fee":184800004893000, + // "confirms_required":80, + // "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "confirms":83, + // "signed_tx":{ + // "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d", + // "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0" + // } + // }, + // "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2", + // "status":"confirmed", + // "relay_status":"", + // "created_at":"2020-05-05T06:32:19.907061Z", + // "cancel_requested":false + // } + // + // withdraw + // + // { + // "code": "initiated", + // "id": 3, + // "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process." + // } + // + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at')); + const id = this.safeString (transaction, 'id'); + const networkData = this.safeValue (transaction, 'network_data', {}); + const unsignedTx = this.safeValue (networkData, 'unsigned_tx', {}); + const addressFrom = this.safeString (unsignedTx, 'from'); + const txid = this.safeString (networkData, 'txid'); + let address = this.safeString (transaction, 'address'); + let tag = undefined; + if (address !== undefined) { + const parts = address.split (':'); + const numParts = parts.length; + if (numParts > 1) { + address = this.safeString (parts, 0); + tag = this.safeString (parts, 1); + } + } + const addressTo = address; + const tagFrom = undefined; + const tagTo = tag; + const cancelRequested = this.safeValue (transaction, 'cancel_requested'); + const type = (cancelRequested === undefined) ? 'deposit' : 'withdrawal'; + const amount = this.safeFloat (transaction, 'amount'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + let status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const statusCode = this.safeString (transaction, 'code'); + if (cancelRequested) { + status = 'canceled'; + } else if (status === undefined) { + status = this.parseTransactionStatus (statusCode); + } + const fee = undefined; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': tagFrom, + 'tagTo': tagTo, + 'tag': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': undefined, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'initiated': 'pending', + 'needs_create': 'pending', + 'credited': 'ok', + 'confirmed': 'ok', + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'address': address, + 'amount': amount, + 'currency': currency['id'], + }; + if (tag !== undefined) { + request['address'] += ':' + tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + // + // { + // "data": { + // "code": "initiated", + // "id": 3, + // "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process." + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const result = this.parseTransaction (data); + return this.extend (result, { + 'currency': code, + 'address': address, + 'addressTo': address, + 'tag': tag, + 'tagTo': tag, + 'amount': amount, + }); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = '/' + this.version + '/'; + if (api === 'private') { + url += 'user/'; + } + url += this.implodeParams (path, params); + const request = this.omit (params, this.extractParams (path)); + if (method === 'POST') { + body = this.json (request); + } else { + if (Object.keys (request).length) { + url += '?' + this.urlencode (request); + } + } + if (api === 'private') { + const timestamp = this.milliseconds ().toString (); + const bodyAsString = (method === 'POST') ? body : ''; + const auth = [ + method, + url, + timestamp, + bodyAsString, + this.secret, + ].join ("\n"); // eslint-disable-line quotes + const hash = this.hash (this.encode (auth), 'sha256', 'base64'); + let key = this.apiKey; + if (typeof key !== 'string') { + key = key.toString (); + } + const signature = 'HMAC-SHA256 ' + key + ':' + hash; + headers = { + 'Authorization': signature, + 'HMAC-Timestamp': timestamp, + }; + if (method === 'POST') { + headers['Content-Type'] = 'application/json'; + } + } + url = this.urls['api'] + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + // + // {"errors":[{"code":"insuff_funds","title":"Your available balance is too low for that action"}]} + // {"errors":[{"code": "invalid_auth","title": "Invalid HMAC signature"}]} + // + if (response === undefined) { + return; + } + const errors = this.safeValue (response, 'errors', []); + const numErrors = errors.length; + if (numErrors < 1) { + return; + } + const feedback = this.id + ' ' + body; + for (let i = 0; i < errors.length; i++) { + const error = errors[i]; + const errorCode = this.safeString (error, 'code'); + this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); + } + throw new ExchangeError (feedback); // unknown message + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/rightbtc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/rightbtc.js new file mode 100644 index 000000000..ca2de52dd --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/rightbtc.js @@ -0,0 +1,757 @@ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ROUND } = require ('./base/functions/number'); +const { ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, OrderNotFound } = require ('./base/errors'); + +module.exports = class rightbtc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'rightbtc', + 'name': 'RightBTC', + 'countries': [ 'AE' ], + 'has': { + 'cancelOrder': true, + 'createOrder': true, + 'privateAPI': false, + 'fetchBalance': true, + 'fetchClosedOrders': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': 'emulated', + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'timeframes': { + '1m': 'min1', + '5m': 'min5', + '15m': 'min15', + '30m': 'min30', + '1h': 'hr1', + '1d': 'day1', + '1w': 'week', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87182092-1f372700-c2ec-11ea-8f9e-01b4d3ff8941.jpg', + 'api': 'https://www.rightbtc.com/api', + 'www': 'https://www.rightbtc.com', + 'doc': [ + 'https://docs.rightbtc.com/api/', + ], + // eslint-disable-next-line no-useless-escape + // 'fees': 'https://www.rightbtc.com/\#\!/support/fee', + }, + 'api': { + 'public': { + 'get': [ + // 'getAssetsTradingPairs/zh', // 404 + 'trading_pairs', + 'ticker/{trading_pair}', + 'tickers', + 'depth/{trading_pair}', + 'depth/{trading_pair}/{count}', + 'trades/{trading_pair}', + 'trades/{trading_pair}/{count}', + 'candlestick/latest/{trading_pair}', + 'candlestick/{timeSymbol}/{trading_pair}', + 'candlestick/{timeSymbol}/{trading_pair}/{count}', + ], + }, + 'trader': { + 'get': [ + 'balance/{symbol}', + 'balances', + 'deposits/{asset}/{page}', + 'withdrawals/{asset}/{page}', + 'orderpage/{trading_pair}/{cursor}', + 'orders/{trading_pair}/{ids}', // ids are a slash-separated list of {id}/{id}/{id}/... + 'history/{trading_pair}/{ids}', + 'historys/{trading_pair}/{page}', + 'trading_pairs', + ], + 'post': [ + 'order', + ], + 'delete': [ + 'order/{trading_pair}/{ids}', + ], + }, + }, + // HARDCODING IS DEPRECATED, THE FEES BELOW SHOULD BE REWRITTEN + 'fees': { + 'trading': { + // min trading fees + // 0.0001 BTC + // 0.01 ETP + // 0.001 ETH + // 0.1 BITCNY + 'maker': 0.1 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'withdraw': { + // 'BTM': n => 3 + n * (1 / 100), + // 'ZDC': n => 1 + n * (0.5 / 100), + // 'ZGC': n => 0.5 + n * (0.5 / 100), + // 'BTS': n => 1 + n * (1 / 100), + // 'DLT': n => 3 + n * (1 / 100), + // 'SNT': n => 10 + n * (1 / 100), + // 'XNC': n => 1 + n * (1 / 100), + // 'ICO': n => 3 + n * (1 / 100), + // 'CMC': n => 1 + n * (0.5 / 100), + // 'GXS': n => 0.2 + n * (1 / 100), + // 'OBITS': n => 0.3 + n * (1 / 100), + // 'ICS': n => 2 + n * (1 / 100), + // 'TIC': n => 2 + n * (1 / 100), + // 'IND': n => 20 + n * (1 / 100), + // 'MVC': n => 20 + n * (1 / 100), + // 'BitCNY': n => 0.1 + n * (1 / 100), + // 'MTX': n => 1 + n * (1 / 100), + 'ETP': 0.01, + 'BTC': 0.0005, + 'ETH': 0.005, + 'ETC': 0.01, + 'STORJ': 3, + 'LTC': 0.01, + 'ZEC': 0.001, + 'BCC': 0.001, + 'XRB': 0, + 'NXS': 0.1, + }, + }, + }, + 'commonCurrencies': { + 'XRB': 'NANO', + }, + 'exceptions': { + 'ERR_USERTOKEN_NOT_FOUND': AuthenticationError, + 'ERR_ASSET_NOT_EXISTS': ExchangeError, + 'ERR_ASSET_NOT_AVAILABLE': ExchangeError, + 'ERR_BALANCE_NOT_ENOUGH': InsufficientFunds, + 'ERR_CREATE_ORDER': InvalidOrder, + 'ERR_CANDLESTICK_DATA': ExchangeError, + }, + }); + } + + async fetchMarkets (params = {}) { + // let zh = await this.publicGetGetAssetsTradingPairsZh (); + const markets = await this.publicGetTradingPairs (params); + const marketIds = Object.keys (markets); + const result = []; + for (let i = 0; i < marketIds.length; i++) { + const id = marketIds[i]; + const market = markets[id]; + const baseId = this.safeString (market, 'bid_asset_symbol'); + const quoteId = this.safeString (market, 'ask_asset_symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'bid_asset_decimals'), + 'price': this.safeInteger (market, 'ask_asset_decimals'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': Math.pow (10, precision['price']), + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': Math.pow (10, precision['price']), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + divideSafeFloat (x, key, divisor) { + const value = this.safeFloat (x, key); + if (value !== undefined) { + return value / divisor; + } + return value; + } + + parseTicker (ticker, market = undefined) { + const symbol = market['symbol']; + const timestamp = this.safeInteger (ticker, 'date'); + const last = this.divideSafeFloat (ticker, 'last', 1e8); + const high = this.divideSafeFloat (ticker, 'high', 1e8); + const low = this.divideSafeFloat (ticker, 'low', 1e8); + const bid = this.divideSafeFloat (ticker, 'buy', 1e8); + const ask = this.divideSafeFloat (ticker, 'sell', 1e8); + const baseVolume = this.divideSafeFloat (ticker, 'vol24h', 1e8); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': bid, + 'bidVolume': undefined, + 'ask': ask, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + }; + const response = await this.publicGetTickerTradingPair (this.extend (request, params)); + const result = this.safeValue (response, 'result'); + if (result === undefined) { + throw new ExchangeError (this.id + ' fetchTicker returned an empty response for symbol ' + symbol); + } + return this.parseTicker (result, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTickers (params); + const tickers = response['result']; + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = tickers[i]; + const id = ticker['market']; + if (!(id in this.marketsById)) { + continue; + } + const market = this.marketsById[id]; + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'trading_pair': this.marketId (symbol), + }; + let method = 'publicGetDepthTradingPair'; + if (limit !== undefined) { + method += 'Count'; + request['count'] = limit; + } + const response = await this[method] (this.extend (request, params)); + const bidsasks = {}; + const types = ['bid', 'ask']; + for (let ti = 0; ti < types.length; ti++) { + const type = types[ti]; + bidsasks[type] = []; + for (let i = 0; i < response['result'][type].length; i++) { + const [ price, amount, total ] = response['result'][type][i]; + bidsasks[type].push ([ + price / 1e8, + amount / 1e8, + total / 1e8, + ]); + } + } + return this.parseOrderBook (bidsasks, undefined, 'bid', 'ask'); + } + + parseTrade (trade, market = undefined) { + // + // { + // "order_id": 118735, + // "trade_id": 7, + // "trading_pair": "BTCCNY", + // "side": "B", + // "quantity": 1000000000, + // "price": 900000000, + // "created_at": "2017-06-06T20:45:27.000Z" + // } + // + let timestamp = this.safeInteger (trade, 'date'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + let id = this.safeString (trade, 'tid'); + id = this.safeString (trade, 'trade_id', id); + const orderId = this.safeString (trade, 'order_id'); + const price = this.divideSafeFloat (trade, 'price', 1e8); + let amount = this.safeFloat (trade, 'amount'); + amount = this.safeFloat (trade, 'quantity', amount); + if (amount !== undefined) { + amount = amount / 1e8; + } + const marketId = this.safeString (trade, 'trading_pair'); + const symbol = this.safeSymbol (marketId, market); + let cost = this.costToPrecision (symbol, price * amount); + cost = parseFloat (cost); + let side = this.safeStringLower (trade, 'side'); + if (side === 'b') { + side = 'buy'; + } else if (side === 's') { + side = 'sell'; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': 'limit', + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + }; + const response = await this.publicGetTradesTradingPair (this.extend (request, params)); + return this.parseTrades (response['result'], market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeInteger (ohlcv, 0), + parseFloat (ohlcv[2]) / 1e8, + parseFloat (ohlcv[3]) / 1e8, + parseFloat (ohlcv[4]) / 1e8, + parseFloat (ohlcv[5]) / 1e8, + parseFloat (ohlcv[1]) / 1e8, + ]; + } + + async fetchOHLCV (symbol, timeframe = '5m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'timeSymbol': this.timeframes[timeframe], + }; + const response = await this.publicGetCandlestickTimeSymbolTradingPair (this.extend (request, params)); + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.traderGetBalances (params); + // + // { + // "status": { + // "success": 1, + // "message": "GET_BALANCES" + // }, + // "result": [ + // { + // "asset": "ETP", + // "balance": "5000000000000", + // "frozen": "0", + // "state": "1" + // }, + // { + // "asset": "CNY", + // "balance": "10000000000000", + // "frozen": "240790000", + // "state": "1" + // } + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'result', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'asset'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + // https://github.com/ccxt/ccxt/issues/3873 + account['free'] = this.divideSafeFloat (balance, 'balance', 1e8); + account['used'] = this.divideSafeFloat (balance, 'frozen', 1e8); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const order = { + 'trading_pair': market['id'], + // We need to use decimalToPrecision here, since + // 0.036*1e8 === 3599999.9999999995 + // which would get truncated to 3599999 after parseInt + // which would then be rejected by rightBtc because it's too precise + 'quantity': parseInt (this.decimalToPrecision (amount * 1e8, ROUND, 0, this.precisionMode)), + 'limit': parseInt (this.decimalToPrecision (price * 1e8, ROUND, 0, this.precisionMode)), + 'type': type.toUpperCase (), + 'side': side.toUpperCase (), + }; + const response = await this.traderPostOrder (this.extend (order, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'ids': id, + }; + const response = await this.traderDeleteOrderTradingPairIds (this.extend (request, params)); + return response; + } + + parseOrderStatus (status) { + const statuses = { + 'NEW': 'open', + 'TRADE': 'closed', // TRADE means filled or partially filled orders + 'CANCEL': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder / fetchOpenOrders + // + // { + // "id": 4180528, + // "quantity": 20000000, + // "rest": 20000000, + // "limit": 1000000, + // "price": null, + // "side": "BUY", + // "created": 1496005693738 + // } + // + // fetchOrders + // + // { + // "trading_pair": "ETPCNY", + // "status": "TRADE", + // "fee": 0.23, + // "min_fee": 10000000, + // "created_at": "2017-05-25T00:12:27.000Z", + // "cost": 1152468000000, + // "limit": 3600000000, + // "id": 11060, + // "quantity": 32013000000, + // "filled_quantity": 32013000000 + // } + // + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString (order, 'trading_pair'); + const symbol = this.safeSymbol (marketId, market); + let timestamp = this.safeInteger (order, 'created'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (order, 'created_at')); + } + if ('time' in order) { + timestamp = order['time']; + } else if ('transactTime' in order) { + timestamp = order['transactTime']; + } + let price = this.safeFloat2 (order, 'limit', 'price'); + if (price !== undefined) { + price = price / 1e8; + } + const amount = this.divideSafeFloat (order, 'quantity', 1e8); + let filled = this.divideSafeFloat (order, 'filled_quantity', 1e8); + let remaining = this.divideSafeFloat (order, 'rest', 1e8); + const cost = this.divideSafeFloat (order, 'cost', 1e8); + // lines 483-494 should be generalized into a base class method + if (amount !== undefined) { + if (remaining === undefined) { + if (filled !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + if (filled === undefined) { + if (remaining !== undefined) { + filled = Math.max (0, amount - remaining); + } + } + } + const type = 'limit'; + const side = this.safeStringLower (order, 'side'); + const feeCost = this.divideSafeFloat (order, 'min_fee', 1e8); + let fee = undefined; + if (feeCost !== undefined) { + let feeCurrency = undefined; + if (market !== undefined) { + feeCurrency = market['quote']; + } + fee = { + 'rate': this.safeFloat (order, 'fee'), + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + const trades = undefined; + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + 'average': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'ids': id, + }; + const response = await this.traderGetOrdersTradingPairIds (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": "SUC_LIST_AVTICE_ORDERS" + // }, + // "result": [ + // { + // "id": 4180528, + // "quantity": 20000000, + // "rest": 20000000, + // "limit": 1000000, + // "price": null, + // "side": "BUY", + // "created": 1496005693738 + // } + // ] + // } + // + const orders = this.parseOrders (response['result'], market); + const ordersById = this.indexBy (orders, 'id'); + if (!(id in ordersById)) { + throw new OrderNotFound (this.id + ' fetchOrder could not find order ' + id.toString () + ' in open orders.'); + } + return ordersById[id]; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'cursor': 0, + }; + const response = await this.traderGetOrderpageTradingPairCursor (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": "SUC_LIST_AVTICE_ORDERS_PAGE" + // }, + // "result": { + // "cursor": "0", + // "orders": [ + // { + // "id": 4180528, + // "quantity": 20000000, + // "rest": 20000000, + // "limit": 1000000, + // "price": null, + // "side": "BUY", + // "created": 1496005693738 + // } + // ] + // } + // } + // + return this.parseOrders (response['result']['orders'], market, since, limit); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + let ids = this.safeString (params, 'ids'); + if ((symbol === undefined) || (ids === undefined)) { + throw new ArgumentsRequired (this.id + " fetchOrders requires a 'symbol' argument and an extra 'ids' parameter. The 'ids' should be an array or a string of one or more order ids separated with slashes."); // eslint-disable-line quotes + } + if (Array.isArray (ids)) { + ids = ids.join ('/'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'ids': ids, + }; + const response = await this.traderGetHistoryTradingPairIds (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": null + // }, + // "result": [ + // { + // "trading_pair": "ETPCNY", + // "status": "TRADE", + // "fee": 0.23, + // "min_fee": 10000000, + // "created_at": "2017-05-25T00:12:27.000Z", + // "cost": 1152468000000, + // "limit": 3600000000, + // "id": 11060, + // "quantity": 32013000000, + // "filled_quantity": 32013000000 + // } + // ] + // } + // + return this.parseOrders (response['result'], undefined, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'trading_pair': market['id'], + 'page': 0, + }; + const response = await this.traderGetHistorysTradingPairPage (this.extend (request, params)); + // + // let response = { + // "status": { + // "success": 1, + // "message": null + // }, + // "result": [ + // { + // "order_id": 118735, + // "trade_id": 7, + // "trading_pair": "BTCCNY", + // "side": "B", + // "quantity": 1000000000, + // "price": 900000000, + // "created_at": "2017-06-06T20:45:27.000Z" + // }, + // { + // "order_id": 118734, + // "trade_id": 7, + // "trading_pair": "BTCCNY", + // "side": "S", + // "quantity": 1000000000, + // "price": 900000000, + // "created_at": "2017-06-06T20:45:27.000Z" + // } + // ] + // } + // + return this.parseTrades (response['result'], undefined, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + '/' + api + '/' + this.implodeParams (path, params); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + headers = { + 'apikey': this.apiKey, + 'signature': this.secret, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + const status = this.safeValue (response, 'status'); + if (status !== undefined) { + // + // {"status":{"success":0,"message":"ERR_USERTOKEN_NOT_FOUND"}} + // + const success = this.safeString (status, 'success'); + if (success !== '1') { + const message = this.safeString (status, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions, message, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ripio.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ripio.js new file mode 100644 index 000000000..4c6595d11 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/ripio.js @@ -0,0 +1,945 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { AuthenticationError, ExchangeError, BadSymbol, BadRequest, InvalidOrder, ArgumentsRequired, OrderNotFound, InsufficientFunds, DDoSProtection } = require ('./base/errors'); +const { TICK_SIZE } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class ripio extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'ripio', + 'name': 'Ripio', + 'countries': [ 'AR', 'BR' ], // Argentina + 'rateLimit': 50, + 'version': 'v1', + 'pro': true, + // new metainfo interface + 'has': { + 'CORS': false, + 'cancelOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/94507548-a83d6a80-0218-11eb-9998-28b9cec54165.jpg', + 'api': { + 'public': 'https://api.exchange.ripio.com/api', + 'private': 'https://api.exchange.ripio.com/api', + }, + 'www': 'https://exchange.ripio.com', + 'doc': [ + 'https://exchange.ripio.com/en/api/', + ], + 'fees': 'https://exchange.ripio.com/en/fee', + }, + 'api': { + 'public': { + 'get': [ + 'rate/all/', + 'rate/{pair}/', + 'orderbook/{pair}/', + 'tradehistory/{pair}/', + 'pair/', + 'currency/', + 'orderbook/{pair}/depth/', + ], + }, + 'private': { + 'get': [ + 'balances/exchange_balances/', + 'order/{pair}/{order_id}/', + 'order/{pair}/', + 'trade/{pair}/', + ], + 'post': [ + 'order/{pair}/', + 'order/{pair}/{order_id}/cancel/', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': true, + 'percentage': true, + 'taker': 0.0 / 100, + 'maker': 0.0 / 100, + }, + }, + 'precisionMode': TICK_SIZE, + 'requiredCredentials': { + 'apiKey': true, + 'secret': false, + }, + 'exceptions': { + 'exact': { + }, + 'broad': { + 'Authentication credentials were not provided': AuthenticationError, // {"detail":"Authentication credentials were not provided."} + 'Disabled pair': BadSymbol, // {"status_code":400,"errors":{"pair":["Invalid/Disabled pair BTC_ARS"]},"message":"An error has occurred, please check the form."} + 'Invalid order type': InvalidOrder, // {"status_code":400,"errors":{"order_type":["Invalid order type. Valid options: ['MARKET', 'LIMIT']"]},"message":"An error has occurred, please check the form."} + 'Your balance is not enough': InsufficientFunds, // {"status_code":400,"errors":{"non_field_errors":["Your balance is not enough for this order: You have 0 BTC but you need 1 BTC"]},"message":"An error has occurred, please check the form."} + "Order couldn't be created": ExchangeError, // {'status_code': 400,'errors': {'non_field_errors': _("Order couldn't be created")}, 'message': _('Seems like an unexpected error occurred. Please try again later or write us to support@ripio.com if the problem persists.') } + // {"status_code":404,"errors":{"order":["Order 286e560e-b8a2-464b-8b84-15a7e2a67eab not found."]},"message":"An error has occurred, please check the form."} + // {"status_code":404,"errors":{"trade":["Trade not found."]},"message":"An error has occurred, please check the form."} + 'not found': OrderNotFound, + 'Invalid pair': BadSymbol, // {"status_code":400,"errors":{"pair":["Invalid pair FOOBAR"]},"message":"An error has occurred, please check the form."} + 'amount must be a number': BadRequest, // {"status_code":400,"errors":{"amount":["amount must be a number"]},"message":"An error has occurred, please check the form."} + 'Total must be at least': InvalidOrder, // {"status_code":400,"errors":{"non_field_errors":["Total must be at least 10."]},"message":"An error has occurred, please check the form."} + 'Account not found': BadRequest, // {"error_description": "Account not found."}, "status": 404 + 'Wrong password provided': AuthenticationError, // {'error': "Wrong password provided."}, “status_code”: 400 + 'User tokens limit': DDoSProtection, // {'error': "User tokens limit. Can't create more than 10 tokens."}, “status_code”: 400 + 'Something unexpected ocurred': ExchangeError, // {'status_code': 400, 'errors': {'non_field_errors': 'Something unexpected ocurred!'}, 'message': 'Seems like an unexpected error occurred. Please try again later or write us to support@ripio.com if the problem persists.'} + // {'status_code': 404, 'errors': {'account_balance': ['Exchange balance not found.']},'message': 'An error has occurred, please check the form.'} + // {'status_code': 404, 'errors': {'account_balance': ['Account balance not found.']},'message': 'An error has occurred, please check the form.'} + 'account_balance': BadRequest, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetPair (params); + // + // { + // "next":null, + // "previous":null, + // "results":[ + // { + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "symbol":"BTC_USDC", + // "fees":[ + // {"traded_volume":0.0,"maker_fee":0.0,"taker_fee":0.0,"cancellation_fee":0.0} + // ], + // "country":"ZZ", + // "enabled":true, + // "priority":10, + // "min_amount":"0.00001", + // "price_tick":"0.000001", + // "min_value":"10", + // "limit_price_threshold":"25.00" + // }, + // ] + // } + // + const result = []; + const results = this.safeValue (response, 'results', []); + for (let i = 0; i < results.length; i++) { + const market = results[i]; + const baseId = this.safeString (market, 'base'); + const quoteId = this.safeString (market, 'quote'); + const id = this.safeString (market, 'symbol'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeFloat (market, 'min_amount'), + 'price': this.safeFloat (market, 'price_tick'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'min_value'), + 'max': undefined, + }, + }; + const active = this.safeValue (market, 'enabled', true); + const fees = this.safeValue (market, 'fees', []); + const firstFee = this.safeValue (fees, 0, {}); + const maker = this.safeFloat (firstFee, 'maker_fee', 0.0); + const taker = this.safeFloat (firstFee, 'taker_fee', 0.0); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'precision': precision, + 'maker': maker, + 'taker': taker, + 'limits': limits, + 'info': market, + 'active': active, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrency (params); + // + // { + // "next":null, + // "previous":null, + // "results":[ + // { + // "name":"Argentine Peso", + // "symbol":"$", + // "currency":"ARS", + // "country":"AR", + // "decimal_places":"2", + // "enabled":true + // }, + // { + // "name":"Bitcoin Cash", + // "symbol":"BCH", + // "currency":"BCH", + // "country":"AR", + // "decimal_places":"8", + // "enabled":true + // }, + // { + // "name":"Bitcoin", + // "symbol":"BTC", + // "currency":"BTC", + // "country":"AR", + // "decimal_places":"8", + // "enabled":true + // } + // ] + // } + // + const results = this.safeValue (response, 'results', []); + const result = {}; + for (let i = 0; i < results.length; i++) { + const currency = results[i]; + const id = this.safeString (currency, 'currency'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + const active = this.safeValue (currency, 'enabled', true); + const precision = this.safeInteger (currency, 'decimal_places'); + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'info': currency, // the original payload + 'active': active, + 'fee': undefined, + 'precision': precision, + 'limits': { + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'withdraw': { 'min': undefined, 'max': undefined }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "pair":"BTC_USDC", + // "last_price":"10850.02", + // "low":"10720.03", + // "high":"10909.99", + // "variation":"1.21", + // "volume":"0.83868", + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "bid":"10811.00", + // "ask":"10720.03", + // "avg":"10851.47", + // "ask_volume":"0.00140", + // "bid_volume":"0.00185", + // "created_at":"2020-09-28 21:44:51.228920+00:00" + // } + // + const timestamp = this.parse8601 (this.safeString (ticker, 'created_at')); + const marketId = this.safeString (ticker, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'last_price'); + const average = this.safeFloat (ticker, 'avg'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': this.safeFloat (ticker, 'bid_volume'), + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': this.safeFloat (ticker, 'ask_volume'), + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': average, + 'baseVolume': undefined, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetRatePair (this.extend (request, params)); + // + // { + // "pair":"BTC_USDC", + // "last_price":"10850.02", + // "low":"10720.03", + // "high":"10909.99", + // "variation":"1.21", + // "volume":"0.83868", + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "bid":"10811.00", + // "ask":"10720.03", + // "avg":"10851.47", + // "ask_volume":"0.00140", + // "bid_volume":"0.00185", + // "created_at":"2020-09-28 21:44:51.228920+00:00" + // } + // + return this.parseTicker (response, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetRateAll (params); + // + // [ + // { + // "pair":"BTC_USDC", + // "last_price":"10850.02", + // "low":"10720.03", + // "high":"10909.99", + // "variation":"1.21", + // "volume":"0.83868", + // "base":"BTC", + // "base_name":"Bitcoin", + // "quote":"USDC", + // "quote_name":"USD Coin", + // "bid":"10811.00", + // "ask":"10720.03", + // "avg":"10851.47", + // "ask_volume":"0.00140", + // "bid_volume":"0.00185", + // "created_at":"2020-09-28 21:44:51.228920+00:00" + // } + // ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const ticker = this.parseTicker (response[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetOrderbookPair (this.extend (request, params)); + // + // { + // "buy":[ + // {"amount":"0.00230","total":"24.95","price":"10850.02"}, + // {"amount":"0.07920","total":"858.52","price":"10840.00"}, + // {"amount":"0.00277","total":"30.00","price":"10833.03"}, + // ], + // "sell":[ + // {"amount":"0.03193","total":"348.16","price":"10904.00"}, + // {"amount":"0.00210","total":"22.90","price":"10905.70"}, + // {"amount":"0.00300","total":"32.72","price":"10907.98"}, + // ], + // "updated_id":47225 + // } + // + const orderbook = this.parseOrderBook (response, undefined, 'buy', 'sell', 'price', 'amount'); + orderbook['nonce'] = this.safeInteger (response, 'updated_id'); + return orderbook; + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades, private fetchMyTrades + // + // { + // "created_at":1601322501, + // "amount":"0.00276", + // "price":"10850.020000", + // "side":"SELL", + // "pair":"BTC_USDC", + // "taker_fee":"0", + // "taker_side":"SELL", + // "maker_fee":"0", + // "taker":2577953, + // "maker":2577937 + // } + // + // createOrder fills + // + // { + // "pair":"BTC_USDC", + // "exchanged":0.002, + // "match_price":10593.99, + // "maker_fee":0.0, + // "taker_fee":0.0, + // "timestamp":1601730306942 + // } + // + const id = this.safeString (trade, 'id'); + let timestamp = this.safeInteger (trade, 'timestamp'); + timestamp = this.safeTimestamp (trade, 'created_at', timestamp); + let side = this.safeString (trade, 'side'); + const takerSide = this.safeString (trade, 'taker_side'); + const takerOrMaker = (takerSide === side) ? 'taker' : 'maker'; + if (side !== undefined) { + side = side.toLowerCase (); + } + const price = this.safeFloat2 (trade, 'price', 'match_price'); + const amount = this.safeFloat2 (trade, 'amount', 'exchanged'); + let cost = undefined; + if ((amount !== undefined) && (price !== undefined)) { + cost = amount * price; + } + const marketId = this.safeString (trade, 'pair'); + market = this.safeMarket (marketId, market); + const feeCost = this.safeFloat (trade, takerOrMaker + '_fee'); + const orderId = this.safeString (trade, takerOrMaker); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': (side === 'buy') ? market['base'] : market['quote'], + }; + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': market['symbol'], + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + const response = await this.publicGetTradehistoryPair (this.extend (request, params)); + // + // [ + // { + // "created_at":1601322501, + // "amount":"0.00276", + // "price":"10850.020000", + // "side":"SELL", + // "pair":"BTC_USDC", + // "taker_fee":"0", + // "taker_side":"SELL", + // "maker_fee":"0", + // "taker":2577953, + // "maker":2577937 + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalancesExchangeBalances (params); + // + // [ + // { + // "id":603794, + // "currency":"USD Coin", + // "symbol":"USDC", + // "available":"0", + // "locked":"0", + // "code":"exchange", + // "balance_type":"crypto" + // }, + // ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'symbol'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const uppercaseType = type.toUpperCase (); + const uppercaseSide = side.toUpperCase (); + const request = { + 'pair': market['id'], + 'order_type': uppercaseType, // LIMIT, MARKET + 'side': uppercaseSide, // BUY or SELL + 'amount': this.amountToPrecision (symbol, amount), + }; + if (uppercaseType === 'LIMIT') { + request['limit_price'] = this.priceToPrecision (symbol, price); + } + const response = await this.privatePostOrderPair (this.extend (request, params)); + // + // { + // "order_id": "160f523c-f6ef-4cd1-a7c9-1a8ede1468d8", + // "pair": "BTC_ARS", + // "side": "BUY", + // "amount": "0.00400", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "OPEN", + // "created_at": 1578413945, + // "filled": "0.00000", + // "limit_price": "10.00", + // "stop_price": null, + // "distance": null + // } + // + // createOrder market type + // + // { + // "order_id":"d6b60c01-8624-44f2-9e6c-9e8cd677ea5c", + // "pair":"BTC_USDC", + // "side":"BUY", + // "amount":"0.00200", + // "notional":"50", + // "fill_or_kill":false, + // "all_or_none":false, + // "order_type":"MARKET", + // "status":"OPEN", + // "created_at":1601730306, + // "filled":"0.00000", + // "fill_price":10593.99, + // "fee":0.0, + // "fills":[ + // { + // "pair":"BTC_USDC", + // "exchanged":0.002, + // "match_price":10593.99, + // "maker_fee":0.0, + // "taker_fee":0.0, + // "timestamp":1601730306942 + // } + // ], + // "filled_at":"2020-10-03T13:05:06.942186Z", + // "limit_price":"0.000000", + // "stop_price":null, + // "distance":null + // } + // + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'order_id': id, + }; + const response = await this.privatePostOrderPairOrderIdCancel (this.extend (request, params)); + // + // { + // "order_id": "286e560e-b8a2-464b-8b84-15a7e2a67eab", + // "pair": "BTC_ARS", + // "side": "SELL", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "CANC", + // "created_at": 1575472707, + // "filled": "0.00000", + // "limit_price": "681000.00", + // "stop_price": null, + // "distance": null + // } + // + return this.parseOrder (response, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'order_id': id, + }; + const response = await this.privateGetOrderPairOrderId (this.extend (request, params)); + // + // { + // "order_id": "0b4ff48e-cfd6-42db-8d8c-3b536da447af", + // "pair": "BTC_ARS", + // "side": "BUY", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "OPEN", + // "created_at": 1575472944, + // "filled": "0.00000", + // "limit_price": "661000.00", + // "stop_price": null, + // "distance": null + // } + // + return this.parseOrder (response, market); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + // 'status': 'OPEN,PART,CLOS,CANC,COMP', + // 'offset': 0, + // 'limit': limit, + }; + if (limit !== undefined) { + request['offset'] = limit; + } + const response = await this.privateGetOrderPair (this.extend (request, params)); + // + // { + // "next": "https://api.exchange.ripio.com/api/v1/order/BTC_ARS/?limit=20&offset=20&page=1&page_size=25&status=OPEN%2CPART", + // "previous": null, + // "results": { + // "data": [ + // { + // "order_id": "ca74280b-6966-4b73-a720-68709078922b", + // "pair": "BTC_ARS", + // "side": "SELL", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "OPEN", + // "created_at": 1578340134, + // "filled": "0.00000", + // "limit_price": "665000.00", + // "stop_price": null, + // "distance": null + // }, + // ] + // } + // } + // + const results = this.safeValue (response, 'results', {}); + const data = this.safeValue (results, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'OPEN,PART', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'CLOS,CANC,COMP', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + 'OPEN': 'open', + 'PART': 'open', + 'CLOS': 'canceled', + 'CANC': 'canceled', + 'COMP': 'closed', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder, cancelOrder, fetchOpenOrders, fetchClosedOrders, fetchOrders, fetchOrder + // + // { + // "order_id": "286e560e-b8a2-464b-8b84-15a7e2a67eab", + // "pair": "BTC_ARS", + // "side": "SELL", + // "amount": "0.00100", + // "notional": null, + // "fill_or_kill": false, + // "all_or_none": false, + // "order_type": "LIMIT", + // "status": "CANC", + // "created_at": 1575472707, + // "filled": "0.00000", + // "limit_price": "681000.00", + // "stop_price": null, + // "distance": null + // } + // + // { + // "order_id":"d6b60c01-8624-44f2-9e6c-9e8cd677ea5c", + // "pair":"BTC_USDC", + // "side":"BUY", + // "amount":"0.00200", + // "notional":"50", + // "fill_or_kill":false, + // "all_or_none":false, + // "order_type":"MARKET", + // "status":"OPEN", + // "created_at":1601730306, + // "filled":"0.00000", + // "fill_price":10593.99, + // "fee":0.0, + // "fills":[ + // { + // "pair":"BTC_USDC", + // "exchanged":0.002, + // "match_price":10593.99, + // "maker_fee":0.0, + // "taker_fee":0.0, + // "timestamp":1601730306942 + // } + // ], + // "filled_at":"2020-10-03T13:05:06.942186Z", + // "limit_price":"0.000000", + // "stop_price":null, + // "distance":null + // } + // + const id = this.safeString (order, 'order_id'); + const amount = this.safeFloat (order, 'amount'); + let cost = this.safeFloat (order, 'notional'); + const type = this.safeStringLower (order, 'order_type'); + const priceField = (type === 'market') ? 'fill_price' : 'limit_price'; + const price = this.safeFloat (order, priceField); + const side = this.safeStringLower (order, 'side'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'created_at'); + let average = this.safeValue (order, 'fill_price'); + let filled = this.safeFloat (order, 'filled'); + let remaining = undefined; + const fills = this.safeValue (order, 'fills'); + let trades = undefined; + let lastTradeTimestamp = undefined; + if (fills !== undefined) { + const numFills = fills.length; + if (numFills > 0) { + filled = 0; + cost = 0; + trades = this.parseTrades (fills, market, undefined, undefined, { + 'order': id, + 'side': side, + }); + for (let i = 0; i < trades.length; i++) { + const trade = trades[i]; + filled = this.sum (trade['amount'], filled); + cost = this.sum (trade['cost'], cost); + lastTradeTimestamp = trade['timestamp']; + } + if ((average === undefined) && (filled > 0)) { + average = cost / filled; + } + } + } + if (filled !== undefined) { + if ((cost === undefined) && (price !== undefined)) { + cost = price * filled; + } + if (amount !== undefined) { + remaining = Math.max (0, amount - filled); + } + } + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const stopPrice = this.safeFloat (order, 'stop_price'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': trades, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + // 'offset': 0, + // 'limit': limit, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradePair (this.extend (request, params)); + // + // { + // "next": "https://api.exchange.ripio.com/api/v1/trade//?limit=20&offset=20", + // "previous": null, + // "results": { + // "data": [ + // { + // "created_at": 1578414028, + // "amount": "0.00100", + // "price": "665000.00", + // "side": "BUY", + // "taker_fee": "0", + // "taker_side": "BUY", + // "match_price": "66500000", + // "maker_fee": "0", + // "taker": 4892, + // "maker": 4889 + // }, + // ] + // } + // } + // + const results = this.safeValue (response, 'results', {}); + const data = this.safeValue (results, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = '/' + this.version + '/' + this.implodeParams (path, params); + let url = this.urls['api'][api] + request; + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + if (method === 'POST') { + body = this.json (query); + } else { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + this.apiKey, + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"detail":"Authentication credentials were not provided."} + // {"status_code":400,"errors":{"pair":["Invalid pair FOOBAR"]},"message":"An error has occurred, please check the form."} + // {"status_code":400,"errors":{"order_type":["Invalid order type. Valid options: ['MARKET', 'LIMIT']"]},"message":"An error has occurred, please check the form."} + // {"status_code":400,"errors":{"non_field_errors":"Something unexpected ocurred!"},"message":"Seems like an unexpected error occurred. Please try again later or write us to support@ripio.com if the problem persists."} + // {"status_code":400,"errors":{"pair":["Invalid/Disabled pair BTC_ARS"]},"message":"An error has occurred, please check the form."} + // + const detail = this.safeString (response, 'detail'); + if (detail !== undefined) { + const feedback = this.id + ' ' + body; + // this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], detail, feedback); + } + const errors = this.safeValue (response, 'errors'); + if (errors !== undefined) { + const feedback = this.id + ' ' + body; + const keys = Object.keys (errors); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const error = this.safeValue (errors, key, []); + const message = this.safeString (error, 0); + // this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/southxchange.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/southxchange.js new file mode 100644 index 000000000..126cfdfb3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/southxchange.js @@ -0,0 +1,697 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class southxchange extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'southxchange', + 'name': 'SouthXchange', + 'countries': [ 'AR' ], // Argentina + 'rateLimit': 1000, + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27838912-4f94ec8a-60f6-11e7-9e5d-bbf9bd50a559.jpg', + 'api': 'https://www.southxchange.com/api', + 'www': 'https://www.southxchange.com', + 'doc': 'https://www.southxchange.com/Home/Api', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'price/{symbol}', + 'prices', + 'book/{symbol}', + 'trades/{symbol}', + ], + }, + 'private': { + 'post': [ + 'cancelMarketOrders', + 'cancelOrder', + 'getOrder', + 'generatenewaddress', + 'listOrders', + 'listBalances', + 'listTransactions', + 'placeOrder', + 'withdraw', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.1 / 100, + 'taker': 0.3 / 100, + }, + }, + 'commonCurrencies': { + 'BHD': 'Bithold', + 'GHOST': 'GHOSTPRISM', + 'MTC': 'Marinecoin', + 'SMT': 'SmartNode', + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const baseId = market[0]; + const quoteId = market[1]; + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = baseId + '/' + quoteId; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': undefined, + 'info': market, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostListBalances (params); + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'Currency'); + const code = this.safeCurrencyCode (currencyId); + const deposited = this.safeFloat (balance, 'Deposited'); + const unconfirmed = this.safeFloat (balance, 'Unconfirmed'); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'Available'); + account['total'] = this.sum (deposited, unconfirmed); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + const response = await this.publicGetBookSymbol (this.extend (request, params)); + return this.parseOrderBook (response, undefined, 'BuyOrders', 'SellOrders', 'Price', 'Amount'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'Last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': undefined, + 'low': undefined, + 'bid': this.safeFloat (ticker, 'Bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'Ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': this.safeFloat (ticker, 'Variation24Hr'), + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'Volume24Hr'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetPrices (params); + const tickers = this.indexBy (response, 'Market'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetPriceSymbol (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market) { + const timestamp = this.safeTimestamp (trade, 'At'); + const price = this.safeFloat (trade, 'Price'); + const amount = this.safeFloat (trade, 'Amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const side = this.safeString (trade, 'Type'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': undefined, + 'order': undefined, + 'type': undefined, + 'side': side, + 'price': price, + 'takerOrMaker': undefined, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + }; + const response = await this.publicGetTradesSymbol (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOrder (order, market = undefined) { + const status = 'open'; + const baseId = this.safeString (order, 'ListingCurrency'); + const quoteId = this.safeString (order, 'ReferenceCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const timestamp = undefined; + const price = this.safeFloat (order, 'LimitPrice'); + const amount = this.safeFloat (order, 'OriginalAmount'); + const remaining = this.safeFloat (order, 'Amount'); + let filled = undefined; + let cost = undefined; + if (amount !== undefined) { + cost = price * amount; + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const type = 'limit'; + const side = this.safeStringLower (order, 'Type'); + const id = this.safeString (order, 'Code'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'average': undefined, + 'trades': undefined, + }; + return result; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const response = await this.privatePostListOrders (params); + return this.parseOrders (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'listingCurrency': market['base'], + 'referenceCurrency': market['quote'], + 'type': side, + 'amount': amount, + }; + if (type === 'limit') { + request['limitPrice'] = price; + } + const response = await this.privatePostPlaceOrder (this.extend (request, params)); + const id = JSON.parse (response); + return { + 'info': response, + 'id': id, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderCode': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privatePostGeneratenewaddress (this.extend (request, params)); + // + // the exchange API returns a quoted-quoted-string + // + // "\"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93\"" + // + let address = response; + if (address[0] === '"') { + address = JSON.parse (address); + if (address[0] === '"') { + address = JSON.parse (address); + } + } + const parts = address.split ('|'); + const numParts = parts.length; + address = parts[0]; + this.checkAddress (address); + let tag = undefined; + if (numParts > 1) { + tag = parts[1]; + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'address': address, + 'amount': amount, + }; + if (tag !== undefined) { + request['address'] = address + '|' + tag; + } + const response = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + parseLedgerEntryType (type) { + const types = { + 'trade': 'trade', + 'tradefee': 'fee', + 'withdraw': 'transaction', + 'deposit': 'transaction', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // + const id = this.safeString (item, 'MovementId'); + let direction = undefined; + const account = undefined; + let referenceId = this.safeString2 (item, 'TradeId', 'OrderCode'); + referenceId = this.safeString (item, 'Hash', referenceId); + const referenceAccount = this.safeString (item, 'Address'); + const type = this.safeString (item, 'Type'); + const ledgerEntryType = this.parseLedgerEntryType (type); + const code = this.safeCurrencyCode (this.safeString (item, 'CurrencyCode'), currency); + let amount = this.safeFloat (item, 'Amount'); + const after = this.safeFloat (item, 'TotalBalance'); + let before = undefined; + if (amount !== undefined) { + if (after !== undefined) { + before = after - amount; + } + if (type === 'withdrawal') { + direction = 'out'; + } else if (type === 'deposit') { + direction = 'in'; + } else if ((type === 'trade') || (type === 'tradefee')) { + direction = (amount < 0) ? 'out' : 'in'; + amount = Math.abs (amount); + } + } + const timestamp = this.parse8601 (this.safeString (item, 'Date')); + const fee = undefined; + const status = this.safeString (item, 'Status'); + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': ledgerEntryType, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchLedger() requires a code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + limit = (limit === undefined) ? 50 : limit; + const request = { + 'Currency': currency['id'], + // 'TransactionType': 'transactions', // deposits, withdrawals, depositswithdrawals, transactions + // 'PageIndex': 0, + 'PageSize': limit, // max 50 + 'SortField': 'Date', + // 'Descending': true, + }; + const pageIndex = this.safeInteger (params, 'PageIndex'); + if (pageIndex === undefined) { + request['Descending'] = true; + } + const response = await this.privatePostListTransactions (this.extend (request, params)); + // + // fetchLedger ('BTC') + // + // { + // "TotalElements":2, + // "Result":[ + // { + // "Date":"2020-08-07T13:06:22.117", + // "CurrencyCode":"BTC", + // "Amount":-0.000000301000000000, + // "TotalBalance":0.000100099000000000, + // "Type":"tradefee", + // "Status":"confirmed", + // "Address":null, + // "Hash":null, + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":5298215, + // "MovementId":null + // }, + // { + // "Date":"2020-08-07T13:06:22.117", + // "CurrencyCode":"BTC", + // "Amount":0.000100400000000000, + // "TotalBalance":0.000100400000000000, + // "Type":"trade", + // "Status":"confirmed", + // "Address":null, + // "Hash":null, + // "Price":11811.474849000000000000, + // "OtherAmount":1.185872, + // "OtherCurrency":"USDT", + // "OrderCode":"78389610", + // "TradeId":5298215, + // "MovementId":null + // } + // ] + // } + // + // fetchLedger ('BTC'), same trade, other side + // + // { + // "TotalElements":2, + // "Result":[ + // { + // "Date":"2020-08-07T13:06:22.133", + // "CurrencyCode":"USDT", + // "Amount":-1.185872000000000000, + // "TotalBalance":26.428806000000000000, + // "Type":"trade", + // "Status":"confirmed", + // "Address":null, + // "Hash":null, + // "Price":11811.474849000000000000, + // "OtherAmount":0.000100400, + // "OtherCurrency":"BTC", + // "OrderCode":"78389610", + // "TradeId":5298215, + // "MovementId":null + // }, + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // ] + // } + // + const result = this.safeValue (response, 'Result', []); + return this.parseLedger (result, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'processed': 'pending', + 'confirmed': 'ok', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // + const id = this.safeString (transaction, 'MovementId'); + const amount = this.safeFloat (transaction, 'Amount'); + const address = this.safeString (transaction, 'Address'); + const addressTo = address; + const addressFrom = undefined; + const tag = undefined; + const tagTo = tag; + const tagFrom = undefined; + const txid = this.safeString (transaction, 'Hash'); + const type = this.safeString (transaction, 'Type'); + const timestamp = this.parse8601 (this.safeString (transaction, 'Date')); + const status = this.parseTransactionStatus (this.safeString (transaction, 'Status')); + const currencyId = this.safeString (transaction, 'CurrencyCode'); + const code = this.safeCurrencyCode (currencyId, currency); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'addressTo': addressTo, + 'addressFrom': addressFrom, + 'tag': tag, + 'tagTo': tagTo, + 'tagFrom': tagFrom, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a code argument'); + } + await this.loadMarkets (); + const currency = this.currency (code); + limit = (limit === undefined) ? 50 : limit; + const request = { + 'Currency': currency['id'], + 'TransactionType': 'depositswithdrawals', // deposits, withdrawals, depositswithdrawals, transactions + // 'PageIndex': 0, + 'PageSize': limit, // max 50 + 'SortField': 'Date', + // 'Descending': true, + }; + const pageIndex = this.safeInteger (params, 'PageIndex'); + if (pageIndex === undefined) { + request['Descending'] = true; + } + const response = await this.privatePostListTransactions (this.extend (request, params)); + // + // { + // "TotalElements":2, + // "Result":[ + // { + // "Date":"2020-08-07T12:36:52.72", + // "CurrencyCode":"USDT", + // "Amount":27.614678000000000000, + // "TotalBalance":27.614678000000000000, + // "Type":"deposit", + // "Status":"confirmed", + // "Address":"0x4d43674209fcb66cc21469a6e5e52de7dd5bcd93", + // "Hash":"0x1809f1950c51a2f64fd2c4a27d4b06450fd249883fd91c852b79a99a124837f3", + // "Price":0.0, + // "OtherAmount":0.0, + // "OtherCurrency":null, + // "OrderCode":null, + // "TradeId":null, + // "MovementId":2732259 + // } + // ] + // } + // + const result = this.safeValue (response, 'Result', []); + return this.parseTransactions (result, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'TransactionType': 'deposits', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'TransactionType': 'withdrawals', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const url = this.urls['api'] + '/' + this.implodeParams (path, params); + let query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + query = this.extend ({ + 'key': this.apiKey, + 'nonce': nonce, + }, query); + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + 'Hash': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/BN/bn.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/BN/bn.js new file mode 100644 index 000000000..6b2485698 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/BN/bn.js @@ -0,0 +1,3526 @@ +(function (module, exports) { + 'use strict'; + + // Utils + function assert (val, msg) { + if (!val) throw new Error(msg || 'Assertion failed'); + } + + // Could use `inherits` module, but don't want to move from single file + // architecture yet. + function inherits (ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + + // BN + + function BN (number, base, endian) { + if (BN.isBN(number)) { + return number; + } + + this.negative = 0; + this.words = null; + this.length = 0; + + // Reduction context + this.red = null; + + if (number !== null) { + if (base === 'le' || base === 'be') { + endian = base; + base = 10; + } + + this._init(number || 0, base || 10, endian || 'be'); + } + } + if (typeof module === 'object') { + module.exports = BN; + } else { + exports.BN = BN; + } + + BN.BN = BN; + BN.wordSize = 26; + + var Buffer; + try { + Buffer = require('buffer').Buffer; + } catch (e) { + } + + BN.isBN = function isBN (num) { + if (num instanceof BN) { + return true; + } + + return num !== null && typeof num === 'object' && + num.constructor.wordSize === BN.wordSize && Array.isArray(num.words); + }; + + BN.max = function max (left, right) { + if (left.cmp(right) > 0) return left; + return right; + }; + + BN.min = function min (left, right) { + if (left.cmp(right) < 0) return left; + return right; + }; + + BN.prototype._init = function init (number, base, endian) { + if (typeof number === 'number') { + return this._initNumber(number, base, endian); + } + + if (typeof number === 'object') { + return this._initArray(number, base, endian); + } + + if (base === 'hex') { + base = 16; + } + assert(base === (base | 0) && base >= 2 && base <= 36); + + number = number.toString().replace(/\s+/g, ''); + var start = 0; + if (number[0] === '-') { + start++; + } + + if (base === 16) { + this._parseHex(number, start); + } else { + this._parseBase(number, base, start); + } + + if (number[0] === '-') { + this.negative = 1; + } + + this._strip(); + + if (endian !== 'le') return; + + this._initArray(this.toArray(), base, endian); + }; + + BN.prototype._initNumber = function _initNumber (number, base, endian) { + if (number < 0) { + this.negative = 1; + number = -number; + } + if (number < 0x4000000) { + this.words = [number & 0x3ffffff]; + this.length = 1; + } else if (number < 0x10000000000000) { + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff + ]; + this.length = 2; + } else { + assert(number < 0x20000000000000); // 2 ^ 53 (unsafe) + this.words = [ + number & 0x3ffffff, + (number / 0x4000000) & 0x3ffffff, + 1 + ]; + this.length = 3; + } + + if (endian !== 'le') return; + + // Reverse the bytes + this._initArray(this.toArray(), base, endian); + }; + + BN.prototype._initArray = function _initArray (number, base, endian) { + // Perhaps a Uint8Array + assert(typeof number.length === 'number'); + if (number.length <= 0) { + this.words = [0]; + this.length = 1; + return this; + } + + this.length = Math.ceil(number.length / 3); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + var j, w; + var off = 0; + if (endian === 'be') { + for (i = number.length - 1, j = 0; i >= 0; i -= 3) { + w = number[i] | (number[i - 1] << 8) | (number[i - 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } else if (endian === 'le') { + for (i = 0, j = 0; i < number.length; i += 3) { + w = number[i] | (number[i + 1] << 8) | (number[i + 2] << 16); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + } + return this._strip(); + }; + + function parseHex (str, start, end) { + var r = 0; + var len = Math.min(str.length, end); + var z = 0; + for (var i = start; i < len; i++) { + var c = str.charCodeAt(i) - 48; + + r <<= 4; + + var b; + + // 'a' - 'f' + if (c >= 49 && c <= 54) { + b = c - 49 + 0xa; + + // 'A' - 'F' + } else if (c >= 17 && c <= 22) { + b = c - 17 + 0xa; + + // '0' - '9' + } else { + b = c; + } + + r |= b; + z |= b; + } + + assert(!(z & 0xf0), 'Invalid character in ' + str); + return r; + } + + BN.prototype._parseHex = function _parseHex (number, start) { + // Create possibly bigger array to ensure that it fits the number + this.length = Math.ceil((number.length - start) / 6); + this.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + this.words[i] = 0; + } + + var j, w; + // Scan 24-bit chunks and add them to the number + var off = 0; + for (i = number.length - 6, j = 0; i >= start; i -= 6) { + w = parseHex(number, i, i + 6); + this.words[j] |= (w << off) & 0x3ffffff; + // NOTE: `0x3fffff` is intentional here, 26bits max shift + 24bit hex limb + this.words[j + 1] |= w >>> (26 - off) & 0x3fffff; + off += 24; + if (off >= 26) { + off -= 26; + j++; + } + } + if (i + 6 !== start) { + w = parseHex(number, start, i + 6); + this.words[j] |= (w << off) & 0x3ffffff; + this.words[j + 1] |= w >>> (26 - off) & 0x3fffff; + } + this._strip(); + }; + + function parseBase (str, start, end, mul) { + var r = 0; + var b = 0; + var len = Math.min(str.length, end); + for (var i = start; i < len; i++) { + var c = str.charCodeAt(i) - 48; + + r *= mul; + + // 'a' + if (c >= 49) { + b = c - 49 + 0xa; + + // 'A' + } else if (c >= 17) { + b = c - 17 + 0xa; + + // '0' - '9' + } else { + b = c; + } + assert(c >= 0 && b < mul, 'Invalid character'); + r += b; + } + return r; + } + + BN.prototype._parseBase = function _parseBase (number, base, start) { + // Initialize as zero + this.words = [0]; + this.length = 1; + + // Find length of limb in base + for (var limbLen = 0, limbPow = 1; limbPow <= 0x3ffffff; limbPow *= base) { + limbLen++; + } + limbLen--; + limbPow = (limbPow / base) | 0; + + var total = number.length - start; + var mod = total % limbLen; + var end = Math.min(total, total - mod) + start; + + var word = 0; + for (var i = start; i < end; i += limbLen) { + word = parseBase(number, i, i + limbLen, base); + + this.imuln(limbPow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + + if (mod !== 0) { + var pow = 1; + word = parseBase(number, i, number.length, base); + + for (i = 0; i < mod; i++) { + pow *= base; + } + + this.imuln(pow); + if (this.words[0] + word < 0x4000000) { + this.words[0] += word; + } else { + this._iaddn(word); + } + } + }; + + BN.prototype.copy = function copy (dest) { + dest.words = new Array(this.length); + for (var i = 0; i < this.length; i++) { + dest.words[i] = this.words[i]; + } + dest.length = this.length; + dest.negative = this.negative; + dest.red = this.red; + }; + + BN.prototype._move = function _move (dest) { + dest.words = this.words; + dest.length = this.length; + dest.negative = this.negative; + dest.red = this.red; + }; + + BN.prototype.clone = function clone () { + var r = new BN(null); + this.copy(r); + return r; + }; + + BN.prototype._expand = function _expand (size) { + while (this.length < size) { + this.words[this.length++] = 0; + } + return this; + }; + + // Remove leading `0` from `this` + BN.prototype._strip = function strip () { + while (this.length > 1 && this.words[this.length - 1] === 0) { + this.length--; + } + return this._normSign(); + }; + + BN.prototype._normSign = function _normSign () { + // -0 = 0 + if (this.length === 1 && this.words[0] === 0) { + this.negative = 0; + } + return this; + }; + + // Check Symbol.for because not everywhere where Symbol defined + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Browser_compatibility + if (typeof Symbol !== 'undefined' && typeof Symbol.for === 'function') { + BN.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspect; + } else { + BN.prototype.inspect = inspect; + } + + function inspect () { + return (this.red ? ''; + } + + /* + + var zeros = []; + var groupSizes = []; + var groupBases = []; + + var s = ''; + var i = -1; + while (++i < BN.wordSize) { + zeros[i] = s; + s += '0'; + } + groupSizes[0] = 0; + groupSizes[1] = 0; + groupBases[0] = 0; + groupBases[1] = 0; + var base = 2 - 1; + while (++base < 36 + 1) { + var groupSize = 0; + var groupBase = 1; + while (groupBase < (1 << BN.wordSize) / base) { + groupBase *= base; + groupSize += 1; + } + groupSizes[base] = groupSize; + groupBases[base] = groupBase; + } + + */ + + var zeros = [ + '', + '0', + '00', + '000', + '0000', + '00000', + '000000', + '0000000', + '00000000', + '000000000', + '0000000000', + '00000000000', + '000000000000', + '0000000000000', + '00000000000000', + '000000000000000', + '0000000000000000', + '00000000000000000', + '000000000000000000', + '0000000000000000000', + '00000000000000000000', + '000000000000000000000', + '0000000000000000000000', + '00000000000000000000000', + '000000000000000000000000', + '0000000000000000000000000' + ]; + + var groupSizes = [ + 0, 0, + 25, 16, 12, 11, 10, 9, 8, + 8, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5 + ]; + + var groupBases = [ + 0, 0, + 33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216, + 43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625, + 16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632, + 6436343, 7962624, 9765625, 11881376, 14348907, 17210368, 20511149, + 24300000, 28629151, 33554432, 39135393, 45435424, 52521875, 60466176 + ]; + + BN.prototype.toString = function toString (base, padding) { + base = base || 10; + padding = padding | 0 || 1; + + var out; + if (base === 16 || base === 'hex') { + out = ''; + var off = 0; + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = this.words[i]; + var word = (((w << off) | carry) & 0xffffff).toString(16); + carry = (w >>> (24 - off)) & 0xffffff; + if (carry !== 0 || i !== this.length - 1) { + out = zeros[6 - word.length] + word + out; + } else { + out = word + out; + } + off += 2; + if (off >= 26) { + off -= 26; + i--; + } + } + if (carry !== 0) { + out = carry.toString(16) + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + if (base === (base | 0) && base >= 2 && base <= 36) { + // var groupSize = Math.floor(BN.wordSize * Math.LN2 / Math.log(base)); + var groupSize = groupSizes[base]; + // var groupBase = Math.pow(base, groupSize); + var groupBase = groupBases[base]; + out = ''; + var c = this.clone(); + c.negative = 0; + while (!c.isZero()) { + var r = c.modrn(groupBase).toString(base); + c = c.idivn(groupBase); + + if (!c.isZero()) { + out = zeros[groupSize - r.length] + r + out; + } else { + out = r + out; + } + } + if (this.isZero()) { + out = '0' + out; + } + while (out.length % padding !== 0) { + out = '0' + out; + } + if (this.negative !== 0) { + out = '-' + out; + } + return out; + } + + assert(false, 'Base should be between 2 and 36'); + }; + + BN.prototype.toNumber = function toNumber () { + var ret = this.words[0]; + if (this.length === 2) { + ret += this.words[1] * 0x4000000; + } else if (this.length === 3 && this.words[2] === 0x01) { + // NOTE: at this stage it is known that the top bit is set + ret += 0x10000000000000 + (this.words[1] * 0x4000000); + } else if (this.length > 2) { + assert(false, 'Number can only safely store up to 53 bits'); + } + return (this.negative !== 0) ? -ret : ret; + }; + + BN.prototype.toJSON = function toJSON () { + return this.toString(16, 2); + }; + + if (Buffer) { + BN.prototype.toBuffer = function toBuffer (endian, length) { + return this.toArrayLike(Buffer, endian, length); + }; + } + + BN.prototype.toArray = function toArray (endian, length) { + return this.toArrayLike(Array, endian, length); + }; + + var allocate = function allocate (ArrayType, size) { + if (ArrayType.allocUnsafe) { + return ArrayType.allocUnsafe(size); + } + return new ArrayType(size); + }; + + BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) { + this._strip(); + + var byteLength = this.byteLength(); + var reqLength = length || Math.max(1, byteLength); + assert(byteLength <= reqLength, 'byte array longer than desired length'); + assert(reqLength > 0, 'Requested array length <= 0'); + + var res = allocate(ArrayType, reqLength); + var postfix = endian === 'le' ? 'LE' : 'BE'; + this['_toArrayLike' + postfix](res, byteLength); + return res; + }; + + BN.prototype._toArrayLikeLE = function _toArrayLikeLE (res, byteLength) { + var position = 0; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position++] = word & 0xff; + if (position < res.length) { + res[position++] = (word >> 8) & 0xff; + } + if (position < res.length) { + res[position++] = (word >> 16) & 0xff; + } + + if (shift === 6) { + if (position < res.length) { + res[position++] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position < res.length) { + res[position++] = carry; + + while (position < res.length) { + res[position++] = 0; + } + } + }; + + BN.prototype._toArrayLikeBE = function _toArrayLikeBE (res, byteLength) { + var position = res.length - 1; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; + + res[position--] = word & 0xff; + if (position >= 0) { + res[position--] = (word >> 8) & 0xff; + } + if (position >= 0) { + res[position--] = (word >> 16) & 0xff; + } + + if (shift === 6) { + if (position >= 0) { + res[position--] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } + + if (position >= 0) { + res[position--] = carry; + + while (position >= 0) { + res[position--] = 0; + } + } + }; + + if (Math.clz32) { + BN.prototype._countBits = function _countBits (w) { + return 32 - Math.clz32(w); + }; + } else { + BN.prototype._countBits = function _countBits (w) { + var t = w; + var r = 0; + if (t >= 0x1000) { + r += 13; + t >>>= 13; + } + if (t >= 0x40) { + r += 7; + t >>>= 7; + } + if (t >= 0x8) { + r += 4; + t >>>= 4; + } + if (t >= 0x02) { + r += 2; + t >>>= 2; + } + return r + t; + }; + } + + BN.prototype._zeroBits = function _zeroBits (w) { + // Short-cut + if (w === 0) return 26; + + var t = w; + var r = 0; + if ((t & 0x1fff) === 0) { + r += 13; + t >>>= 13; + } + if ((t & 0x7f) === 0) { + r += 7; + t >>>= 7; + } + if ((t & 0xf) === 0) { + r += 4; + t >>>= 4; + } + if ((t & 0x3) === 0) { + r += 2; + t >>>= 2; + } + if ((t & 0x1) === 0) { + r++; + } + return r; + }; + + // Return number of used bits in a BN + BN.prototype.bitLength = function bitLength () { + var w = this.words[this.length - 1]; + var hi = this._countBits(w); + return (this.length - 1) * 26 + hi; + }; + + function toBitArray (num) { + var w = new Array(num.bitLength()); + + for (var bit = 0; bit < w.length; bit++) { + var off = (bit / 26) | 0; + var wbit = bit % 26; + + w[bit] = (num.words[off] >>> wbit) & 0x01; + } + + return w; + } + + // Number of trailing zero bits + BN.prototype.zeroBits = function zeroBits () { + if (this.isZero()) return 0; + + var r = 0; + for (var i = 0; i < this.length; i++) { + var b = this._zeroBits(this.words[i]); + r += b; + if (b !== 26) break; + } + return r; + }; + + BN.prototype.byteLength = function byteLength () { + return Math.ceil(this.bitLength() / 8); + }; + + BN.prototype.toTwos = function toTwos (width) { + if (this.negative !== 0) { + return this.abs().inotn(width).iaddn(1); + } + return this.clone(); + }; + + BN.prototype.fromTwos = function fromTwos (width) { + if (this.testn(width - 1)) { + return this.notn(width).iaddn(1).ineg(); + } + return this.clone(); + }; + + BN.prototype.isNeg = function isNeg () { + return this.negative !== 0; + }; + + // Return negative clone of `this` + BN.prototype.neg = function neg () { + return this.clone().ineg(); + }; + + BN.prototype.ineg = function ineg () { + if (!this.isZero()) { + this.negative ^= 1; + } + + return this; + }; + + // Or `num` with `this` in-place + BN.prototype.iuor = function iuor (num) { + while (this.length < num.length) { + this.words[this.length++] = 0; + } + + for (var i = 0; i < num.length; i++) { + this.words[i] = this.words[i] | num.words[i]; + } + + return this._strip(); + }; + + BN.prototype.ior = function ior (num) { + assert((this.negative | num.negative) === 0); + return this.iuor(num); + }; + + // Or `num` with `this` + BN.prototype.or = function or (num) { + if (this.length > num.length) return this.clone().ior(num); + return num.clone().ior(this); + }; + + BN.prototype.uor = function uor (num) { + if (this.length > num.length) return this.clone().iuor(num); + return num.clone().iuor(this); + }; + + // And `num` with `this` in-place + BN.prototype.iuand = function iuand (num) { + // b = min-length(num, this) + var b; + if (this.length > num.length) { + b = num; + } else { + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = this.words[i] & num.words[i]; + } + + this.length = b.length; + + return this._strip(); + }; + + BN.prototype.iand = function iand (num) { + assert((this.negative | num.negative) === 0); + return this.iuand(num); + }; + + // And `num` with `this` + BN.prototype.and = function and (num) { + if (this.length > num.length) return this.clone().iand(num); + return num.clone().iand(this); + }; + + BN.prototype.uand = function uand (num) { + if (this.length > num.length) return this.clone().iuand(num); + return num.clone().iuand(this); + }; + + // Xor `num` with `this` in-place + BN.prototype.iuxor = function iuxor (num) { + // a.length > b.length + var a; + var b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + for (var i = 0; i < b.length; i++) { + this.words[i] = a.words[i] ^ b.words[i]; + } + + if (this !== a) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = a.length; + + return this._strip(); + }; + + BN.prototype.ixor = function ixor (num) { + assert((this.negative | num.negative) === 0); + return this.iuxor(num); + }; + + // Xor `num` with `this` + BN.prototype.xor = function xor (num) { + if (this.length > num.length) return this.clone().ixor(num); + return num.clone().ixor(this); + }; + + BN.prototype.uxor = function uxor (num) { + if (this.length > num.length) return this.clone().iuxor(num); + return num.clone().iuxor(this); + }; + + // Not ``this`` with ``width`` bitwidth + BN.prototype.inotn = function inotn (width) { + assert(typeof width === 'number' && width >= 0); + + var bytesNeeded = Math.ceil(width / 26) | 0; + var bitsLeft = width % 26; + + // Extend the buffer with leading zeroes + this._expand(bytesNeeded); + + if (bitsLeft > 0) { + bytesNeeded--; + } + + // Handle complete words + for (var i = 0; i < bytesNeeded; i++) { + this.words[i] = ~this.words[i] & 0x3ffffff; + } + + // Handle the residue + if (bitsLeft > 0) { + this.words[i] = ~this.words[i] & (0x3ffffff >> (26 - bitsLeft)); + } + + // And remove leading zeroes + return this._strip(); + }; + + BN.prototype.notn = function notn (width) { + return this.clone().inotn(width); + }; + + // Set `bit` of `this` + BN.prototype.setn = function setn (bit, val) { + assert(typeof bit === 'number' && bit >= 0); + + var off = (bit / 26) | 0; + var wbit = bit % 26; + + this._expand(off + 1); + + if (val) { + this.words[off] = this.words[off] | (1 << wbit); + } else { + this.words[off] = this.words[off] & ~(1 << wbit); + } + + return this._strip(); + }; + + // Add `num` to `this` in-place + BN.prototype.iadd = function iadd (num) { + var r; + + // negative + positive + if (this.negative !== 0 && num.negative === 0) { + this.negative = 0; + r = this.isub(num); + this.negative ^= 1; + return this._normSign(); + + // positive + negative + } else if (this.negative === 0 && num.negative !== 0) { + num.negative = 0; + r = this.isub(num); + num.negative = 1; + return r._normSign(); + } + + // a.length > b.length + var a, b; + if (this.length > num.length) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) + (b.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >>> 26; + } + + this.length = a.length; + if (carry !== 0) { + this.words[this.length] = carry; + this.length++; + // Copy the rest of the words + } else if (a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + return this; + }; + + // Add `num` to `this` + BN.prototype.add = function add (num) { + var res; + if (num.negative !== 0 && this.negative === 0) { + num.negative = 0; + res = this.sub(num); + num.negative ^= 1; + return res; + } else if (num.negative === 0 && this.negative !== 0) { + this.negative = 0; + res = num.sub(this); + this.negative = 1; + return res; + } + + if (this.length > num.length) return this.clone().iadd(num); + + return num.clone().iadd(this); + }; + + // Subtract `num` from `this` in-place + BN.prototype.isub = function isub (num) { + // this - (-num) = this + num + if (num.negative !== 0) { + num.negative = 0; + var r = this.iadd(num); + num.negative = 1; + return r._normSign(); + + // -this - num = -(this + num) + } else if (this.negative !== 0) { + this.negative = 0; + this.iadd(num); + this.negative = 1; + return this._normSign(); + } + + // At this point both numbers are positive + var cmp = this.cmp(num); + + // Optimization - zeroify + if (cmp === 0) { + this.negative = 0; + this.length = 1; + this.words[0] = 0; + return this; + } + + // a > b + var a, b; + if (cmp > 0) { + a = this; + b = num; + } else { + a = num; + b = this; + } + + var carry = 0; + for (var i = 0; i < b.length; i++) { + r = (a.words[i] | 0) - (b.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + for (; carry !== 0 && i < a.length; i++) { + r = (a.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + + // Copy rest of the words + if (carry === 0 && i < a.length && a !== this) { + for (; i < a.length; i++) { + this.words[i] = a.words[i]; + } + } + + this.length = Math.max(this.length, i); + + if (a !== this) { + this.negative = 1; + } + + return this._strip(); + }; + + // Subtract `num` from `this` + BN.prototype.sub = function sub (num) { + return this.clone().isub(num); + }; + + function smallMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + var len = (self.length + num.length) | 0; + out.length = len; + len = (len - 1) | 0; + + // Peel one iteration (compiler can't do it, because of code complexity) + var a = self.words[0] | 0; + var b = num.words[0] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + var carry = (r / 0x4000000) | 0; + out.words[0] = lo; + + for (var k = 1; k < len; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = carry >>> 26; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = (k - j) | 0; + a = self.words[i] | 0; + b = num.words[j] | 0; + r = a * b + rword; + ncarry += (r / 0x4000000) | 0; + rword = r & 0x3ffffff; + } + out.words[k] = rword | 0; + carry = ncarry | 0; + } + if (carry !== 0) { + out.words[k] = carry | 0; + } else { + out.length--; + } + + return out._strip(); + } + + // TODO(indutny): it may be reasonable to omit it for users who don't need + // to work with 256-bit numbers, otherwise it gives 20% improvement for 256-bit + // multiplication (like elliptic secp256k1). + var comb10MulTo = function comb10MulTo (self, num, out) { + var a = self.words; + var b = num.words; + var o = out.words; + var c = 0; + var lo; + var mid; + var hi; + var a0 = a[0] | 0; + var al0 = a0 & 0x1fff; + var ah0 = a0 >>> 13; + var a1 = a[1] | 0; + var al1 = a1 & 0x1fff; + var ah1 = a1 >>> 13; + var a2 = a[2] | 0; + var al2 = a2 & 0x1fff; + var ah2 = a2 >>> 13; + var a3 = a[3] | 0; + var al3 = a3 & 0x1fff; + var ah3 = a3 >>> 13; + var a4 = a[4] | 0; + var al4 = a4 & 0x1fff; + var ah4 = a4 >>> 13; + var a5 = a[5] | 0; + var al5 = a5 & 0x1fff; + var ah5 = a5 >>> 13; + var a6 = a[6] | 0; + var al6 = a6 & 0x1fff; + var ah6 = a6 >>> 13; + var a7 = a[7] | 0; + var al7 = a7 & 0x1fff; + var ah7 = a7 >>> 13; + var a8 = a[8] | 0; + var al8 = a8 & 0x1fff; + var ah8 = a8 >>> 13; + var a9 = a[9] | 0; + var al9 = a9 & 0x1fff; + var ah9 = a9 >>> 13; + var b0 = b[0] | 0; + var bl0 = b0 & 0x1fff; + var bh0 = b0 >>> 13; + var b1 = b[1] | 0; + var bl1 = b1 & 0x1fff; + var bh1 = b1 >>> 13; + var b2 = b[2] | 0; + var bl2 = b2 & 0x1fff; + var bh2 = b2 >>> 13; + var b3 = b[3] | 0; + var bl3 = b3 & 0x1fff; + var bh3 = b3 >>> 13; + var b4 = b[4] | 0; + var bl4 = b4 & 0x1fff; + var bh4 = b4 >>> 13; + var b5 = b[5] | 0; + var bl5 = b5 & 0x1fff; + var bh5 = b5 >>> 13; + var b6 = b[6] | 0; + var bl6 = b6 & 0x1fff; + var bh6 = b6 >>> 13; + var b7 = b[7] | 0; + var bl7 = b7 & 0x1fff; + var bh7 = b7 >>> 13; + var b8 = b[8] | 0; + var bl8 = b8 & 0x1fff; + var bh8 = b8 >>> 13; + var b9 = b[9] | 0; + var bl9 = b9 & 0x1fff; + var bh9 = b9 >>> 13; + + out.negative = self.negative ^ num.negative; + out.length = 19; + /* k = 0 */ + lo = Math.imul(al0, bl0); + mid = Math.imul(al0, bh0); + mid = (mid + Math.imul(ah0, bl0)) | 0; + hi = Math.imul(ah0, bh0); + var w0 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w0 >>> 26)) | 0; + w0 &= 0x3ffffff; + /* k = 1 */ + lo = Math.imul(al1, bl0); + mid = Math.imul(al1, bh0); + mid = (mid + Math.imul(ah1, bl0)) | 0; + hi = Math.imul(ah1, bh0); + lo = (lo + Math.imul(al0, bl1)) | 0; + mid = (mid + Math.imul(al0, bh1)) | 0; + mid = (mid + Math.imul(ah0, bl1)) | 0; + hi = (hi + Math.imul(ah0, bh1)) | 0; + var w1 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w1 >>> 26)) | 0; + w1 &= 0x3ffffff; + /* k = 2 */ + lo = Math.imul(al2, bl0); + mid = Math.imul(al2, bh0); + mid = (mid + Math.imul(ah2, bl0)) | 0; + hi = Math.imul(ah2, bh0); + lo = (lo + Math.imul(al1, bl1)) | 0; + mid = (mid + Math.imul(al1, bh1)) | 0; + mid = (mid + Math.imul(ah1, bl1)) | 0; + hi = (hi + Math.imul(ah1, bh1)) | 0; + lo = (lo + Math.imul(al0, bl2)) | 0; + mid = (mid + Math.imul(al0, bh2)) | 0; + mid = (mid + Math.imul(ah0, bl2)) | 0; + hi = (hi + Math.imul(ah0, bh2)) | 0; + var w2 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w2 >>> 26)) | 0; + w2 &= 0x3ffffff; + /* k = 3 */ + lo = Math.imul(al3, bl0); + mid = Math.imul(al3, bh0); + mid = (mid + Math.imul(ah3, bl0)) | 0; + hi = Math.imul(ah3, bh0); + lo = (lo + Math.imul(al2, bl1)) | 0; + mid = (mid + Math.imul(al2, bh1)) | 0; + mid = (mid + Math.imul(ah2, bl1)) | 0; + hi = (hi + Math.imul(ah2, bh1)) | 0; + lo = (lo + Math.imul(al1, bl2)) | 0; + mid = (mid + Math.imul(al1, bh2)) | 0; + mid = (mid + Math.imul(ah1, bl2)) | 0; + hi = (hi + Math.imul(ah1, bh2)) | 0; + lo = (lo + Math.imul(al0, bl3)) | 0; + mid = (mid + Math.imul(al0, bh3)) | 0; + mid = (mid + Math.imul(ah0, bl3)) | 0; + hi = (hi + Math.imul(ah0, bh3)) | 0; + var w3 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w3 >>> 26)) | 0; + w3 &= 0x3ffffff; + /* k = 4 */ + lo = Math.imul(al4, bl0); + mid = Math.imul(al4, bh0); + mid = (mid + Math.imul(ah4, bl0)) | 0; + hi = Math.imul(ah4, bh0); + lo = (lo + Math.imul(al3, bl1)) | 0; + mid = (mid + Math.imul(al3, bh1)) | 0; + mid = (mid + Math.imul(ah3, bl1)) | 0; + hi = (hi + Math.imul(ah3, bh1)) | 0; + lo = (lo + Math.imul(al2, bl2)) | 0; + mid = (mid + Math.imul(al2, bh2)) | 0; + mid = (mid + Math.imul(ah2, bl2)) | 0; + hi = (hi + Math.imul(ah2, bh2)) | 0; + lo = (lo + Math.imul(al1, bl3)) | 0; + mid = (mid + Math.imul(al1, bh3)) | 0; + mid = (mid + Math.imul(ah1, bl3)) | 0; + hi = (hi + Math.imul(ah1, bh3)) | 0; + lo = (lo + Math.imul(al0, bl4)) | 0; + mid = (mid + Math.imul(al0, bh4)) | 0; + mid = (mid + Math.imul(ah0, bl4)) | 0; + hi = (hi + Math.imul(ah0, bh4)) | 0; + var w4 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w4 >>> 26)) | 0; + w4 &= 0x3ffffff; + /* k = 5 */ + lo = Math.imul(al5, bl0); + mid = Math.imul(al5, bh0); + mid = (mid + Math.imul(ah5, bl0)) | 0; + hi = Math.imul(ah5, bh0); + lo = (lo + Math.imul(al4, bl1)) | 0; + mid = (mid + Math.imul(al4, bh1)) | 0; + mid = (mid + Math.imul(ah4, bl1)) | 0; + hi = (hi + Math.imul(ah4, bh1)) | 0; + lo = (lo + Math.imul(al3, bl2)) | 0; + mid = (mid + Math.imul(al3, bh2)) | 0; + mid = (mid + Math.imul(ah3, bl2)) | 0; + hi = (hi + Math.imul(ah3, bh2)) | 0; + lo = (lo + Math.imul(al2, bl3)) | 0; + mid = (mid + Math.imul(al2, bh3)) | 0; + mid = (mid + Math.imul(ah2, bl3)) | 0; + hi = (hi + Math.imul(ah2, bh3)) | 0; + lo = (lo + Math.imul(al1, bl4)) | 0; + mid = (mid + Math.imul(al1, bh4)) | 0; + mid = (mid + Math.imul(ah1, bl4)) | 0; + hi = (hi + Math.imul(ah1, bh4)) | 0; + lo = (lo + Math.imul(al0, bl5)) | 0; + mid = (mid + Math.imul(al0, bh5)) | 0; + mid = (mid + Math.imul(ah0, bl5)) | 0; + hi = (hi + Math.imul(ah0, bh5)) | 0; + var w5 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w5 >>> 26)) | 0; + w5 &= 0x3ffffff; + /* k = 6 */ + lo = Math.imul(al6, bl0); + mid = Math.imul(al6, bh0); + mid = (mid + Math.imul(ah6, bl0)) | 0; + hi = Math.imul(ah6, bh0); + lo = (lo + Math.imul(al5, bl1)) | 0; + mid = (mid + Math.imul(al5, bh1)) | 0; + mid = (mid + Math.imul(ah5, bl1)) | 0; + hi = (hi + Math.imul(ah5, bh1)) | 0; + lo = (lo + Math.imul(al4, bl2)) | 0; + mid = (mid + Math.imul(al4, bh2)) | 0; + mid = (mid + Math.imul(ah4, bl2)) | 0; + hi = (hi + Math.imul(ah4, bh2)) | 0; + lo = (lo + Math.imul(al3, bl3)) | 0; + mid = (mid + Math.imul(al3, bh3)) | 0; + mid = (mid + Math.imul(ah3, bl3)) | 0; + hi = (hi + Math.imul(ah3, bh3)) | 0; + lo = (lo + Math.imul(al2, bl4)) | 0; + mid = (mid + Math.imul(al2, bh4)) | 0; + mid = (mid + Math.imul(ah2, bl4)) | 0; + hi = (hi + Math.imul(ah2, bh4)) | 0; + lo = (lo + Math.imul(al1, bl5)) | 0; + mid = (mid + Math.imul(al1, bh5)) | 0; + mid = (mid + Math.imul(ah1, bl5)) | 0; + hi = (hi + Math.imul(ah1, bh5)) | 0; + lo = (lo + Math.imul(al0, bl6)) | 0; + mid = (mid + Math.imul(al0, bh6)) | 0; + mid = (mid + Math.imul(ah0, bl6)) | 0; + hi = (hi + Math.imul(ah0, bh6)) | 0; + var w6 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w6 >>> 26)) | 0; + w6 &= 0x3ffffff; + /* k = 7 */ + lo = Math.imul(al7, bl0); + mid = Math.imul(al7, bh0); + mid = (mid + Math.imul(ah7, bl0)) | 0; + hi = Math.imul(ah7, bh0); + lo = (lo + Math.imul(al6, bl1)) | 0; + mid = (mid + Math.imul(al6, bh1)) | 0; + mid = (mid + Math.imul(ah6, bl1)) | 0; + hi = (hi + Math.imul(ah6, bh1)) | 0; + lo = (lo + Math.imul(al5, bl2)) | 0; + mid = (mid + Math.imul(al5, bh2)) | 0; + mid = (mid + Math.imul(ah5, bl2)) | 0; + hi = (hi + Math.imul(ah5, bh2)) | 0; + lo = (lo + Math.imul(al4, bl3)) | 0; + mid = (mid + Math.imul(al4, bh3)) | 0; + mid = (mid + Math.imul(ah4, bl3)) | 0; + hi = (hi + Math.imul(ah4, bh3)) | 0; + lo = (lo + Math.imul(al3, bl4)) | 0; + mid = (mid + Math.imul(al3, bh4)) | 0; + mid = (mid + Math.imul(ah3, bl4)) | 0; + hi = (hi + Math.imul(ah3, bh4)) | 0; + lo = (lo + Math.imul(al2, bl5)) | 0; + mid = (mid + Math.imul(al2, bh5)) | 0; + mid = (mid + Math.imul(ah2, bl5)) | 0; + hi = (hi + Math.imul(ah2, bh5)) | 0; + lo = (lo + Math.imul(al1, bl6)) | 0; + mid = (mid + Math.imul(al1, bh6)) | 0; + mid = (mid + Math.imul(ah1, bl6)) | 0; + hi = (hi + Math.imul(ah1, bh6)) | 0; + lo = (lo + Math.imul(al0, bl7)) | 0; + mid = (mid + Math.imul(al0, bh7)) | 0; + mid = (mid + Math.imul(ah0, bl7)) | 0; + hi = (hi + Math.imul(ah0, bh7)) | 0; + var w7 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w7 >>> 26)) | 0; + w7 &= 0x3ffffff; + /* k = 8 */ + lo = Math.imul(al8, bl0); + mid = Math.imul(al8, bh0); + mid = (mid + Math.imul(ah8, bl0)) | 0; + hi = Math.imul(ah8, bh0); + lo = (lo + Math.imul(al7, bl1)) | 0; + mid = (mid + Math.imul(al7, bh1)) | 0; + mid = (mid + Math.imul(ah7, bl1)) | 0; + hi = (hi + Math.imul(ah7, bh1)) | 0; + lo = (lo + Math.imul(al6, bl2)) | 0; + mid = (mid + Math.imul(al6, bh2)) | 0; + mid = (mid + Math.imul(ah6, bl2)) | 0; + hi = (hi + Math.imul(ah6, bh2)) | 0; + lo = (lo + Math.imul(al5, bl3)) | 0; + mid = (mid + Math.imul(al5, bh3)) | 0; + mid = (mid + Math.imul(ah5, bl3)) | 0; + hi = (hi + Math.imul(ah5, bh3)) | 0; + lo = (lo + Math.imul(al4, bl4)) | 0; + mid = (mid + Math.imul(al4, bh4)) | 0; + mid = (mid + Math.imul(ah4, bl4)) | 0; + hi = (hi + Math.imul(ah4, bh4)) | 0; + lo = (lo + Math.imul(al3, bl5)) | 0; + mid = (mid + Math.imul(al3, bh5)) | 0; + mid = (mid + Math.imul(ah3, bl5)) | 0; + hi = (hi + Math.imul(ah3, bh5)) | 0; + lo = (lo + Math.imul(al2, bl6)) | 0; + mid = (mid + Math.imul(al2, bh6)) | 0; + mid = (mid + Math.imul(ah2, bl6)) | 0; + hi = (hi + Math.imul(ah2, bh6)) | 0; + lo = (lo + Math.imul(al1, bl7)) | 0; + mid = (mid + Math.imul(al1, bh7)) | 0; + mid = (mid + Math.imul(ah1, bl7)) | 0; + hi = (hi + Math.imul(ah1, bh7)) | 0; + lo = (lo + Math.imul(al0, bl8)) | 0; + mid = (mid + Math.imul(al0, bh8)) | 0; + mid = (mid + Math.imul(ah0, bl8)) | 0; + hi = (hi + Math.imul(ah0, bh8)) | 0; + var w8 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w8 >>> 26)) | 0; + w8 &= 0x3ffffff; + /* k = 9 */ + lo = Math.imul(al9, bl0); + mid = Math.imul(al9, bh0); + mid = (mid + Math.imul(ah9, bl0)) | 0; + hi = Math.imul(ah9, bh0); + lo = (lo + Math.imul(al8, bl1)) | 0; + mid = (mid + Math.imul(al8, bh1)) | 0; + mid = (mid + Math.imul(ah8, bl1)) | 0; + hi = (hi + Math.imul(ah8, bh1)) | 0; + lo = (lo + Math.imul(al7, bl2)) | 0; + mid = (mid + Math.imul(al7, bh2)) | 0; + mid = (mid + Math.imul(ah7, bl2)) | 0; + hi = (hi + Math.imul(ah7, bh2)) | 0; + lo = (lo + Math.imul(al6, bl3)) | 0; + mid = (mid + Math.imul(al6, bh3)) | 0; + mid = (mid + Math.imul(ah6, bl3)) | 0; + hi = (hi + Math.imul(ah6, bh3)) | 0; + lo = (lo + Math.imul(al5, bl4)) | 0; + mid = (mid + Math.imul(al5, bh4)) | 0; + mid = (mid + Math.imul(ah5, bl4)) | 0; + hi = (hi + Math.imul(ah5, bh4)) | 0; + lo = (lo + Math.imul(al4, bl5)) | 0; + mid = (mid + Math.imul(al4, bh5)) | 0; + mid = (mid + Math.imul(ah4, bl5)) | 0; + hi = (hi + Math.imul(ah4, bh5)) | 0; + lo = (lo + Math.imul(al3, bl6)) | 0; + mid = (mid + Math.imul(al3, bh6)) | 0; + mid = (mid + Math.imul(ah3, bl6)) | 0; + hi = (hi + Math.imul(ah3, bh6)) | 0; + lo = (lo + Math.imul(al2, bl7)) | 0; + mid = (mid + Math.imul(al2, bh7)) | 0; + mid = (mid + Math.imul(ah2, bl7)) | 0; + hi = (hi + Math.imul(ah2, bh7)) | 0; + lo = (lo + Math.imul(al1, bl8)) | 0; + mid = (mid + Math.imul(al1, bh8)) | 0; + mid = (mid + Math.imul(ah1, bl8)) | 0; + hi = (hi + Math.imul(ah1, bh8)) | 0; + lo = (lo + Math.imul(al0, bl9)) | 0; + mid = (mid + Math.imul(al0, bh9)) | 0; + mid = (mid + Math.imul(ah0, bl9)) | 0; + hi = (hi + Math.imul(ah0, bh9)) | 0; + var w9 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w9 >>> 26)) | 0; + w9 &= 0x3ffffff; + /* k = 10 */ + lo = Math.imul(al9, bl1); + mid = Math.imul(al9, bh1); + mid = (mid + Math.imul(ah9, bl1)) | 0; + hi = Math.imul(ah9, bh1); + lo = (lo + Math.imul(al8, bl2)) | 0; + mid = (mid + Math.imul(al8, bh2)) | 0; + mid = (mid + Math.imul(ah8, bl2)) | 0; + hi = (hi + Math.imul(ah8, bh2)) | 0; + lo = (lo + Math.imul(al7, bl3)) | 0; + mid = (mid + Math.imul(al7, bh3)) | 0; + mid = (mid + Math.imul(ah7, bl3)) | 0; + hi = (hi + Math.imul(ah7, bh3)) | 0; + lo = (lo + Math.imul(al6, bl4)) | 0; + mid = (mid + Math.imul(al6, bh4)) | 0; + mid = (mid + Math.imul(ah6, bl4)) | 0; + hi = (hi + Math.imul(ah6, bh4)) | 0; + lo = (lo + Math.imul(al5, bl5)) | 0; + mid = (mid + Math.imul(al5, bh5)) | 0; + mid = (mid + Math.imul(ah5, bl5)) | 0; + hi = (hi + Math.imul(ah5, bh5)) | 0; + lo = (lo + Math.imul(al4, bl6)) | 0; + mid = (mid + Math.imul(al4, bh6)) | 0; + mid = (mid + Math.imul(ah4, bl6)) | 0; + hi = (hi + Math.imul(ah4, bh6)) | 0; + lo = (lo + Math.imul(al3, bl7)) | 0; + mid = (mid + Math.imul(al3, bh7)) | 0; + mid = (mid + Math.imul(ah3, bl7)) | 0; + hi = (hi + Math.imul(ah3, bh7)) | 0; + lo = (lo + Math.imul(al2, bl8)) | 0; + mid = (mid + Math.imul(al2, bh8)) | 0; + mid = (mid + Math.imul(ah2, bl8)) | 0; + hi = (hi + Math.imul(ah2, bh8)) | 0; + lo = (lo + Math.imul(al1, bl9)) | 0; + mid = (mid + Math.imul(al1, bh9)) | 0; + mid = (mid + Math.imul(ah1, bl9)) | 0; + hi = (hi + Math.imul(ah1, bh9)) | 0; + var w10 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w10 >>> 26)) | 0; + w10 &= 0x3ffffff; + /* k = 11 */ + lo = Math.imul(al9, bl2); + mid = Math.imul(al9, bh2); + mid = (mid + Math.imul(ah9, bl2)) | 0; + hi = Math.imul(ah9, bh2); + lo = (lo + Math.imul(al8, bl3)) | 0; + mid = (mid + Math.imul(al8, bh3)) | 0; + mid = (mid + Math.imul(ah8, bl3)) | 0; + hi = (hi + Math.imul(ah8, bh3)) | 0; + lo = (lo + Math.imul(al7, bl4)) | 0; + mid = (mid + Math.imul(al7, bh4)) | 0; + mid = (mid + Math.imul(ah7, bl4)) | 0; + hi = (hi + Math.imul(ah7, bh4)) | 0; + lo = (lo + Math.imul(al6, bl5)) | 0; + mid = (mid + Math.imul(al6, bh5)) | 0; + mid = (mid + Math.imul(ah6, bl5)) | 0; + hi = (hi + Math.imul(ah6, bh5)) | 0; + lo = (lo + Math.imul(al5, bl6)) | 0; + mid = (mid + Math.imul(al5, bh6)) | 0; + mid = (mid + Math.imul(ah5, bl6)) | 0; + hi = (hi + Math.imul(ah5, bh6)) | 0; + lo = (lo + Math.imul(al4, bl7)) | 0; + mid = (mid + Math.imul(al4, bh7)) | 0; + mid = (mid + Math.imul(ah4, bl7)) | 0; + hi = (hi + Math.imul(ah4, bh7)) | 0; + lo = (lo + Math.imul(al3, bl8)) | 0; + mid = (mid + Math.imul(al3, bh8)) | 0; + mid = (mid + Math.imul(ah3, bl8)) | 0; + hi = (hi + Math.imul(ah3, bh8)) | 0; + lo = (lo + Math.imul(al2, bl9)) | 0; + mid = (mid + Math.imul(al2, bh9)) | 0; + mid = (mid + Math.imul(ah2, bl9)) | 0; + hi = (hi + Math.imul(ah2, bh9)) | 0; + var w11 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w11 >>> 26)) | 0; + w11 &= 0x3ffffff; + /* k = 12 */ + lo = Math.imul(al9, bl3); + mid = Math.imul(al9, bh3); + mid = (mid + Math.imul(ah9, bl3)) | 0; + hi = Math.imul(ah9, bh3); + lo = (lo + Math.imul(al8, bl4)) | 0; + mid = (mid + Math.imul(al8, bh4)) | 0; + mid = (mid + Math.imul(ah8, bl4)) | 0; + hi = (hi + Math.imul(ah8, bh4)) | 0; + lo = (lo + Math.imul(al7, bl5)) | 0; + mid = (mid + Math.imul(al7, bh5)) | 0; + mid = (mid + Math.imul(ah7, bl5)) | 0; + hi = (hi + Math.imul(ah7, bh5)) | 0; + lo = (lo + Math.imul(al6, bl6)) | 0; + mid = (mid + Math.imul(al6, bh6)) | 0; + mid = (mid + Math.imul(ah6, bl6)) | 0; + hi = (hi + Math.imul(ah6, bh6)) | 0; + lo = (lo + Math.imul(al5, bl7)) | 0; + mid = (mid + Math.imul(al5, bh7)) | 0; + mid = (mid + Math.imul(ah5, bl7)) | 0; + hi = (hi + Math.imul(ah5, bh7)) | 0; + lo = (lo + Math.imul(al4, bl8)) | 0; + mid = (mid + Math.imul(al4, bh8)) | 0; + mid = (mid + Math.imul(ah4, bl8)) | 0; + hi = (hi + Math.imul(ah4, bh8)) | 0; + lo = (lo + Math.imul(al3, bl9)) | 0; + mid = (mid + Math.imul(al3, bh9)) | 0; + mid = (mid + Math.imul(ah3, bl9)) | 0; + hi = (hi + Math.imul(ah3, bh9)) | 0; + var w12 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w12 >>> 26)) | 0; + w12 &= 0x3ffffff; + /* k = 13 */ + lo = Math.imul(al9, bl4); + mid = Math.imul(al9, bh4); + mid = (mid + Math.imul(ah9, bl4)) | 0; + hi = Math.imul(ah9, bh4); + lo = (lo + Math.imul(al8, bl5)) | 0; + mid = (mid + Math.imul(al8, bh5)) | 0; + mid = (mid + Math.imul(ah8, bl5)) | 0; + hi = (hi + Math.imul(ah8, bh5)) | 0; + lo = (lo + Math.imul(al7, bl6)) | 0; + mid = (mid + Math.imul(al7, bh6)) | 0; + mid = (mid + Math.imul(ah7, bl6)) | 0; + hi = (hi + Math.imul(ah7, bh6)) | 0; + lo = (lo + Math.imul(al6, bl7)) | 0; + mid = (mid + Math.imul(al6, bh7)) | 0; + mid = (mid + Math.imul(ah6, bl7)) | 0; + hi = (hi + Math.imul(ah6, bh7)) | 0; + lo = (lo + Math.imul(al5, bl8)) | 0; + mid = (mid + Math.imul(al5, bh8)) | 0; + mid = (mid + Math.imul(ah5, bl8)) | 0; + hi = (hi + Math.imul(ah5, bh8)) | 0; + lo = (lo + Math.imul(al4, bl9)) | 0; + mid = (mid + Math.imul(al4, bh9)) | 0; + mid = (mid + Math.imul(ah4, bl9)) | 0; + hi = (hi + Math.imul(ah4, bh9)) | 0; + var w13 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w13 >>> 26)) | 0; + w13 &= 0x3ffffff; + /* k = 14 */ + lo = Math.imul(al9, bl5); + mid = Math.imul(al9, bh5); + mid = (mid + Math.imul(ah9, bl5)) | 0; + hi = Math.imul(ah9, bh5); + lo = (lo + Math.imul(al8, bl6)) | 0; + mid = (mid + Math.imul(al8, bh6)) | 0; + mid = (mid + Math.imul(ah8, bl6)) | 0; + hi = (hi + Math.imul(ah8, bh6)) | 0; + lo = (lo + Math.imul(al7, bl7)) | 0; + mid = (mid + Math.imul(al7, bh7)) | 0; + mid = (mid + Math.imul(ah7, bl7)) | 0; + hi = (hi + Math.imul(ah7, bh7)) | 0; + lo = (lo + Math.imul(al6, bl8)) | 0; + mid = (mid + Math.imul(al6, bh8)) | 0; + mid = (mid + Math.imul(ah6, bl8)) | 0; + hi = (hi + Math.imul(ah6, bh8)) | 0; + lo = (lo + Math.imul(al5, bl9)) | 0; + mid = (mid + Math.imul(al5, bh9)) | 0; + mid = (mid + Math.imul(ah5, bl9)) | 0; + hi = (hi + Math.imul(ah5, bh9)) | 0; + var w14 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w14 >>> 26)) | 0; + w14 &= 0x3ffffff; + /* k = 15 */ + lo = Math.imul(al9, bl6); + mid = Math.imul(al9, bh6); + mid = (mid + Math.imul(ah9, bl6)) | 0; + hi = Math.imul(ah9, bh6); + lo = (lo + Math.imul(al8, bl7)) | 0; + mid = (mid + Math.imul(al8, bh7)) | 0; + mid = (mid + Math.imul(ah8, bl7)) | 0; + hi = (hi + Math.imul(ah8, bh7)) | 0; + lo = (lo + Math.imul(al7, bl8)) | 0; + mid = (mid + Math.imul(al7, bh8)) | 0; + mid = (mid + Math.imul(ah7, bl8)) | 0; + hi = (hi + Math.imul(ah7, bh8)) | 0; + lo = (lo + Math.imul(al6, bl9)) | 0; + mid = (mid + Math.imul(al6, bh9)) | 0; + mid = (mid + Math.imul(ah6, bl9)) | 0; + hi = (hi + Math.imul(ah6, bh9)) | 0; + var w15 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w15 >>> 26)) | 0; + w15 &= 0x3ffffff; + /* k = 16 */ + lo = Math.imul(al9, bl7); + mid = Math.imul(al9, bh7); + mid = (mid + Math.imul(ah9, bl7)) | 0; + hi = Math.imul(ah9, bh7); + lo = (lo + Math.imul(al8, bl8)) | 0; + mid = (mid + Math.imul(al8, bh8)) | 0; + mid = (mid + Math.imul(ah8, bl8)) | 0; + hi = (hi + Math.imul(ah8, bh8)) | 0; + lo = (lo + Math.imul(al7, bl9)) | 0; + mid = (mid + Math.imul(al7, bh9)) | 0; + mid = (mid + Math.imul(ah7, bl9)) | 0; + hi = (hi + Math.imul(ah7, bh9)) | 0; + var w16 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w16 >>> 26)) | 0; + w16 &= 0x3ffffff; + /* k = 17 */ + lo = Math.imul(al9, bl8); + mid = Math.imul(al9, bh8); + mid = (mid + Math.imul(ah9, bl8)) | 0; + hi = Math.imul(ah9, bh8); + lo = (lo + Math.imul(al8, bl9)) | 0; + mid = (mid + Math.imul(al8, bh9)) | 0; + mid = (mid + Math.imul(ah8, bl9)) | 0; + hi = (hi + Math.imul(ah8, bh9)) | 0; + var w17 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w17 >>> 26)) | 0; + w17 &= 0x3ffffff; + /* k = 18 */ + lo = Math.imul(al9, bl9); + mid = Math.imul(al9, bh9); + mid = (mid + Math.imul(ah9, bl9)) | 0; + hi = Math.imul(ah9, bh9); + var w18 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >>> 13)) | 0) + (w18 >>> 26)) | 0; + w18 &= 0x3ffffff; + o[0] = w0; + o[1] = w1; + o[2] = w2; + o[3] = w3; + o[4] = w4; + o[5] = w5; + o[6] = w6; + o[7] = w7; + o[8] = w8; + o[9] = w9; + o[10] = w10; + o[11] = w11; + o[12] = w12; + o[13] = w13; + o[14] = w14; + o[15] = w15; + o[16] = w16; + o[17] = w17; + o[18] = w18; + if (c !== 0) { + o[19] = c; + out.length++; + } + return out; + }; + + // Polyfill comb + if (!Math.imul) { + comb10MulTo = smallMulTo; + } + + function bigMulTo (self, num, out) { + out.negative = num.negative ^ self.negative; + out.length = self.length + num.length; + + var carry = 0; + var hncarry = 0; + for (var k = 0; k < out.length - 1; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = hncarry; + hncarry = 0; + var rword = carry & 0x3ffffff; + var maxJ = Math.min(k, num.length - 1); + for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) { + var i = k - j; + var a = self.words[i] | 0; + var b = num.words[j] | 0; + var r = a * b; + + var lo = r & 0x3ffffff; + ncarry = (ncarry + ((r / 0x4000000) | 0)) | 0; + lo = (lo + rword) | 0; + rword = lo & 0x3ffffff; + ncarry = (ncarry + (lo >>> 26)) | 0; + + hncarry += ncarry >>> 26; + ncarry &= 0x3ffffff; + } + out.words[k] = rword; + carry = ncarry; + ncarry = hncarry; + } + if (carry !== 0) { + out.words[k] = carry; + } else { + out.length--; + } + + return out._strip(); + } + + function jumboMulTo (self, num, out) { + // Temporary disable, see https://github.com/indutny/bn.js/issues/211 + // var fftm = new FFTM(); + // return fftm.mulp(self, num, out); + return bigMulTo(self, num, out); + } + + BN.prototype.mulTo = function mulTo (num, out) { + var res; + var len = this.length + num.length; + if (this.length === 10 && num.length === 10) { + res = comb10MulTo(this, num, out); + } else if (len < 63) { + res = smallMulTo(this, num, out); + } else if (len < 1024) { + res = bigMulTo(this, num, out); + } else { + res = jumboMulTo(this, num, out); + } + + return res; + }; + + // Cooley-Tukey algorithm for FFT + // slightly revisited to rely on looping instead of recursion + + function FFTM (x, y) { + this.x = x; + this.y = y; + } + + FFTM.prototype.makeRBT = function makeRBT (N) { + var t = new Array(N); + var l = BN.prototype._countBits(N) - 1; + for (var i = 0; i < N; i++) { + t[i] = this.revBin(i, l, N); + } + + return t; + }; + + // Returns binary-reversed representation of `x` + FFTM.prototype.revBin = function revBin (x, l, N) { + if (x === 0 || x === N - 1) return x; + + var rb = 0; + for (var i = 0; i < l; i++) { + rb |= (x & 1) << (l - i - 1); + x >>= 1; + } + + return rb; + }; + + // Performs "tweedling" phase, therefore 'emulating' + // behaviour of the recursive algorithm + FFTM.prototype.permute = function permute (rbt, rws, iws, rtws, itws, N) { + for (var i = 0; i < N; i++) { + rtws[i] = rws[rbt[i]]; + itws[i] = iws[rbt[i]]; + } + }; + + FFTM.prototype.transform = function transform (rws, iws, rtws, itws, N, rbt) { + this.permute(rbt, rws, iws, rtws, itws, N); + + for (var s = 1; s < N; s <<= 1) { + var l = s << 1; + + var rtwdf = Math.cos(2 * Math.PI / l); + var itwdf = Math.sin(2 * Math.PI / l); + + for (var p = 0; p < N; p += l) { + var rtwdf_ = rtwdf; + var itwdf_ = itwdf; + + for (var j = 0; j < s; j++) { + var re = rtws[p + j]; + var ie = itws[p + j]; + + var ro = rtws[p + j + s]; + var io = itws[p + j + s]; + + var rx = rtwdf_ * ro - itwdf_ * io; + + io = rtwdf_ * io + itwdf_ * ro; + ro = rx; + + rtws[p + j] = re + ro; + itws[p + j] = ie + io; + + rtws[p + j + s] = re - ro; + itws[p + j + s] = ie - io; + + /* jshint maxdepth : false */ + if (j !== l) { + rx = rtwdf * rtwdf_ - itwdf * itwdf_; + + itwdf_ = rtwdf * itwdf_ + itwdf * rtwdf_; + rtwdf_ = rx; + } + } + } + } + }; + + FFTM.prototype.guessLen13b = function guessLen13b (n, m) { + var N = Math.max(m, n) | 1; + var odd = N & 1; + var i = 0; + for (N = N / 2 | 0; N; N = N >>> 1) { + i++; + } + + return 1 << i + 1 + odd; + }; + + FFTM.prototype.conjugate = function conjugate (rws, iws, N) { + if (N <= 1) return; + + for (var i = 0; i < N / 2; i++) { + var t = rws[i]; + + rws[i] = rws[N - i - 1]; + rws[N - i - 1] = t; + + t = iws[i]; + + iws[i] = -iws[N - i - 1]; + iws[N - i - 1] = -t; + } + }; + + FFTM.prototype.normalize13b = function normalize13b (ws, N) { + var carry = 0; + for (var i = 0; i < N / 2; i++) { + var w = Math.round(ws[2 * i + 1] / N) * 0x2000 + + Math.round(ws[2 * i] / N) + + carry; + + ws[i] = w & 0x3ffffff; + + if (w < 0x4000000) { + carry = 0; + } else { + carry = w / 0x4000000 | 0; + } + } + + return ws; + }; + + FFTM.prototype.convert13b = function convert13b (ws, len, rws, N) { + var carry = 0; + for (var i = 0; i < len; i++) { + carry = carry + (ws[i] | 0); + + rws[2 * i] = carry & 0x1fff; carry = carry >>> 13; + rws[2 * i + 1] = carry & 0x1fff; carry = carry >>> 13; + } + + // Pad with zeroes + for (i = 2 * len; i < N; ++i) { + rws[i] = 0; + } + + assert(carry === 0); + assert((carry & ~0x1fff) === 0); + }; + + FFTM.prototype.stub = function stub (N) { + var ph = new Array(N); + for (var i = 0; i < N; i++) { + ph[i] = 0; + } + + return ph; + }; + + FFTM.prototype.mulp = function mulp (x, y, out) { + var N = 2 * this.guessLen13b(x.length, y.length); + + var rbt = this.makeRBT(N); + + var _ = this.stub(N); + + var rws = new Array(N); + var rwst = new Array(N); + var iwst = new Array(N); + + var nrws = new Array(N); + var nrwst = new Array(N); + var niwst = new Array(N); + + var rmws = out.words; + rmws.length = N; + + this.convert13b(x.words, x.length, rws, N); + this.convert13b(y.words, y.length, nrws, N); + + this.transform(rws, _, rwst, iwst, N, rbt); + this.transform(nrws, _, nrwst, niwst, N, rbt); + + for (var i = 0; i < N; i++) { + var rx = rwst[i] * nrwst[i] - iwst[i] * niwst[i]; + iwst[i] = rwst[i] * niwst[i] + iwst[i] * nrwst[i]; + rwst[i] = rx; + } + + this.conjugate(rwst, iwst, N); + this.transform(rwst, iwst, rmws, _, N, rbt); + this.conjugate(rmws, _, N); + this.normalize13b(rmws, N); + + out.negative = x.negative ^ y.negative; + out.length = x.length + y.length; + return out._strip(); + }; + + // Multiply `this` by `num` + BN.prototype.mul = function mul (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return this.mulTo(num, out); + }; + + // Multiply employing FFT + BN.prototype.mulf = function mulf (num) { + var out = new BN(null); + out.words = new Array(this.length + num.length); + return jumboMulTo(this, num, out); + }; + + // In-place Multiplication + BN.prototype.imul = function imul (num) { + return this.clone().mulTo(num, this); + }; + + BN.prototype.imuln = function imuln (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(typeof num === 'number'); + assert(num < 0x4000000); + + // Carry + var carry = 0; + for (var i = 0; i < this.length; i++) { + var w = (this.words[i] | 0) * num; + var lo = (w & 0x3ffffff) + (carry & 0x3ffffff); + carry >>= 26; + carry += (w / 0x4000000) | 0; + // NOTE: lo is 27bit maximum + carry += lo >>> 26; + this.words[i] = lo & 0x3ffffff; + } + + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + + return isNegNum ? this.ineg() : this; + }; + + BN.prototype.muln = function muln (num) { + return this.clone().imuln(num); + }; + + // `this` * `this` + BN.prototype.sqr = function sqr () { + return this.mul(this); + }; + + // `this` * `this` in-place + BN.prototype.isqr = function isqr () { + return this.imul(this.clone()); + }; + + // Math.pow(`this`, `num`) + BN.prototype.pow = function pow (num) { + var w = toBitArray(num); + if (w.length === 0) return new BN(1); + + // Skip leading zeroes + var res = this; + for (var i = 0; i < w.length; i++, res = res.sqr()) { + if (w[i] !== 0) break; + } + + if (++i < w.length) { + for (var q = res.sqr(); i < w.length; i++, q = q.sqr()) { + if (w[i] === 0) continue; + + res = res.mul(q); + } + } + + return res; + }; + + // Shift-left in-place + BN.prototype.iushln = function iushln (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + var carryMask = (0x3ffffff >>> (26 - r)) << (26 - r); + var i; + + if (r !== 0) { + var carry = 0; + + for (i = 0; i < this.length; i++) { + var newCarry = this.words[i] & carryMask; + var c = ((this.words[i] | 0) - newCarry) << r; + this.words[i] = c | carry; + carry = newCarry >>> (26 - r); + } + + if (carry) { + this.words[i] = carry; + this.length++; + } + } + + if (s !== 0) { + for (i = this.length - 1; i >= 0; i--) { + this.words[i + s] = this.words[i]; + } + + for (i = 0; i < s; i++) { + this.words[i] = 0; + } + + this.length += s; + } + + return this._strip(); + }; + + BN.prototype.ishln = function ishln (bits) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushln(bits); + }; + + // Shift-right in-place + // NOTE: `hint` is a lowest bit before trailing zeroes + // NOTE: if `extended` is present - it will be filled with destroyed bits + BN.prototype.iushrn = function iushrn (bits, hint, extended) { + assert(typeof bits === 'number' && bits >= 0); + var h; + if (hint) { + h = (hint - (hint % 26)) / 26; + } else { + h = 0; + } + + var r = bits % 26; + var s = Math.min((bits - r) / 26, this.length); + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + var maskedWords = extended; + + h -= s; + h = Math.max(0, h); + + // Extended mode, copy masked part + if (maskedWords) { + for (var i = 0; i < s; i++) { + maskedWords.words[i] = this.words[i]; + } + maskedWords.length = s; + } + + if (s === 0) { + // No-op, we should not move anything at all + } else if (this.length > s) { + this.length -= s; + for (i = 0; i < this.length; i++) { + this.words[i] = this.words[i + s]; + } + } else { + this.words[0] = 0; + this.length = 1; + } + + var carry = 0; + for (i = this.length - 1; i >= 0 && (carry !== 0 || i >= h); i--) { + var word = this.words[i] | 0; + this.words[i] = (carry << (26 - r)) | (word >>> r); + carry = word & mask; + } + + // Push carried bits as a mask + if (maskedWords && carry !== 0) { + maskedWords.words[maskedWords.length++] = carry; + } + + if (this.length === 0) { + this.words[0] = 0; + this.length = 1; + } + + return this._strip(); + }; + + BN.prototype.ishrn = function ishrn (bits, hint, extended) { + // TODO(indutny): implement me + assert(this.negative === 0); + return this.iushrn(bits, hint, extended); + }; + + // Shift-left + BN.prototype.shln = function shln (bits) { + return this.clone().ishln(bits); + }; + + BN.prototype.ushln = function ushln (bits) { + return this.clone().iushln(bits); + }; + + // Shift-right + BN.prototype.shrn = function shrn (bits) { + return this.clone().ishrn(bits); + }; + + BN.prototype.ushrn = function ushrn (bits) { + return this.clone().iushrn(bits); + }; + + // Test if n bit is set + BN.prototype.testn = function testn (bit) { + assert(typeof bit === 'number' && bit >= 0); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) return false; + + // Check bit and return + var w = this.words[s]; + + return !!(w & q); + }; + + // Return only lowers bits of number (in-place) + BN.prototype.imaskn = function imaskn (bits) { + assert(typeof bits === 'number' && bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + + assert(this.negative === 0, 'imaskn works only with positive numbers'); + + if (this.length <= s) { + return this; + } + + if (r !== 0) { + s++; + } + this.length = Math.min(s, this.length); + + if (r !== 0) { + var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r); + this.words[this.length - 1] &= mask; + } + + return this._strip(); + }; + + // Return only lowers bits of number + BN.prototype.maskn = function maskn (bits) { + return this.clone().imaskn(bits); + }; + + // Add plain number `num` to `this` + BN.prototype.iaddn = function iaddn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.isubn(-num); + + // Possible sign change + if (this.negative !== 0) { + if (this.length === 1 && (this.words[0] | 0) <= num) { + this.words[0] = num - (this.words[0] | 0); + this.negative = 0; + return this; + } + + this.negative = 0; + this.isubn(num); + this.negative = 1; + return this; + } + + // Add without checks + return this._iaddn(num); + }; + + BN.prototype._iaddn = function _iaddn (num) { + this.words[0] += num; + + // Carry + for (var i = 0; i < this.length && this.words[i] >= 0x4000000; i++) { + this.words[i] -= 0x4000000; + if (i === this.length - 1) { + this.words[i + 1] = 1; + } else { + this.words[i + 1]++; + } + } + this.length = Math.max(this.length, i + 1); + + return this; + }; + + // Subtract plain number `num` from `this` + BN.prototype.isubn = function isubn (num) { + assert(typeof num === 'number'); + assert(num < 0x4000000); + if (num < 0) return this.iaddn(-num); + + if (this.negative !== 0) { + this.negative = 0; + this.iaddn(num); + this.negative = 1; + return this; + } + + this.words[0] -= num; + + if (this.length === 1 && this.words[0] < 0) { + this.words[0] = -this.words[0]; + this.negative = 1; + } else { + // Carry + for (var i = 0; i < this.length && this.words[i] < 0; i++) { + this.words[i] += 0x4000000; + this.words[i + 1] -= 1; + } + } + + return this._strip(); + }; + + BN.prototype.addn = function addn (num) { + return this.clone().iaddn(num); + }; + + BN.prototype.subn = function subn (num) { + return this.clone().isubn(num); + }; + + BN.prototype.iabs = function iabs () { + this.negative = 0; + + return this; + }; + + BN.prototype.abs = function abs () { + return this.clone().iabs(); + }; + + BN.prototype._ishlnsubmul = function _ishlnsubmul (num, mul, shift) { + var len = num.length + shift; + var i; + + this._expand(len); + + var w; + var carry = 0; + for (i = 0; i < num.length; i++) { + w = (this.words[i + shift] | 0) + carry; + var right = (num.words[i] | 0) * mul; + w -= right & 0x3ffffff; + carry = (w >> 26) - ((right / 0x4000000) | 0); + this.words[i + shift] = w & 0x3ffffff; + } + for (; i < this.length - shift; i++) { + w = (this.words[i + shift] | 0) + carry; + carry = w >> 26; + this.words[i + shift] = w & 0x3ffffff; + } + + if (carry === 0) return this._strip(); + + // Subtraction overflow + assert(carry === -1); + carry = 0; + for (i = 0; i < this.length; i++) { + w = -(this.words[i] | 0) + carry; + carry = w >> 26; + this.words[i] = w & 0x3ffffff; + } + this.negative = 1; + + return this._strip(); + }; + + BN.prototype._wordDiv = function _wordDiv (num, mode) { + var shift = this.length - num.length; + + var a = this.clone(); + var b = num; + + // Normalize + var bhi = b.words[b.length - 1] | 0; + var bhiBits = this._countBits(bhi); + shift = 26 - bhiBits; + if (shift !== 0) { + b = b.ushln(shift); + a.iushln(shift); + bhi = b.words[b.length - 1] | 0; + } + + // Initialize quotient + var m = a.length - b.length; + var q; + + if (mode !== 'mod') { + q = new BN(null); + q.length = m + 1; + q.words = new Array(q.length); + for (var i = 0; i < q.length; i++) { + q.words[i] = 0; + } + } + + var diff = a.clone()._ishlnsubmul(b, 1, m); + if (diff.negative === 0) { + a = diff; + if (q) { + q.words[m] = 1; + } + } + + for (var j = m - 1; j >= 0; j--) { + var qj = (a.words[b.length + j] | 0) * 0x4000000 + + (a.words[b.length + j - 1] | 0); + + // NOTE: (qj / bhi) is (0x3ffffff * 0x4000000 + 0x3ffffff) / 0x2000000 max + // (0x7ffffff) + qj = Math.min((qj / bhi) | 0, 0x3ffffff); + + a._ishlnsubmul(b, qj, j); + while (a.negative !== 0) { + qj--; + a.negative = 0; + a._ishlnsubmul(b, 1, j); + if (!a.isZero()) { + a.negative ^= 1; + } + } + if (q) { + q.words[j] = qj; + } + } + if (q) { + q._strip(); + } + a._strip(); + + // Denormalize + if (mode !== 'div' && shift !== 0) { + a.iushrn(shift); + } + + return { + div: q || null, + mod: a + }; + }; + + // NOTE: 1) `mode` can be set to `mod` to request mod only, + // to `div` to request div only, or be absent to + // request both div & mod + // 2) `positive` is true if unsigned mod is requested + BN.prototype.divmod = function divmod (num, mode, positive) { + assert(!num.isZero()); + + if (this.isZero()) { + return { + div: new BN(0), + mod: new BN(0) + }; + } + + var div, mod, res; + if (this.negative !== 0 && num.negative === 0) { + res = this.neg().divmod(num, mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.iadd(num); + } + } + + return { + div: div, + mod: mod + }; + } + + if (this.negative === 0 && num.negative !== 0) { + res = this.divmod(num.neg(), mode); + + if (mode !== 'mod') { + div = res.div.neg(); + } + + return { + div: div, + mod: res.mod + }; + } + + if ((this.negative & num.negative) !== 0) { + res = this.neg().divmod(num.neg(), mode); + + if (mode !== 'div') { + mod = res.mod.neg(); + if (positive && mod.negative !== 0) { + mod.isub(num); + } + } + + return { + div: res.div, + mod: mod + }; + } + + // Both numbers are positive at this point + + // Strip both numbers to approximate shift value + if (num.length > this.length || this.cmp(num) < 0) { + return { + div: new BN(0), + mod: this + }; + } + + // Very short reduction + if (num.length === 1) { + if (mode === 'div') { + return { + div: this.divn(num.words[0]), + mod: null + }; + } + + if (mode === 'mod') { + return { + div: null, + mod: new BN(this.modrn(num.words[0])) + }; + } + + return { + div: this.divn(num.words[0]), + mod: new BN(this.modrn(num.words[0])) + }; + } + + return this._wordDiv(num, mode); + }; + + // Find `this` / `num` + BN.prototype.div = function div (num) { + return this.divmod(num, 'div', false).div; + }; + + // Find `this` % `num` + BN.prototype.mod = function mod (num) { + return this.divmod(num, 'mod', false).mod; + }; + + BN.prototype.umod = function umod (num) { + return this.divmod(num, 'mod', true).mod; + }; + + // Find Round(`this` / `num`) + BN.prototype.divRound = function divRound (num) { + var dm = this.divmod(num); + + // Fast case - exact division + if (dm.mod.isZero()) return dm.div; + + var mod = dm.div.negative !== 0 ? dm.mod.isub(num) : dm.mod; + + var half = num.ushrn(1); + var r2 = num.andln(1); + var cmp = mod.cmp(half); + + // Round down + if (cmp < 0 || (r2 === 1 && cmp === 0)) return dm.div; + + // Round up + return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1); + }; + + BN.prototype.modrn = function modrn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(num <= 0x3ffffff); + var p = (1 << 26) % num; + + var acc = 0; + for (var i = this.length - 1; i >= 0; i--) { + acc = (p * acc + (this.words[i] | 0)) % num; + } + + return isNegNum ? -acc : acc; + }; + + // WARNING: DEPRECATED + BN.prototype.modn = function modn (num) { + return this.modrn(num); + }; + + // In-place division by number + BN.prototype.idivn = function idivn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + assert(num <= 0x3ffffff); + + var carry = 0; + for (var i = this.length - 1; i >= 0; i--) { + var w = (this.words[i] | 0) + carry * 0x4000000; + this.words[i] = (w / num) | 0; + carry = w % num; + } + + this._strip(); + return isNegNum ? this.ineg() : this; + }; + + BN.prototype.divn = function divn (num) { + return this.clone().idivn(num); + }; + + BN.prototype.egcd = function egcd (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var x = this; + var y = p.clone(); + + if (x.negative !== 0) { + x = x.umod(p); + } else { + x = x.clone(); + } + + // A * x + B * y = x + var A = new BN(1); + var B = new BN(0); + + // C * x + D * y = y + var C = new BN(0); + var D = new BN(1); + + var g = 0; + + while (x.isEven() && y.isEven()) { + x.iushrn(1); + y.iushrn(1); + ++g; + } + + var yp = y.clone(); + var xp = x.clone(); + + while (!x.isZero()) { + for (var i = 0, im = 1; (x.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + x.iushrn(i); + while (i-- > 0) { + if (A.isOdd() || B.isOdd()) { + A.iadd(yp); + B.isub(xp); + } + + A.iushrn(1); + B.iushrn(1); + } + } + + for (var j = 0, jm = 1; (y.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + y.iushrn(j); + while (j-- > 0) { + if (C.isOdd() || D.isOdd()) { + C.iadd(yp); + D.isub(xp); + } + + C.iushrn(1); + D.iushrn(1); + } + } + + if (x.cmp(y) >= 0) { + x.isub(y); + A.isub(C); + B.isub(D); + } else { + y.isub(x); + C.isub(A); + D.isub(B); + } + } + + return { + a: C, + b: D, + gcd: y.iushln(g) + }; + }; + + // This is reduced incarnation of the binary EEA + // above, designated to invert members of the + // _prime_ fields F(p) at a maximal speed + BN.prototype._invmp = function _invmp (p) { + assert(p.negative === 0); + assert(!p.isZero()); + + var a = this; + var b = p.clone(); + + if (a.negative !== 0) { + a = a.umod(p); + } else { + a = a.clone(); + } + + var x1 = new BN(1); + var x2 = new BN(0); + + var delta = b.clone(); + + while (a.cmpn(1) > 0 && b.cmpn(1) > 0) { + for (var i = 0, im = 1; (a.words[0] & im) === 0 && i < 26; ++i, im <<= 1); + if (i > 0) { + a.iushrn(i); + while (i-- > 0) { + if (x1.isOdd()) { + x1.iadd(delta); + } + + x1.iushrn(1); + } + } + + for (var j = 0, jm = 1; (b.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1); + if (j > 0) { + b.iushrn(j); + while (j-- > 0) { + if (x2.isOdd()) { + x2.iadd(delta); + } + + x2.iushrn(1); + } + } + + if (a.cmp(b) >= 0) { + a.isub(b); + x1.isub(x2); + } else { + b.isub(a); + x2.isub(x1); + } + } + + var res; + if (a.cmpn(1) === 0) { + res = x1; + } else { + res = x2; + } + + if (res.cmpn(0) < 0) { + res.iadd(p); + } + + return res; + }; + + BN.prototype.gcd = function gcd (num) { + if (this.isZero()) return num.abs(); + if (num.isZero()) return this.abs(); + + var a = this.clone(); + var b = num.clone(); + a.negative = 0; + b.negative = 0; + + // Remove common factor of two + for (var shift = 0; a.isEven() && b.isEven(); shift++) { + a.iushrn(1); + b.iushrn(1); + } + + do { + while (a.isEven()) { + a.iushrn(1); + } + while (b.isEven()) { + b.iushrn(1); + } + + var r = a.cmp(b); + if (r < 0) { + // Swap `a` and `b` to make `a` always bigger than `b` + var t = a; + a = b; + b = t; + } else if (r === 0 || b.cmpn(1) === 0) { + break; + } + + a.isub(b); + } while (true); + + return b.iushln(shift); + }; + + // Invert number in the field F(num) + BN.prototype.invm = function invm (num) { + return this.egcd(num).a.umod(num); + }; + + BN.prototype.isEven = function isEven () { + return (this.words[0] & 1) === 0; + }; + + BN.prototype.isOdd = function isOdd () { + return (this.words[0] & 1) === 1; + }; + + // And first word and num + BN.prototype.andln = function andln (num) { + return this.words[0] & num; + }; + + // Increment at the bit position in-line + BN.prototype.bincn = function bincn (bit) { + assert(typeof bit === 'number'); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.length <= s) { + this._expand(s + 1); + this.words[s] |= q; + return this; + } + + // Add bit and propagate, if needed + var carry = q; + for (var i = s; carry !== 0 && i < this.length; i++) { + var w = this.words[i] | 0; + w += carry; + carry = w >>> 26; + w &= 0x3ffffff; + this.words[i] = w; + } + if (carry !== 0) { + this.words[i] = carry; + this.length++; + } + return this; + }; + + BN.prototype.isZero = function isZero () { + return this.length === 1 && this.words[0] === 0; + }; + + BN.prototype.cmpn = function cmpn (num) { + var negative = num < 0; + + if (this.negative !== 0 && !negative) return -1; + if (this.negative === 0 && negative) return 1; + + this._strip(); + + var res; + if (this.length > 1) { + res = 1; + } else { + if (negative) { + num = -num; + } + + assert(num <= 0x3ffffff, 'Number is too big'); + + var w = this.words[0] | 0; + res = w === num ? 0 : w < num ? -1 : 1; + } + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Compare two numbers and return: + // 1 - if `this` > `num` + // 0 - if `this` == `num` + // -1 - if `this` < `num` + BN.prototype.cmp = function cmp (num) { + if (this.negative !== 0 && num.negative === 0) return -1; + if (this.negative === 0 && num.negative !== 0) return 1; + + var res = this.ucmp(num); + if (this.negative !== 0) return -res | 0; + return res; + }; + + // Unsigned comparison + BN.prototype.ucmp = function ucmp (num) { + // At this point both numbers have the same sign + if (this.length > num.length) return 1; + if (this.length < num.length) return -1; + + var res = 0; + for (var i = this.length - 1; i >= 0; i--) { + var a = this.words[i] | 0; + var b = num.words[i] | 0; + + if (a === b) continue; + if (a < b) { + res = -1; + } else if (a > b) { + res = 1; + } + break; + } + return res; + }; + + BN.prototype.gtn = function gtn (num) { + return this.cmpn(num) === 1; + }; + + BN.prototype.gt = function gt (num) { + return this.cmp(num) === 1; + }; + + BN.prototype.gten = function gten (num) { + return this.cmpn(num) >= 0; + }; + + BN.prototype.gte = function gte (num) { + return this.cmp(num) >= 0; + }; + + BN.prototype.ltn = function ltn (num) { + return this.cmpn(num) === -1; + }; + + BN.prototype.lt = function lt (num) { + return this.cmp(num) === -1; + }; + + BN.prototype.lten = function lten (num) { + return this.cmpn(num) <= 0; + }; + + BN.prototype.lte = function lte (num) { + return this.cmp(num) <= 0; + }; + + BN.prototype.eqn = function eqn (num) { + return this.cmpn(num) === 0; + }; + + BN.prototype.eq = function eq (num) { + return this.cmp(num) === 0; + }; + + // + // A reduce context, could be using montgomery or something better, depending + // on the `m` itself. + // + BN.red = function red (num) { + return new Red(num); + }; + + BN.prototype.toRed = function toRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + assert(this.negative === 0, 'red works only with positives'); + return ctx.convertTo(this)._forceRed(ctx); + }; + + BN.prototype.fromRed = function fromRed () { + assert(this.red, 'fromRed works only with numbers in reduction context'); + return this.red.convertFrom(this); + }; + + BN.prototype._forceRed = function _forceRed (ctx) { + this.red = ctx; + return this; + }; + + BN.prototype.forceRed = function forceRed (ctx) { + assert(!this.red, 'Already a number in reduction context'); + return this._forceRed(ctx); + }; + + BN.prototype.redAdd = function redAdd (num) { + assert(this.red, 'redAdd works only with red numbers'); + return this.red.add(this, num); + }; + + BN.prototype.redIAdd = function redIAdd (num) { + assert(this.red, 'redIAdd works only with red numbers'); + return this.red.iadd(this, num); + }; + + BN.prototype.redSub = function redSub (num) { + assert(this.red, 'redSub works only with red numbers'); + return this.red.sub(this, num); + }; + + BN.prototype.redISub = function redISub (num) { + assert(this.red, 'redISub works only with red numbers'); + return this.red.isub(this, num); + }; + + BN.prototype.redShl = function redShl (num) { + assert(this.red, 'redShl works only with red numbers'); + return this.red.shl(this, num); + }; + + BN.prototype.redMul = function redMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.mul(this, num); + }; + + BN.prototype.redIMul = function redIMul (num) { + assert(this.red, 'redMul works only with red numbers'); + this.red._verify2(this, num); + return this.red.imul(this, num); + }; + + BN.prototype.redSqr = function redSqr () { + assert(this.red, 'redSqr works only with red numbers'); + this.red._verify1(this); + return this.red.sqr(this); + }; + + BN.prototype.redISqr = function redISqr () { + assert(this.red, 'redISqr works only with red numbers'); + this.red._verify1(this); + return this.red.isqr(this); + }; + + // Square root over p + BN.prototype.redSqrt = function redSqrt () { + assert(this.red, 'redSqrt works only with red numbers'); + this.red._verify1(this); + return this.red.sqrt(this); + }; + + BN.prototype.redInvm = function redInvm () { + assert(this.red, 'redInvm works only with red numbers'); + this.red._verify1(this); + return this.red.invm(this); + }; + + // Return negative clone of `this` % `red modulo` + BN.prototype.redNeg = function redNeg () { + assert(this.red, 'redNeg works only with red numbers'); + this.red._verify1(this); + return this.red.neg(this); + }; + + BN.prototype.redPow = function redPow (num) { + assert(this.red && !num.red, 'redPow(normalNum)'); + this.red._verify1(this); + return this.red.pow(this, num); + }; + + // Prime numbers with efficient reduction + var primes = { + k256: null, + p224: null, + p192: null, + p25519: null + }; + + // Pseudo-Mersenne prime + function MPrime (name, p) { + // P = 2 ^ N - K + this.name = name; + this.p = new BN(p, 16); + this.n = this.p.bitLength(); + this.k = new BN(1).iushln(this.n).isub(this.p); + + this.tmp = this._tmp(); + } + + MPrime.prototype._tmp = function _tmp () { + var tmp = new BN(null); + tmp.words = new Array(Math.ceil(this.n / 13)); + return tmp; + }; + + MPrime.prototype.ireduce = function ireduce (num) { + // Assumes that `num` is less than `P^2` + // num = HI * (2 ^ N - K) + HI * K + LO = HI * K + LO (mod P) + var r = num; + var rlen; + + do { + this.split(r, this.tmp); + r = this.imulK(r); + r = r.iadd(this.tmp); + rlen = r.bitLength(); + } while (rlen > this.n); + + var cmp = rlen < this.n ? -1 : r.ucmp(this.p); + if (cmp === 0) { + r.words[0] = 0; + r.length = 1; + } else if (cmp > 0) { + r.isub(this.p); + } else { + r._strip(); + } + + return r; + }; + + MPrime.prototype.split = function split (input, out) { + input.iushrn(this.n, 0, out); + }; + + MPrime.prototype.imulK = function imulK (num) { + return num.imul(this.k); + }; + + function K256 () { + MPrime.call( + this, + 'k256', + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f'); + } + inherits(K256, MPrime); + + K256.prototype.split = function split (input, output) { + // 256 = 9 * 26 + 22 + var mask = 0x3fffff; + + var outLen = Math.min(input.length, 9); + for (var i = 0; i < outLen; i++) { + output.words[i] = input.words[i]; + } + output.length = outLen; + + if (input.length <= 9) { + input.words[0] = 0; + input.length = 1; + return; + } + + // Shift by 9 limbs + var prev = input.words[9]; + output.words[output.length++] = prev & mask; + + for (i = 10; i < input.length; i++) { + var next = input.words[i] | 0; + input.words[i - 10] = ((next & mask) << 4) | (prev >>> 22); + prev = next; + } + prev >>>= 22; + input.words[i - 10] = prev; + if (prev === 0 && input.length > 10) { + input.length -= 10; + } else { + input.length -= 9; + } + }; + + K256.prototype.imulK = function imulK (num) { + // K = 0x1000003d1 = [ 0x40, 0x3d1 ] + num.words[num.length] = 0; + num.words[num.length + 1] = 0; + num.length += 2; + + // bounded at: 0x40 * 0x3ffffff + 0x3d0 = 0x100000390 + var lo = 0; + for (var i = 0; i < num.length; i++) { + var w = num.words[i] | 0; + lo += w * 0x3d1; + num.words[i] = lo & 0x3ffffff; + lo = w * 0x40 + ((lo / 0x4000000) | 0); + } + + // Fast length reduction + if (num.words[num.length - 1] === 0) { + num.length--; + if (num.words[num.length - 1] === 0) { + num.length--; + } + } + return num; + }; + + function P224 () { + MPrime.call( + this, + 'p224', + 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001'); + } + inherits(P224, MPrime); + + function P192 () { + MPrime.call( + this, + 'p192', + 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff'); + } + inherits(P192, MPrime); + + function P25519 () { + // 2 ^ 255 - 19 + MPrime.call( + this, + '25519', + '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed'); + } + inherits(P25519, MPrime); + + P25519.prototype.imulK = function imulK (num) { + // K = 0x13 + var carry = 0; + for (var i = 0; i < num.length; i++) { + var hi = (num.words[i] | 0) * 0x13 + carry; + var lo = hi & 0x3ffffff; + hi >>>= 26; + + num.words[i] = lo; + carry = hi; + } + if (carry !== 0) { + num.words[num.length++] = carry; + } + return num; + }; + + // Exported mostly for testing purposes, use plain name instead + BN._prime = function prime (name) { + // Cached version of prime + if (primes[name]) return primes[name]; + + var prime; + if (name === 'k256') { + prime = new K256(); + } else if (name === 'p224') { + prime = new P224(); + } else if (name === 'p192') { + prime = new P192(); + } else if (name === 'p25519') { + prime = new P25519(); + } else { + throw new Error('Unknown prime ' + name); + } + primes[name] = prime; + + return prime; + }; + + // + // Base reduction engine + // + function Red (m) { + if (typeof m === 'string') { + var prime = BN._prime(m); + this.m = prime.p; + this.prime = prime; + } else { + assert(m.gtn(1), 'modulus must be greater than 1'); + this.m = m; + this.prime = null; + } + } + + Red.prototype._verify1 = function _verify1 (a) { + assert(a.negative === 0, 'red works only with positives'); + assert(a.red, 'red works only with red numbers'); + }; + + Red.prototype._verify2 = function _verify2 (a, b) { + assert((a.negative | b.negative) === 0, 'red works only with positives'); + assert(a.red && a.red === b.red, + 'red works only with red numbers'); + }; + + Red.prototype.imod = function imod (a) { + if (this.prime) return this.prime.ireduce(a)._forceRed(this); + + a.umod(this.m)._forceRed(this)._move(a); + return a; + }; + + Red.prototype.neg = function neg (a) { + if (a.isZero()) { + return a.clone(); + } + + return this.m.sub(a)._forceRed(this); + }; + + Red.prototype.add = function add (a, b) { + this._verify2(a, b); + + var res = a.add(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.iadd = function iadd (a, b) { + this._verify2(a, b); + + var res = a.iadd(b); + if (res.cmp(this.m) >= 0) { + res.isub(this.m); + } + return res; + }; + + Red.prototype.sub = function sub (a, b) { + this._verify2(a, b); + + var res = a.sub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res._forceRed(this); + }; + + Red.prototype.isub = function isub (a, b) { + this._verify2(a, b); + + var res = a.isub(b); + if (res.cmpn(0) < 0) { + res.iadd(this.m); + } + return res; + }; + + Red.prototype.shl = function shl (a, num) { + this._verify1(a); + return this.imod(a.ushln(num)); + }; + + Red.prototype.imul = function imul (a, b) { + this._verify2(a, b); + return this.imod(a.imul(b)); + }; + + Red.prototype.mul = function mul (a, b) { + this._verify2(a, b); + return this.imod(a.mul(b)); + }; + + Red.prototype.isqr = function isqr (a) { + return this.imul(a, a.clone()); + }; + + Red.prototype.sqr = function sqr (a) { + return this.mul(a, a); + }; + + Red.prototype.sqrt = function sqrt (a) { + if (a.isZero()) return a.clone(); + + var mod3 = this.m.andln(3); + assert(mod3 % 2 === 1); + + // Fast case + if (mod3 === 3) { + var pow = this.m.add(new BN(1)).iushrn(2); + return this.pow(a, pow); + } + + // Tonelli-Shanks algorithm (Totally unoptimized and slow) + // + // Find Q and S, that Q * 2 ^ S = (P - 1) + var q = this.m.subn(1); + var s = 0; + while (!q.isZero() && q.andln(1) === 0) { + s++; + q.iushrn(1); + } + assert(!q.isZero()); + + var one = new BN(1).toRed(this); + var nOne = one.redNeg(); + + // Find quadratic non-residue + // NOTE: Max is such because of generalized Riemann hypothesis. + var lpow = this.m.subn(1).iushrn(1); + var z = this.m.bitLength(); + z = new BN(2 * z * z).toRed(this); + + while (this.pow(z, lpow).cmp(nOne) !== 0) { + z.redIAdd(nOne); + } + + var c = this.pow(z, q); + var r = this.pow(a, q.addn(1).iushrn(1)); + var t = this.pow(a, q); + var m = s; + while (t.cmp(one) !== 0) { + var tmp = t; + for (var i = 0; tmp.cmp(one) !== 0; i++) { + tmp = tmp.redSqr(); + } + assert(i < m); + var b = this.pow(c, new BN(1).iushln(m - i - 1)); + + r = r.redMul(b); + c = b.redSqr(); + t = t.redMul(c); + m = i; + } + + return r; + }; + + Red.prototype.invm = function invm (a) { + var inv = a._invmp(this.m); + if (inv.negative !== 0) { + inv.negative = 0; + return this.imod(inv).redNeg(); + } else { + return this.imod(inv); + } + }; + + Red.prototype.pow = function pow (a, num) { + if (num.isZero()) return new BN(1).toRed(this); + if (num.cmpn(1) === 0) return a.clone(); + + var windowSize = 4; + var wnd = new Array(1 << windowSize); + wnd[0] = new BN(1).toRed(this); + wnd[1] = a; + for (var i = 2; i < wnd.length; i++) { + wnd[i] = this.mul(wnd[i - 1], a); + } + + var res = wnd[0]; + var current = 0; + var currentLen = 0; + var start = num.bitLength() % 26; + if (start === 0) { + start = 26; + } + + for (i = num.length - 1; i >= 0; i--) { + var word = num.words[i]; + for (var j = start - 1; j >= 0; j--) { + var bit = (word >> j) & 1; + if (res !== wnd[0]) { + res = this.sqr(res); + } + + if (bit === 0 && current === 0) { + currentLen = 0; + continue; + } + + current <<= 1; + current |= bit; + currentLen++; + if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue; + + res = this.mul(res, wnd[current]); + currentLen = 0; + current = 0; + } + start = 26; + } + + return res; + }; + + Red.prototype.convertTo = function convertTo (num) { + var r = num.umod(this.m); + + return r === num ? r.clone() : r; + }; + + Red.prototype.convertFrom = function convertFrom (num) { + var res = num.clone(); + res.red = null; + return res; + }; + + // + // Montgomery method engine + // + + BN.mont = function mont (num) { + return new Mont(num); + }; + + function Mont (m) { + Red.call(this, m); + + this.shift = this.m.bitLength(); + if (this.shift % 26 !== 0) { + this.shift += 26 - (this.shift % 26); + } + + this.r = new BN(1).iushln(this.shift); + this.r2 = this.imod(this.r.sqr()); + this.rinv = this.r._invmp(this.m); + + this.minv = this.rinv.mul(this.r).isubn(1).div(this.m); + this.minv = this.minv.umod(this.r); + this.minv = this.r.sub(this.minv); + } + inherits(Mont, Red); + + Mont.prototype.convertTo = function convertTo (num) { + return this.imod(num.ushln(this.shift)); + }; + + Mont.prototype.convertFrom = function convertFrom (num) { + var r = this.imod(num.mul(this.rinv)); + r.red = null; + return r; + }; + + Mont.prototype.imul = function imul (a, b) { + if (a.isZero() || b.isZero()) { + a.words[0] = 0; + a.length = 1; + return a; + } + + var t = a.imul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.mul = function mul (a, b) { + if (a.isZero() || b.isZero()) return new BN(0)._forceRed(this); + + var t = a.mul(b); + var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m); + var u = t.isub(c).iushrn(this.shift); + var res = u; + if (u.cmp(this.m) >= 0) { + res = u.isub(this.m); + } else if (u.cmpn(0) < 0) { + res = u.iadd(this.m); + } + + return res._forceRed(this); + }; + + Mont.prototype.invm = function invm (a) { + // (AR)^-1 * R^2 = (A^-1 * R^-1) * R^2 = A^-1 * R + var res = this.imod(a._invmp(this.m).mul(this.r2)); + return res._forceRed(this); + }; +})(typeof module === 'undefined' || module, this); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/README.md b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/README.md new file mode 100644 index 000000000..7ff838973 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/README.md @@ -0,0 +1 @@ +// TODO: add web3 and cryptoJS here diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/crypto-js/crypto-js.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/crypto-js/crypto-js.js new file mode 100644 index 000000000..17f2b1163 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/crypto-js/crypto-js.js @@ -0,0 +1,5988 @@ +;(function (root, factory) { + if (typeof exports === "object") { + // CommonJS + module.exports = exports = factory(); + } + else if (typeof define === "function" && define.amd) { + // AMD + define([], factory); + } + else { + // Global (browser) + root.CryptoJS = factory(); + } +}(this, function () { + + /** + * CryptoJS core components. + */ + var CryptoJS = CryptoJS || (function (Math, undefined) { + /* + * Local polyfil of Object.create + */ + var create = Object.create || (function () { + function F() {}; + + return function (obj) { + var subtype; + + F.prototype = obj; + + subtype = new F(); + + F.prototype = null; + + return subtype; + }; + }()) + + /** + * CryptoJS namespace. + */ + var C = {}; + + /** + * Library namespace. + */ + var C_lib = C.lib = {}; + + /** + * Base object for prototypal inheritance. + */ + var Base = C_lib.Base = (function () { + + + return { + /** + * Creates a new object that inherits from this object. + * + * @param {Object} overrides Properties to copy into the new object. + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * field: 'value', + * + * method: function () { + * } + * }); + */ + extend: function (overrides) { + // Spawn + var subtype = create(this); + + // Augment + if (overrides) { + subtype.mixIn(overrides); + } + + // Create default initializer + if (!subtype.hasOwnProperty('init') || this.init === subtype.init) { + subtype.init = function () { + subtype.$super.init.apply(this, arguments); + }; + } + + // Initializer's prototype is the subtype object + subtype.init.prototype = subtype; + + // Reference supertype + subtype.$super = this; + + return subtype; + }, + + /** + * Extends this object and runs the init method. + * Arguments to create() will be passed to init(). + * + * @return {Object} The new object. + * + * @static + * + * @example + * + * var instance = MyType.create(); + */ + create: function () { + var instance = this.extend(); + instance.init.apply(instance, arguments); + + return instance; + }, + + /** + * Initializes a newly created object. + * Override this method to add some logic when your objects are created. + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * init: function () { + * // ... + * } + * }); + */ + init: function () { + }, + + /** + * Copies properties into this object. + * + * @param {Object} properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn: function (properties) { + for (var propertyName in properties) { + if (properties.hasOwnProperty(propertyName)) { + this[propertyName] = properties[propertyName]; + } + } + + // IE won't copy toString using the loop above + if (properties.hasOwnProperty('toString')) { + this.toString = properties.toString; + } + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = instance.clone(); + */ + clone: function () { + return this.init.prototype.extend(this); + } + }; + }()); + + /** + * An array of 32-bit words. + * + * @property {Array} words The array of 32-bit words. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var WordArray = C_lib.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of 32-bit words. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.create(); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 4; + } + }, + + /** + * Converts this word array to a string. + * + * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return {string} The stringified word array. + * + * @example + * + * var string = wordArray + ''; + * var string = wordArray.toString(); + * var string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString: function (encoder) { + return (encoder || Hex).stringify(this); + }, + + /** + * Concatenates a word array to this word array. + * + * @param {WordArray} wordArray The word array to append. + * + * @return {WordArray} This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat: function (wordArray) { + // Shortcuts + var thisWords = this.words; + var thatWords = wordArray.words; + var thisSigBytes = this.sigBytes; + var thatSigBytes = wordArray.sigBytes; + + // Clamp excess bits + this.clamp(); + + // Concat + if (thisSigBytes % 4) { + // Copy one byte at a time + for (var i = 0; i < thatSigBytes; i++) { + var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var i = 0; i < thatSigBytes; i += 4) { + thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + var r = (function (m_w) { + var m_w = m_w; + var m_z = 0x3ade68b1; + var mask = 0xffffffff; + + return function () { + m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask; + m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask; + var result = ((m_z << 0x10) + m_w) & mask; + result /= 0x100000000; + result += 0.5; + return result * (Math.random() > .5 ? 1 : -1); + } + }); + + for (var i = 0, rcache; i < nBytes; i += 4) { + var _r = r((rcache || Math.random()) * 0x100000000); + + rcache = _r() * 0x3ade67b7; + words.push((_r() * 0x100000000) | 0); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + var processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Reusable object + var W = []; + + /** + * SHA-1 hash algorithm. + */ + var SHA1 = C_algo.SHA1 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476, + 0xc3d2e1f0 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + + // Computation + for (var i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + var t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + C.SHA1 = Hasher._createHelper(SHA1); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + C.HmacSHA1 = Hasher._createHmacHelper(SHA1); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Initialization and round constants tables + var H = []; + var K = []; + + // Compute constants + (function () { + function isPrime(n) { + var sqrtN = Math.sqrt(n); + for (var factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } + + return true; + } + + function getFractionalBits(n) { + return ((n - (n | 0)) * 0x100000000) | 0; + } + + var n = 2; + var nPrime = 0; + while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; + } + }()); + + // Reusable object + var W = []; + + /** + * SHA-256 hash algorithm. + */ + var SHA256 = C_algo.SHA256 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init(H.slice(0)); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + var f = H[5]; + var g = H[6]; + var h = H[7]; + + // Computation + for (var i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var gamma0x = W[i - 15]; + var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3); + + var gamma1x = W[i - 2]; + var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + var ch = (e & f) ^ (~e & g); + var maj = (a & b) ^ (a & c) ^ (b & c); + + var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + var t1 = h + sigma1 + ch + K[i] + W[i]; + var t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + C.SHA256 = Hasher._createHelper(SHA256); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + C.HmacSHA256 = Hasher._createHmacHelper(SHA256); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * UTF-16 BE encoding strategy. + */ + var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + /** + * UTF-16 LE encoding strategy. + */ + C_enc.Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + function swapEndian(word) { + return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + } + }()); + + + (function () { + // Check if typed arrays are supported + if (typeof ArrayBuffer != 'function') { + return; + } + + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + + // Reference original init + var superInit = WordArray.init; + + // Augment WordArray.init to handle typed arrays + var subInit = WordArray.init = function (typedArray) { + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array || + (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || + typedArray instanceof Int16Array || + typedArray instanceof Uint16Array || + typedArray instanceof Int32Array || + typedArray instanceof Uint32Array || + typedArray instanceof Float32Array || + typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + var typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + var words = []; + for (var i = 0; i < typedArrayByteLength; i++) { + words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + superInit.call(this, words, typedArrayByteLength); + } else { + // Else call normal init + superInit.apply(this, arguments); + } + }; + + subInit.prototype = WordArray; + }()); + + + /** @preserve + (c) 2012 by Cédric Mesnil. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var _zl = WordArray.create([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); + var _zr = WordArray.create([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); + var _sl = WordArray.create([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); + var _sr = WordArray.create([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); + + var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); + var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + + /** + * RIPEMD160 hash algorithm. + */ + var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ + _doReset: function () { + this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + }, + + _doProcessBlock: function (M, offset) { + + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + // Swap + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + var H = this._hash.words; + var hl = _hl.words; + var hr = _hr.words; + var zl = _zl.words; + var zr = _zr.words; + var sl = _sl.words; + var sr = _sr.words; + + // Working variables + var al, bl, cl, dl, el; + var ar, br, cr, dr, er; + + ar = al = H[0]; + br = bl = H[1]; + cr = cl = H[2]; + dr = dl = H[3]; + er = el = H[4]; + // Computation + var t; + for (var i = 0; i < 80; i += 1) { + t = (al + M[offset+zl[i]])|0; + if (i<16){ + t += f1(bl,cl,dl) + hl[0]; + } else if (i<32) { + t += f2(bl,cl,dl) + hl[1]; + } else if (i<48) { + t += f3(bl,cl,dl) + hl[2]; + } else if (i<64) { + t += f4(bl,cl,dl) + hl[3]; + } else {// if (i<80) { + t += f5(bl,cl,dl) + hl[4]; + } + t = t|0; + t = rotl(t,sl[i]); + t = (t+el)|0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + M[offset+zr[i]])|0; + if (i<16){ + t += f5(br,cr,dr) + hr[0]; + } else if (i<32) { + t += f4(br,cr,dr) + hr[1]; + } else if (i<48) { + t += f3(br,cr,dr) + hr[2]; + } else if (i<64) { + t += f2(br,cr,dr) + hr[3]; + } else {// if (i<80) { + t += f1(br,cr,dr) + hr[4]; + } + t = t|0; + t = rotl(t,sr[i]) ; + t = (t+er)|0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr)|0; + H[1] = (H[2] + dl + er)|0; + H[2] = (H[3] + el + ar)|0; + H[3] = (H[4] + al + br)|0; + H[4] = (H[0] + bl + cr)|0; + H[0] = t; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | + (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 5; i++) { + // Shortcut + var H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + + function f1(x, y, z) { + return ((x) ^ (y) ^ (z)); + + } + + function f2(x, y, z) { + return (((x)&(y)) | ((~x)&(z))); + } + + function f3(x, y, z) { + return (((x) | (~(y))) ^ (z)); + } + + function f4(x, y, z) { + return (((x) & (z)) | ((y)&(~(z)))); + } + + function f5(x, y, z) { + return ((x) ^ ((y) |(~(z)))); + + } + + function rotl(x,n) { + return (x<>>(32-n)); + } + + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + C.RIPEMD160 = Hasher._createHelper(RIPEMD160); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA1 = C_algo.SHA1; + var HMAC = C_algo.HMAC; + + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + var PBKDF2 = C_algo.PBKDF2 = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: SHA1, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init HMAC + var hmac = HMAC.create(cfg.hasher, password); + + // Initial values + var derivedKey = WordArray.create(); + var blockIndex = WordArray.create([0x00000001]); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var blockIndexWords = blockIndex.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + var block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + var blockWords = block.words; + var blockWordsLength = blockWords.length; + + // Iterations + var intermediate = block; + for (var i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + var intermediateWords = intermediate.words; + + // XOR intermediate with block + for (var j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } + + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.PBKDF2 = function (password, salt, cfg) { + return PBKDF2.create(cfg).compute(password, salt); + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + var block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA256 = C_algo.SHA256; + + /** + * SHA-224 hash algorithm. + */ + var SHA224 = C_algo.SHA224 = SHA256.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + ]); + }, + + _doFinalize: function () { + var hash = SHA256._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + C.SHA224 = SHA256._createHelper(SHA224); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + C.HmacSHA224 = SHA256._createHmacHelper(SHA224); + }()); + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var X32WordArray = C_lib.WordArray; + + /** + * x64 namespace. + */ + var C_x64 = C.x64 = {}; + + /** + * A 64-bit word. + */ + var X64Word = C_x64.Word = Base.extend({ + /** + * Initializes a newly created 64-bit word. + * + * @param {number} high The high 32 bits. + * @param {number} low The low 32 bits. + * + * @example + * + * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); + */ + init: function (high, low) { + this.high = high; + this.low = low; + } + + /** + * Bitwise NOTs this word. + * + * @return {X64Word} A new x64-Word object after negating. + * + * @example + * + * var negated = x64Word.not(); + */ + // not: function () { + // var high = ~this.high; + // var low = ~this.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ANDs this word with the passed word. + * + * @param {X64Word} word The x64-Word to AND with this word. + * + * @return {X64Word} A new x64-Word object after ANDing. + * + * @example + * + * var anded = x64Word.and(anotherX64Word); + */ + // and: function (word) { + // var high = this.high & word.high; + // var low = this.low & word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to OR with this word. + * + * @return {X64Word} A new x64-Word object after ORing. + * + * @example + * + * var ored = x64Word.or(anotherX64Word); + */ + // or: function (word) { + // var high = this.high | word.high; + // var low = this.low | word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise XORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to XOR with this word. + * + * @return {X64Word} A new x64-Word object after XORing. + * + * @example + * + * var xored = x64Word.xor(anotherX64Word); + */ + // xor: function (word) { + // var high = this.high ^ word.high; + // var low = this.low ^ word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the left. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftL(25); + */ + // shiftL: function (n) { + // if (n < 32) { + // var high = (this.high << n) | (this.low >>> (32 - n)); + // var low = this.low << n; + // } else { + // var high = this.low << (n - 32); + // var low = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the right. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftR(7); + */ + // shiftR: function (n) { + // if (n < 32) { + // var low = (this.low >>> n) | (this.high << (32 - n)); + // var high = this.high >>> n; + // } else { + // var low = this.high >>> (n - 32); + // var high = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Rotates this word n bits to the left. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotL(25); + */ + // rotL: function (n) { + // return this.shiftL(n).or(this.shiftR(64 - n)); + // }, + + /** + * Rotates this word n bits to the right. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotR(7); + */ + // rotR: function (n) { + // return this.shiftR(n).or(this.shiftL(64 - n)); + // }, + + /** + * Adds this word with the passed word. + * + * @param {X64Word} word The x64-Word to add with this word. + * + * @return {X64Word} A new x64-Word object after adding. + * + * @example + * + * var added = x64Word.add(anotherX64Word); + */ + // add: function (word) { + // var low = (this.low + word.low) | 0; + // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; + // var high = (this.high + word.high + carry) | 0; + + // return X64Word.create(high, low); + // } + }); + + /** + * An array of 64-bit words. + * + * @property {Array} words The array of CryptoJS.x64.Word objects. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var X64WordArray = C_x64.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.x64.WordArray.create(); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ]); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ], 10); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 8; + } + }, + + /** + * Converts this 64-bit word array to a 32-bit word array. + * + * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. + * + * @example + * + * var x32WordArray = x64WordArray.toX32(); + */ + toX32: function () { + // Shortcuts + var x64Words = this.words; + var x64WordsLength = x64Words.length; + + // Convert + var x32Words = []; + for (var i = 0; i < x64WordsLength; i++) { + var x64Word = x64Words[i]; + x32Words.push(x64Word.high); + x32Words.push(x64Word.low); + } + + return X32WordArray.create(x32Words, this.sigBytes); + }, + + /** + * Creates a copy of this word array. + * + * @return {X64WordArray} The clone. + * + * @example + * + * var clone = x64WordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + + // Clone "words" array + var words = clone.words = this.words.slice(0); + + // Clone each X64Word object + var wordsLength = words.length; + for (var i = 0; i < wordsLength; i++) { + words[i] = words[i].clone(); + } + + return clone; + } + }); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var C_algo = C.algo; + + // Constants tables + var RHO_OFFSETS = []; + var PI_INDEXES = []; + var ROUND_CONSTANTS = []; + + // Compute Constants + (function () { + // Compute rho offset constants + var x = 1, y = 0; + for (var t = 0; t < 24; t++) { + RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64; + + var newX = y % 5; + var newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + // Compute pi index constants + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; + } + } + + // Compute round constants + var LFSR = 0x01; + for (var i = 0; i < 24; i++) { + var roundConstantMsw = 0; + var roundConstantLsw = 0; + + for (var j = 0; j < 7; j++) { + if (LFSR & 0x01) { + var bitPosition = (1 << j) - 1; + if (bitPosition < 32) { + roundConstantLsw ^= 1 << bitPosition; + } else /* if (bitPosition >= 32) */ { + roundConstantMsw ^= 1 << (bitPosition - 32); + } + } + + // Compute next LFSR + if (LFSR & 0x80) { + // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 + LFSR = (LFSR << 1) ^ 0x71; + } else { + LFSR <<= 1; + } + } + + ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw); + } + }()); + + // Reusable objects for temporary values + var T = []; + (function () { + for (var i = 0; i < 25; i++) { + T[i] = X64Word.create(); + } + }()); + + /** + * SHA-3 hash algorithm. + */ + var SHA3 = C_algo.SHA3 = Hasher.extend({ + /** + * Configuration options. + * + * @property {number} outputLength + * The desired number of bits in the output hash. + * Only values permitted are: 224, 256, 384, 512. + * Default: 512 + */ + cfg: Hasher.cfg.extend({ + outputLength: 512 + }), + + _doReset: function () { + var state = this._state = [] + for (var i = 0; i < 25; i++) { + state[i] = new X64Word.init(); + } + + this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var state = this._state; + var nBlockSizeLanes = this.blockSize / 2; + + // Absorb + for (var i = 0; i < nBlockSizeLanes; i++) { + // Shortcuts + var M2i = M[offset + 2 * i]; + var M2i1 = M[offset + 2 * i + 1]; + + // Swap endian + M2i = ( + (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | + (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00) + ); + M2i1 = ( + (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | + (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00) + ); + + // Absorb message into state + var lane = state[i]; + lane.high ^= M2i1; + lane.low ^= M2i; + } + + // Rounds + for (var round = 0; round < 24; round++) { + // Theta + for (var x = 0; x < 5; x++) { + // Mix column lanes + var tMsw = 0, tLsw = 0; + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + tMsw ^= lane.high; + tLsw ^= lane.low; + } + + // Temporary values + var Tx = T[x]; + Tx.high = tMsw; + Tx.low = tLsw; + } + for (var x = 0; x < 5; x++) { + // Shortcuts + var Tx4 = T[(x + 4) % 5]; + var Tx1 = T[(x + 1) % 5]; + var Tx1Msw = Tx1.high; + var Tx1Lsw = Tx1.low; + + // Mix surrounding columns + var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); + var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + lane.high ^= tMsw; + lane.low ^= tLsw; + } + } + + // Rho Pi + for (var laneIndex = 1; laneIndex < 25; laneIndex++) { + // Shortcuts + var lane = state[laneIndex]; + var laneMsw = lane.high; + var laneLsw = lane.low; + var rhoOffset = RHO_OFFSETS[laneIndex]; + + // Rotate lanes + if (rhoOffset < 32) { + var tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); + var tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); + } else /* if (rhoOffset >= 32) */ { + var tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); + var tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); + } + + // Transpose lanes + var TPiLane = T[PI_INDEXES[laneIndex]]; + TPiLane.high = tMsw; + TPiLane.low = tLsw; + } + + // Rho pi at x = y = 0 + var T0 = T[0]; + var state0 = state[0]; + T0.high = state0.high; + T0.low = state0.low; + + // Chi + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + // Shortcuts + var laneIndex = x + 5 * y; + var lane = state[laneIndex]; + var TLane = T[laneIndex]; + var Tx1Lane = T[((x + 1) % 5) + 5 * y]; + var Tx2Lane = T[((x + 2) % 5) + 5 * y]; + + // Mix rows + lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); + lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); + } + } + + // Iota + var lane = state[0]; + var roundConstant = ROUND_CONSTANTS[round]; + lane.high ^= roundConstant.high; + lane.low ^= roundConstant.low;; + } + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + var blockSizeBits = this.blockSize * 32; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); + dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var state = this._state; + var outputLengthBytes = this.cfg.outputLength / 8; + var outputLengthLanes = outputLengthBytes / 8; + + // Squeeze + var hashWords = []; + for (var i = 0; i < outputLengthLanes; i++) { + // Shortcuts + var lane = state[i]; + var laneMsw = lane.high; + var laneLsw = lane.low; + + // Swap endian + laneMsw = ( + (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | + (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00) + ); + laneLsw = ( + (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | + (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00) + ); + + // Squeeze state to retrieve hash + hashWords.push(laneLsw); + hashWords.push(laneMsw); + } + + // Return final computed hash + return new WordArray.init(hashWords, outputLengthBytes); + }, + + clone: function () { + var clone = Hasher.clone.call(this); + + var state = clone._state = this._state.slice(0); + for (var i = 0; i < 25; i++) { + state[i] = state[i].clone(); + } + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ + C.SHA3 = Hasher._createHelper(SHA3); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ + C.HmacSHA3 = Hasher._createHmacHelper(SHA3); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + + function X64Word_create() { + return X64Word.create.apply(X64Word, arguments); + } + + // Constants + var K = [ + X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), + X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), + X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), + X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), + X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), + X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), + X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), + X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), + X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), + X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), + X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), + X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), + X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), + X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), + X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), + X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), + X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), + X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), + X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), + X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), + X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), + X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), + X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), + X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), + X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), + X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), + X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), + X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), + X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), + X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), + X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), + X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), + X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), + X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), + X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), + X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), + X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), + X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), + X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), + X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) + ]; + + // Reusable objects + var W = []; + (function () { + for (var i = 0; i < 80; i++) { + W[i] = X64Word_create(); + } + }()); + + /** + * SHA-512 hash algorithm. + */ + var SHA512 = C_algo.SHA512 = Hasher.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), + new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), + new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), + new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var H = this._hash.words; + + var H0 = H[0]; + var H1 = H[1]; + var H2 = H[2]; + var H3 = H[3]; + var H4 = H[4]; + var H5 = H[5]; + var H6 = H[6]; + var H7 = H[7]; + + var H0h = H0.high; + var H0l = H0.low; + var H1h = H1.high; + var H1l = H1.low; + var H2h = H2.high; + var H2l = H2.low; + var H3h = H3.high; + var H3l = H3.low; + var H4h = H4.high; + var H4l = H4.low; + var H5h = H5.high; + var H5l = H5.low; + var H6h = H6.high; + var H6l = H6.low; + var H7h = H7.high; + var H7l = H7.low; + + // Working variables + var ah = H0h; + var al = H0l; + var bh = H1h; + var bl = H1l; + var ch = H2h; + var cl = H2l; + var dh = H3h; + var dl = H3l; + var eh = H4h; + var el = H4l; + var fh = H5h; + var fl = H5l; + var gh = H6h; + var gl = H6l; + var hh = H7h; + var hl = H7l; + + // Rounds + for (var i = 0; i < 80; i++) { + // Shortcut + var Wi = W[i]; + + // Extend message + if (i < 16) { + var Wih = Wi.high = M[offset + i * 2] | 0; + var Wil = Wi.low = M[offset + i * 2 + 1] | 0; + } else { + // Gamma0 + var gamma0x = W[i - 15]; + var gamma0xh = gamma0x.high; + var gamma0xl = gamma0x.low; + var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); + var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + var gamma1x = W[i - 2]; + var gamma1xh = gamma1x.high; + var gamma1xl = gamma1x.low; + var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); + var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[i - 7]; + var Wi7h = Wi7.high; + var Wi7l = Wi7.low; + + var Wi16 = W[i - 16]; + var Wi16h = Wi16.high; + var Wi16l = Wi16.low; + + var Wil = gamma0l + Wi7l; + var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + var Wil = Wil + gamma1l; + var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + var Wil = Wil + Wi16l; + var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[i]; + var Kih = Ki.high; + var Kil = Ki.low; + + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + var t1l = t1l + chl; + var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + var t1l = t1l + Kil; + var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + var t1l = t1l + Wil; + var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash value + H0l = H0.low = (H0l + al); + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1l = H1.low = (H1l + bl); + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2l = H2.low = (H2l + cl); + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3l = H3.low = (H3l + dl); + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4l = H4.low = (H4l + el); + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5l = H5.low = (H5l + fl); + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6l = H6.low = (H6l + gl); + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7l = H7.low = (H7l + hl); + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + var hash = this._hash.toX32(); + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + }, + + blockSize: 1024/32 + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + C.SHA512 = Hasher._createHelper(SHA512); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + C.HmacSHA512 = Hasher._createHmacHelper(SHA512); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + var SHA512 = C_algo.SHA512; + + /** + * SHA-384 hash algorithm. + */ + var SHA384 = C_algo.SHA384 = SHA512.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), + new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), + new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), + new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) + ]); + }, + + _doFinalize: function () { + var hash = SHA512._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + C.SHA384 = SHA512._createHelper(SHA384); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + C.HmacSHA384 = SHA512._createHmacHelper(SHA384); + }()); + + + /** + * Cipher core components. + */ + CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + var block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + var modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + var modeCreator = mode.createDecryptor; + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + + if (this._mode && this._mode.__creator == modeCreator) { + this._mode.init(this, iv && iv.words); + } else { + this._mode = modeCreator.call(mode, this, iv && iv.words); + this._mode.__creator = modeCreator; + } + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + var finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + var wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + var salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); + }()); + + + /** + * Cipher Feedback block mode. + */ + CryptoJS.mode.CFB = (function () { + var CFB = CryptoJS.lib.BlockCipherMode.extend(); + + CFB.Encryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + CFB.Decryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + // Shortcut + var iv = this._iv; + + // Generate keystream + if (iv) { + var keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + var keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + + return CFB; + }()); + + + /** + * Electronic Codebook block mode. + */ + CryptoJS.mode.ECB = (function () { + var ECB = CryptoJS.lib.BlockCipherMode.extend(); + + ECB.Encryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.encryptBlock(words, offset); + } + }); + + ECB.Decryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.decryptBlock(words, offset); + } + }); + + return ECB; + }()); + + + /** + * ANSI X.923 padding strategy. + */ + CryptoJS.pad.AnsiX923 = { + pad: function (data, blockSize) { + // Shortcuts + var dataSigBytes = data.sigBytes; + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; + + // Compute last byte position + var lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + data.clamp(); + data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + data.sigBytes += nPaddingBytes; + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO 10126 padding strategy. + */ + CryptoJS.pad.Iso10126 = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Pad + data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). + concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + CryptoJS.pad.Iso97971 = { + pad: function (data, blockSize) { + // Add 0x80 byte + data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); + + // Zero pad the rest + CryptoJS.pad.ZeroPadding.pad(data, blockSize); + }, + + unpad: function (data) { + // Remove zero padding + CryptoJS.pad.ZeroPadding.unpad(data); + + // Remove one more byte -- the 0x80 byte + data.sigBytes--; + } + }; + + + /** + * Output Feedback block mode. + */ + CryptoJS.mode.OFB = (function () { + var OFB = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = OFB.Encryptor = OFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var keystream = this._keystream; + + // Generate keystream + if (iv) { + keystream = this._keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + OFB.Decryptor = Encryptor; + + return OFB; + }()); + + + /** + * A noop padding strategy. + */ + CryptoJS.pad.NoPadding = { + pad: function () { + }, + + unpad: function () { + } + }; + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var CipherParams = C_lib.CipherParams; + var C_enc = C.enc; + var Hex = C_enc.Hex; + var C_format = C.format; + + var HexFormatter = C_format.Hex = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify: function (cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse: function (input) { + var ciphertext = Hex.parse(input); + return CipherParams.create({ ciphertext: ciphertext }); + } + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Lookup tables + var SBOX = []; + var INV_SBOX = []; + var SUB_MIX_0 = []; + var SUB_MIX_1 = []; + var SUB_MIX_2 = []; + var SUB_MIX_3 = []; + var INV_SUB_MIX_0 = []; + var INV_SUB_MIX_1 = []; + var INV_SUB_MIX_2 = []; + var INV_SUB_MIX_3 = []; + + // Compute lookup tables + (function () { + // Compute double table + var d = []; + for (var i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } + } + + // Walk GF(2^8) + var x = 0; + var xi = 0; + for (var i = 0; i < 256; i++) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + var x2 = d[x]; + var x4 = d[x2]; + var x8 = d[x4]; + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100); + SUB_MIX_0[x] = (t << 24) | (t >>> 8); + SUB_MIX_1[x] = (t << 16) | (t >>> 16); + SUB_MIX_2[x] = (t << 8) | (t >>> 24); + SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + x = xi = 1; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } + } + }()); + + // Precomputed Rcon lookup + var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + + /** + * AES block cipher algorithm. + */ + var AES = C_algo.AES = BlockCipher.extend({ + _doReset: function () { + // Skip reset of nRounds has been set before and key did not change + if (this._nRounds && this._keyPriorReset === this._key) { + return; + } + + // Shortcuts + var key = this._keyPriorReset = this._key; + var keyWords = key.words; + var keySize = key.sigBytes / 4; + + // Compute number of rounds + var nRounds = this._nRounds = keySize + 6; + + // Compute number of key schedule rows + var ksRows = (nRounds + 1) * 4; + + // Compute key schedule + var keySchedule = this._keySchedule = []; + for (var ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + var t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize == 4) { + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + var invKeySchedule = this._invKeySchedule = []; + for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { + var ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + var t = keySchedule[ksRow]; + } else { + var t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ + INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; + } + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); + }, + + decryptBlock: function (M, offset) { + // Swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + + this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); + + // Inv swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + }, + + _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + // Shortcut + var nRounds = this._nRounds; + + // Get input, add round key + var s0 = M[offset] ^ keySchedule[0]; + var s1 = M[offset + 1] ^ keySchedule[1]; + var s2 = M[offset + 2] ^ keySchedule[2]; + var s3 = M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + var ksRow = 4; + + // Rounds + for (var round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; + var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; + var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; + var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; + var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; + var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; + var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; + + // Set output + M[offset] = t0; + M[offset + 1] = t1; + M[offset + 2] = t2; + M[offset + 3] = t3; + }, + + keySize: 256/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + C.AES = BlockCipher._createHelper(AES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Permuted Choice 1 constants + var PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4 + ]; + + // Permuted Choice 2 constants + var PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 + ]; + + // Cumulative bit shift constants + var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + + // SBOXes and round permutation constants + var SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } + ]; + + // Masks that select the SBOX input + var SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f + ]; + + /** + * DES block cipher algorithm. + */ + var DES = C_algo.DES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Select 56 bits according to PC1 + var keyBits = []; + for (var i = 0; i < 56; i++) { + var keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; + } + + // Assemble 16 subkeys + var subKeys = this._subKeys = []; + for (var nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + var subKey = subKeys[nSubKey] = []; + + // Shortcut + var bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (var i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (var i = 1; i < 7; i++) { + subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } + + // Compute inverse subkeys + var invSubKeys = this._invSubKeys = []; + for (var i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + }, + + decryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + }, + + _doCryptBlock: function (M, offset, subKeys) { + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (var round = 0; round < 16; round++) { + // Shortcuts + var subKey = subKeys[round]; + var lBlock = this._lBlock; + var rBlock = this._rBlock; + + // Feistel function + var f = 0; + for (var i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } + + // Undo swap from last round + var t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; + + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); + + // Set output + M[offset] = this._lBlock; + M[offset + 1] = this._rBlock; + }, + + keySize: 64/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + // Swap bits across the left and right words + function exchangeLR(offset, mask) { + var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; + } + + function exchangeRL(offset, mask) { + var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + C.DES = BlockCipher._createHelper(DES); + + /** + * Triple-DES block cipher algorithm. + */ + var TripleDES = C_algo.TripleDES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Create DES instances + this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2))); + this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4))); + this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6))); + }, + + encryptBlock: function (M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + }, + + decryptBlock: function (M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + }, + + keySize: 192/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + C.TripleDES = BlockCipher._createHelper(TripleDES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + /** + * RC4 stream cipher algorithm. + */ + var RC4 = C_algo.RC4 = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySigBytes = key.sigBytes; + + // Init sbox + var S = this._S = []; + for (var i = 0; i < 256; i++) { + S[i] = i; + } + + // Key setup + for (var i = 0, j = 0; i < 256; i++) { + var keyByteIndex = i % keySigBytes; + var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; + + j = (j + S[i] + keyByte) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + } + + // Counters + this._i = this._j = 0; + }, + + _doProcessBlock: function (M, offset) { + M[offset] ^= generateKeystreamWord.call(this); + }, + + keySize: 256/32, + + ivSize: 0 + }); + + function generateKeystreamWord() { + // Shortcuts + var S = this._S; + var i = this._i; + var j = this._j; + + // Generate keystream word + var keystreamWord = 0; + for (var n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } + + // Update counters + this._i = i; + this._j = j; + + return keystreamWord; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + C.RC4 = StreamCipher._createHelper(RC4); + + /** + * Modified RC4 stream cipher algorithm. + */ + var RC4Drop = C_algo.RC4Drop = RC4.extend({ + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + cfg: RC4.cfg.extend({ + drop: 192 + }), + + _doReset: function () { + RC4._doReset.call(this); + + // Drop + for (var i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + C.RC4Drop = StreamCipher._createHelper(RC4Drop); + }()); + + + /** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + CryptoJS.mode.CTRGladman = (function () { + var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); + + function incWord(word) + { + if (((word >> 24) & 0xff) === 0xff) { //overflow + var b1 = (word >> 16)&0xff; + var b2 = (word >> 8)&0xff; + var b3 = word & 0xff; + + if (b1 === 0xff) // overflow b1 + { + b1 = 0; + if (b2 === 0xff) + { + b2 = 0; + if (b3 === 0xff) + { + b3 = 0; + } + else + { + ++b3; + } + } + else + { + ++b2; + } + } + else + { + ++b1; + } + + word = 0; + word += (b1 << 16); + word += (b2 << 8); + word += b3; + } + else + { + word += (0x01 << 24); + } + return word; + } + + function incCounter(counter) + { + if ((counter[0] = incWord(counter[0])) === 0) + { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + counter[1] = incWord(counter[1]); + } + return counter; + } + + var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTRGladman.Decryptor = Encryptor; + + return CTRGladman; + }()); + + + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm + */ + var Rabbit = C_algo.Rabbit = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Swap endian + for (var i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | + (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + C.Rabbit = StreamCipher._createHelper(Rabbit); + }()); + + + /** + * Counter block mode. + */ + CryptoJS.mode.CTR = (function () { + var CTR = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = CTR.Encryptor = CTR.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTR.Decryptor = Encryptor; + + return CTR; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); + }()); + + + /** + * Zero padding strategy. + */ + CryptoJS.pad.ZeroPadding = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Pad + data.clamp(); + data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad: function (data) { + // Shortcut + var dataWords = data.words; + + // Unpad + var i = data.sigBytes - 1; + while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + i--; + } + data.sigBytes = i + 1; + } + }; + + + return CryptoJS; + +})); \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic.js new file mode 100644 index 000000000..12fd61bc1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic.js @@ -0,0 +1,22 @@ +'use strict'; + +var elliptic = exports; + +// hello ladies ;) +function inherits (ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; +} + +elliptic.inherits = inherits +elliptic.version = '6.5.0'; +elliptic.utils = require('./elliptic/utils'); +elliptic.curve = require('./elliptic/curve'); +elliptic.curves = require('./elliptic/curves'); + +// Protocols +elliptic.ec = require('./elliptic/ec'); +elliptic.eddsa = require('./elliptic/eddsa'); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/base.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/base.js new file mode 100644 index 000000000..051a106e3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/base.js @@ -0,0 +1,375 @@ +'use strict'; + +var BN = require('../../../../BN/bn'); +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var getNAF = utils.getNAF; +var getJSF = utils.getJSF; +var assert = utils.assert; + +function BaseCurve(type, conf) { + this.type = type; + this.p = new BN(conf.p, 16); + + // Use Montgomery, when there is no fast reduction for the prime + this.red = conf.prime ? BN.red(conf.prime) : BN.mont(this.p); + + // Useful for many curves + this.zero = new BN(0).toRed(this.red); + this.one = new BN(1).toRed(this.red); + this.two = new BN(2).toRed(this.red); + + // Curve configuration, optional + this.n = conf.n && new BN(conf.n, 16); + this.g = conf.g && this.pointFromJSON(conf.g, conf.gRed); + + // Temporary arrays + this._wnafT1 = new Array(4); + this._wnafT2 = new Array(4); + this._wnafT3 = new Array(4); + this._wnafT4 = new Array(4); + + // Generalized Greg Maxwell's trick + var adjustCount = this.n && this.p.div(this.n); + if (!adjustCount || adjustCount.cmpn(100) > 0) { + this.redN = null; + } else { + this._maxwellTrick = true; + this.redN = this.n.toRed(this.red); + } +} +module.exports = BaseCurve; + +BaseCurve.prototype.point = function point() { + throw new Error('Not implemented'); +}; + +BaseCurve.prototype.validate = function validate() { + throw new Error('Not implemented'); +}; + +BaseCurve.prototype._fixedNafMul = function _fixedNafMul(p, k) { + assert(p.precomputed); + var doubles = p._getDoubles(); + + var naf = getNAF(k, 1); + var I = (1 << (doubles.step + 1)) - (doubles.step % 2 === 0 ? 2 : 1); + I /= 3; + + // Translate into more windowed form + var repr = []; + for (var j = 0; j < naf.length; j += doubles.step) { + var nafW = 0; + for (var k = j + doubles.step - 1; k >= j; k--) + nafW = (nafW << 1) + naf[k]; + repr.push(nafW); + } + + var a = this.jpoint(null, null, null); + var b = this.jpoint(null, null, null); + for (var i = I; i > 0; i--) { + for (var j = 0; j < repr.length; j++) { + var nafW = repr[j]; + if (nafW === i) + b = b.mixedAdd(doubles.points[j]); + else if (nafW === -i) + b = b.mixedAdd(doubles.points[j].neg()); + } + a = a.add(b); + } + return a.toP(); +}; + +BaseCurve.prototype._wnafMul = function _wnafMul(p, k) { + var w = 4; + + // Precompute window + var nafPoints = p._getNAFPoints(w); + w = nafPoints.wnd; + var wnd = nafPoints.points; + + // Get NAF form + var naf = getNAF(k, w); + + // Add `this`*(N+1) for every w-NAF index + var acc = this.jpoint(null, null, null); + for (var i = naf.length - 1; i >= 0; i--) { + // Count zeroes + for (var k = 0; i >= 0 && naf[i] === 0; i--) + k++; + if (i >= 0) + k++; + acc = acc.dblp(k); + + if (i < 0) + break; + var z = naf[i]; + assert(z !== 0); + if (p.type === 'affine') { + // J +- P + if (z > 0) + acc = acc.mixedAdd(wnd[(z - 1) >> 1]); + else + acc = acc.mixedAdd(wnd[(-z - 1) >> 1].neg()); + } else { + // J +- J + if (z > 0) + acc = acc.add(wnd[(z - 1) >> 1]); + else + acc = acc.add(wnd[(-z - 1) >> 1].neg()); + } + } + return p.type === 'affine' ? acc.toP() : acc; +}; + +BaseCurve.prototype._wnafMulAdd = function _wnafMulAdd(defW, + points, + coeffs, + len, + jacobianResult) { + var wndWidth = this._wnafT1; + var wnd = this._wnafT2; + var naf = this._wnafT3; + + // Fill all arrays + var max = 0; + for (var i = 0; i < len; i++) { + var p = points[i]; + var nafPoints = p._getNAFPoints(defW); + wndWidth[i] = nafPoints.wnd; + wnd[i] = nafPoints.points; + } + + // Comb small window NAFs + for (var i = len - 1; i >= 1; i -= 2) { + var a = i - 1; + var b = i; + if (wndWidth[a] !== 1 || wndWidth[b] !== 1) { + naf[a] = getNAF(coeffs[a], wndWidth[a]); + naf[b] = getNAF(coeffs[b], wndWidth[b]); + max = Math.max(naf[a].length, max); + max = Math.max(naf[b].length, max); + continue; + } + + var comb = [ + points[a], /* 1 */ + null, /* 3 */ + null, /* 5 */ + points[b] /* 7 */ + ]; + + // Try to avoid Projective points, if possible + if (points[a].y.cmp(points[b].y) === 0) { + comb[1] = points[a].add(points[b]); + comb[2] = points[a].toJ().mixedAdd(points[b].neg()); + } else if (points[a].y.cmp(points[b].y.redNeg()) === 0) { + comb[1] = points[a].toJ().mixedAdd(points[b]); + comb[2] = points[a].add(points[b].neg()); + } else { + comb[1] = points[a].toJ().mixedAdd(points[b]); + comb[2] = points[a].toJ().mixedAdd(points[b].neg()); + } + + var index = [ + -3, /* -1 -1 */ + -1, /* -1 0 */ + -5, /* -1 1 */ + -7, /* 0 -1 */ + 0, /* 0 0 */ + 7, /* 0 1 */ + 5, /* 1 -1 */ + 1, /* 1 0 */ + 3 /* 1 1 */ + ]; + + var jsf = getJSF(coeffs[a], coeffs[b]); + max = Math.max(jsf[0].length, max); + naf[a] = new Array(max); + naf[b] = new Array(max); + for (var j = 0; j < max; j++) { + var ja = jsf[0][j] | 0; + var jb = jsf[1][j] | 0; + + naf[a][j] = index[(ja + 1) * 3 + (jb + 1)]; + naf[b][j] = 0; + wnd[a] = comb; + } + } + + var acc = this.jpoint(null, null, null); + var tmp = this._wnafT4; + for (var i = max; i >= 0; i--) { + var k = 0; + + while (i >= 0) { + var zero = true; + for (var j = 0; j < len; j++) { + tmp[j] = naf[j][i] | 0; + if (tmp[j] !== 0) + zero = false; + } + if (!zero) + break; + k++; + i--; + } + if (i >= 0) + k++; + acc = acc.dblp(k); + if (i < 0) + break; + + for (var j = 0; j < len; j++) { + var z = tmp[j]; + var p; + if (z === 0) + continue; + else if (z > 0) + p = wnd[j][(z - 1) >> 1]; + else if (z < 0) + p = wnd[j][(-z - 1) >> 1].neg(); + + if (p.type === 'affine') + acc = acc.mixedAdd(p); + else + acc = acc.add(p); + } + } + // Zeroify references + for (var i = 0; i < len; i++) + wnd[i] = null; + + if (jacobianResult) + return acc; + else + return acc.toP(); +}; + +function BasePoint(curve, type) { + this.curve = curve; + this.type = type; + this.precomputed = null; +} +BaseCurve.BasePoint = BasePoint; + +BasePoint.prototype.eq = function eq(/*other*/) { + throw new Error('Not implemented'); +}; + +BasePoint.prototype.validate = function validate() { + return this.curve.validate(this); +}; + +BaseCurve.prototype.decodePoint = function decodePoint(bytes, enc) { + bytes = utils.toArray(bytes, enc); + + var len = this.p.byteLength(); + + // uncompressed, hybrid-odd, hybrid-even + if ((bytes[0] === 0x04 || bytes[0] === 0x06 || bytes[0] === 0x07) && + bytes.length - 1 === 2 * len) { + if (bytes[0] === 0x06) + assert(bytes[bytes.length - 1] % 2 === 0); + else if (bytes[0] === 0x07) + assert(bytes[bytes.length - 1] % 2 === 1); + + var res = this.point(bytes.slice(1, 1 + len), + bytes.slice(1 + len, 1 + 2 * len)); + + return res; + } else if ((bytes[0] === 0x02 || bytes[0] === 0x03) && + bytes.length - 1 === len) { + return this.pointFromX(bytes.slice(1, 1 + len), bytes[0] === 0x03); + } + throw new Error('Unknown point format'); +}; + +BasePoint.prototype.encodeCompressed = function encodeCompressed(enc) { + return this.encode(enc, true); +}; + +BasePoint.prototype._encode = function _encode(compact) { + var len = this.curve.p.byteLength(); + var x = this.getX().toArray('be', len); + + if (compact) + return [ this.getY().isEven() ? 0x02 : 0x03 ].concat(x); + + return [ 0x04 ].concat(x, this.getY().toArray('be', len)) ; +}; + +BasePoint.prototype.encode = function encode(enc, compact) { + return utils.encode(this._encode(compact), enc); +}; + +BasePoint.prototype.precompute = function precompute(power) { + if (this.precomputed) + return this; + + var precomputed = { + doubles: null, + naf: null, + beta: null + }; + precomputed.naf = this._getNAFPoints(8); + precomputed.doubles = this._getDoubles(4, power); + precomputed.beta = this._getBeta(); + this.precomputed = precomputed; + + return this; +}; + +BasePoint.prototype._hasDoubles = function _hasDoubles(k) { + if (!this.precomputed) + return false; + + var doubles = this.precomputed.doubles; + if (!doubles) + return false; + + return doubles.points.length >= Math.ceil((k.bitLength() + 1) / doubles.step); +}; + +BasePoint.prototype._getDoubles = function _getDoubles(step, power) { + if (this.precomputed && this.precomputed.doubles) + return this.precomputed.doubles; + + var doubles = [ this ]; + var acc = this; + for (var i = 0; i < power; i += step) { + for (var j = 0; j < step; j++) + acc = acc.dbl(); + doubles.push(acc); + } + return { + step: step, + points: doubles + }; +}; + +BasePoint.prototype._getNAFPoints = function _getNAFPoints(wnd) { + if (this.precomputed && this.precomputed.naf) + return this.precomputed.naf; + + var res = [ this ]; + var max = (1 << wnd) - 1; + var dbl = max === 1 ? null : this.dbl(); + for (var i = 1; i < max; i++) + res[i] = res[i - 1].add(dbl); + return { + wnd: wnd, + points: res + }; +}; + +BasePoint.prototype._getBeta = function _getBeta() { + return null; +}; + +BasePoint.prototype.dblp = function dblp(k) { + var r = this; + for (var i = 0; i < k; i++) + r = r.dbl(); + return r; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/edwards.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/edwards.js new file mode 100644 index 000000000..28eeb1b00 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/edwards.js @@ -0,0 +1,433 @@ +'use strict'; + +var curve = require('../curve'); +var elliptic = require('../../elliptic'); +var BN = require('../../../../BN/bn'); +var inherits = elliptic.inherits; +var Base = curve.base; + +var assert = elliptic.utils.assert; + +function EdwardsCurve(conf) { + // NOTE: Important as we are creating point in Base.call() + this.twisted = (conf.a | 0) !== 1; + this.mOneA = this.twisted && (conf.a | 0) === -1; + this.extended = this.mOneA; + + Base.call(this, 'edwards', conf); + + this.a = new BN(conf.a, 16).umod(this.red.m); + this.a = this.a.toRed(this.red); + this.c = new BN(conf.c, 16).toRed(this.red); + this.c2 = this.c.redSqr(); + this.d = new BN(conf.d, 16).toRed(this.red); + this.dd = this.d.redAdd(this.d); + + assert(!this.twisted || this.c.fromRed().cmpn(1) === 0); + this.oneC = (conf.c | 0) === 1; +} +inherits(EdwardsCurve, Base); +module.exports = EdwardsCurve; + +EdwardsCurve.prototype._mulA = function _mulA(num) { + if (this.mOneA) + return num.redNeg(); + else + return this.a.redMul(num); +}; + +EdwardsCurve.prototype._mulC = function _mulC(num) { + if (this.oneC) + return num; + else + return this.c.redMul(num); +}; + +// Just for compatibility with Short curve +EdwardsCurve.prototype.jpoint = function jpoint(x, y, z, t) { + return this.point(x, y, z, t); +}; + +EdwardsCurve.prototype.pointFromX = function pointFromX(x, odd) { + x = new BN(x, 16); + if (!x.red) + x = x.toRed(this.red); + + var x2 = x.redSqr(); + var rhs = this.c2.redSub(this.a.redMul(x2)); + var lhs = this.one.redSub(this.c2.redMul(this.d).redMul(x2)); + + var y2 = rhs.redMul(lhs.redInvm()); + var y = y2.redSqrt(); + if (y.redSqr().redSub(y2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + var isOdd = y.fromRed().isOdd(); + if (odd && !isOdd || !odd && isOdd) + y = y.redNeg(); + + return this.point(x, y); +}; + +EdwardsCurve.prototype.pointFromY = function pointFromY(y, odd) { + y = new BN(y, 16); + if (!y.red) + y = y.toRed(this.red); + + // x^2 = (y^2 - 1) / (d y^2 + 1) + var y2 = y.redSqr(); + var lhs = y2.redSub(this.one); + var rhs = y2.redMul(this.d).redAdd(this.one); + var x2 = lhs.redMul(rhs.redInvm()); + + if (x2.cmp(this.zero) === 0) { + if (odd) + throw new Error('invalid point'); + else + return this.point(this.zero, y); + } + + var x = x2.redSqrt(); + if (x.redSqr().redSub(x2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + if (x.isOdd() !== odd) + x = x.redNeg(); + + return this.point(x, y); +}; + +EdwardsCurve.prototype.validate = function validate(point) { + if (point.isInfinity()) + return true; + + // Curve: A * X^2 + Y^2 = C^2 * (1 + D * X^2 * Y^2) + point.normalize(); + + var x2 = point.x.redSqr(); + var y2 = point.y.redSqr(); + var lhs = x2.redMul(this.a).redAdd(y2); + var rhs = this.c2.redMul(this.one.redAdd(this.d.redMul(x2).redMul(y2))); + + return lhs.cmp(rhs) === 0; +}; + +function Point(curve, x, y, z, t) { + Base.BasePoint.call(this, curve, 'projective'); + if (x === null && y === null && z === null) { + this.x = this.curve.zero; + this.y = this.curve.one; + this.z = this.curve.one; + this.t = this.curve.zero; + this.zOne = true; + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + this.z = z ? new BN(z, 16) : this.curve.one; + this.t = t && new BN(t, 16); + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + if (this.t && !this.t.red) + this.t = this.t.toRed(this.curve.red); + this.zOne = this.z === this.curve.one; + + // Use extended coordinates + if (this.curve.extended && !this.t) { + this.t = this.x.redMul(this.y); + if (!this.zOne) + this.t = this.t.redMul(this.z.redInvm()); + } + } +} +inherits(Point, Base.BasePoint); + +EdwardsCurve.prototype.pointFromJSON = function pointFromJSON(obj) { + return Point.fromJSON(this, obj); +}; + +EdwardsCurve.prototype.point = function point(x, y, z, t) { + return new Point(this, x, y, z, t); +}; + +Point.fromJSON = function fromJSON(curve, obj) { + return new Point(curve, obj[0], obj[1], obj[2]); +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.x.cmpn(0) === 0 && + this.y.cmp(this.z) === 0; +}; + +Point.prototype._extDbl = function _extDbl() { + // hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + // #doubling-dbl-2008-hwcd + // 4M + 4S + + // A = X1^2 + var a = this.x.redSqr(); + // B = Y1^2 + var b = this.y.redSqr(); + // C = 2 * Z1^2 + var c = this.z.redSqr(); + c = c.redIAdd(c); + // D = a * A + var d = this.curve._mulA(a); + // E = (X1 + Y1)^2 - A - B + var e = this.x.redAdd(this.y).redSqr().redISub(a).redISub(b); + // G = D + B + var g = d.redAdd(b); + // F = G - C + var f = g.redSub(c); + // H = D - B + var h = d.redSub(b); + // X3 = E * F + var nx = e.redMul(f); + // Y3 = G * H + var ny = g.redMul(h); + // T3 = E * H + var nt = e.redMul(h); + // Z3 = F * G + var nz = f.redMul(g); + return this.curve.point(nx, ny, nz, nt); +}; + +Point.prototype._projDbl = function _projDbl() { + // hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + // #doubling-dbl-2008-bbjlp + // #doubling-dbl-2007-bl + // and others + // Generally 3M + 4S or 2M + 4S + + // B = (X1 + Y1)^2 + var b = this.x.redAdd(this.y).redSqr(); + // C = X1^2 + var c = this.x.redSqr(); + // D = Y1^2 + var d = this.y.redSqr(); + + var nx; + var ny; + var nz; + if (this.curve.twisted) { + // E = a * C + var e = this.curve._mulA(c); + // F = E + D + var f = e.redAdd(d); + if (this.zOne) { + // X3 = (B - C - D) * (F - 2) + nx = b.redSub(c).redSub(d).redMul(f.redSub(this.curve.two)); + // Y3 = F * (E - D) + ny = f.redMul(e.redSub(d)); + // Z3 = F^2 - 2 * F + nz = f.redSqr().redSub(f).redSub(f); + } else { + // H = Z1^2 + var h = this.z.redSqr(); + // J = F - 2 * H + var j = f.redSub(h).redISub(h); + // X3 = (B-C-D)*J + nx = b.redSub(c).redISub(d).redMul(j); + // Y3 = F * (E - D) + ny = f.redMul(e.redSub(d)); + // Z3 = F * J + nz = f.redMul(j); + } + } else { + // E = C + D + var e = c.redAdd(d); + // H = (c * Z1)^2 + var h = this.curve._mulC(this.c.redMul(this.z)).redSqr(); + // J = E - 2 * H + var j = e.redSub(h).redSub(h); + // X3 = c * (B - E) * J + nx = this.curve._mulC(b.redISub(e)).redMul(j); + // Y3 = c * E * (C - D) + ny = this.curve._mulC(e).redMul(c.redISub(d)); + // Z3 = E * J + nz = e.redMul(j); + } + return this.curve.point(nx, ny, nz); +}; + +Point.prototype.dbl = function dbl() { + if (this.isInfinity()) + return this; + + // Double in extended coordinates + if (this.curve.extended) + return this._extDbl(); + else + return this._projDbl(); +}; + +Point.prototype._extAdd = function _extAdd(p) { + // hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + // #addition-add-2008-hwcd-3 + // 8M + + // A = (Y1 - X1) * (Y2 - X2) + var a = this.y.redSub(this.x).redMul(p.y.redSub(p.x)); + // B = (Y1 + X1) * (Y2 + X2) + var b = this.y.redAdd(this.x).redMul(p.y.redAdd(p.x)); + // C = T1 * k * T2 + var c = this.t.redMul(this.curve.dd).redMul(p.t); + // D = Z1 * 2 * Z2 + var d = this.z.redMul(p.z.redAdd(p.z)); + // E = B - A + var e = b.redSub(a); + // F = D - C + var f = d.redSub(c); + // G = D + C + var g = d.redAdd(c); + // H = B + A + var h = b.redAdd(a); + // X3 = E * F + var nx = e.redMul(f); + // Y3 = G * H + var ny = g.redMul(h); + // T3 = E * H + var nt = e.redMul(h); + // Z3 = F * G + var nz = f.redMul(g); + return this.curve.point(nx, ny, nz, nt); +}; + +Point.prototype._projAdd = function _projAdd(p) { + // hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + // #addition-add-2008-bbjlp + // #addition-add-2007-bl + // 10M + 1S + + // A = Z1 * Z2 + var a = this.z.redMul(p.z); + // B = A^2 + var b = a.redSqr(); + // C = X1 * X2 + var c = this.x.redMul(p.x); + // D = Y1 * Y2 + var d = this.y.redMul(p.y); + // E = d * C * D + var e = this.curve.d.redMul(c).redMul(d); + // F = B - E + var f = b.redSub(e); + // G = B + E + var g = b.redAdd(e); + // X3 = A * F * ((X1 + Y1) * (X2 + Y2) - C - D) + var tmp = this.x.redAdd(this.y).redMul(p.x.redAdd(p.y)).redISub(c).redISub(d); + var nx = a.redMul(f).redMul(tmp); + var ny; + var nz; + if (this.curve.twisted) { + // Y3 = A * G * (D - a * C) + ny = a.redMul(g).redMul(d.redSub(this.curve._mulA(c))); + // Z3 = F * G + nz = f.redMul(g); + } else { + // Y3 = A * G * (D - C) + ny = a.redMul(g).redMul(d.redSub(c)); + // Z3 = c * F * G + nz = this.curve._mulC(f).redMul(g); + } + return this.curve.point(nx, ny, nz); +}; + +Point.prototype.add = function add(p) { + if (this.isInfinity()) + return p; + if (p.isInfinity()) + return this; + + if (this.curve.extended) + return this._extAdd(p); + else + return this._projAdd(p); +}; + +Point.prototype.mul = function mul(k) { + if (this._hasDoubles(k)) + return this.curve._fixedNafMul(this, k); + else + return this.curve._wnafMul(this, k); +}; + +Point.prototype.mulAdd = function mulAdd(k1, p, k2) { + return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, false); +}; + +Point.prototype.jmulAdd = function jmulAdd(k1, p, k2) { + return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, true); +}; + +Point.prototype.normalize = function normalize() { + if (this.zOne) + return this; + + // Normalize coordinates + var zi = this.z.redInvm(); + this.x = this.x.redMul(zi); + this.y = this.y.redMul(zi); + if (this.t) + this.t = this.t.redMul(zi); + this.z = this.curve.one; + this.zOne = true; + return this; +}; + +Point.prototype.neg = function neg() { + return this.curve.point(this.x.redNeg(), + this.y, + this.z, + this.t && this.t.redNeg()); +}; + +Point.prototype.getX = function getX() { + this.normalize(); + return this.x.fromRed(); +}; + +Point.prototype.getY = function getY() { + this.normalize(); + return this.y.fromRed(); +}; + +Point.prototype.eq = function eq(other) { + return this === other || + this.getX().cmp(other.getX()) === 0 && + this.getY().cmp(other.getY()) === 0; +}; + +Point.prototype.eqXToP = function eqXToP(x) { + var rx = x.toRed(this.curve.red).redMul(this.z); + if (this.x.cmp(rx) === 0) + return true; + + var xc = x.clone(); + var t = this.curve.redN.redMul(this.z); + for (;;) { + xc.iadd(this.curve.n); + if (xc.cmp(this.curve.p) >= 0) + return false; + + rx.redIAdd(t); + if (this.x.cmp(rx) === 0) + return true; + } + return false; +}; + +// Compatibility with BaseCurve +Point.prototype.toP = Point.prototype.normalize; +Point.prototype.mixedAdd = Point.prototype.add; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/index.js new file mode 100644 index 000000000..c589281df --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/index.js @@ -0,0 +1,8 @@ +'use strict'; + +var curve = exports; + +curve.base = require('./base'); +curve.short = require('./short'); +curve.mont = require('./mont'); +curve.edwards = require('./edwards'); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/mont.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/mont.js new file mode 100644 index 000000000..297620f62 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/mont.js @@ -0,0 +1,180 @@ +'use strict'; + +var curve = require('../curve'); +var BN = require('../../../../BN/bn'); +var inherits = require('../../elliptic').inherits; +var Base = curve.base; + +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; + +function MontCurve(conf) { + Base.call(this, 'mont', conf); + + this.a = new BN(conf.a, 16).toRed(this.red); + this.b = new BN(conf.b, 16).toRed(this.red); + this.i4 = new BN(4).toRed(this.red).redInvm(); + this.two = new BN(2).toRed(this.red); + this.a24 = this.i4.redMul(this.a.redAdd(this.two)); +} +inherits(MontCurve, Base); +module.exports = MontCurve; + +MontCurve.prototype.validate = function validate(point) { + var x = point.normalize().x; + var x2 = x.redSqr(); + var rhs = x2.redMul(x).redAdd(x2.redMul(this.a)).redAdd(x); + var y = rhs.redSqrt(); + + return y.redSqr().cmp(rhs) === 0; +}; + +function Point(curve, x, z) { + Base.BasePoint.call(this, curve, 'projective'); + if (x === null && z === null) { + this.x = this.curve.one; + this.z = this.curve.zero; + } else { + this.x = new BN(x, 16); + this.z = new BN(z, 16); + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + } +} +inherits(Point, Base.BasePoint); + +MontCurve.prototype.decodePoint = function decodePoint(bytes, enc) { + return this.point(utils.toArray(bytes, enc), 1); +}; + +MontCurve.prototype.point = function point(x, z) { + return new Point(this, x, z); +}; + +MontCurve.prototype.pointFromJSON = function pointFromJSON(obj) { + return Point.fromJSON(this, obj); +}; + +Point.prototype.precompute = function precompute() { + // No-op +}; + +Point.prototype._encode = function _encode() { + return this.getX().toArray('be', this.curve.p.byteLength()); +}; + +Point.fromJSON = function fromJSON(curve, obj) { + return new Point(curve, obj[0], obj[1] || curve.one); +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.z.cmpn(0) === 0; +}; + +Point.prototype.dbl = function dbl() { + // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#doubling-dbl-1987-m-3 + // 2M + 2S + 4A + + // A = X1 + Z1 + var a = this.x.redAdd(this.z); + // AA = A^2 + var aa = a.redSqr(); + // B = X1 - Z1 + var b = this.x.redSub(this.z); + // BB = B^2 + var bb = b.redSqr(); + // C = AA - BB + var c = aa.redSub(bb); + // X3 = AA * BB + var nx = aa.redMul(bb); + // Z3 = C * (BB + A24 * C) + var nz = c.redMul(bb.redAdd(this.curve.a24.redMul(c))); + return this.curve.point(nx, nz); +}; + +Point.prototype.add = function add() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.diffAdd = function diffAdd(p, diff) { + // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#diffadd-dadd-1987-m-3 + // 4M + 2S + 6A + + // A = X2 + Z2 + var a = this.x.redAdd(this.z); + // B = X2 - Z2 + var b = this.x.redSub(this.z); + // C = X3 + Z3 + var c = p.x.redAdd(p.z); + // D = X3 - Z3 + var d = p.x.redSub(p.z); + // DA = D * A + var da = d.redMul(a); + // CB = C * B + var cb = c.redMul(b); + // X5 = Z1 * (DA + CB)^2 + var nx = diff.z.redMul(da.redAdd(cb).redSqr()); + // Z5 = X1 * (DA - CB)^2 + var nz = diff.x.redMul(da.redISub(cb).redSqr()); + return this.curve.point(nx, nz); +}; + +Point.prototype.mul = function mul(k) { + var t = k.clone(); + var a = this; // (N / 2) * Q + Q + var b = this.curve.point(null, null); // (N / 2) * Q + var c = this; // Q + + for (var bits = []; t.cmpn(0) !== 0; t.iushrn(1)) + bits.push(t.andln(1)); + + for (var i = bits.length - 1; i >= 0; i--) { + if (bits[i] === 0) { + // N * Q + Q = ((N / 2) * Q + Q)) + (N / 2) * Q + a = a.diffAdd(b, c); + // N * Q = 2 * ((N / 2) * Q + Q)) + b = b.dbl(); + } else { + // N * Q = ((N / 2) * Q + Q) + ((N / 2) * Q) + b = a.diffAdd(b, c); + // N * Q + Q = 2 * ((N / 2) * Q + Q) + a = a.dbl(); + } + } + return b; +}; + +Point.prototype.mulAdd = function mulAdd() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.jumlAdd = function jumlAdd() { + throw new Error('Not supported on Montgomery curve'); +}; + +Point.prototype.eq = function eq(other) { + return this.getX().cmp(other.getX()) === 0; +}; + +Point.prototype.normalize = function normalize() { + this.x = this.x.redMul(this.z.redInvm()); + this.z = this.curve.one; + return this; +}; + +Point.prototype.getX = function getX() { + // Normalize coordinates + this.normalize(); + + return this.x.fromRed(); +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/short.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/short.js new file mode 100644 index 000000000..5f79988d7 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curve/short.js @@ -0,0 +1,938 @@ +'use strict'; + +var curve = require('../curve'); +var elliptic = require('../../elliptic'); +var BN = require('../../../../BN/bn'); +var inherits = elliptic.inherits; +var Base = curve.base; + +var assert = elliptic.utils.assert; + +function ShortCurve(conf) { + Base.call(this, 'short', conf); + + this.a = new BN(conf.a, 16).toRed(this.red); + this.b = new BN(conf.b, 16).toRed(this.red); + this.tinv = this.two.redInvm(); + + this.zeroA = this.a.fromRed().cmpn(0) === 0; + this.threeA = this.a.fromRed().sub(this.p).cmpn(-3) === 0; + + // If the curve is endomorphic, precalculate beta and lambda + this.endo = this._getEndomorphism(conf); + this._endoWnafT1 = new Array(4); + this._endoWnafT2 = new Array(4); +} +inherits(ShortCurve, Base); +module.exports = ShortCurve; + +ShortCurve.prototype._getEndomorphism = function _getEndomorphism(conf) { + // No efficient endomorphism + if (!this.zeroA || !this.g || !this.n || this.p.modn(3) !== 1) + return; + + // Compute beta and lambda, that lambda * P = (beta * Px; Py) + var beta; + var lambda; + if (conf.beta) { + beta = new BN(conf.beta, 16).toRed(this.red); + } else { + var betas = this._getEndoRoots(this.p); + // Choose the smallest beta + beta = betas[0].cmp(betas[1]) < 0 ? betas[0] : betas[1]; + beta = beta.toRed(this.red); + } + if (conf.lambda) { + lambda = new BN(conf.lambda, 16); + } else { + // Choose the lambda that is matching selected beta + var lambdas = this._getEndoRoots(this.n); + if (this.g.mul(lambdas[0]).x.cmp(this.g.x.redMul(beta)) === 0) { + lambda = lambdas[0]; + } else { + lambda = lambdas[1]; + assert(this.g.mul(lambda).x.cmp(this.g.x.redMul(beta)) === 0); + } + } + + // Get basis vectors, used for balanced length-two representation + var basis; + if (conf.basis) { + basis = conf.basis.map(function(vec) { + return { + a: new BN(vec.a, 16), + b: new BN(vec.b, 16) + }; + }); + } else { + basis = this._getEndoBasis(lambda); + } + + return { + beta: beta, + lambda: lambda, + basis: basis + }; +}; + +ShortCurve.prototype._getEndoRoots = function _getEndoRoots(num) { + // Find roots of for x^2 + x + 1 in F + // Root = (-1 +- Sqrt(-3)) / 2 + // + var red = num === this.p ? this.red : BN.mont(num); + var tinv = new BN(2).toRed(red).redInvm(); + var ntinv = tinv.redNeg(); + + var s = new BN(3).toRed(red).redNeg().redSqrt().redMul(tinv); + + var l1 = ntinv.redAdd(s).fromRed(); + var l2 = ntinv.redSub(s).fromRed(); + return [ l1, l2 ]; +}; + +ShortCurve.prototype._getEndoBasis = function _getEndoBasis(lambda) { + // aprxSqrt >= sqrt(this.n) + var aprxSqrt = this.n.ushrn(Math.floor(this.n.bitLength() / 2)); + + // 3.74 + // Run EGCD, until r(L + 1) < aprxSqrt + var u = lambda; + var v = this.n.clone(); + var x1 = new BN(1); + var y1 = new BN(0); + var x2 = new BN(0); + var y2 = new BN(1); + + // NOTE: all vectors are roots of: a + b * lambda = 0 (mod n) + var a0; + var b0; + // First vector + var a1; + var b1; + // Second vector + var a2; + var b2; + + var prevR; + var i = 0; + var r; + var x; + while (u.cmpn(0) !== 0) { + var q = v.div(u); + r = v.sub(q.mul(u)); + x = x2.sub(q.mul(x1)); + var y = y2.sub(q.mul(y1)); + + if (!a1 && r.cmp(aprxSqrt) < 0) { + a0 = prevR.neg(); + b0 = x1; + a1 = r.neg(); + b1 = x; + } else if (a1 && ++i === 2) { + break; + } + prevR = r; + + v = u; + u = r; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + a2 = r.neg(); + b2 = x; + + var len1 = a1.sqr().add(b1.sqr()); + var len2 = a2.sqr().add(b2.sqr()); + if (len2.cmp(len1) >= 0) { + a2 = a0; + b2 = b0; + } + + // Normalize signs + if (a1.negative) { + a1 = a1.neg(); + b1 = b1.neg(); + } + if (a2.negative) { + a2 = a2.neg(); + b2 = b2.neg(); + } + + return [ + { a: a1, b: b1 }, + { a: a2, b: b2 } + ]; +}; + +ShortCurve.prototype._endoSplit = function _endoSplit(k) { + var basis = this.endo.basis; + var v1 = basis[0]; + var v2 = basis[1]; + + var c1 = v2.b.mul(k).divRound(this.n); + var c2 = v1.b.neg().mul(k).divRound(this.n); + + var p1 = c1.mul(v1.a); + var p2 = c2.mul(v2.a); + var q1 = c1.mul(v1.b); + var q2 = c2.mul(v2.b); + + // Calculate answer + var k1 = k.sub(p1).sub(p2); + var k2 = q1.add(q2).neg(); + return { k1: k1, k2: k2 }; +}; + +ShortCurve.prototype.pointFromX = function pointFromX(x, odd) { + x = new BN(x, 16); + if (!x.red) + x = x.toRed(this.red); + + var y2 = x.redSqr().redMul(x).redIAdd(x.redMul(this.a)).redIAdd(this.b); + var y = y2.redSqrt(); + if (y.redSqr().redSub(y2).cmp(this.zero) !== 0) + throw new Error('invalid point'); + + // XXX Is there any way to tell if the number is odd without converting it + // to non-red form? + var isOdd = y.fromRed().isOdd(); + if (odd && !isOdd || !odd && isOdd) + y = y.redNeg(); + + return this.point(x, y); +}; + +ShortCurve.prototype.validate = function validate(point) { + if (point.inf) + return true; + + var x = point.x; + var y = point.y; + + var ax = this.a.redMul(x); + var rhs = x.redSqr().redMul(x).redIAdd(ax).redIAdd(this.b); + return y.redSqr().redISub(rhs).cmpn(0) === 0; +}; + +ShortCurve.prototype._endoWnafMulAdd = + function _endoWnafMulAdd(points, coeffs, jacobianResult) { + var npoints = this._endoWnafT1; + var ncoeffs = this._endoWnafT2; + for (var i = 0; i < points.length; i++) { + var split = this._endoSplit(coeffs[i]); + var p = points[i]; + var beta = p._getBeta(); + + if (split.k1.negative) { + split.k1.ineg(); + p = p.neg(true); + } + if (split.k2.negative) { + split.k2.ineg(); + beta = beta.neg(true); + } + + npoints[i * 2] = p; + npoints[i * 2 + 1] = beta; + ncoeffs[i * 2] = split.k1; + ncoeffs[i * 2 + 1] = split.k2; + } + var res = this._wnafMulAdd(1, npoints, ncoeffs, i * 2, jacobianResult); + + // Clean-up references to points and coefficients + for (var j = 0; j < i * 2; j++) { + npoints[j] = null; + ncoeffs[j] = null; + } + return res; +}; + +function Point(curve, x, y, isRed) { + Base.BasePoint.call(this, curve, 'affine'); + if (x === null && y === null) { + this.x = null; + this.y = null; + this.inf = true; + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + // Force redgomery representation when loading from JSON + if (isRed) { + this.x.forceRed(this.curve.red); + this.y.forceRed(this.curve.red); + } + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + this.inf = false; + } +} +inherits(Point, Base.BasePoint); + +ShortCurve.prototype.point = function point(x, y, isRed) { + return new Point(this, x, y, isRed); +}; + +ShortCurve.prototype.pointFromJSON = function pointFromJSON(obj, red) { + return Point.fromJSON(this, obj, red); +}; + +Point.prototype._getBeta = function _getBeta() { + if (!this.curve.endo) + return; + + var pre = this.precomputed; + if (pre && pre.beta) + return pre.beta; + + var beta = this.curve.point(this.x.redMul(this.curve.endo.beta), this.y); + if (pre) { + var curve = this.curve; + var endoMul = function(p) { + return curve.point(p.x.redMul(curve.endo.beta), p.y); + }; + pre.beta = beta; + beta.precomputed = { + beta: null, + naf: pre.naf && { + wnd: pre.naf.wnd, + points: pre.naf.points.map(endoMul) + }, + doubles: pre.doubles && { + step: pre.doubles.step, + points: pre.doubles.points.map(endoMul) + } + }; + } + return beta; +}; + +Point.prototype.toJSON = function toJSON() { + if (!this.precomputed) + return [ this.x, this.y ]; + + return [ this.x, this.y, this.precomputed && { + doubles: this.precomputed.doubles && { + step: this.precomputed.doubles.step, + points: this.precomputed.doubles.points.slice(1) + }, + naf: this.precomputed.naf && { + wnd: this.precomputed.naf.wnd, + points: this.precomputed.naf.points.slice(1) + } + } ]; +}; + +Point.fromJSON = function fromJSON(curve, obj, red) { + if (typeof obj === 'string') + obj = JSON.parse(obj); + var res = curve.point(obj[0], obj[1], red); + if (!obj[2]) + return res; + + function obj2point(obj) { + return curve.point(obj[0], obj[1], red); + } + + var pre = obj[2]; + res.precomputed = { + beta: null, + doubles: pre.doubles && { + step: pre.doubles.step, + points: [ res ].concat(pre.doubles.points.map(obj2point)) + }, + naf: pre.naf && { + wnd: pre.naf.wnd, + points: [ res ].concat(pre.naf.points.map(obj2point)) + } + }; + return res; +}; + +Point.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +Point.prototype.isInfinity = function isInfinity() { + return this.inf; +}; + +Point.prototype.add = function add(p) { + // O + P = P + if (this.inf) + return p; + + // P + O = P + if (p.inf) + return this; + + // P + P = 2P + if (this.eq(p)) + return this.dbl(); + + // P + (-P) = O + if (this.neg().eq(p)) + return this.curve.point(null, null); + + // P + Q = O + if (this.x.cmp(p.x) === 0) + return this.curve.point(null, null); + + var c = this.y.redSub(p.y); + if (c.cmpn(0) !== 0) + c = c.redMul(this.x.redSub(p.x).redInvm()); + var nx = c.redSqr().redISub(this.x).redISub(p.x); + var ny = c.redMul(this.x.redSub(nx)).redISub(this.y); + return this.curve.point(nx, ny); +}; + +Point.prototype.dbl = function dbl() { + if (this.inf) + return this; + + // 2P = O + var ys1 = this.y.redAdd(this.y); + if (ys1.cmpn(0) === 0) + return this.curve.point(null, null); + + var a = this.curve.a; + + var x2 = this.x.redSqr(); + var dyinv = ys1.redInvm(); + var c = x2.redAdd(x2).redIAdd(x2).redIAdd(a).redMul(dyinv); + + var nx = c.redSqr().redISub(this.x.redAdd(this.x)); + var ny = c.redMul(this.x.redSub(nx)).redISub(this.y); + return this.curve.point(nx, ny); +}; + +Point.prototype.getX = function getX() { + return this.x.fromRed(); +}; + +Point.prototype.getY = function getY() { + return this.y.fromRed(); +}; + +Point.prototype.mul = function mul(k) { + k = new BN(k, 16); + + if (this._hasDoubles(k)) + return this.curve._fixedNafMul(this, k); + else if (this.curve.endo) + return this.curve._endoWnafMulAdd([ this ], [ k ]); + else + return this.curve._wnafMul(this, k); +}; + +Point.prototype.mulAdd = function mulAdd(k1, p2, k2) { + var points = [ this, p2 ]; + var coeffs = [ k1, k2 ]; + if (this.curve.endo) + return this.curve._endoWnafMulAdd(points, coeffs); + else + return this.curve._wnafMulAdd(1, points, coeffs, 2); +}; + +Point.prototype.jmulAdd = function jmulAdd(k1, p2, k2) { + var points = [ this, p2 ]; + var coeffs = [ k1, k2 ]; + if (this.curve.endo) + return this.curve._endoWnafMulAdd(points, coeffs, true); + else + return this.curve._wnafMulAdd(1, points, coeffs, 2, true); +}; + +Point.prototype.eq = function eq(p) { + return this === p || + this.inf === p.inf && + (this.inf || this.x.cmp(p.x) === 0 && this.y.cmp(p.y) === 0); +}; + +Point.prototype.neg = function neg(_precompute) { + if (this.inf) + return this; + + var res = this.curve.point(this.x, this.y.redNeg()); + if (_precompute && this.precomputed) { + var pre = this.precomputed; + var negate = function(p) { + return p.neg(); + }; + res.precomputed = { + naf: pre.naf && { + wnd: pre.naf.wnd, + points: pre.naf.points.map(negate) + }, + doubles: pre.doubles && { + step: pre.doubles.step, + points: pre.doubles.points.map(negate) + } + }; + } + return res; +}; + +Point.prototype.toJ = function toJ() { + if (this.inf) + return this.curve.jpoint(null, null, null); + + var res = this.curve.jpoint(this.x, this.y, this.curve.one); + return res; +}; + +function JPoint(curve, x, y, z) { + Base.BasePoint.call(this, curve, 'jacobian'); + if (x === null && y === null && z === null) { + this.x = this.curve.one; + this.y = this.curve.one; + this.z = new BN(0); + } else { + this.x = new BN(x, 16); + this.y = new BN(y, 16); + this.z = new BN(z, 16); + } + if (!this.x.red) + this.x = this.x.toRed(this.curve.red); + if (!this.y.red) + this.y = this.y.toRed(this.curve.red); + if (!this.z.red) + this.z = this.z.toRed(this.curve.red); + + this.zOne = this.z === this.curve.one; +} +inherits(JPoint, Base.BasePoint); + +ShortCurve.prototype.jpoint = function jpoint(x, y, z) { + return new JPoint(this, x, y, z); +}; + +JPoint.prototype.toP = function toP() { + if (this.isInfinity()) + return this.curve.point(null, null); + + var zinv = this.z.redInvm(); + var zinv2 = zinv.redSqr(); + var ax = this.x.redMul(zinv2); + var ay = this.y.redMul(zinv2).redMul(zinv); + + return this.curve.point(ax, ay); +}; + +JPoint.prototype.neg = function neg() { + return this.curve.jpoint(this.x, this.y.redNeg(), this.z); +}; + +JPoint.prototype.add = function add(p) { + // O + P = P + if (this.isInfinity()) + return p; + + // P + O = P + if (p.isInfinity()) + return this; + + // 12M + 4S + 7A + var pz2 = p.z.redSqr(); + var z2 = this.z.redSqr(); + var u1 = this.x.redMul(pz2); + var u2 = p.x.redMul(z2); + var s1 = this.y.redMul(pz2.redMul(p.z)); + var s2 = p.y.redMul(z2.redMul(this.z)); + + var h = u1.redSub(u2); + var r = s1.redSub(s2); + if (h.cmpn(0) === 0) { + if (r.cmpn(0) !== 0) + return this.curve.jpoint(null, null, null); + else + return this.dbl(); + } + + var h2 = h.redSqr(); + var h3 = h2.redMul(h); + var v = u1.redMul(h2); + + var nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v); + var ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3)); + var nz = this.z.redMul(p.z).redMul(h); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.mixedAdd = function mixedAdd(p) { + // O + P = P + if (this.isInfinity()) + return p.toJ(); + + // P + O = P + if (p.isInfinity()) + return this; + + // 8M + 3S + 7A + var z2 = this.z.redSqr(); + var u1 = this.x; + var u2 = p.x.redMul(z2); + var s1 = this.y; + var s2 = p.y.redMul(z2).redMul(this.z); + + var h = u1.redSub(u2); + var r = s1.redSub(s2); + if (h.cmpn(0) === 0) { + if (r.cmpn(0) !== 0) + return this.curve.jpoint(null, null, null); + else + return this.dbl(); + } + + var h2 = h.redSqr(); + var h3 = h2.redMul(h); + var v = u1.redMul(h2); + + var nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v); + var ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3)); + var nz = this.z.redMul(h); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.dblp = function dblp(pow) { + if (pow === 0) + return this; + if (this.isInfinity()) + return this; + if (!pow) + return this.dbl(); + + if (this.curve.zeroA || this.curve.threeA) { + var r = this; + for (var i = 0; i < pow; i++) + r = r.dbl(); + return r; + } + + // 1M + 2S + 1A + N * (4S + 5M + 8A) + // N = 1 => 6M + 6S + 9A + var a = this.curve.a; + var tinv = this.curve.tinv; + + var jx = this.x; + var jy = this.y; + var jz = this.z; + var jz4 = jz.redSqr().redSqr(); + + // Reuse results + var jyd = jy.redAdd(jy); + for (var i = 0; i < pow; i++) { + var jx2 = jx.redSqr(); + var jyd2 = jyd.redSqr(); + var jyd4 = jyd2.redSqr(); + var c = jx2.redAdd(jx2).redIAdd(jx2).redIAdd(a.redMul(jz4)); + + var t1 = jx.redMul(jyd2); + var nx = c.redSqr().redISub(t1.redAdd(t1)); + var t2 = t1.redISub(nx); + var dny = c.redMul(t2); + dny = dny.redIAdd(dny).redISub(jyd4); + var nz = jyd.redMul(jz); + if (i + 1 < pow) + jz4 = jz4.redMul(jyd4); + + jx = nx; + jz = nz; + jyd = dny; + } + + return this.curve.jpoint(jx, jyd.redMul(tinv), jz); +}; + +JPoint.prototype.dbl = function dbl() { + if (this.isInfinity()) + return this; + + if (this.curve.zeroA) + return this._zeroDbl(); + else if (this.curve.threeA) + return this._threeDbl(); + else + return this._dbl(); +}; + +JPoint.prototype._zeroDbl = function _zeroDbl() { + var nx; + var ny; + var nz; + // Z = 1 + if (this.zOne) { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html + // #doubling-mdbl-2007-bl + // 1M + 5S + 14A + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // S = 2 * ((X1 + YY)^2 - XX - YYYY) + var s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + s = s.redIAdd(s); + // M = 3 * XX + a; a = 0 + var m = xx.redAdd(xx).redIAdd(xx); + // T = M ^ 2 - 2*S + var t = m.redSqr().redISub(s).redISub(s); + + // 8 * YYYY + var yyyy8 = yyyy.redIAdd(yyyy); + yyyy8 = yyyy8.redIAdd(yyyy8); + yyyy8 = yyyy8.redIAdd(yyyy8); + + // X3 = T + nx = t; + // Y3 = M * (S - T) - 8 * YYYY + ny = m.redMul(s.redISub(t)).redISub(yyyy8); + // Z3 = 2*Y1 + nz = this.y.redAdd(this.y); + } else { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html + // #doubling-dbl-2009-l + // 2M + 5S + 13A + + // A = X1^2 + var a = this.x.redSqr(); + // B = Y1^2 + var b = this.y.redSqr(); + // C = B^2 + var c = b.redSqr(); + // D = 2 * ((X1 + B)^2 - A - C) + var d = this.x.redAdd(b).redSqr().redISub(a).redISub(c); + d = d.redIAdd(d); + // E = 3 * A + var e = a.redAdd(a).redIAdd(a); + // F = E^2 + var f = e.redSqr(); + + // 8 * C + var c8 = c.redIAdd(c); + c8 = c8.redIAdd(c8); + c8 = c8.redIAdd(c8); + + // X3 = F - 2 * D + nx = f.redISub(d).redISub(d); + // Y3 = E * (D - X3) - 8 * C + ny = e.redMul(d.redISub(nx)).redISub(c8); + // Z3 = 2 * Y1 * Z1 + nz = this.y.redMul(this.z); + nz = nz.redIAdd(nz); + } + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype._threeDbl = function _threeDbl() { + var nx; + var ny; + var nz; + // Z = 1 + if (this.zOne) { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html + // #doubling-mdbl-2007-bl + // 1M + 5S + 15A + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // S = 2 * ((X1 + YY)^2 - XX - YYYY) + var s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + s = s.redIAdd(s); + // M = 3 * XX + a + var m = xx.redAdd(xx).redIAdd(xx).redIAdd(this.curve.a); + // T = M^2 - 2 * S + var t = m.redSqr().redISub(s).redISub(s); + // X3 = T + nx = t; + // Y3 = M * (S - T) - 8 * YYYY + var yyyy8 = yyyy.redIAdd(yyyy); + yyyy8 = yyyy8.redIAdd(yyyy8); + yyyy8 = yyyy8.redIAdd(yyyy8); + ny = m.redMul(s.redISub(t)).redISub(yyyy8); + // Z3 = 2 * Y1 + nz = this.y.redAdd(this.y); + } else { + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b + // 3M + 5S + + // delta = Z1^2 + var delta = this.z.redSqr(); + // gamma = Y1^2 + var gamma = this.y.redSqr(); + // beta = X1 * gamma + var beta = this.x.redMul(gamma); + // alpha = 3 * (X1 - delta) * (X1 + delta) + var alpha = this.x.redSub(delta).redMul(this.x.redAdd(delta)); + alpha = alpha.redAdd(alpha).redIAdd(alpha); + // X3 = alpha^2 - 8 * beta + var beta4 = beta.redIAdd(beta); + beta4 = beta4.redIAdd(beta4); + var beta8 = beta4.redAdd(beta4); + nx = alpha.redSqr().redISub(beta8); + // Z3 = (Y1 + Z1)^2 - gamma - delta + nz = this.y.redAdd(this.z).redSqr().redISub(gamma).redISub(delta); + // Y3 = alpha * (4 * beta - X3) - 8 * gamma^2 + var ggamma8 = gamma.redSqr(); + ggamma8 = ggamma8.redIAdd(ggamma8); + ggamma8 = ggamma8.redIAdd(ggamma8); + ggamma8 = ggamma8.redIAdd(ggamma8); + ny = alpha.redMul(beta4.redISub(nx)).redISub(ggamma8); + } + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype._dbl = function _dbl() { + var a = this.curve.a; + + // 4M + 6S + 10A + var jx = this.x; + var jy = this.y; + var jz = this.z; + var jz4 = jz.redSqr().redSqr(); + + var jx2 = jx.redSqr(); + var jy2 = jy.redSqr(); + + var c = jx2.redAdd(jx2).redIAdd(jx2).redIAdd(a.redMul(jz4)); + + var jxd4 = jx.redAdd(jx); + jxd4 = jxd4.redIAdd(jxd4); + var t1 = jxd4.redMul(jy2); + var nx = c.redSqr().redISub(t1.redAdd(t1)); + var t2 = t1.redISub(nx); + + var jyd8 = jy2.redSqr(); + jyd8 = jyd8.redIAdd(jyd8); + jyd8 = jyd8.redIAdd(jyd8); + jyd8 = jyd8.redIAdd(jyd8); + var ny = c.redMul(t2).redISub(jyd8); + var nz = jy.redAdd(jy).redMul(jz); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.trpl = function trpl() { + if (!this.curve.zeroA) + return this.dbl().add(this); + + // hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#tripling-tpl-2007-bl + // 5M + 10S + ... + + // XX = X1^2 + var xx = this.x.redSqr(); + // YY = Y1^2 + var yy = this.y.redSqr(); + // ZZ = Z1^2 + var zz = this.z.redSqr(); + // YYYY = YY^2 + var yyyy = yy.redSqr(); + // M = 3 * XX + a * ZZ2; a = 0 + var m = xx.redAdd(xx).redIAdd(xx); + // MM = M^2 + var mm = m.redSqr(); + // E = 6 * ((X1 + YY)^2 - XX - YYYY) - MM + var e = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy); + e = e.redIAdd(e); + e = e.redAdd(e).redIAdd(e); + e = e.redISub(mm); + // EE = E^2 + var ee = e.redSqr(); + // T = 16*YYYY + var t = yyyy.redIAdd(yyyy); + t = t.redIAdd(t); + t = t.redIAdd(t); + t = t.redIAdd(t); + // U = (M + E)^2 - MM - EE - T + var u = m.redIAdd(e).redSqr().redISub(mm).redISub(ee).redISub(t); + // X3 = 4 * (X1 * EE - 4 * YY * U) + var yyu4 = yy.redMul(u); + yyu4 = yyu4.redIAdd(yyu4); + yyu4 = yyu4.redIAdd(yyu4); + var nx = this.x.redMul(ee).redISub(yyu4); + nx = nx.redIAdd(nx); + nx = nx.redIAdd(nx); + // Y3 = 8 * Y1 * (U * (T - U) - E * EE) + var ny = this.y.redMul(u.redMul(t.redISub(u)).redISub(e.redMul(ee))); + ny = ny.redIAdd(ny); + ny = ny.redIAdd(ny); + ny = ny.redIAdd(ny); + // Z3 = (Z1 + E)^2 - ZZ - EE + var nz = this.z.redAdd(e).redSqr().redISub(zz).redISub(ee); + + return this.curve.jpoint(nx, ny, nz); +}; + +JPoint.prototype.mul = function mul(k, kbase) { + k = new BN(k, kbase); + + return this.curve._wnafMul(this, k); +}; + +JPoint.prototype.eq = function eq(p) { + if (p.type === 'affine') + return this.eq(p.toJ()); + + if (this === p) + return true; + + // x1 * z2^2 == x2 * z1^2 + var z2 = this.z.redSqr(); + var pz2 = p.z.redSqr(); + if (this.x.redMul(pz2).redISub(p.x.redMul(z2)).cmpn(0) !== 0) + return false; + + // y1 * z2^3 == y2 * z1^3 + var z3 = z2.redMul(this.z); + var pz3 = pz2.redMul(p.z); + return this.y.redMul(pz3).redISub(p.y.redMul(z3)).cmpn(0) === 0; +}; + +JPoint.prototype.eqXToP = function eqXToP(x) { + var zs = this.z.redSqr(); + var rx = x.toRed(this.curve.red).redMul(zs); + if (this.x.cmp(rx) === 0) + return true; + + var xc = x.clone(); + var t = this.curve.redN.redMul(zs); + for (;;) { + xc.iadd(this.curve.n); + if (xc.cmp(this.curve.p) >= 0) + return false; + + rx.redIAdd(t); + if (this.x.cmp(rx) === 0) + return true; + } + return false; +}; + +JPoint.prototype.inspect = function inspect() { + if (this.isInfinity()) + return ''; + return ''; +}; + +JPoint.prototype.isInfinity = function isInfinity() { + // XXX This code assumes that zero is always zero in red + return this.z.cmpn(0) === 0; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curves.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curves.js new file mode 100644 index 000000000..1d5e598b0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/curves.js @@ -0,0 +1,204 @@ +'use strict'; + +var curves = exports; + +var elliptic = require('../elliptic'); + +var assert = elliptic.utils.assert; + +function PresetCurve(options) { + if (options.type === 'short') + this.curve = new elliptic.curve.short(options); + else if (options.type === 'edwards') + this.curve = new elliptic.curve.edwards(options); + else + this.curve = new elliptic.curve.mont(options); + this.g = this.curve.g; + this.n = this.curve.n; + this.hash = options.hash; + + assert(this.g.validate(), 'Invalid curve'); + assert(this.g.mul(this.n).isInfinity(), 'Invalid curve, G*N != O'); +} +curves.PresetCurve = PresetCurve; + +function defineCurve(name, options) { + Object.defineProperty(curves, name, { + configurable: true, + enumerable: true, + get: function() { + var curve = new PresetCurve(options); + Object.defineProperty(curves, name, { + configurable: true, + enumerable: true, + value: curve + }); + return curve; + } + }); +} + +defineCurve('p192', { + type: 'short', + prime: 'p192', + p: 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff', + a: 'ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc', + b: '64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1', + n: 'ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831', + hash: 'SHA256', + gRed: false, + g: [ + '188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012', + '07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811' + ] +}); + +defineCurve('p224', { + type: 'short', + prime: 'p224', + p: 'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001', + a: 'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe', + b: 'b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4', + n: 'ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d', + hash: 'SHA256', + gRed: false, + g: [ + 'b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21', + 'bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34' + ] +}); + +defineCurve('p256', { + type: 'short', + prime: null, + p: 'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff', + a: 'ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc', + b: '5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b', + n: 'ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551', + hash: 'SHA256', + gRed: false, + g: [ + '6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296', + '4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5' + ] +}); + +defineCurve('p384', { + type: 'short', + prime: null, + p: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'fffffffe ffffffff 00000000 00000000 ffffffff', + a: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'fffffffe ffffffff 00000000 00000000 fffffffc', + b: 'b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f ' + + '5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef', + n: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 ' + + 'f4372ddf 581a0db2 48b0a77a ecec196a ccc52973', + hash: 'SHA384', + gRed: false, + g: [ + 'aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 ' + + '5502f25d bf55296c 3a545e38 72760ab7', + '3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 ' + + '0a60b1ce 1d7e819d 7a431d7c 90ea0e5f' + ] +}); + +defineCurve('p521', { + type: 'short', + prime: null, + p: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff', + a: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff ffffffff ffffffff fffffffc', + b: '00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b ' + + '99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd ' + + '3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00', + n: '000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ' + + 'ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 ' + + 'f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409', + hash: 'SHA512', + gRed: false, + g: [ + '000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 ' + + '053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 ' + + 'a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66', + '00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 ' + + '579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 ' + + '3fad0761 353c7086 a272c240 88be9476 9fd16650' + ] +}); + +defineCurve('curve25519', { + type: 'mont', + prime: 'p25519', + p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed', + a: '76d06', + b: '1', + n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed', + hash: 'SHA256', + gRed: false, + g: [ + '9' + ] +}); + +defineCurve('ed25519', { + type: 'edwards', + prime: 'p25519', + p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed', + a: '-1', + c: '1', + // -121665 * (121666^(-1)) (mod P) + d: '52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3', + n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed', + hash: 'SHA256', + gRed: false, + g: [ + '216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a', + + // 4/5 + '6666666666666666666666666666666666666666666666666666666666666658' + ] +}); + +var pre; +try { + pre = require('./precomputed/secp256k1'); +} catch (e) { + pre = undefined; +} + +defineCurve('secp256k1', { + type: 'short', + prime: 'k256', + p: 'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f', + a: '0', + b: '7', + n: 'ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141', + h: '1', + hash: 'SHA256', + + // Precomputed endomorphism + beta: '7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee', + lambda: '5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72', + basis: [ + { + a: '3086d221a7d46bcde86c90e49284eb15', + b: '-e4437ed6010e88286f547fa90abfe4c3' + }, + { + a: '114ca50f7a8e2f3f657c1108d9d44cfd8', + b: '3086d221a7d46bcde86c90e49284eb15' + } + ], + + gRed: false, + g: [ + '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', + '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', + pre + ] +}); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/index.js new file mode 100644 index 000000000..b117d53a1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/index.js @@ -0,0 +1,240 @@ +'use strict'; + +var BN = require('../../../../BN/bn'); +var HmacDRBG = require('../../hmac-drbg/hmac-drbg'); +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var assert = utils.assert; + +var KeyPair = require('./key'); +var Signature = require('./signature'); + +function EC(options) { + if (!(this instanceof EC)) + return new EC(options); + + // Shortcut `elliptic.ec(curve-name)` + if (typeof options === 'string') { + assert(elliptic.curves.hasOwnProperty(options), 'Unknown curve ' + options); + + options = elliptic.curves[options]; + } + + // Shortcut for `elliptic.ec(elliptic.curves.curveName)` + if (options instanceof elliptic.curves.PresetCurve) + options = { curve: options }; + + this.curve = options.curve.curve; + this.n = this.curve.n; + this.nh = this.n.ushrn(1); + this.g = this.curve.g; + + // Point on curve + this.g = options.curve.g; + this.g.precompute(options.curve.n.bitLength() + 1); + + // Hash for function for DRBG + this.hash = options.hash || options.curve.hash; +} +module.exports = EC; + +EC.prototype.keyPair = function keyPair(options) { + return new KeyPair(this, options); +}; + +EC.prototype.keyFromPrivate = function keyFromPrivate(priv, enc) { + return KeyPair.fromPrivate(this, priv, enc); +}; + +EC.prototype.keyFromPublic = function keyFromPublic(pub, enc) { + return KeyPair.fromPublic(this, pub, enc); +}; + +EC.prototype.genKeyPair = function genKeyPair(options) { + if (!options) + options = {}; + + // Instantiate Hmac_DRBG + var drbg = new HmacDRBG({ + hash: this.hash, + pers: options.pers, + persEnc: options.persEnc || 'utf8', + entropy: options.entropy || null, //elliptic.rand(this.hash.hmacStrength), + entropyEnc: options.entropy && options.entropyEnc || 'utf8', + nonce: this.n.toArray() + }); + + var bytes = this.n.byteLength(); + var ns2 = this.n.sub(new BN(2)); + do { + var priv = new BN(drbg.generate(bytes)); + if (priv.cmp(ns2) > 0) + continue; + + priv.iaddn(1); + return this.keyFromPrivate(priv); + } while (true); +}; + +EC.prototype._truncateToN = function truncateToN(msg, truncOnly) { + var delta = msg.byteLength() * 8 - this.n.bitLength(); + if (delta > 0) + msg = msg.ushrn(delta); + if (!truncOnly && msg.cmp(this.n) >= 0) + return msg.sub(this.n); + else + return msg; +}; + +EC.prototype.sign = function sign(msg, key, enc, options) { + if (typeof enc === 'object') { + options = enc; + enc = null; + } + if (!options) + options = {}; + + key = this.keyFromPrivate(key, enc); + msg = this._truncateToN(new BN(msg, 16)); + + // Zero-extend key to provide enough entropy + var bytes = this.n.byteLength(); + var bkey = key.getPrivate().toArray('be', bytes); + + // Zero-extend nonce to have the same byte size as N + var nonce = msg.toArray('be', bytes); + + // Instantiate Hmac_DRBG + var drbg = new HmacDRBG({ + hash: this.hash, + entropy: options['extraEntropy'] ? bkey.concat (options['extraEntropy']) : bkey, + nonce: nonce, + pers: options.pers, + persEnc: options.persEnc || 'utf8' + }); + + // Number of bytes to generate + var ns1 = this.n.sub(new BN(1)); + + for (var iter = 0; true; iter++) { + var k = options.k ? + options.k(iter) : + new BN(drbg.generate(this.n.byteLength())); + k = this._truncateToN(k, true); + if (k.cmpn(1) <= 0 || k.cmp(ns1) >= 0) + continue; + + var kp = this.g.mul(k); + if (kp.isInfinity()) + continue; + + var kpX = kp.getX(); + var r = kpX.umod(this.n); + if (r.cmpn(0) === 0) + continue; + + var s = k.invm(this.n).mul(r.mul(key.getPrivate()).iadd(msg)); + s = s.umod(this.n); + if (s.cmpn(0) === 0) + continue; + + var recoveryParam = (kp.getY().isOdd() ? 1 : 0) | + (kpX.cmp(r) !== 0 ? 2 : 0); + + // Use complement of `s`, if it is > `n / 2` + if (options.canonical && s.cmp(this.nh) > 0) { + s = this.n.sub(s); + recoveryParam ^= 1; + } + + return new Signature({ r: r, s: s, recoveryParam: recoveryParam }); + } +}; + +EC.prototype.verify = function verify(msg, signature, key, enc) { + msg = this._truncateToN(new BN(msg, 16)); + key = this.keyFromPublic(key, enc); + signature = new Signature(signature, 'hex'); + + // Perform primitive values validation + var r = signature.r; + var s = signature.s; + if (r.cmpn(1) < 0 || r.cmp(this.n) >= 0) + return false; + if (s.cmpn(1) < 0 || s.cmp(this.n) >= 0) + return false; + + // Validate signature + var sinv = s.invm(this.n); + var u1 = sinv.mul(msg).umod(this.n); + var u2 = sinv.mul(r).umod(this.n); + + if (!this.curve._maxwellTrick) { + var p = this.g.mulAdd(u1, key.getPublic(), u2); + if (p.isInfinity()) + return false; + + return p.getX().umod(this.n).cmp(r) === 0; + } + + // NOTE: Greg Maxwell's trick, inspired by: + // https://git.io/vad3K + + var p = this.g.jmulAdd(u1, key.getPublic(), u2); + if (p.isInfinity()) + return false; + + // Compare `p.x` of Jacobian point with `r`, + // this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the + // inverse of `p.z^2` + return p.eqXToP(r); +}; + +EC.prototype.recoverPubKey = function(msg, signature, j, enc) { + assert((3 & j) === j, 'The recovery param is more than two bits'); + signature = new Signature(signature, enc); + + var n = this.n; + var e = new BN(msg); + var r = signature.r; + var s = signature.s; + + // A set LSB signifies that the y-coordinate is odd + var isYOdd = j & 1; + var isSecondKey = j >> 1; + if (r.cmp(this.curve.p.umod(this.curve.n)) >= 0 && isSecondKey) + throw new Error('Unable to find sencond key candinate'); + + // 1.1. Let x = r + jn. + if (isSecondKey) + r = this.curve.pointFromX(r.add(this.curve.n), isYOdd); + else + r = this.curve.pointFromX(r, isYOdd); + + var rInv = signature.r.invm(n); + var s1 = n.sub(e).mul(rInv).umod(n); + var s2 = s.mul(rInv).umod(n); + + // 1.6.1 Compute Q = r^-1 (sR - eG) + // Q = r^-1 (sR + -eG) + return this.g.mulAdd(s1, r, s2); +}; + +EC.prototype.getKeyRecoveryParam = function(e, signature, Q, enc) { + signature = new Signature(signature, enc); + if (signature.recoveryParam !== null) + return signature.recoveryParam; + + for (var i = 0; i < 4; i++) { + var Qprime; + try { + Qprime = this.recoverPubKey(e, signature, i); + } catch (e) { + continue; + } + + if (Qprime.eq(Q)) + return i; + } + throw new Error('Unable to find valid recovery factor'); +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/key.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/key.js new file mode 100644 index 000000000..6154740f5 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/key.js @@ -0,0 +1,119 @@ +'use strict'; + +var BN = require('../../../../BN/bn'); +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var assert = utils.assert; + +function KeyPair(ec, options) { + this.ec = ec; + this.priv = null; + this.pub = null; + + // KeyPair(ec, { priv: ..., pub: ... }) + if (options.priv) + this._importPrivate(options.priv, options.privEnc); + if (options.pub) + this._importPublic(options.pub, options.pubEnc); +} +module.exports = KeyPair; + +KeyPair.fromPublic = function fromPublic(ec, pub, enc) { + if (pub instanceof KeyPair) + return pub; + + return new KeyPair(ec, { + pub: pub, + pubEnc: enc + }); +}; + +KeyPair.fromPrivate = function fromPrivate(ec, priv, enc) { + if (priv instanceof KeyPair) + return priv; + + return new KeyPair(ec, { + priv: priv, + privEnc: enc + }); +}; + +KeyPair.prototype.validate = function validate() { + var pub = this.getPublic(); + + if (pub.isInfinity()) + return { result: false, reason: 'Invalid public key' }; + if (!pub.validate()) + return { result: false, reason: 'Public key is not a point' }; + if (!pub.mul(this.ec.curve.n).isInfinity()) + return { result: false, reason: 'Public key * N != O' }; + + return { result: true, reason: null }; +}; + +KeyPair.prototype.getPublic = function getPublic(compact, enc) { + // compact is optional argument + if (typeof compact === 'string') { + enc = compact; + compact = null; + } + + if (!this.pub) + this.pub = this.ec.g.mul(this.priv); + + if (!enc) + return this.pub; + + return this.pub.encode(enc, compact); +}; + +KeyPair.prototype.getPrivate = function getPrivate(enc) { + if (enc === 'hex') + return this.priv.toString(16, 2); + else + return this.priv; +}; + +KeyPair.prototype._importPrivate = function _importPrivate(key, enc) { + this.priv = new BN(key, enc || 16); + + // Ensure that the priv won't be bigger than n, otherwise we may fail + // in fixed multiplication method + this.priv = this.priv.umod(this.ec.curve.n); +}; + +KeyPair.prototype._importPublic = function _importPublic(key, enc) { + if (key.x || key.y) { + // Montgomery points only have an `x` coordinate. + // Weierstrass/Edwards points on the other hand have both `x` and + // `y` coordinates. + if (this.ec.curve.type === 'mont') { + assert(key.x, 'Need x coordinate'); + } else if (this.ec.curve.type === 'short' || + this.ec.curve.type === 'edwards') { + assert(key.x && key.y, 'Need both x and y coordinate'); + } + this.pub = this.ec.curve.point(key.x, key.y); + return; + } + this.pub = this.ec.curve.decodePoint(key, enc); +}; + +// ECDH +KeyPair.prototype.derive = function derive(pub) { + return pub.mul(this.priv).getX(); +}; + +// ECDSA +KeyPair.prototype.sign = function sign(msg, enc, options) { + return this.ec.sign(msg, this, enc, options); +}; + +KeyPair.prototype.verify = function verify(msg, signature) { + return this.ec.verify(msg, signature, this); +}; + +KeyPair.prototype.inspect = function inspect() { + return ''; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/signature.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/signature.js new file mode 100644 index 000000000..df2136c95 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/ec/signature.js @@ -0,0 +1,24 @@ +'use strict'; + +var BN = require('../../../../BN/bn'); + +var elliptic = require('../../elliptic'); +var utils = elliptic.utils; +var assert = utils.assert; + +function Signature(options, enc) { + if (options instanceof Signature) + return options; + + //if (this._importDER(options, enc)) + // return; + + assert(options.r && options.s, 'Signature without r or s'); + this.r = new BN(options.r, 16); + this.s = new BN(options.s, 16); + if (options.recoveryParam === undefined) + this.recoveryParam = null; + else + this.recoveryParam = options.recoveryParam; +} +module.exports = Signature; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/index.js new file mode 100644 index 000000000..903f68d3a --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/index.js @@ -0,0 +1,145 @@ +'use strict'; + +var curves = require('../curves'); +var utils = require('../utils'); +var assert = utils.assert; +var parseBytes = utils.parseBytes; +var KeyPair = require('./key'); +var Signature = require('./signature'); +const BN = require ('../../../../BN/bn.js') +const { byteArrayToWordArray } = require ('../../../../../base/functions/encode') +const CryptoJS = require('../../../../crypto-js/crypto-js'); + + +function EDDSA(curveName) { + assert(curveName === 'ed25519', 'only tested with ed25519 so far'); + + if (!(this instanceof EDDSA)) + return new EDDSA(curveName); + + var curve = curves[curveName].curve; + this.curve = curve; + this.g = curve.g; + this.g.precompute(curve.n.bitLength() + 1); + + this.pointClass = curve.point().constructor; + this.encodingLength = Math.ceil(curve.n.bitLength() / 8); + //this.hash = hash.sha512; +} + +module.exports = EDDSA; + +/** +* @param {Array|String} message - message bytes +* @param {Array|String|KeyPair} secret - secret bytes or a keypair +* @returns {Signature} - signature +*/ +EDDSA.prototype.sign = function sign(message, secret) { + message = parseBytes(message); + var key = this.keyFromSecret(secret); + var r = this.hashInt(key.secret (), message); + var R = this.g.mul(r); + var Rencoded = this.encodePoint(R); + var s_ = this.hashInt(Rencoded, key.pubBytes(), message) + s_ = s_.mul (key.priv ()) + var S = r.add(s_).umod(this.curve.n); + return this.makeSignature({ R: R, S: S, Rencoded: Rencoded }); +}; + +/** + * @param {Array|String} message - message bytes + * @param {Array|String|KeyPair} secret - secret bytes or a keypair + * @returns {Signature} - signature + */ +EDDSA.prototype.signModified = function sign(message, secret) { + message = parseBytes(message); + var key = this.keyFromSecret(secret); + // convert between curve25519 and ed25519 keys + const secretLE = new BN (key.secret (), 16, 'le') + const pubKey = this.encodePoint (this.g.mul (secretLE)) + const signBit = pubKey[31] & 0x80 + var r = this.hashInt (key.secret (), message) + var R = this.g.mul(r); + var Rencoded = this.encodePoint (R); + let s_ = this.hashInt (Rencoded, pubKey, message) + s_ = s_.mul (secretLE) + var S = r.add(s_).umod(this.curve.n); + var Sencoded = S.toArray ('le') + Sencoded[31] |= signBit + return this.makeSignature({ R: R, S: S, Rencoded: Rencoded, Sencoded: Sencoded }); +}; + + +/** +* @param {Array} message - message bytes +* @param {Array|String|Signature} sig - sig bytes +* @param {Array|String|Point|KeyPair} pub - public key +* @returns {Boolean} - true if public key matches sig of message +*/ +EDDSA.prototype.verify = function verify(message, sig, pub) { + message = parseBytes(message); + sig = this.makeSignature(sig); + var key = this.keyFromPublic(pub); + var h = this.hashInt(sig.Rencoded(), key.pubBytes(), message); + var SG = this.g.mul(sig.S()); + var RplusAh = sig.R().add(key.pub().mul(h)); + return RplusAh.eq(SG); +}; + +EDDSA.prototype.hashInt = function hashInt() { + let toHash = Array.from (arguments).reduce ((a, b) => a.concat (b)) + toHash = byteArrayToWordArray (toHash) + const digest = CryptoJS['SHA512'] (toHash).toString (CryptoJS.enc.Hex) + return utils.intFromLE(digest).umod(this.curve.n); +}; + +EDDSA.prototype.keyFromPublic = function keyFromPublic(pub) { + return KeyPair.fromPublic(this, pub); +}; + +EDDSA.prototype.keyFromSecret = function keyFromSecret(secret) { + return KeyPair.fromSecret(this, secret); +}; + +EDDSA.prototype.makeSignature = function makeSignature(sig) { + if (sig instanceof Signature) + return sig; + return new Signature(this, sig); +}; + +/** +* * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03#section-5.2 +* +* EDDSA defines methods for encoding and decoding points and integers. These are +* helper convenience methods, that pass along to utility functions implied +* parameters. +* +*/ +EDDSA.prototype.encodePoint = function encodePoint(point) { + var enc = point.getY().toArray('le', this.encodingLength); + enc[this.encodingLength - 1] |= point.getX().isOdd() ? 0x80 : 0; + return enc; +}; + +EDDSA.prototype.decodePoint = function decodePoint(bytes) { + bytes = utils.parseBytes(bytes); + + var lastIx = bytes.length - 1; + var normed = bytes.slice(0, lastIx).concat(bytes[lastIx] & ~0x80); + var xIsOdd = (bytes[lastIx] & 0x80) !== 0; + + var y = utils.intFromLE(normed); + return this.curve.pointFromY(y, xIsOdd); +}; + +EDDSA.prototype.encodeInt = function encodeInt(num) { + return num.toArray('le', this.encodingLength); +}; + +EDDSA.prototype.decodeInt = function decodeInt(bytes) { + return utils.intFromLE(bytes); +}; + +EDDSA.prototype.isPoint = function isPoint(val) { + return val instanceof this.pointClass; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/key.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/key.js new file mode 100644 index 000000000..c620694b8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/key.js @@ -0,0 +1,100 @@ +'use strict'; + +var utils = require('../utils'); +var assert = utils.assert; +var parseBytes = utils.parseBytes; +var cachedProperty = utils.cachedProperty; + +/** +* @param {EDDSA} eddsa - instance +* @param {Object} params - public/private key parameters +* +* @param {Array} [params.secret] - secret seed bytes +* @param {Point} [params.pub] - public key point (aka `A` in eddsa terms) +* @param {Array} [params.pub] - public key point encoded as bytes +* +*/ +function KeyPair(eddsa, params) { + this.eddsa = eddsa; + this._secret = parseBytes(params.secret); + if (eddsa.isPoint(params.pub)) + this._pub = params.pub; + else + this._pubBytes = parseBytes(params.pub); +} + +KeyPair.fromPublic = function fromPublic(eddsa, pub) { + if (pub instanceof KeyPair) + return pub; + return new KeyPair(eddsa, { pub: pub }); +}; + +KeyPair.fromSecret = function fromSecret(eddsa, secret) { + if (secret instanceof KeyPair) + return secret; + return new KeyPair(eddsa, { secret: secret }); +}; + +KeyPair.prototype.secret = function secret() { + return this._secret; +}; + +cachedProperty(KeyPair, 'pubBytes', function pubBytes() { + return this.eddsa.encodePoint(this.pub()); +}); + +cachedProperty(KeyPair, 'pub', function pub() { + if (this._pubBytes) + return this.eddsa.decodePoint(this._pubBytes); + return this.eddsa.g.mul(this.priv()); +}); + +cachedProperty(KeyPair, 'privBytes', function privBytes() { + var eddsa = this.eddsa; + var hash = this.hash(); + var lastIx = eddsa.encodingLength - 1; + + var a = hash.slice(0, eddsa.encodingLength); + a[0] &= 248; + a[lastIx] &= 127; + a[lastIx] |= 64; + + return a; +}); + +cachedProperty(KeyPair, 'priv', function priv() { + return this.eddsa.decodeInt(this.privBytes()); +}); + +cachedProperty(KeyPair, 'hash', function hash() { + return this.eddsa.hash().update(this.secret()).digest(); +}); + +cachedProperty(KeyPair, 'messagePrefix', function messagePrefix() { + return this.hash().slice(this.eddsa.encodingLength); +}); + +KeyPair.prototype.sign = function sign(message) { + assert(this._secret, 'KeyPair can only verify'); + return this.eddsa.sign(message, this); +}; + +KeyPair.prototype.signModified = function sign(message) { + assert(this._secret, 'KeyPair can only verify'); + return this.eddsa.signModified(message, this); +}; + +KeyPair.prototype.verify = function verify(message, sig) { + return this.eddsa.verify(message, sig, this); +}; + +KeyPair.prototype.getSecret = function getSecret(enc) { + assert(this._secret, 'KeyPair is public only'); + return utils.encode(this.secret(), enc); +}; + +KeyPair.prototype.getPublic = function getPublic(enc) { + return utils.encode(this.pubBytes(), enc); +}; + +module.exports = KeyPair; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/signature.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/signature.js new file mode 100644 index 000000000..211550be3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/eddsa/signature.js @@ -0,0 +1,65 @@ +'use strict'; + +var BN = require('../../../../BN/bn'); +var utils = require('../utils'); +var assert = utils.assert; +var cachedProperty = utils.cachedProperty; +var parseBytes = utils.parseBytes; + +/** +* @param {EDDSA} eddsa - eddsa instance +* @param {Array|Object} sig - +* @param {Array|Point} [sig.R] - R point as Point or bytes +* @param {Array|bn} [sig.S] - S scalar as bn or bytes +* @param {Array} [sig.Rencoded] - R point encoded +* @param {Array} [sig.Sencoded] - S scalar encoded +*/ +function Signature(eddsa, sig) { + this.eddsa = eddsa; + + if (typeof sig !== 'object') + sig = parseBytes(sig); + + if (Array.isArray(sig)) { + sig = { + R: sig.slice(0, eddsa.encodingLength), + S: sig.slice(eddsa.encodingLength) + }; + } + + assert(sig.R && sig.S, 'Signature without R or S'); + + if (eddsa.isPoint(sig.R)) + this._R = sig.R; + if (sig.S instanceof BN) + this._S = sig.S; + + this._Rencoded = Array.isArray(sig.R) ? sig.R : sig.Rencoded; + this._Sencoded = Array.isArray(sig.S) ? sig.S : sig.Sencoded; +} + +cachedProperty(Signature, 'S', function S() { + return this.eddsa.decodeInt(this.Sencoded()); +}); + +cachedProperty(Signature, 'R', function R() { + return this.eddsa.decodePoint(this.Rencoded()); +}); + +cachedProperty(Signature, 'Rencoded', function Rencoded() { + return this.eddsa.encodePoint(this.R()); +}); + +cachedProperty(Signature, 'Sencoded', function Sencoded() { + return this.eddsa.encodeInt(this.S()); +}); + +Signature.prototype.toBytes = function toBytes() { + return this.Rencoded().concat(this.Sencoded()); +}; + +Signature.prototype.toHex = function toHex() { + return utils.encode(this.toBytes(), 'hex').toUpperCase(); +}; + +module.exports = Signature; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/precomputed/secp256k1.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/precomputed/secp256k1.js new file mode 100644 index 000000000..e4c91e543 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/precomputed/secp256k1.js @@ -0,0 +1,780 @@ +module.exports = { + doubles: { + step: 4, + points: [ + [ + 'e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a', + 'f7e3507399e595929db99f34f57937101296891e44d23f0be1f32cce69616821' + ], + [ + '8282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508', + '11f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf' + ], + [ + '175e159f728b865a72f99cc6c6fc846de0b93833fd2222ed73fce5b551e5b739', + 'd3506e0d9e3c79eba4ef97a51ff71f5eacb5955add24345c6efa6ffee9fed695' + ], + [ + '363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640', + '4e273adfc732221953b445397f3363145b9a89008199ecb62003c7f3bee9de9' + ], + [ + '8b4b5f165df3c2be8c6244b5b745638843e4a781a15bcd1b69f79a55dffdf80c', + '4aad0a6f68d308b4b3fbd7813ab0da04f9e336546162ee56b3eff0c65fd4fd36' + ], + [ + '723cbaa6e5db996d6bf771c00bd548c7b700dbffa6c0e77bcb6115925232fcda', + '96e867b5595cc498a921137488824d6e2660a0653779494801dc069d9eb39f5f' + ], + [ + 'eebfa4d493bebf98ba5feec812c2d3b50947961237a919839a533eca0e7dd7fa', + '5d9a8ca3970ef0f269ee7edaf178089d9ae4cdc3a711f712ddfd4fdae1de8999' + ], + [ + '100f44da696e71672791d0a09b7bde459f1215a29b3c03bfefd7835b39a48db0', + 'cdd9e13192a00b772ec8f3300c090666b7ff4a18ff5195ac0fbd5cd62bc65a09' + ], + [ + 'e1031be262c7ed1b1dc9227a4a04c017a77f8d4464f3b3852c8acde6e534fd2d', + '9d7061928940405e6bb6a4176597535af292dd419e1ced79a44f18f29456a00d' + ], + [ + 'feea6cae46d55b530ac2839f143bd7ec5cf8b266a41d6af52d5e688d9094696d', + 'e57c6b6c97dce1bab06e4e12bf3ecd5c981c8957cc41442d3155debf18090088' + ], + [ + 'da67a91d91049cdcb367be4be6ffca3cfeed657d808583de33fa978bc1ec6cb1', + '9bacaa35481642bc41f463f7ec9780e5dec7adc508f740a17e9ea8e27a68be1d' + ], + [ + '53904faa0b334cdda6e000935ef22151ec08d0f7bb11069f57545ccc1a37b7c0', + '5bc087d0bc80106d88c9eccac20d3c1c13999981e14434699dcb096b022771c8' + ], + [ + '8e7bcd0bd35983a7719cca7764ca906779b53a043a9b8bcaeff959f43ad86047', + '10b7770b2a3da4b3940310420ca9514579e88e2e47fd68b3ea10047e8460372a' + ], + [ + '385eed34c1cdff21e6d0818689b81bde71a7f4f18397e6690a841e1599c43862', + '283bebc3e8ea23f56701de19e9ebf4576b304eec2086dc8cc0458fe5542e5453' + ], + [ + '6f9d9b803ecf191637c73a4413dfa180fddf84a5947fbc9c606ed86c3fac3a7', + '7c80c68e603059ba69b8e2a30e45c4d47ea4dd2f5c281002d86890603a842160' + ], + [ + '3322d401243c4e2582a2147c104d6ecbf774d163db0f5e5313b7e0e742d0e6bd', + '56e70797e9664ef5bfb019bc4ddaf9b72805f63ea2873af624f3a2e96c28b2a0' + ], + [ + '85672c7d2de0b7da2bd1770d89665868741b3f9af7643397721d74d28134ab83', + '7c481b9b5b43b2eb6374049bfa62c2e5e77f17fcc5298f44c8e3094f790313a6' + ], + [ + '948bf809b1988a46b06c9f1919413b10f9226c60f668832ffd959af60c82a0a', + '53a562856dcb6646dc6b74c5d1c3418c6d4dff08c97cd2bed4cb7f88d8c8e589' + ], + [ + '6260ce7f461801c34f067ce0f02873a8f1b0e44dfc69752accecd819f38fd8e8', + 'bc2da82b6fa5b571a7f09049776a1ef7ecd292238051c198c1a84e95b2b4ae17' + ], + [ + 'e5037de0afc1d8d43d8348414bbf4103043ec8f575bfdc432953cc8d2037fa2d', + '4571534baa94d3b5f9f98d09fb990bddbd5f5b03ec481f10e0e5dc841d755bda' + ], + [ + 'e06372b0f4a207adf5ea905e8f1771b4e7e8dbd1c6a6c5b725866a0ae4fce725', + '7a908974bce18cfe12a27bb2ad5a488cd7484a7787104870b27034f94eee31dd' + ], + [ + '213c7a715cd5d45358d0bbf9dc0ce02204b10bdde2a3f58540ad6908d0559754', + '4b6dad0b5ae462507013ad06245ba190bb4850f5f36a7eeddff2c27534b458f2' + ], + [ + '4e7c272a7af4b34e8dbb9352a5419a87e2838c70adc62cddf0cc3a3b08fbd53c', + '17749c766c9d0b18e16fd09f6def681b530b9614bff7dd33e0b3941817dcaae6' + ], + [ + 'fea74e3dbe778b1b10f238ad61686aa5c76e3db2be43057632427e2840fb27b6', + '6e0568db9b0b13297cf674deccb6af93126b596b973f7b77701d3db7f23cb96f' + ], + [ + '76e64113f677cf0e10a2570d599968d31544e179b760432952c02a4417bdde39', + 'c90ddf8dee4e95cf577066d70681f0d35e2a33d2b56d2032b4b1752d1901ac01' + ], + [ + 'c738c56b03b2abe1e8281baa743f8f9a8f7cc643df26cbee3ab150242bcbb891', + '893fb578951ad2537f718f2eacbfbbbb82314eef7880cfe917e735d9699a84c3' + ], + [ + 'd895626548b65b81e264c7637c972877d1d72e5f3a925014372e9f6588f6c14b', + 'febfaa38f2bc7eae728ec60818c340eb03428d632bb067e179363ed75d7d991f' + ], + [ + 'b8da94032a957518eb0f6433571e8761ceffc73693e84edd49150a564f676e03', + '2804dfa44805a1e4d7c99cc9762808b092cc584d95ff3b511488e4e74efdf6e7' + ], + [ + 'e80fea14441fb33a7d8adab9475d7fab2019effb5156a792f1a11778e3c0df5d', + 'eed1de7f638e00771e89768ca3ca94472d155e80af322ea9fcb4291b6ac9ec78' + ], + [ + 'a301697bdfcd704313ba48e51d567543f2a182031efd6915ddc07bbcc4e16070', + '7370f91cfb67e4f5081809fa25d40f9b1735dbf7c0a11a130c0d1a041e177ea1' + ], + [ + '90ad85b389d6b936463f9d0512678de208cc330b11307fffab7ac63e3fb04ed4', + 'e507a3620a38261affdcbd9427222b839aefabe1582894d991d4d48cb6ef150' + ], + [ + '8f68b9d2f63b5f339239c1ad981f162ee88c5678723ea3351b7b444c9ec4c0da', + '662a9f2dba063986de1d90c2b6be215dbbea2cfe95510bfdf23cbf79501fff82' + ], + [ + 'e4f3fb0176af85d65ff99ff9198c36091f48e86503681e3e6686fd5053231e11', + '1e63633ad0ef4f1c1661a6d0ea02b7286cc7e74ec951d1c9822c38576feb73bc' + ], + [ + '8c00fa9b18ebf331eb961537a45a4266c7034f2f0d4e1d0716fb6eae20eae29e', + 'efa47267fea521a1a9dc343a3736c974c2fadafa81e36c54e7d2a4c66702414b' + ], + [ + 'e7a26ce69dd4829f3e10cec0a9e98ed3143d084f308b92c0997fddfc60cb3e41', + '2a758e300fa7984b471b006a1aafbb18d0a6b2c0420e83e20e8a9421cf2cfd51' + ], + [ + 'b6459e0ee3662ec8d23540c223bcbdc571cbcb967d79424f3cf29eb3de6b80ef', + '67c876d06f3e06de1dadf16e5661db3c4b3ae6d48e35b2ff30bf0b61a71ba45' + ], + [ + 'd68a80c8280bb840793234aa118f06231d6f1fc67e73c5a5deda0f5b496943e8', + 'db8ba9fff4b586d00c4b1f9177b0e28b5b0e7b8f7845295a294c84266b133120' + ], + [ + '324aed7df65c804252dc0270907a30b09612aeb973449cea4095980fc28d3d5d', + '648a365774b61f2ff130c0c35aec1f4f19213b0c7e332843967224af96ab7c84' + ], + [ + '4df9c14919cde61f6d51dfdbe5fee5dceec4143ba8d1ca888e8bd373fd054c96', + '35ec51092d8728050974c23a1d85d4b5d506cdc288490192ebac06cad10d5d' + ], + [ + '9c3919a84a474870faed8a9c1cc66021523489054d7f0308cbfc99c8ac1f98cd', + 'ddb84f0f4a4ddd57584f044bf260e641905326f76c64c8e6be7e5e03d4fc599d' + ], + [ + '6057170b1dd12fdf8de05f281d8e06bb91e1493a8b91d4cc5a21382120a959e5', + '9a1af0b26a6a4807add9a2daf71df262465152bc3ee24c65e899be932385a2a8' + ], + [ + 'a576df8e23a08411421439a4518da31880cef0fba7d4df12b1a6973eecb94266', + '40a6bf20e76640b2c92b97afe58cd82c432e10a7f514d9f3ee8be11ae1b28ec8' + ], + [ + '7778a78c28dec3e30a05fe9629de8c38bb30d1f5cf9a3a208f763889be58ad71', + '34626d9ab5a5b22ff7098e12f2ff580087b38411ff24ac563b513fc1fd9f43ac' + ], + [ + '928955ee637a84463729fd30e7afd2ed5f96274e5ad7e5cb09eda9c06d903ac', + 'c25621003d3f42a827b78a13093a95eeac3d26efa8a8d83fc5180e935bcd091f' + ], + [ + '85d0fef3ec6db109399064f3a0e3b2855645b4a907ad354527aae75163d82751', + '1f03648413a38c0be29d496e582cf5663e8751e96877331582c237a24eb1f962' + ], + [ + 'ff2b0dce97eece97c1c9b6041798b85dfdfb6d8882da20308f5404824526087e', + '493d13fef524ba188af4c4dc54d07936c7b7ed6fb90e2ceb2c951e01f0c29907' + ], + [ + '827fbbe4b1e880ea9ed2b2e6301b212b57f1ee148cd6dd28780e5e2cf856e241', + 'c60f9c923c727b0b71bef2c67d1d12687ff7a63186903166d605b68baec293ec' + ], + [ + 'eaa649f21f51bdbae7be4ae34ce6e5217a58fdce7f47f9aa7f3b58fa2120e2b3', + 'be3279ed5bbbb03ac69a80f89879aa5a01a6b965f13f7e59d47a5305ba5ad93d' + ], + [ + 'e4a42d43c5cf169d9391df6decf42ee541b6d8f0c9a137401e23632dda34d24f', + '4d9f92e716d1c73526fc99ccfb8ad34ce886eedfa8d8e4f13a7f7131deba9414' + ], + [ + '1ec80fef360cbdd954160fadab352b6b92b53576a88fea4947173b9d4300bf19', + 'aeefe93756b5340d2f3a4958a7abbf5e0146e77f6295a07b671cdc1cc107cefd' + ], + [ + '146a778c04670c2f91b00af4680dfa8bce3490717d58ba889ddb5928366642be', + 'b318e0ec3354028add669827f9d4b2870aaa971d2f7e5ed1d0b297483d83efd0' + ], + [ + 'fa50c0f61d22e5f07e3acebb1aa07b128d0012209a28b9776d76a8793180eef9', + '6b84c6922397eba9b72cd2872281a68a5e683293a57a213b38cd8d7d3f4f2811' + ], + [ + 'da1d61d0ca721a11b1a5bf6b7d88e8421a288ab5d5bba5220e53d32b5f067ec2', + '8157f55a7c99306c79c0766161c91e2966a73899d279b48a655fba0f1ad836f1' + ], + [ + 'a8e282ff0c9706907215ff98e8fd416615311de0446f1e062a73b0610d064e13', + '7f97355b8db81c09abfb7f3c5b2515888b679a3e50dd6bd6cef7c73111f4cc0c' + ], + [ + '174a53b9c9a285872d39e56e6913cab15d59b1fa512508c022f382de8319497c', + 'ccc9dc37abfc9c1657b4155f2c47f9e6646b3a1d8cb9854383da13ac079afa73' + ], + [ + '959396981943785c3d3e57edf5018cdbe039e730e4918b3d884fdff09475b7ba', + '2e7e552888c331dd8ba0386a4b9cd6849c653f64c8709385e9b8abf87524f2fd' + ], + [ + 'd2a63a50ae401e56d645a1153b109a8fcca0a43d561fba2dbb51340c9d82b151', + 'e82d86fb6443fcb7565aee58b2948220a70f750af484ca52d4142174dcf89405' + ], + [ + '64587e2335471eb890ee7896d7cfdc866bacbdbd3839317b3436f9b45617e073', + 'd99fcdd5bf6902e2ae96dd6447c299a185b90a39133aeab358299e5e9faf6589' + ], + [ + '8481bde0e4e4d885b3a546d3e549de042f0aa6cea250e7fd358d6c86dd45e458', + '38ee7b8cba5404dd84a25bf39cecb2ca900a79c42b262e556d64b1b59779057e' + ], + [ + '13464a57a78102aa62b6979ae817f4637ffcfed3c4b1ce30bcd6303f6caf666b', + '69be159004614580ef7e433453ccb0ca48f300a81d0942e13f495a907f6ecc27' + ], + [ + 'bc4a9df5b713fe2e9aef430bcc1dc97a0cd9ccede2f28588cada3a0d2d83f366', + 'd3a81ca6e785c06383937adf4b798caa6e8a9fbfa547b16d758d666581f33c1' + ], + [ + '8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa', + '40a30463a3305193378fedf31f7cc0eb7ae784f0451cb9459e71dc73cbef9482' + ], + [ + '8ea9666139527a8c1dd94ce4f071fd23c8b350c5a4bb33748c4ba111faccae0', + '620efabbc8ee2782e24e7c0cfb95c5d735b783be9cf0f8e955af34a30e62b945' + ], + [ + 'dd3625faef5ba06074669716bbd3788d89bdde815959968092f76cc4eb9a9787', + '7a188fa3520e30d461da2501045731ca941461982883395937f68d00c644a573' + ], + [ + 'f710d79d9eb962297e4f6232b40e8f7feb2bc63814614d692c12de752408221e', + 'ea98e67232d3b3295d3b535532115ccac8612c721851617526ae47a9c77bfc82' + ] + ] + }, + naf: { + wnd: 7, + points: [ + [ + 'f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9', + '388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672' + ], + [ + '2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4', + 'd8ac222636e5e3d6d4dba9dda6c9c426f788271bab0d6840dca87d3aa6ac62d6' + ], + [ + '5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc', + '6aebca40ba255960a3178d6d861a54dba813d0b813fde7b5a5082628087264da' + ], + [ + 'acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe', + 'cc338921b0a7d9fd64380971763b61e9add888a4375f8e0f05cc262ac64f9c37' + ], + [ + '774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb', + 'd984a032eb6b5e190243dd56d7b7b365372db1e2dff9d6a8301d74c9c953c61b' + ], + [ + 'f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8', + 'ab0902e8d880a89758212eb65cdaf473a1a06da521fa91f29b5cb52db03ed81' + ], + [ + 'd7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e', + '581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58' + ], + [ + 'defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34', + '4211ab0694635168e997b0ead2a93daeced1f4a04a95c0f6cfb199f69e56eb77' + ], + [ + '2b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6c', + '85e89bc037945d93b343083b5a1c86131a01f60c50269763b570c854e5c09b7a' + ], + [ + '352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5', + '321eb4075348f534d59c18259dda3e1f4a1b3b2e71b1039c67bd3d8bcf81998c' + ], + [ + '2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f', + '2de1068295dd865b64569335bd5dd80181d70ecfc882648423ba76b532b7d67' + ], + [ + '9248279b09b4d68dab21a9b066edda83263c3d84e09572e269ca0cd7f5453714', + '73016f7bf234aade5d1aa71bdea2b1ff3fc0de2a887912ffe54a32ce97cb3402' + ], + [ + 'daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729', + 'a69dce4a7d6c98e8d4a1aca87ef8d7003f83c230f3afa726ab40e52290be1c55' + ], + [ + 'c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db', + '2119a460ce326cdc76c45926c982fdac0e106e861edf61c5a039063f0e0e6482' + ], + [ + '6a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4', + 'e022cf42c2bd4a708b3f5126f16a24ad8b33ba48d0423b6efd5e6348100d8a82' + ], + [ + '1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5', + 'b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396' + ], + [ + '605bdb019981718b986d0f07e834cb0d9deb8360ffb7f61df982345ef27a7479', + '2972d2de4f8d20681a78d93ec96fe23c26bfae84fb14db43b01e1e9056b8c49' + ], + [ + '62d14dab4150bf497402fdc45a215e10dcb01c354959b10cfe31c7e9d87ff33d', + '80fc06bd8cc5b01098088a1950eed0db01aa132967ab472235f5642483b25eaf' + ], + [ + '80c60ad0040f27dade5b4b06c408e56b2c50e9f56b9b8b425e555c2f86308b6f', + '1c38303f1cc5c30f26e66bad7fe72f70a65eed4cbe7024eb1aa01f56430bd57a' + ], + [ + '7a9375ad6167ad54aa74c6348cc54d344cc5dc9487d847049d5eabb0fa03c8fb', + 'd0e3fa9eca8726909559e0d79269046bdc59ea10c70ce2b02d499ec224dc7f7' + ], + [ + 'd528ecd9b696b54c907a9ed045447a79bb408ec39b68df504bb51f459bc3ffc9', + 'eecf41253136e5f99966f21881fd656ebc4345405c520dbc063465b521409933' + ], + [ + '49370a4b5f43412ea25f514e8ecdad05266115e4a7ecb1387231808f8b45963', + '758f3f41afd6ed428b3081b0512fd62a54c3f3afbb5b6764b653052a12949c9a' + ], + [ + '77f230936ee88cbbd73df930d64702ef881d811e0e1498e2f1c13eb1fc345d74', + '958ef42a7886b6400a08266e9ba1b37896c95330d97077cbbe8eb3c7671c60d6' + ], + [ + 'f2dac991cc4ce4b9ea44887e5c7c0bce58c80074ab9d4dbaeb28531b7739f530', + 'e0dedc9b3b2f8dad4da1f32dec2531df9eb5fbeb0598e4fd1a117dba703a3c37' + ], + [ + '463b3d9f662621fb1b4be8fbbe2520125a216cdfc9dae3debcba4850c690d45b', + '5ed430d78c296c3543114306dd8622d7c622e27c970a1de31cb377b01af7307e' + ], + [ + 'f16f804244e46e2a09232d4aff3b59976b98fac14328a2d1a32496b49998f247', + 'cedabd9b82203f7e13d206fcdf4e33d92a6c53c26e5cce26d6579962c4e31df6' + ], + [ + 'caf754272dc84563b0352b7a14311af55d245315ace27c65369e15f7151d41d1', + 'cb474660ef35f5f2a41b643fa5e460575f4fa9b7962232a5c32f908318a04476' + ], + [ + '2600ca4b282cb986f85d0f1709979d8b44a09c07cb86d7c124497bc86f082120', + '4119b88753c15bd6a693b03fcddbb45d5ac6be74ab5f0ef44b0be9475a7e4b40' + ], + [ + '7635ca72d7e8432c338ec53cd12220bc01c48685e24f7dc8c602a7746998e435', + '91b649609489d613d1d5e590f78e6d74ecfc061d57048bad9e76f302c5b9c61' + ], + [ + '754e3239f325570cdbbf4a87deee8a66b7f2b33479d468fbc1a50743bf56cc18', + '673fb86e5bda30fb3cd0ed304ea49a023ee33d0197a695d0c5d98093c536683' + ], + [ + 'e3e6bd1071a1e96aff57859c82d570f0330800661d1c952f9fe2694691d9b9e8', + '59c9e0bba394e76f40c0aa58379a3cb6a5a2283993e90c4167002af4920e37f5' + ], + [ + '186b483d056a033826ae73d88f732985c4ccb1f32ba35f4b4cc47fdcf04aa6eb', + '3b952d32c67cf77e2e17446e204180ab21fb8090895138b4a4a797f86e80888b' + ], + [ + 'df9d70a6b9876ce544c98561f4be4f725442e6d2b737d9c91a8321724ce0963f', + '55eb2dafd84d6ccd5f862b785dc39d4ab157222720ef9da217b8c45cf2ba2417' + ], + [ + '5edd5cc23c51e87a497ca815d5dce0f8ab52554f849ed8995de64c5f34ce7143', + 'efae9c8dbc14130661e8cec030c89ad0c13c66c0d17a2905cdc706ab7399a868' + ], + [ + '290798c2b6476830da12fe02287e9e777aa3fba1c355b17a722d362f84614fba', + 'e38da76dcd440621988d00bcf79af25d5b29c094db2a23146d003afd41943e7a' + ], + [ + 'af3c423a95d9f5b3054754efa150ac39cd29552fe360257362dfdecef4053b45', + 'f98a3fd831eb2b749a93b0e6f35cfb40c8cd5aa667a15581bc2feded498fd9c6' + ], + [ + '766dbb24d134e745cccaa28c99bf274906bb66b26dcf98df8d2fed50d884249a', + '744b1152eacbe5e38dcc887980da38b897584a65fa06cedd2c924f97cbac5996' + ], + [ + '59dbf46f8c94759ba21277c33784f41645f7b44f6c596a58ce92e666191abe3e', + 'c534ad44175fbc300f4ea6ce648309a042ce739a7919798cd85e216c4a307f6e' + ], + [ + 'f13ada95103c4537305e691e74e9a4a8dd647e711a95e73cb62dc6018cfd87b8', + 'e13817b44ee14de663bf4bc808341f326949e21a6a75c2570778419bdaf5733d' + ], + [ + '7754b4fa0e8aced06d4167a2c59cca4cda1869c06ebadfb6488550015a88522c', + '30e93e864e669d82224b967c3020b8fa8d1e4e350b6cbcc537a48b57841163a2' + ], + [ + '948dcadf5990e048aa3874d46abef9d701858f95de8041d2a6828c99e2262519', + 'e491a42537f6e597d5d28a3224b1bc25df9154efbd2ef1d2cbba2cae5347d57e' + ], + [ + '7962414450c76c1689c7b48f8202ec37fb224cf5ac0bfa1570328a8a3d7c77ab', + '100b610ec4ffb4760d5c1fc133ef6f6b12507a051f04ac5760afa5b29db83437' + ], + [ + '3514087834964b54b15b160644d915485a16977225b8847bb0dd085137ec47ca', + 'ef0afbb2056205448e1652c48e8127fc6039e77c15c2378b7e7d15a0de293311' + ], + [ + 'd3cc30ad6b483e4bc79ce2c9dd8bc54993e947eb8df787b442943d3f7b527eaf', + '8b378a22d827278d89c5e9be8f9508ae3c2ad46290358630afb34db04eede0a4' + ], + [ + '1624d84780732860ce1c78fcbfefe08b2b29823db913f6493975ba0ff4847610', + '68651cf9b6da903e0914448c6cd9d4ca896878f5282be4c8cc06e2a404078575' + ], + [ + '733ce80da955a8a26902c95633e62a985192474b5af207da6df7b4fd5fc61cd4', + 'f5435a2bd2badf7d485a4d8b8db9fcce3e1ef8e0201e4578c54673bc1dc5ea1d' + ], + [ + '15d9441254945064cf1a1c33bbd3b49f8966c5092171e699ef258dfab81c045c', + 'd56eb30b69463e7234f5137b73b84177434800bacebfc685fc37bbe9efe4070d' + ], + [ + 'a1d0fcf2ec9de675b612136e5ce70d271c21417c9d2b8aaaac138599d0717940', + 'edd77f50bcb5a3cab2e90737309667f2641462a54070f3d519212d39c197a629' + ], + [ + 'e22fbe15c0af8ccc5780c0735f84dbe9a790badee8245c06c7ca37331cb36980', + 'a855babad5cd60c88b430a69f53a1a7a38289154964799be43d06d77d31da06' + ], + [ + '311091dd9860e8e20ee13473c1155f5f69635e394704eaa74009452246cfa9b3', + '66db656f87d1f04fffd1f04788c06830871ec5a64feee685bd80f0b1286d8374' + ], + [ + '34c1fd04d301be89b31c0442d3e6ac24883928b45a9340781867d4232ec2dbdf', + '9414685e97b1b5954bd46f730174136d57f1ceeb487443dc5321857ba73abee' + ], + [ + 'f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d63', + '4cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1' + ], + [ + 'd7b8740f74a8fbaab1f683db8f45de26543a5490bca627087236912469a0b448', + 'fa77968128d9c92ee1010f337ad4717eff15db5ed3c049b3411e0315eaa4593b' + ], + [ + '32d31c222f8f6f0ef86f7c98d3a3335ead5bcd32abdd94289fe4d3091aa824bf', + '5f3032f5892156e39ccd3d7915b9e1da2e6dac9e6f26e961118d14b8462e1661' + ], + [ + '7461f371914ab32671045a155d9831ea8793d77cd59592c4340f86cbc18347b5', + '8ec0ba238b96bec0cbdddcae0aa442542eee1ff50c986ea6b39847b3cc092ff6' + ], + [ + 'ee079adb1df1860074356a25aa38206a6d716b2c3e67453d287698bad7b2b2d6', + '8dc2412aafe3be5c4c5f37e0ecc5f9f6a446989af04c4e25ebaac479ec1c8c1e' + ], + [ + '16ec93e447ec83f0467b18302ee620f7e65de331874c9dc72bfd8616ba9da6b5', + '5e4631150e62fb40d0e8c2a7ca5804a39d58186a50e497139626778e25b0674d' + ], + [ + 'eaa5f980c245f6f038978290afa70b6bd8855897f98b6aa485b96065d537bd99', + 'f65f5d3e292c2e0819a528391c994624d784869d7e6ea67fb18041024edc07dc' + ], + [ + '78c9407544ac132692ee1910a02439958ae04877151342ea96c4b6b35a49f51', + 'f3e0319169eb9b85d5404795539a5e68fa1fbd583c064d2462b675f194a3ddb4' + ], + [ + '494f4be219a1a77016dcd838431aea0001cdc8ae7a6fc688726578d9702857a5', + '42242a969283a5f339ba7f075e36ba2af925ce30d767ed6e55f4b031880d562c' + ], + [ + 'a598a8030da6d86c6bc7f2f5144ea549d28211ea58faa70ebf4c1e665c1fe9b5', + '204b5d6f84822c307e4b4a7140737aec23fc63b65b35f86a10026dbd2d864e6b' + ], + [ + 'c41916365abb2b5d09192f5f2dbeafec208f020f12570a184dbadc3e58595997', + '4f14351d0087efa49d245b328984989d5caf9450f34bfc0ed16e96b58fa9913' + ], + [ + '841d6063a586fa475a724604da03bc5b92a2e0d2e0a36acfe4c73a5514742881', + '73867f59c0659e81904f9a1c7543698e62562d6744c169ce7a36de01a8d6154' + ], + [ + '5e95bb399a6971d376026947f89bde2f282b33810928be4ded112ac4d70e20d5', + '39f23f366809085beebfc71181313775a99c9aed7d8ba38b161384c746012865' + ], + [ + '36e4641a53948fd476c39f8a99fd974e5ec07564b5315d8bf99471bca0ef2f66', + 'd2424b1b1abe4eb8164227b085c9aa9456ea13493fd563e06fd51cf5694c78fc' + ], + [ + '336581ea7bfbbb290c191a2f507a41cf5643842170e914faeab27c2c579f726', + 'ead12168595fe1be99252129b6e56b3391f7ab1410cd1e0ef3dcdcabd2fda224' + ], + [ + '8ab89816dadfd6b6a1f2634fcf00ec8403781025ed6890c4849742706bd43ede', + '6fdcef09f2f6d0a044e654aef624136f503d459c3e89845858a47a9129cdd24e' + ], + [ + '1e33f1a746c9c5778133344d9299fcaa20b0938e8acff2544bb40284b8c5fb94', + '60660257dd11b3aa9c8ed618d24edff2306d320f1d03010e33a7d2057f3b3b6' + ], + [ + '85b7c1dcb3cec1b7ee7f30ded79dd20a0ed1f4cc18cbcfcfa410361fd8f08f31', + '3d98a9cdd026dd43f39048f25a8847f4fcafad1895d7a633c6fed3c35e999511' + ], + [ + '29df9fbd8d9e46509275f4b125d6d45d7fbe9a3b878a7af872a2800661ac5f51', + 'b4c4fe99c775a606e2d8862179139ffda61dc861c019e55cd2876eb2a27d84b' + ], + [ + 'a0b1cae06b0a847a3fea6e671aaf8adfdfe58ca2f768105c8082b2e449fce252', + 'ae434102edde0958ec4b19d917a6a28e6b72da1834aff0e650f049503a296cf2' + ], + [ + '4e8ceafb9b3e9a136dc7ff67e840295b499dfb3b2133e4ba113f2e4c0e121e5', + 'cf2174118c8b6d7a4b48f6d534ce5c79422c086a63460502b827ce62a326683c' + ], + [ + 'd24a44e047e19b6f5afb81c7ca2f69080a5076689a010919f42725c2b789a33b', + '6fb8d5591b466f8fc63db50f1c0f1c69013f996887b8244d2cdec417afea8fa3' + ], + [ + 'ea01606a7a6c9cdd249fdfcfacb99584001edd28abbab77b5104e98e8e3b35d4', + '322af4908c7312b0cfbfe369f7a7b3cdb7d4494bc2823700cfd652188a3ea98d' + ], + [ + 'af8addbf2b661c8a6c6328655eb96651252007d8c5ea31be4ad196de8ce2131f', + '6749e67c029b85f52a034eafd096836b2520818680e26ac8f3dfbcdb71749700' + ], + [ + 'e3ae1974566ca06cc516d47e0fb165a674a3dabcfca15e722f0e3450f45889', + '2aeabe7e4531510116217f07bf4d07300de97e4874f81f533420a72eeb0bd6a4' + ], + [ + '591ee355313d99721cf6993ffed1e3e301993ff3ed258802075ea8ced397e246', + 'b0ea558a113c30bea60fc4775460c7901ff0b053d25ca2bdeee98f1a4be5d196' + ], + [ + '11396d55fda54c49f19aa97318d8da61fa8584e47b084945077cf03255b52984', + '998c74a8cd45ac01289d5833a7beb4744ff536b01b257be4c5767bea93ea57a4' + ], + [ + '3c5d2a1ba39c5a1790000738c9e0c40b8dcdfd5468754b6405540157e017aa7a', + 'b2284279995a34e2f9d4de7396fc18b80f9b8b9fdd270f6661f79ca4c81bd257' + ], + [ + 'cc8704b8a60a0defa3a99a7299f2e9c3fbc395afb04ac078425ef8a1793cc030', + 'bdd46039feed17881d1e0862db347f8cf395b74fc4bcdc4e940b74e3ac1f1b13' + ], + [ + 'c533e4f7ea8555aacd9777ac5cad29b97dd4defccc53ee7ea204119b2889b197', + '6f0a256bc5efdf429a2fb6242f1a43a2d9b925bb4a4b3a26bb8e0f45eb596096' + ], + [ + 'c14f8f2ccb27d6f109f6d08d03cc96a69ba8c34eec07bbcf566d48e33da6593', + 'c359d6923bb398f7fd4473e16fe1c28475b740dd098075e6c0e8649113dc3a38' + ], + [ + 'a6cbc3046bc6a450bac24789fa17115a4c9739ed75f8f21ce441f72e0b90e6ef', + '21ae7f4680e889bb130619e2c0f95a360ceb573c70603139862afd617fa9b9f' + ], + [ + '347d6d9a02c48927ebfb86c1359b1caf130a3c0267d11ce6344b39f99d43cc38', + '60ea7f61a353524d1c987f6ecec92f086d565ab687870cb12689ff1e31c74448' + ], + [ + 'da6545d2181db8d983f7dcb375ef5866d47c67b1bf31c8cf855ef7437b72656a', + '49b96715ab6878a79e78f07ce5680c5d6673051b4935bd897fea824b77dc208a' + ], + [ + 'c40747cc9d012cb1a13b8148309c6de7ec25d6945d657146b9d5994b8feb1111', + '5ca560753be2a12fc6de6caf2cb489565db936156b9514e1bb5e83037e0fa2d4' + ], + [ + '4e42c8ec82c99798ccf3a610be870e78338c7f713348bd34c8203ef4037f3502', + '7571d74ee5e0fb92a7a8b33a07783341a5492144cc54bcc40a94473693606437' + ], + [ + '3775ab7089bc6af823aba2e1af70b236d251cadb0c86743287522a1b3b0dedea', + 'be52d107bcfa09d8bcb9736a828cfa7fac8db17bf7a76a2c42ad961409018cf7' + ], + [ + 'cee31cbf7e34ec379d94fb814d3d775ad954595d1314ba8846959e3e82f74e26', + '8fd64a14c06b589c26b947ae2bcf6bfa0149ef0be14ed4d80f448a01c43b1c6d' + ], + [ + 'b4f9eaea09b6917619f6ea6a4eb5464efddb58fd45b1ebefcdc1a01d08b47986', + '39e5c9925b5a54b07433a4f18c61726f8bb131c012ca542eb24a8ac07200682a' + ], + [ + 'd4263dfc3d2df923a0179a48966d30ce84e2515afc3dccc1b77907792ebcc60e', + '62dfaf07a0f78feb30e30d6295853ce189e127760ad6cf7fae164e122a208d54' + ], + [ + '48457524820fa65a4f8d35eb6930857c0032acc0a4a2de422233eeda897612c4', + '25a748ab367979d98733c38a1fa1c2e7dc6cc07db2d60a9ae7a76aaa49bd0f77' + ], + [ + 'dfeeef1881101f2cb11644f3a2afdfc2045e19919152923f367a1767c11cceda', + 'ecfb7056cf1de042f9420bab396793c0c390bde74b4bbdff16a83ae09a9a7517' + ], + [ + '6d7ef6b17543f8373c573f44e1f389835d89bcbc6062ced36c82df83b8fae859', + 'cd450ec335438986dfefa10c57fea9bcc521a0959b2d80bbf74b190dca712d10' + ], + [ + 'e75605d59102a5a2684500d3b991f2e3f3c88b93225547035af25af66e04541f', + 'f5c54754a8f71ee540b9b48728473e314f729ac5308b06938360990e2bfad125' + ], + [ + 'eb98660f4c4dfaa06a2be453d5020bc99a0c2e60abe388457dd43fefb1ed620c', + '6cb9a8876d9cb8520609af3add26cd20a0a7cd8a9411131ce85f44100099223e' + ], + [ + '13e87b027d8514d35939f2e6892b19922154596941888336dc3563e3b8dba942', + 'fef5a3c68059a6dec5d624114bf1e91aac2b9da568d6abeb2570d55646b8adf1' + ], + [ + 'ee163026e9fd6fe017c38f06a5be6fc125424b371ce2708e7bf4491691e5764a', + '1acb250f255dd61c43d94ccc670d0f58f49ae3fa15b96623e5430da0ad6c62b2' + ], + [ + 'b268f5ef9ad51e4d78de3a750c2dc89b1e626d43505867999932e5db33af3d80', + '5f310d4b3c99b9ebb19f77d41c1dee018cf0d34fd4191614003e945a1216e423' + ], + [ + 'ff07f3118a9df035e9fad85eb6c7bfe42b02f01ca99ceea3bf7ffdba93c4750d', + '438136d603e858a3a5c440c38eccbaddc1d2942114e2eddd4740d098ced1f0d8' + ], + [ + '8d8b9855c7c052a34146fd20ffb658bea4b9f69e0d825ebec16e8c3ce2b526a1', + 'cdb559eedc2d79f926baf44fb84ea4d44bcf50fee51d7ceb30e2e7f463036758' + ], + [ + '52db0b5384dfbf05bfa9d472d7ae26dfe4b851ceca91b1eba54263180da32b63', + 'c3b997d050ee5d423ebaf66a6db9f57b3180c902875679de924b69d84a7b375' + ], + [ + 'e62f9490d3d51da6395efd24e80919cc7d0f29c3f3fa48c6fff543becbd43352', + '6d89ad7ba4876b0b22c2ca280c682862f342c8591f1daf5170e07bfd9ccafa7d' + ], + [ + '7f30ea2476b399b4957509c88f77d0191afa2ff5cb7b14fd6d8e7d65aaab1193', + 'ca5ef7d4b231c94c3b15389a5f6311e9daff7bb67b103e9880ef4bff637acaec' + ], + [ + '5098ff1e1d9f14fb46a210fada6c903fef0fb7b4a1dd1d9ac60a0361800b7a00', + '9731141d81fc8f8084d37c6e7542006b3ee1b40d60dfe5362a5b132fd17ddc0' + ], + [ + '32b78c7de9ee512a72895be6b9cbefa6e2f3c4ccce445c96b9f2c81e2778ad58', + 'ee1849f513df71e32efc3896ee28260c73bb80547ae2275ba497237794c8753c' + ], + [ + 'e2cb74fddc8e9fbcd076eef2a7c72b0ce37d50f08269dfc074b581550547a4f7', + 'd3aa2ed71c9dd2247a62df062736eb0baddea9e36122d2be8641abcb005cc4a4' + ], + [ + '8438447566d4d7bedadc299496ab357426009a35f235cb141be0d99cd10ae3a8', + 'c4e1020916980a4da5d01ac5e6ad330734ef0d7906631c4f2390426b2edd791f' + ], + [ + '4162d488b89402039b584c6fc6c308870587d9c46f660b878ab65c82c711d67e', + '67163e903236289f776f22c25fb8a3afc1732f2b84b4e95dbda47ae5a0852649' + ], + [ + '3fad3fa84caf0f34f0f89bfd2dcf54fc175d767aec3e50684f3ba4a4bf5f683d', + 'cd1bc7cb6cc407bb2f0ca647c718a730cf71872e7d0d2a53fa20efcdfe61826' + ], + [ + '674f2600a3007a00568c1a7ce05d0816c1fb84bf1370798f1c69532faeb1a86b', + '299d21f9413f33b3edf43b257004580b70db57da0b182259e09eecc69e0d38a5' + ], + [ + 'd32f4da54ade74abb81b815ad1fb3b263d82d6c692714bcff87d29bd5ee9f08f', + 'f9429e738b8e53b968e99016c059707782e14f4535359d582fc416910b3eea87' + ], + [ + '30e4e670435385556e593657135845d36fbb6931f72b08cb1ed954f1e3ce3ff6', + '462f9bce619898638499350113bbc9b10a878d35da70740dc695a559eb88db7b' + ], + [ + 'be2062003c51cc3004682904330e4dee7f3dcd10b01e580bf1971b04d4cad297', + '62188bc49d61e5428573d48a74e1c655b1c61090905682a0d5558ed72dccb9bc' + ], + [ + '93144423ace3451ed29e0fb9ac2af211cb6e84a601df5993c419859fff5df04a', + '7c10dfb164c3425f5c71a3f9d7992038f1065224f72bb9d1d902a6d13037b47c' + ], + [ + 'b015f8044f5fcbdcf21ca26d6c34fb8197829205c7b7d2a7cb66418c157b112c', + 'ab8c1e086d04e813744a655b2df8d5f83b3cdc6faa3088c1d3aea1454e3a1d5f' + ], + [ + 'd5e9e1da649d97d89e4868117a465a3a4f8a18de57a140d36b3f2af341a21b52', + '4cb04437f391ed73111a13cc1d4dd0db1693465c2240480d8955e8592f27447a' + ], + [ + 'd3ae41047dd7ca065dbf8ed77b992439983005cd72e16d6f996a5316d36966bb', + 'bd1aeb21ad22ebb22a10f0303417c6d964f8cdd7df0aca614b10dc14d125ac46' + ], + [ + '463e2763d885f958fc66cdd22800f0a487197d0a82e377b49f80af87c897b065', + 'bfefacdb0e5d0fd7df3a311a94de062b26b80c61fbc97508b79992671ef7ca7f' + ], + [ + '7985fdfd127c0567c6f53ec1bb63ec3158e597c40bfe747c83cddfc910641917', + '603c12daf3d9862ef2b25fe1de289aed24ed291e0ec6708703a5bd567f32ed03' + ], + [ + '74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9', + 'cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08' + ], + [ + '30682a50703375f602d416664ba19b7fc9bab42c72747463a71d0896b22f6da3', + '553e04f6b018b4fa6c8f39e7f311d3176290d0e0f19ca73f17714d9977a22ff8' + ], + [ + '9e2158f0d7c0d5f26c3791efefa79597654e7a2b2464f52b1ee6c1347769ef57', + '712fcdd1b9053f09003a3481fa7762e9ffd7c8ef35a38509e2fbf2629008373' + ], + [ + '176e26989a43c9cfeba4029c202538c28172e566e3c4fce7322857f3be327d66', + 'ed8cc9d04b29eb877d270b4878dc43c19aefd31f4eee09ee7b47834c1fa4b1c3' + ], + [ + '75d46efea3771e6e68abb89a13ad747ecf1892393dfc4f1b7004788c50374da8', + '9852390a99507679fd0b86fd2b39a868d7efc22151346e1a3ca4726586a6bed8' + ], + [ + '809a20c67d64900ffb698c4c825f6d5f2310fb0451c869345b7319f645605721', + '9e994980d9917e22b76b061927fa04143d096ccc54963e6a5ebfa5f3f8e286c1' + ], + [ + '1b38903a43f7f114ed4500b4eac7083fdefece1cf29c63528d563446f972c180', + '4036edc931a60ae889353f77fd53de4a2708b26b6f5da72ad3394119daf408f9' + ] + ] + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/utils.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/utils.js new file mode 100644 index 000000000..d5811a56b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/elliptic/utils.js @@ -0,0 +1,214 @@ +'use strict'; + +var utils = exports; +var BN = require('../../../BN/bn'); + +utils.assert = function (condition, errorMessage) { + if (!condition) { + throw new Error(errorMessage) + } +}; + +// Represent num in a w-NAF form +function getNAF(num, w) { + var naf = []; + var ws = 1 << (w + 1); + var k = num.clone(); + while (k.cmpn(1) >= 0) { + var z; + if (k.isOdd()) { + var mod = k.andln(ws - 1); + if (mod > (ws >> 1) - 1) + z = (ws >> 1) - mod; + else + z = mod; + k.isubn(z); + } else { + z = 0; + } + naf.push(z); + + // Optimization, shift by word if possible + var shift = (k.cmpn(0) !== 0 && k.andln(ws - 1) === 0) ? (w + 1) : 1; + for (var i = 1; i < shift; i++) + naf.push(0); + k.iushrn(shift); + } + + return naf; +} +utils.getNAF = getNAF; + +// Represent k1, k2 in a Joint Sparse Form +function getJSF(k1, k2) { + var jsf = [ + [], + [] + ]; + + k1 = k1.clone(); + k2 = k2.clone(); + var d1 = 0; + var d2 = 0; + while (k1.cmpn(-d1) > 0 || k2.cmpn(-d2) > 0) { + + // First phase + var m14 = (k1.andln(3) + d1) & 3; + var m24 = (k2.andln(3) + d2) & 3; + if (m14 === 3) + m14 = -1; + if (m24 === 3) + m24 = -1; + var u1; + if ((m14 & 1) === 0) { + u1 = 0; + } else { + var m8 = (k1.andln(7) + d1) & 7; + if ((m8 === 3 || m8 === 5) && m24 === 2) + u1 = -m14; + else + u1 = m14; + } + jsf[0].push(u1); + + var u2; + if ((m24 & 1) === 0) { + u2 = 0; + } else { + var m8 = (k2.andln(7) + d2) & 7; + if ((m8 === 3 || m8 === 5) && m14 === 2) + u2 = -m24; + else + u2 = m24; + } + jsf[1].push(u2); + + // Second phase + if (2 * d1 === u1 + 1) + d1 = 1 - d1; + if (2 * d2 === u2 + 1) + d2 = 1 - d2; + k1.iushrn(1); + k2.iushrn(1); + } + + return jsf; +} +utils.getJSF = getJSF; + +function cachedProperty(obj, name, computer) { + var key = '_' + name; + obj.prototype[name] = function cachedProperty() { + return this[key] !== undefined ? this[key] : + this[key] = computer.call(this); + }; +} +utils.cachedProperty = cachedProperty; + +function parseBytes(bytes) { + return typeof bytes === 'string' ? utils.toArray(bytes, 'hex') : + bytes; +} +utils.parseBytes = parseBytes; + +function intFromLE(bytes) { + return new BN(bytes, 'hex', 'le'); +} +utils.intFromLE = intFromLE; + +// used to convert `CryptoJS` wordArrays into `crypto` hex buffers +function wordToByteArray(word, length) { + var ba = [], + xFF = 0xFF; + if (length > 0) + ba.push(word >>> 24); + if (length > 1) + ba.push((word >>> 16) & xFF); + if (length > 2) + ba.push((word >>> 8) & xFF); + if (length > 3) + ba.push(word & xFF); + + return ba; +} + +function wordArrayToBuffer(wordArray) { + let length = undefined; + if (wordArray.hasOwnProperty("sigBytes") && wordArray.hasOwnProperty("words")) { + length = wordArray.sigBytes; + wordArray = wordArray.words; + } else { + throw Error('Argument not a wordArray') + } + + const result = [] + let bytes = [] + let i = 0; + while (length > 0) { + bytes = wordToByteArray(wordArray[i], Math.min(4, length)); + length -= bytes.length; + result.push(bytes); + i++; + } + return [].concat.apply([], result) +} + +utils.wordArrayToBuffer = wordArrayToBuffer; + +// https://github.com/indutny/minimalistic-crypto-utils/blob/master/lib/utils.js +// moved here to remove the dep + +function toArray(msg, enc) { + if (Array.isArray(msg)) + return msg.slice(); + if (!msg) + return []; + var res = []; + if (typeof msg !== 'string') { + for (var i = 0; i < msg.length; i++) + res[i] = msg[i] | 0; + return res; + } + if (enc === 'hex') { + msg = msg.replace(/[^a-z0-9]+/ig, ''); + if (msg.length % 2 !== 0) + msg = '0' + msg; + for (var i = 0; i < msg.length; i += 2) + res.push(parseInt(msg[i] + msg[i + 1], 16)); + } else { + for (var i = 0; i < msg.length; i++) { + var c = msg.charCodeAt(i); + var hi = c >> 8; + var lo = c & 0xff; + if (hi) + res.push(hi, lo); + else + res.push(lo); + } + } + return res; +} +utils.toArray = toArray; + +function zero2(word) { + if (word.length === 1) + return '0' + word; + else + return word; +} +utils.zero2 = zero2; + +function toHex(msg) { + var res = ''; + for (var i = 0; i < msg.length; i++) + res += zero2(msg[i].toString(16)); + return res; +} +utils.toHex = toHex; + +utils.encode = function encode(arr, enc) { + if (enc === 'hex') + return toHex(arr); + else + return arr; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/hmac-drbg/hmac-drbg.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/hmac-drbg/hmac-drbg.js new file mode 100644 index 000000000..c0be3eaef --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/elliptic/lib/hmac-drbg/hmac-drbg.js @@ -0,0 +1,114 @@ +'use strict'; + +var CryptoJS = require('../../../crypto-js/crypto-js'); +var assert = require('../elliptic/utils').assert; +var utils = require ('../elliptic/utils') +var { byteArrayToWordArray } = require('../../../../base/functions/encode'); + +// some static stuff +const ONE = CryptoJS.enc.Utf8.parse ('\x01') +const ZERO = CryptoJS.enc.Utf8.parse ('\x00') + +function HmacDRBG(options) { + if (!(this instanceof HmacDRBG)) + return new HmacDRBG(options); + this.hash = options.hash + this.predResist = !!options.predResist; + + this.outLen = this.hash.slice (3, 6); // only support SHAXXX hashes + this.minEntropy = options.minEntropy || 192; + + this._reseed = null; + this.reseedInterval = null; + this.K = null; + this.V = null; + + var entropy = options.entropy + var nonce = options.nonce + var pers = [] + assert(entropy.length >= (this.minEntropy / 8), + 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); + this._init(entropy, nonce, pers); +} +module.exports = HmacDRBG; + +HmacDRBG.prototype._init = function init(entropy, nonce, pers) { + var seed = entropy.concat(nonce).concat(pers); + + this.K = new CryptoJS.lib.WordArray.init () + this.V = new CryptoJS.lib.WordArray.init () + const magicNumber = (1 << 24) + (1 << 16) + (1 << 8) + 1 + this.V.words = Array (this.outLen / 32).fill (magicNumber) + this.V.sigBytes = 32 + this._update(seed); + this._reseed = 1; + this.reseedInterval = 0x1000000000000; // 2^48 +}; + +HmacDRBG.prototype._hmac = function hmac() { + return new CryptoJS.lib.WordArray.init (); +}; + +HmacDRBG.prototype._update = function update(seed) { + var kmac = this._hmac () + kmac.concat (this.V) + kmac.concat (ZERO) + if (seed) + kmac.concat (byteArrayToWordArray (seed)); + this.K = CryptoJS['Hmac' + this.hash] (kmac, this.K) + this.V = CryptoJS['Hmac' + this.hash] (this.V, this.K) + if (!seed) + return; + + kmac = this._hmac () + kmac.concat (this.V) + kmac.concat (ONE) + kmac.concat (byteArrayToWordArray (seed)) + this.K = CryptoJS['Hmac' + this.hash] (kmac, this.K) + this.V = CryptoJS['Hmac' + this.hash] (this.V, this.K) +}; + +HmacDRBG.prototype.reseed = function reseed(entropy, entropyEnc, add, addEnc) { + // Optional entropy enc + if (typeof entropyEnc !== 'string') { + addEnc = add; + add = entropyEnc; + entropyEnc = null; + } + + //entropy = utils.toArray(entropy, entropyEnc); + //add = utils.toArray(add, addEnc); + + assert(entropy.length >= (this.minEntropy / 8), + 'Not enough entropy. Minimum is: ' + this.minEntropy + ' bits'); + + this._update(entropy.concat(add || [])); + this._reseed = 1; +}; + +HmacDRBG.prototype.generate = function generate(len, enc, add, addEnc) { + if (this._reseed > this.reseedInterval) + throw new Error ('Reseed is required'); + + // Optional encoding + if (typeof enc !== 'string') { + addEnc = add; + add = enc; + enc = null; + } + + // Optional additional data + if (add) { + add = CryptoJS.enc.Utf8.parse (add); + this._update (add); + } + + var res = new CryptoJS.lib.WordArray.init (); + while (res.sigBytes < len) { + this.V = CryptoJS['Hmac' + this.hash] (this.V, this.K) + res.concat (this.V); + } + this._update (add); + this._reseed++; + return utils.wordArrayToBuffer(res); +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/fetch-ponyfill/fetch-node.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/fetch-ponyfill/fetch-node.js new file mode 100644 index 000000000..3755285c1 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/fetch-ponyfill/fetch-node.js @@ -0,0 +1,39 @@ +'use strict'; + +var fetch = require('../node-fetch/index'); + +function wrapFetchForNode(fetch) { + // Support schemaless URIs on the server for parity with the browser. + // https://github.com/matthew-andrews/isomorphic-fetch/pull/10 + const f = function (u, options) { + if (typeof u === 'string' && u.slice(0, 2) === '//') { + return fetch('https:' + u, options); + } + + return fetch(u, options); + }; + // Igor Kroitor 2019 Dec 30 + // expose http and https + // https://github.com/ccxt/ccxt/issues/6327 + f.http = fetch.http; + f.https = fetch.https; + return f; +} + +module.exports = function (context) { + // This modifies the global `node-fetch` object, which isn't great, since + // different callers to `fetch-ponyfill` which pass a different Promise + // implementation would each expect to have their implementation used. But, + // given the way `node-fetch` is implemented, this is the only way to make + // it work at all. + if (context && context.Promise) { + fetch.Promise = context.Promise; + } + + return { + fetch: wrapFetchForNode(fetch), + Headers: fetch.Headers, + Request: fetch.Request, + Response: fetch.Response + }; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-fetch/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-fetch/index.js new file mode 100644 index 000000000..8fcbabfdd --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-fetch/index.js @@ -0,0 +1,1564 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js +// (MIT licensed) + +const BUFFER = Symbol('buffer'); +const TYPE = Symbol('type'); + +class Blob { + constructor() { + this[TYPE] = ''; + + const blobParts = arguments[0]; + const options = arguments[1]; + + const buffers = []; + + if (blobParts) { + const a = blobParts; + const length = Number(a.length); + for (let i = 0; i < length; i++) { + const element = a[i]; + let buffer; + if (element instanceof Buffer) { + buffer = element; + } else if (ArrayBuffer.isView(element)) { + buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); + } else if (element instanceof ArrayBuffer) { + buffer = Buffer.from(element); + } else if (element instanceof Blob) { + buffer = element[BUFFER]; + } else { + buffer = Buffer.from(typeof element === 'string' ? element : String(element)); + } + buffers.push(buffer); + } + } + + this[BUFFER] = Buffer.concat(buffers); + + let type = options && options.type !== undefined && String(options.type).toLowerCase(); + if (type && !/[^\u0020-\u007E]/.test(type)) { + this[TYPE] = type; + } + } + get size() { + return this[BUFFER].length; + } + get type() { + return this[TYPE]; + } + slice() { + const size = this.size; + + const start = arguments[0]; + const end = arguments[1]; + let relativeStart, relativeEnd; + if (start === undefined) { + relativeStart = 0; + } else if (start < 0) { + relativeStart = Math.max(size + start, 0); + } else { + relativeStart = Math.min(start, size); + } + if (end === undefined) { + relativeEnd = size; + } else if (end < 0) { + relativeEnd = Math.max(size + end, 0); + } else { + relativeEnd = Math.min(end, size); + } + const span = Math.max(relativeEnd - relativeStart, 0); + + const buffer = this[BUFFER]; + const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); + const blob = new Blob([], { type: arguments[2] }); + blob[BUFFER] = slicedBuffer; + return blob; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(Blob.prototype, Symbol.toStringTag, { + value: 'Blob', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * fetch-error.js + * + * FetchError interface for operational errors + */ + +/** + * Create FetchError instance + * + * @param String message Error message for human + * @param String type Error type for machine + * @param String systemError For Node.js system error + * @return FetchError + */ +function FetchError(message, type, systemError) { + Error.call(this, message); + + this.message = message; + this.type = type; + + // when err.type is `system`, err.code contains system error code + if (systemError) { + this.code = this.errno = systemError.code; + } + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +FetchError.prototype = Object.create(Error.prototype); +FetchError.prototype.constructor = FetchError; +FetchError.prototype.name = 'FetchError'; + +/** + * body.js + * + * Body interface provides common methods for Request and Response + */ + +const Stream = require('stream'); + +var _require = require('stream'); + +const PassThrough = _require.PassThrough; + +/* +let convert; +try { + convert = require('encoding').convert; +} catch (e) {} +*/ + +const INTERNALS = Symbol('Body internals'); + +/** + * Body mixin + * + * Ref: https://fetch.spec.whatwg.org/#body + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$size = _ref.size; + + let size = _ref$size === undefined ? 0 : _ref$size; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; + + if (body == null) { + // body is undefined or null + body = null; + } else if (typeof body === 'string') { + // body is string + } else if (isURLSearchParams(body)) { + // body is a URLSearchParams + } else if (body instanceof Blob) { + // body is blob + } else if (Buffer.isBuffer(body)) { + // body is buffer + } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { + // body is array buffer + } else if (body instanceof Stream) { + // body is stream + } else { + // none of the above + // coerce to string + body = String(body); + } + this[INTERNALS] = { + body, + disturbed: false, + error: null + }; + this.size = size; + this.timeout = timeout; + + if (body instanceof Stream) { + body.on('error', function (err) { + _this[INTERNALS].error = new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); + }); + } +} + +Body.prototype = { + get body() { + return this[INTERNALS].body; + }, + + get bodyUsed() { + return this[INTERNALS].disturbed; + }, + + /** + * Decode response as ArrayBuffer + * + * @return Promise + */ + arrayBuffer() { + return consumeBody.call(this).then(function (buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + }); + }, + + /** + * Return raw response as Blob + * + * @return Promise + */ + blob() { + let ct = this.headers && this.headers.get('content-type') || ''; + return consumeBody.call(this).then(function (buf) { + return Object.assign( + // Prevent copying + new Blob([], { + type: ct.toLowerCase() + }), { + [BUFFER]: buf + }); + }); + }, + + /** + * Decode response as json + * + * @return Promise + */ + json() { + var _this2 = this; + + return consumeBody.call(this).then(function (buffer) { + try { + return JSON.parse(buffer.toString()); + } catch (err) { + return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); + } + }); + }, + + /** + * Decode response as text + * + * @return Promise + */ + text() { + return consumeBody.call(this).then(function (buffer) { + return buffer.toString(); + }); + }, + + /** + * Decode response as buffer (non-spec api) + * + * @return Promise + */ + buffer() { + return consumeBody.call(this); + }, + + /** + * Decode response as text, while automatically detecting the encoding and + * trying to decode to UTF-8 (non-spec api) + * + * @return Promise + */ + textConverted() { + var _this3 = this; + + return consumeBody.call(this).then(function (buffer) { + return convertBody(buffer, _this3.headers); + }); + } + +}; + +// In browsers, all properties are enumerable. +Object.defineProperties(Body.prototype, { + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true } +}); + +Body.mixIn = function (proto) { + for (const name of Object.getOwnPropertyNames(Body.prototype)) { + // istanbul ignore else: future proof + if (!(name in proto)) { + const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); + Object.defineProperty(proto, name, desc); + } + } +}; + +/** + * Consume and convert an entire Body to a Buffer. + * + * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body + * + * @return Promise + */ +function consumeBody() { + var _this4 = this; + + if (this[INTERNALS].disturbed) { + return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); + } + + this[INTERNALS].disturbed = true; + + if (this[INTERNALS].error) { + return Body.Promise.reject(this[INTERNALS].error); + } + + // body is null + if (this.body === null) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is string + if (typeof this.body === 'string') { + return Body.Promise.resolve(Buffer.from(this.body)); + } + + // body is blob + if (this.body instanceof Blob) { + return Body.Promise.resolve(this.body[BUFFER]); + } + + // body is buffer + if (Buffer.isBuffer(this.body)) { + return Body.Promise.resolve(this.body); + } + + // body is buffer + if (Object.prototype.toString.call(this.body) === '[object ArrayBuffer]') { + return Body.Promise.resolve(Buffer.from(this.body)); + } + + // istanbul ignore if: should never happen + if (!(this.body instanceof Stream)) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is stream + // get ready to actually consume the body + let accum = []; + let accumBytes = 0; + let abort = false; + + return new Body.Promise(function (resolve, reject) { + let resTimeout; + + // allow timeout on slow response body + if (_this4.timeout) { + resTimeout = setTimeout(function () { + abort = true; + reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); + }, _this4.timeout); + } + + // handle stream error, such as incorrect content-encoding + _this4.body.on('error', function (err) { + reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); + }); + + _this4.body.on('data', function (chunk) { + if (abort || chunk === null) { + return; + } + + if (_this4.size && accumBytes + chunk.length > _this4.size) { + abort = true; + reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); + return; + } + + accumBytes += chunk.length; + accum.push(chunk); + }); + + _this4.body.on('end', function () { + if (abort) { + return; + } + + clearTimeout(resTimeout); + + try { + resolve(Buffer.concat(accum)); + } catch (err) { + // handle streams that have accumulated too much data (issue #414) + reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + }); +} + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param Buffer buffer Incoming buffer + * @param String encoding Target encoding + * @return String + */ +function convertBody(buffer, headers) { + if (typeof convert !== 'function') { + throw new Error('The package `encoding` must be installed to use the textConverted() function'); + } + + const ct = headers.get('content-type'); + let charset = 'utf-8'; + let res, str; + + // header + if (ct) { + res = /charset=([^;]*)/i.exec(ct); + } + + // no charset in content type, peek at response body for at most 1024 bytes + str = buffer.slice(0, 1024).toString(); + + // html5 + if (!res && str) { + res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined; + + this[MAP] = Object.create(null); + + if (init instanceof Headers) { + const rawHeaders = init.raw(); + const headerNames = Object.keys(rawHeaders); + + for (const headerName of headerNames) { + for (const value of rawHeaders[headerName]) { + this.append(headerName, value); + } + } + + return; + } + + // We don't worry about converting prop to ByteString here as append() + // will handle it. + if (init == null) { + // no op + } else if (typeof init === 'object') { + const method = init[Symbol.iterator]; + if (method != null) { + if (typeof method !== 'function') { + throw new TypeError('Header pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError('Each header pair must be iterable'); + } + pairs.push(Array.from(pair)); + } + + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError('Each header pair must be a name/value tuple'); + } + this.append(pair[0], pair[1]); + } + } else { + // record + for (const key of Object.keys(init)) { + const value = init[key]; + this.append(key, value); + } + } + } else { + throw new TypeError('Provided initializer must be an object'); + } + } + + /** + * Return combined header value given name + * + * @param String name Header name + * @return Mixed + */ + get(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key === undefined) { + return null; + } + + return this[MAP][key].join(', '); + } + + /** + * Iterate over all headers + * + * @param Function callback Executed for each item with parameters (value, name, thisArg) + * @param Boolean thisArg `this` context for callback function + * @return Void + */ + forEach(callback) { + let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + + let pairs = getHeaders(this); + let i = 0; + while (i < pairs.length) { + var _pairs$i = pairs[i]; + const name = _pairs$i[0], + value = _pairs$i[1]; + + callback.call(thisArg, value, name, this); + pairs = getHeaders(this); + i++; + } + } + + /** + * Overwrite header values given name + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + set(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + this[MAP][key !== undefined ? key : name] = [value]; + } + + /** + * Append a value onto existing header + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + append(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + if (key !== undefined) { + this[MAP][key].push(value); + } else { + this[MAP][name] = [value]; + } + } + + /** + * Check for header name existence + * + * @param String name Header name + * @return Boolean + */ + has(name) { + name = `${name}`; + validateName(name); + return find(this[MAP], name) !== undefined; + } + + /** + * Delete all header values given name + * + * @param String name Header name + * @return Void + */ + delete(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key !== undefined) { + delete this[MAP][key]; + } + } + + /** + * Return raw headers (non-spec api) + * + * @return Object + */ + raw() { + return this[MAP]; + } + + /** + * Get an iterator on keys. + * + * @return Iterator + */ + keys() { + return createHeadersIterator(this, 'key'); + } + + /** + * Get an iterator on values. + * + * @return Iterator + */ + values() { + return createHeadersIterator(this, 'value'); + } + + /** + * Get an iterator on entries. + * + * This is the default iterator of the Headers object. + * + * @return Iterator + */ + [Symbol.iterator]() { + return createHeadersIterator(this, 'key+value'); + } +} +Headers.prototype.entries = Headers.prototype[Symbol.iterator]; + +Object.defineProperty(Headers.prototype, Symbol.toStringTag, { + value: 'Headers', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Headers.prototype, { + get: { enumerable: true }, + forEach: { enumerable: true }, + set: { enumerable: true }, + append: { enumerable: true }, + has: { enumerable: true }, + delete: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true } +}); + +function getHeaders(headers) { + let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; + + const keys = Object.keys(headers[MAP]).sort(); + return keys.map(kind === 'key' ? function (k) { + return k.toLowerCase(); + } : kind === 'value' ? function (k) { + return headers[MAP][k].join(', '); + } : function (k) { + return [k.toLowerCase(), headers[MAP][k].join(', ')]; + }); +} + +const INTERNAL = Symbol('internal'); + +function createHeadersIterator(target, kind) { + const iterator = Object.create(HeadersIteratorPrototype); + iterator[INTERNAL] = { + target, + kind, + index: 0 + }; + return iterator; +} + +const HeadersIteratorPrototype = Object.setPrototypeOf({ + next() { + // istanbul ignore if + if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { + throw new TypeError('Value of `this` is not a HeadersIterator'); + } + + var _INTERNAL = this[INTERNAL]; + const target = _INTERNAL.target, + kind = _INTERNAL.kind, + index = _INTERNAL.index; + + const values = getHeaders(target, kind); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + this[INTERNAL].index = index + 1; + + return { + value: values[index], + done: false + }; + } +}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); + +Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { + value: 'HeadersIterator', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * Export the Headers object in a form that Node.js can consume. + * + * @param Headers headers + * @return Object + */ +function exportNodeCompatibleHeaders(headers) { + const obj = Object.assign({ __proto__: null }, headers[MAP]); + + // http.request() only supports string as Host header. This hack makes + // specifying custom Host header possible. + const hostHeaderKey = find(headers[MAP], 'Host'); + if (hostHeaderKey !== undefined) { + obj[hostHeaderKey] = obj[hostHeaderKey][0]; + } + + return obj; +} + +/** + * Create a Headers object from an object of headers, ignoring those that do + * not conform to HTTP grammar productions. + * + * @param Object obj Object of headers + * @return Headers + */ +function createHeadersLenient(obj) { + const headers = new Headers(); + for (const name of Object.keys(obj)) { + if (invalidTokenRegex.test(name)) { + continue; + } + if (Array.isArray(obj[name])) { + for (const val of obj[name]) { + if (invalidHeaderCharRegex.test(val)) { + continue; + } + if (headers[MAP][name] === undefined) { + headers[MAP][name] = [val]; + } else { + headers[MAP][name].push(val); + } + } + } else if (!invalidHeaderCharRegex.test(obj[name])) { + headers[MAP][name] = [obj[name]]; + } + } + return headers; +} + +/** + * response.js + * + * Response class provides content decoding + */ + +var _require$1 = require('http'); + +const STATUS_CODES = _require$1.STATUS_CODES; + + +const INTERNALS$1 = Symbol('Response internals'); + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +class Response { + constructor() { + let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + Body.call(this, body, opts); + + const status = opts.status || 200; + + this[INTERNALS$1] = { + url: opts.url, + status, + statusText: opts.statusText || STATUS_CODES[status], + headers: new Headers(opts.headers) + }; + } + + get url() { + return this[INTERNALS$1].url; + } + + get status() { + return this[INTERNALS$1].status; + } + + /** + * Convenience property representing if the request ended normally + */ + get ok() { + return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; + } + + get statusText() { + return this[INTERNALS$1].statusText; + } + + get headers() { + return this[INTERNALS$1].headers; + } + + /** + * Clone this response + * + * @return Response + */ + clone() { + return new Response(clone(this), { + url: this.url, + status: this.status, + statusText: this.statusText, + headers: this.headers, + ok: this.ok + }); + } +} + +Body.mixIn(Response.prototype); + +Object.defineProperties(Response.prototype, { + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true } +}); + +Object.defineProperty(Response.prototype, Symbol.toStringTag, { + value: 'Response', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * request.js + * + * Request class contains server only options + * + * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/. + */ + +var _require$2 = require('url'); + +const format_url = _require$2.format; +const parse_url = _require$2.parse; + + +const INTERNALS$2 = Symbol('Request internals'); + +/** + * Check if a value is an instance of Request. + * + * @param Mixed input + * @return Boolean + */ +function isRequest(input) { + return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; +} + +/** + * Request class + * + * @param Mixed input Url or Request instance + * @param Object init Custom options + * @return Void + */ +class Request { + constructor(input) { + let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + let parsedURL; + + // normalize input + if (!isRequest(input)) { + if (input && input.href) { + // in order to support Node.js' Url objects; though WHATWG's URL objects + // will fall into this branch also (since their `toString()` will return + // `href` property anyway) + parsedURL = parse_url(input.href); + } else { + // coerce input to a string before attempting to parse + parsedURL = parse_url(`${input}`); + } + input = {}; + } else { + parsedURL = parse_url(input.url); + } + + let method = init.method || input.method || 'GET'; + method = method.toUpperCase(); + + if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { + throw new TypeError('Request with GET/HEAD method cannot have body'); + } + + let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; + + Body.call(this, inputBody, { + timeout: init.timeout || input.timeout || 0, + size: init.size || input.size || 0 + }); + + const headers = new Headers(init.headers || input.headers || {}); + + if (init.body != null) { + const contentType = extractContentType(this); + if (contentType !== null && !headers.has('Content-Type')) { + headers.append('Content-Type', contentType); + } + } + + this[INTERNALS$2] = { + method, + redirect: init.redirect || input.redirect || 'follow', + headers, + parsedURL + }; + + // node-fetch-only options + this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; + this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; + this.counter = init.counter || input.counter || 0; + this.agent = init.agent || input.agent; + } + + get method() { + return this[INTERNALS$2].method; + } + + get url() { + return format_url(this[INTERNALS$2].parsedURL); + } + + get headers() { + return this[INTERNALS$2].headers; + } + + get redirect() { + return this[INTERNALS$2].redirect; + } + + /** + * Clone this request + * + * @return Request + */ + clone() { + return new Request(this); + } +} + +Body.mixIn(Request.prototype); + +Object.defineProperty(Request.prototype, Symbol.toStringTag, { + value: 'Request', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Request.prototype, { + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true } +}); + +/** + * Convert a Request to Node.js http request options. + * + * @param Request A Request instance + * @return Object The options object to be passed to http.request + */ +function getNodeRequestOptions(request) { + const parsedURL = request[INTERNALS$2].parsedURL; + const headers = new Headers(request[INTERNALS$2].headers); + + // fetch step 1.3 + if (!headers.has('Accept')) { + headers.set('Accept', '*/*'); + } + + // Basic fetch + if (!parsedURL.protocol || !parsedURL.hostname) { + throw new TypeError('Only absolute URLs are supported'); + } + + if (!/^https?:$/.test(parsedURL.protocol)) { + throw new TypeError('Only HTTP(S) protocols are supported'); + } + + // HTTP-network-or-cache fetch steps 2.4-2.7 + let contentLengthValue = null; + if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { + contentLengthValue = '0'; + } + if (request.body != null) { + const totalBytes = getTotalBytes(request); + if (typeof totalBytes === 'number') { + contentLengthValue = String(totalBytes); + } + } + if (contentLengthValue) { + headers.set('Content-Length', contentLengthValue); + } + + // HTTP-network-or-cache fetch step 2.11 + if (!headers.has('User-Agent')) { + headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); + } + + // HTTP-network-or-cache fetch step 2.15 + if (request.compress) { + headers.set('Accept-Encoding', 'gzip,deflate'); + } + if (!headers.has('Connection') && !request.agent) { + headers.set('Connection', 'close'); + } + + // HTTP-network fetch step 4.2 + // chunked encoding is handled by Node.js + + return Object.assign({}, parsedURL, { + method: request.method, + headers: exportNodeCompatibleHeaders(headers), + agent: request.agent + }); +} + +/** + * index.js + * + * a request API compatible with window.fetch + * + * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/. + */ + +const http = require('http'); +const https = require('https'); + +var _require$3 = require('stream'); + +const PassThrough$1 = _require$3.PassThrough; + +var _require2 = require('url'); + +const resolve_url = _require2.resolve; + +const zlib = require('zlib'); + +/** + * Fetch function + * + * @param Mixed url Absolute url or Request instance + * @param Object opts Fetch options + * @return Promise + */ +function fetch(url, opts) { + + // allow custom promise + if (!fetch.Promise) { + throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); + } + + Body.Promise = fetch.Promise; + + // wrap http.request into fetch + return new fetch.Promise(function (resolve, reject) { + // build request object + const request = new Request(url, opts); + const options = getNodeRequestOptions(request); + + const send = (options.protocol === 'https:' ? https : http).request; + + // send request + const req = send(options); + let reqTimeout; + + function finalize() { + req.abort(); + clearTimeout(reqTimeout); + } + + if (request.timeout) { + req.once('socket', function (socket) { + reqTimeout = setTimeout(function () { + reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); + finalize(); + }, request.timeout); + }); + } + + req.on('error', function (err) { + reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + finalize(); + }); + + req.on('response', function (res) { + clearTimeout(reqTimeout); + + const headers = createHeadersLenient(res.headers); + + // HTTP fetch step 5 + if (fetch.isRedirect(res.statusCode)) { + // HTTP fetch step 5.2 + const location = headers.get('Location'); + + // HTTP fetch step 5.3 + const locationURL = location === null ? null : resolve_url(request.url, location); + + // HTTP fetch step 5.5 + switch (request.redirect) { + case 'error': + reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); + finalize(); + return; + case 'manual': + // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + headers.set('Location', locationURL); + } + break; + case 'follow': + // HTTP-redirect fetch step 2 + if (locationURL === null) { + break; + } + + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body + }; + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { + reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { + requestOpts.method = 'GET'; + requestOpts.body = undefined; + requestOpts.headers.delete('content-length'); + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))); + finalize(); + return; + } + } + + // prepare response + let body = res.pipe(new PassThrough$1()); + const response_options = { + url: request.url, + status: res.statusCode, + statusText: res.statusMessage, + headers: headers, + size: request.size, + timeout: request.timeout + }; + + // HTTP-network fetch step 12.1.1.3 + const codings = headers.get('Content-Encoding'); + + // HTTP-network fetch step 12.1.1.4: handle content codings + + // in following scenarios we ignore compression support + // 1. compression support is disabled + // 2. HEAD request + // 3. no Content-Encoding header + // 4. no content response (204) + // 5. content not modified response (304) + if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { + resolve(new Response(body, response_options)); + return; + } + + // For Node v6+ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + const zlibOptions = { + flush: zlib.Z_SYNC_FLUSH, + finishFlush: zlib.Z_SYNC_FLUSH + }; + + // for gzip + if (codings == 'gzip' || codings == 'x-gzip') { + body = body.pipe(zlib.createGunzip(zlibOptions)); + resolve(new Response(body, response_options)); + return; + } + + // for deflate + if (codings == 'deflate' || codings == 'x-deflate') { + // handle the infamous raw deflate response from old servers + // a hack for old IIS and Apache servers + const raw = res.pipe(new PassThrough$1()); + raw.once('data', function (chunk) { + // see http://stackoverflow.com/questions/37519828 + if ((chunk[0] & 0x0F) === 0x08) { + body = body.pipe(zlib.createInflate()); + } else { + body = body.pipe(zlib.createInflateRaw()); + } + resolve(new Response(body, response_options)); + }); + return; + } + + // otherwise, use response as-is + resolve(new Response(body, response_options)); + }); + + writeToStream(req, request); + }); +} + +/** + * Redirect code matching + * + * @param Number code Status code + * @return Boolean + */ +fetch.isRedirect = function (code) { + return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; +}; + +// Needed for TypeScript. +fetch.default = fetch; + +// expose Promise +fetch.Promise = global.Promise; + +// Igor Kroitor 2019 Dec 30 +// expose http and https +// https://github.com/ccxt/ccxt/issues/6327 +fetch.http = http +fetch.https = https + +module.exports = exports = fetch; +exports.Headers = Headers; +exports.Request = Request; +exports.Response = Response; +exports.FetchError = FetchError; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/NodeRSA.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/NodeRSA.js new file mode 100644 index 000000000..d03983525 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/NodeRSA.js @@ -0,0 +1,223 @@ +/*! + * RSA library for Node.js + * + * Author: rzcoder + * License MIT + */ + +var rsa = require('./libs/rsa.js'); +var _ = require('./utils')._; +var schemes = require('./schemes/schemes.js'); +var formats = require('./formats/formats.js'); + +module.exports = (function () { + var SUPPORTED_HASH_ALGORITHMS = { + browser: ['md5', 'ripemd160', 'sha1', 'sha256', 'sha512'] + }; + + var DEFAULT_ENCRYPTION_SCHEME = 'pkcs1'; + var DEFAULT_SIGNING_SCHEME = 'pkcs1'; + + var EXPORT_FORMAT_ALIASES = { + 'private': 'pkcs1-private-pem', + 'private-der': 'pkcs1-private-der', + 'public': 'pkcs8-public-pem', + 'public-der': 'pkcs8-public-der', + }; + + /** + * @param key {string|buffer|object} Key in PEM format, or data for generate key {b: bits, e: exponent} + * @constructor + */ + function NodeRSA(key, format, options) { + if (!(this instanceof NodeRSA)) { + return new NodeRSA(key, format, options); + } + + if (_.isObject(format)) { + options = format; + format = undefined; + } + + this.$options = { + signingScheme: DEFAULT_SIGNING_SCHEME, + signingSchemeOptions: { + hash: 'sha256', + saltLength: null + }, + encryptionScheme: DEFAULT_ENCRYPTION_SCHEME, + encryptionSchemeOptions: { + hash: 'sha1', + label: null + }, + environment: 'browser', + rsaUtils: this + }; + this.keyPair = new rsa.Key(); + this.$cache = {}; + + if (Buffer.isBuffer(key) || _.isString(key)) { + this.importKey(key, format); + } else if (_.isObject(key)) { + this.generateKeyPair(key.b, key.e); + } + + this.setOptions(options); + } + + /** + * Set and validate options for key instance + * @param options + */ + NodeRSA.prototype.setOptions = function (options) { + options = options || {}; + if (options.environment) { + this.$options.environment = options.environment; + } + + if (options.signingScheme) { + if (_.isString(options.signingScheme)) { + var signingScheme = options.signingScheme.toLowerCase().split('-'); + if (signingScheme.length == 1) { + if (SUPPORTED_HASH_ALGORITHMS.node.indexOf(signingScheme[0]) > -1) { + this.$options.signingSchemeOptions = { + hash: signingScheme[0] + }; + this.$options.signingScheme = DEFAULT_SIGNING_SCHEME; + } else { + this.$options.signingScheme = signingScheme[0]; + this.$options.signingSchemeOptions = { + hash: null + }; + } + } else { + this.$options.signingSchemeOptions = { + hash: signingScheme[1] + }; + this.$options.signingScheme = signingScheme[0]; + } + } else if (_.isObject(options.signingScheme)) { + this.$options.signingScheme = options.signingScheme.scheme || DEFAULT_SIGNING_SCHEME; + this.$options.signingSchemeOptions = _.omit(options.signingScheme, 'scheme'); + } + + if (!schemes.isSignature(this.$options.signingScheme)) { + throw Error('Unsupported signing scheme'); + } + + if (this.$options.signingSchemeOptions.hash && + SUPPORTED_HASH_ALGORITHMS[this.$options.environment].indexOf(this.$options.signingSchemeOptions.hash) === -1) { + throw Error('Unsupported hashing algorithm for ' + this.$options.environment + ' environment'); + } + } + + if (options.encryptionScheme) { + if (_.isString(options.encryptionScheme)) { + this.$options.encryptionScheme = options.encryptionScheme.toLowerCase(); + this.$options.encryptionSchemeOptions = {}; + } else if (_.isObject(options.encryptionScheme)) { + this.$options.encryptionScheme = options.encryptionScheme.scheme || DEFAULT_ENCRYPTION_SCHEME; + this.$options.encryptionSchemeOptions = _.omit(options.encryptionScheme, 'scheme'); + } + + if (!schemes.isEncryption(this.$options.encryptionScheme)) { + throw Error('Unsupported encryption scheme'); + } + + if (this.$options.encryptionSchemeOptions.hash && + SUPPORTED_HASH_ALGORITHMS[this.$options.environment].indexOf(this.$options.encryptionSchemeOptions.hash) === -1) { + throw Error('Unsupported hashing algorithm for ' + this.$options.environment + ' environment'); + } + } + + this.keyPair.setOptions(this.$options); + }; + + /** + * Importing key + * @param keyData {string|buffer|Object} + * @param format {string} + */ + NodeRSA.prototype.importKey = function (keyData, format) { + if (!keyData) { + throw Error("Empty key given"); + } + + if (format) { + format = EXPORT_FORMAT_ALIASES[format] || format; + } + + if (!formats.detectAndImport(this.keyPair, keyData, format) && format === undefined) { + throw Error("Key format must be specified"); + } + + this.$cache = {}; + + return this; + }; + + /** + * Check if key pair contains private key + */ + NodeRSA.prototype.isPrivate = function () { + return this.keyPair.isPrivate(); + }; + + /** + * Check if key pair contains public key + * @param [strict] {boolean} - public key only, return false if have private exponent + */ + NodeRSA.prototype.isPublic = function (strict) { + return this.keyPair.isPublic(strict); + }; + + /** + * Check if key pair doesn't contains any data + */ + NodeRSA.prototype.isEmpty = function (strict) { + return !(this.keyPair.n || this.keyPair.e || this.keyPair.d); + }; + + /** + * Signing data + * + * @param buffer {string|number|object|array|Buffer} - data for signing. Object and array will convert to JSON string. + * @param encoding {string} - optional. Encoding for output result, may be 'buffer', 'binary', 'hex' or 'base64'. Default 'buffer'. + * @param source_encoding {string} - optional. Encoding for given string. Default utf8. + * @returns {string|Buffer} + */ + NodeRSA.prototype.sign = function (buffer, encoding, source_encoding) { + if (!this.isPrivate()) { + throw Error("This is not private key"); + } + + var res = this.keyPair.sign(this.$getDataForEncrypt(buffer, source_encoding)); + + if (encoding && encoding != 'buffer') { + res = res.toString(encoding); + } + + return res; + }; + + /** + * Preparing given data for encrypting/signing. Just make new/return Buffer object. + * + * @param buffer {string|number|object|array|Buffer} - data for encrypting. Object and array will convert to JSON string. + * @param encoding {string} - optional. Encoding for given string. Default utf8. + * @returns {Buffer} + */ + NodeRSA.prototype.$getDataForEncrypt = function (buffer, encoding) { + if (_.isString(buffer) || _.isNumber(buffer)) { + return Buffer.from('' + buffer, encoding || 'utf8'); + } else if (Buffer.isBuffer(buffer)) { + return buffer; + } else if (_.isObject(buffer)) { + return Buffer.from(JSON.stringify(buffer)); + } else { + throw Error("Unexpected data type"); + } + }; + + return NodeRSA; +})(); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/errors.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/errors.js new file mode 100644 index 000000000..4557b8aea --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/errors.js @@ -0,0 +1,13 @@ +// Copyright 2011 Mark Cavage All rights reserved. + + +module.exports = { + + newInvalidAsn1Error: function (msg) { + var e = new Error(); + e.name = 'InvalidAsn1Error'; + e.message = msg || ''; + return e; + } + +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/index.js new file mode 100644 index 000000000..f6000d9f3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/index.js @@ -0,0 +1,21 @@ +// Copyright 2011 Mark Cavage All rights reserved. + +var errors = require('./errors'); +var types = require('./types'); + +var Reader = require('./reader'); + +// --- Exports + +module.exports = { + Reader: Reader, +}; + +for (var t in types) { + if (types.hasOwnProperty(t)) + module.exports[t] = types[t]; +} +for (var e in errors) { + if (errors.hasOwnProperty(e)) + module.exports[e] = errors[e]; +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/reader.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/reader.js new file mode 100644 index 000000000..b1d0cb6d5 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/reader.js @@ -0,0 +1,262 @@ +// Copyright 2011 Mark Cavage All rights reserved. + +var assert = require('assert'); +// var Buffer = require('safer-buffer').Buffer; + +var ASN1 = require('./types'); +var errors = require('./errors'); + + +// --- Globals + +var newInvalidAsn1Error = errors.newInvalidAsn1Error; + + + +// --- API + +function Reader(data) { + if (!data || !Buffer.isBuffer(data)) + throw new TypeError('data must be a node Buffer'); + + this._buf = data; + this._size = data.length; + + // These hold the "current" state + this._len = 0; + this._offset = 0; +} + +Object.defineProperty(Reader.prototype, 'length', { + enumerable: true, + get: function () { return (this._len); } +}); + +Object.defineProperty(Reader.prototype, 'offset', { + enumerable: true, + get: function () { return (this._offset); } +}); + +Object.defineProperty(Reader.prototype, 'remain', { + get: function () { return (this._size - this._offset); } +}); + +Object.defineProperty(Reader.prototype, 'buffer', { + get: function () { return (this._buf.slice(this._offset)); } +}); + + +/** + * Reads a single byte and advances offset; you can pass in `true` to make this + * a "peek" operation (i.e., get the byte, but don't advance the offset). + * + * @param {Boolean} peek true means don't move offset. + * @return {Number} the next byte, null if not enough data. + */ +Reader.prototype.readByte = function (peek) { + if (this._size - this._offset < 1) + return null; + + var b = this._buf[this._offset] & 0xff; + + if (!peek) + this._offset += 1; + + return b; +}; + + +Reader.prototype.peek = function () { + return this.readByte(true); +}; + + +/** + * Reads a (potentially) variable length off the BER buffer. This call is + * not really meant to be called directly, as callers have to manipulate + * the internal buffer afterwards. + * + * As a result of this call, you can call `Reader.length`, until the + * next thing called that does a readLength. + * + * @return {Number} the amount of offset to advance the buffer. + * @throws {InvalidAsn1Error} on bad ASN.1 + */ +Reader.prototype.readLength = function (offset) { + if (offset === undefined) + offset = this._offset; + + if (offset >= this._size) + return null; + + var lenB = this._buf[offset++] & 0xff; + if (lenB === null) + return null; + + if ((lenB & 0x80) === 0x80) { + lenB &= 0x7f; + + if (lenB === 0) + throw newInvalidAsn1Error('Indefinite length not supported'); + + if (lenB > 4) + throw newInvalidAsn1Error('encoding too long'); + + if (this._size - offset < lenB) + return null; + + this._len = 0; + for (var i = 0; i < lenB; i++) + this._len = (this._len << 8) + (this._buf[offset++] & 0xff); + + } else { + // Wasn't a variable length + this._len = lenB; + } + + return offset; +}; + + +/** + * Parses the next sequence in this BER buffer. + * + * To get the length of the sequence, call `Reader.length`. + * + * @return {Number} the sequence's tag. + */ +Reader.prototype.readSequence = function (tag) { + var seq = this.peek(); + if (seq === null) + return null; + if (tag !== undefined && tag !== seq) + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + seq.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + if (o === null) + return null; + + this._offset = o; + return seq; +}; + + +Reader.prototype.readInt = function () { + return this._readTag(ASN1.Integer); +}; + + +Reader.prototype.readBoolean = function () { + return (this._readTag(ASN1.Boolean) === 0 ? false : true); +}; + + +Reader.prototype.readEnumeration = function () { + return this._readTag(ASN1.Enumeration); +}; + + +Reader.prototype.readString = function (tag, retbuf) { + if (!tag) + tag = ASN1.OctetString; + + var b = this.peek(); + if (b === null) + return null; + + if (b !== tag) + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + + if (o === null) + return null; + + if (this.length > this._size - o) + return null; + + this._offset = o; + + if (this.length === 0) + return retbuf ? Buffer.alloc(0) : ''; + + var str = this._buf.slice(this._offset, this._offset + this.length); + this._offset += this.length; + + return retbuf ? str : str.toString('utf8'); +}; + +Reader.prototype.readOID = function (tag) { + if (!tag) + tag = ASN1.OID; + + var b = this.readString(tag, true); + if (b === null) + return null; + + var values = []; + var value = 0; + + for (var i = 0; i < b.length; i++) { + var byte = b[i] & 0xff; + + value <<= 7; + value += byte & 0x7f; + if ((byte & 0x80) === 0) { + values.push(value); + value = 0; + } + } + + value = values.shift(); + values.unshift(value % 40); + values.unshift((value / 40) >> 0); + + return values.join('.'); +}; + + +Reader.prototype._readTag = function (tag) { + assert.ok(tag !== undefined); + + var b = this.peek(); + + if (b === null) + return null; + + if (b !== tag) + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + if (o === null) + return null; + + if (this.length > 4) + throw newInvalidAsn1Error('Integer too long: ' + this.length); + + if (this.length > this._size - o) + return null; + this._offset = o; + + var fb = this._buf[this._offset]; + var value = 0; + + for (var i = 0; i < this.length; i++) { + value <<= 8; + value |= (this._buf[this._offset++] & 0xff); + } + + if ((fb & 0x80) === 0x80 && i !== 4) + value -= (1 << (i * 8)); + + return value >> 0; +}; + + + +// --- Exported API + +module.exports = Reader; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/types.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/types.js new file mode 100644 index 000000000..8aea00013 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/ber/types.js @@ -0,0 +1,36 @@ +// Copyright 2011 Mark Cavage All rights reserved. + + +module.exports = { + EOC: 0, + Boolean: 1, + Integer: 2, + BitString: 3, + OctetString: 4, + Null: 5, + OID: 6, + ObjectDescriptor: 7, + External: 8, + Real: 9, // float + Enumeration: 10, + PDV: 11, + Utf8String: 12, + RelativeOID: 13, + Sequence: 16, + Set: 17, + NumericString: 18, + PrintableString: 19, + T61String: 20, + VideotexString: 21, + IA5String: 22, + UTCTime: 23, + GeneralizedTime: 24, + GraphicString: 25, + VisibleString: 26, + GeneralString: 28, + UniversalString: 29, + CharacterString: 30, + BMPString: 31, + Constructor: 32, + Context: 128 +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/index.js new file mode 100644 index 000000000..abc8ffb76 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/asn1/index.js @@ -0,0 +1,17 @@ +// Copyright 2011 Mark Cavage All rights reserved. + +// If you have no idea what ASN.1 or BER is, see this: +// ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc + +var Ber = require('./ber/index'); + + + +// --- Exported API + +module.exports = { + + Ber: Ber, + + BerReader: Ber.Reader, +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/encryptEngines/js.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/encryptEngines/js.js new file mode 100644 index 000000000..f14844153 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/encryptEngines/js.js @@ -0,0 +1,34 @@ +var BigInteger = require('../libs/jsbn.js'); +var schemes = require('../schemes/schemes.js'); + +module.exports = function (keyPair, options) { + var pkcs1Scheme = schemes.pkcs1.makeScheme(keyPair, options); + + return { + encrypt: function (buffer, usePrivate) { + var m, c; + if (usePrivate) { + /* Type 1: zeros padding for private key encrypt */ + m = new BigInteger(pkcs1Scheme.encPad(buffer, {type: 1})); + c = keyPair.$doPrivate(m); + } else { + m = new BigInteger(keyPair.encryptionScheme.encPad(buffer)); + c = keyPair.$doPublic(m); + } + return c.toBuffer(keyPair.encryptedDataLength); + }, + + decrypt: function (buffer, usePublic) { + var m, c = new BigInteger(buffer); + + if (usePublic) { + m = keyPair.$doPublic(c); + /* Type 1: zeros padding for private key decrypt */ + return pkcs1Scheme.encUnPad(m.toBuffer(keyPair.encryptedDataLength), {type: 1}); + } else { + m = keyPair.$doPrivate(c); + return keyPair.encryptionScheme.encUnPad(m.toBuffer(keyPair.encryptedDataLength)); + } + } + }; +}; \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/components.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/components.js new file mode 100644 index 000000000..a27531480 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/components.js @@ -0,0 +1,71 @@ +var _ = require('../utils')._; +var utils = require('../utils'); + +module.exports = { + privateExport: function (key, options) { + return { + n: key.n.toBuffer(), + e: key.e, + d: key.d.toBuffer(), + p: key.p.toBuffer(), + q: key.q.toBuffer(), + dmp1: key.dmp1.toBuffer(), + dmq1: key.dmq1.toBuffer(), + coeff: key.coeff.toBuffer() + }; + }, + + privateImport: function (key, data, options) { + if (data.n && data.e && data.d && data.p && data.q && data.dmp1 && data.dmq1 && data.coeff) { + key.setPrivate( + data.n, + data.e, + data.d, + data.p, + data.q, + data.dmp1, + data.dmq1, + data.coeff + ); + } else { + throw Error("Invalid key data"); + } + }, + + publicExport: function (key, options) { + return { + n: key.n.toBuffer(), + e: key.e + }; + }, + + publicImport: function (key, data, options) { + if (data.n && data.e) { + key.setPublic( + data.n, + data.e + ); + } else { + throw Error("Invalid key data"); + } + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + if (data.n && data.e) { + if (data.d && data.p && data.q && data.dmp1 && data.dmq1 && data.coeff) { + module.exports.privateImport(key, data); + return true; + } else { + module.exports.publicImport(key, data); + return true; + } + } + + return false; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/formats.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/formats.js new file mode 100644 index 000000000..c5c676e00 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/formats.js @@ -0,0 +1,31 @@ +var _ = require('../utils')._; + +module.exports = { + pkcs1: require('./pkcs1'), + pkcs8: require('./pkcs8'), + components: require('./components'), + + detectAndImport: function (key, data, format) { + if (format === undefined) { + for (var scheme in module.exports) { + if (typeof module.exports[scheme].autoImport === 'function' && module.exports[scheme].autoImport(key, data)) { + return true; + } + } + } else if (format) { + var fmt = formatParse(format); + + if (module.exports[fmt.scheme]) { + if (fmt.keyType === 'private') { + module.exports[fmt.scheme].privateImport(key, data, fmt.keyOpt); + } else { + module.exports[fmt.scheme].publicImport(key, data, fmt.keyOpt); + } + } else { + throw Error('Unsupported key format'); + } + } + + return false; + }, +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/pkcs1.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/pkcs1.js new file mode 100644 index 000000000..2199c7380 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/pkcs1.js @@ -0,0 +1,148 @@ +var ber = require('../asn1/index').Ber; +var _ = require('../utils')._; +var utils = require('../utils'); + +const PRIVATE_OPENING_BOUNDARY = '-----BEGIN RSA PRIVATE KEY-----'; +const PRIVATE_CLOSING_BOUNDARY = '-----END RSA PRIVATE KEY-----'; + +const PUBLIC_OPENING_BOUNDARY = '-----BEGIN RSA PUBLIC KEY-----'; +const PUBLIC_CLOSING_BOUNDARY = '-----END RSA PUBLIC KEY-----'; + +module.exports = { + privateExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var d = key.d.toBuffer(); + var p = key.p.toBuffer(); + var q = key.q.toBuffer(); + var dmp1 = key.dmp1.toBuffer(); + var dmq1 = key.dmq1.toBuffer(); + var coeff = key.coeff.toBuffer(); + + var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic + var writer = new ber.Writer({size: length}); + + writer.startSequence(); + writer.writeInt(0); + writer.writeBuffer(n, 2); + writer.writeInt(key.e); + writer.writeBuffer(d, 2); + writer.writeBuffer(p, 2); + writer.writeBuffer(q, 2); + writer.writeBuffer(dmp1, 2); + writer.writeBuffer(dmq1, 2); + writer.writeBuffer(coeff, 2); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return PRIVATE_OPENING_BOUNDARY + '\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n' + PRIVATE_CLOSING_BOUNDARY; + } + }, + + privateImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PRIVATE_OPENING_BOUNDARY, PRIVATE_CLOSING_BOUNDARY) + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } else { + throw Error('Unsupported key format'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + reader.readString(2, true); // just zero + key.setPrivate( + reader.readString(2, true), // modulus + reader.readString(2, true), // publicExponent + reader.readString(2, true), // privateExponent + reader.readString(2, true), // prime1 + reader.readString(2, true), // prime2 + reader.readString(2, true), // exponent1 -- d mod (p1) + reader.readString(2, true), // exponent2 -- d mod (q-1) + reader.readString(2, true) // coefficient -- (inverse of q) mod p + ); + }, + + publicExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var length = n.length + 512; // magic + + var bodyWriter = new ber.Writer({size: length}); + bodyWriter.startSequence(); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.endSequence(); + + if (options.type === 'der') { + return bodyWriter.buffer; + } else { + return PUBLIC_OPENING_BOUNDARY + '\n' + utils.linebrk(bodyWriter.buffer.toString('base64'), 64) + '\n' + PUBLIC_CLOSING_BOUNDARY; + } + }, + + publicImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PUBLIC_OPENING_BOUNDARY, PUBLIC_CLOSING_BOUNDARY) + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var body = new ber.Reader(buffer); + body.readSequence(); + key.setPublic( + body.readString(0x02, true), // modulus + body.readString(0x02, true) // publicExponent + ); + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + // [\S\s]* matches zero or more of any character + if (/^[\S\s]*-----BEGIN RSA PRIVATE KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END RSA PRIVATE KEY-----[\S\s]*$/g.test(data)) { + module.exports.privateImport(key, data); + return true; + } + + if (/^[\S\s]*-----BEGIN RSA PUBLIC KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END RSA PUBLIC KEY-----[\S\s]*$/g.test(data)) { + module.exports.publicImport(key, data); + return true; + } + + return false; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/pkcs8.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/pkcs8.js new file mode 100644 index 000000000..15012bda8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/formats/pkcs8.js @@ -0,0 +1,187 @@ +var ber = require('../asn1/index').Ber; +var _ = require('../utils')._; +var PUBLIC_RSA_OID = '1.2.840.113549.1.1.1'; +var utils = require('../utils'); + +const PRIVATE_OPENING_BOUNDARY = '-----BEGIN PRIVATE KEY-----'; +const PRIVATE_CLOSING_BOUNDARY = '-----END PRIVATE KEY-----'; + +const PUBLIC_OPENING_BOUNDARY = '-----BEGIN PUBLIC KEY-----'; +const PUBLIC_CLOSING_BOUNDARY = '-----END PUBLIC KEY-----'; + +module.exports = { + privateExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var d = key.d.toBuffer(); + var p = key.p.toBuffer(); + var q = key.q.toBuffer(); + var dmp1 = key.dmp1.toBuffer(); + var dmq1 = key.dmq1.toBuffer(); + var coeff = key.coeff.toBuffer(); + + var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic + var bodyWriter = new ber.Writer({size: length}); + + bodyWriter.startSequence(); + bodyWriter.writeInt(0); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.writeBuffer(d, 2); + bodyWriter.writeBuffer(p, 2); + bodyWriter.writeBuffer(q, 2); + bodyWriter.writeBuffer(dmp1, 2); + bodyWriter.writeBuffer(dmq1, 2); + bodyWriter.writeBuffer(coeff, 2); + bodyWriter.endSequence(); + + var writer = new ber.Writer({size: length}); + writer.startSequence(); + writer.writeInt(0); + writer.startSequence(); + writer.writeOID(PUBLIC_RSA_OID); + writer.writeNull(); + writer.endSequence(); + writer.writeBuffer(bodyWriter.buffer, 4); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return PRIVATE_OPENING_BOUNDARY + '\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n' + PRIVATE_CLOSING_BOUNDARY; + } + }, + + privateImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PRIVATE_OPENING_BOUNDARY, PRIVATE_CLOSING_BOUNDARY) + .replace('-----END PRIVATE KEY-----', '') + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } else { + throw Error('Unsupported key format'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + reader.readInt(0); + var header = new ber.Reader(reader.readString(0x30, true)); + + if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { + throw Error('Invalid Public key format'); + } + + var body = new ber.Reader(reader.readString(0x04, true)); + body.readSequence(); + body.readString(2, true); // just zero + key.setPrivate( + body.readString(2, true), // modulus + body.readString(2, true), // publicExponent + body.readString(2, true), // privateExponent + body.readString(2, true), // prime1 + body.readString(2, true), // prime2 + body.readString(2, true), // exponent1 -- d mod (p1) + body.readString(2, true), // exponent2 -- d mod (q-1) + body.readString(2, true) // coefficient -- (inverse of q) mod p + ); + }, + + publicExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var length = n.length + 512; // magic + + var bodyWriter = new ber.Writer({size: length}); + bodyWriter.writeByte(0); + bodyWriter.startSequence(); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.endSequence(); + + var writer = new ber.Writer({size: length}); + writer.startSequence(); + writer.startSequence(); + writer.writeOID(PUBLIC_RSA_OID); + writer.writeNull(); + writer.endSequence(); + writer.writeBuffer(bodyWriter.buffer, 3); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return PUBLIC_OPENING_BOUNDARY + '\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n' + PUBLIC_CLOSING_BOUNDARY; + } + }, + + publicImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = utils.trimSurroundingText(data, PUBLIC_OPENING_BOUNDARY, PUBLIC_CLOSING_BOUNDARY) + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = Buffer.from(pem, 'base64'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + var header = new ber.Reader(reader.readString(0x30, true)); + + if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { + throw Error('Invalid Public key format'); + } + + var body = new ber.Reader(reader.readString(0x03, true)); + body.readByte(); + body.readSequence(); + key.setPublic( + body.readString(0x02, true), // modulus + body.readString(0x02, true) // publicExponent + ); + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + if (/^[\S\s]*-----BEGIN PRIVATE KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END PRIVATE KEY-----[\S\s]*$/g.test(data)) { + module.exports.privateImport(key, data); + return true; + } + + if (/^[\S\s]*-----BEGIN PUBLIC KEY-----\s*(?=(([A-Za-z0-9+/=]+\s*)+))\1-----END PUBLIC KEY-----[\S\s]*$/g.test(data)) { + module.exports.publicImport(key, data); + return true; + } + + return false; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/libs/jsbn.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/libs/jsbn.js new file mode 100644 index 000000000..7276f976b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/libs/jsbn.js @@ -0,0 +1,1252 @@ +/* + * Basic JavaScript BN library - subset useful for RSA encryption. + * + * Copyright (c) 2003-2005 Tom Wu + * ~BSD license~ + * + * Added Node.js Buffers support + * 2014 rzcoder + * + * Deleted some stuff to save bytes + * frosty00 + */ + +var _ = require('../utils')._; + +// Bits per digit +var dbits; + +// (public) Constructor +function BigInteger(a, b) { + if (a != null) { + if ("number" == typeof a) { + this.fromNumber(a, b); + } else if (Buffer.isBuffer(a)) { + this.fromBuffer(a); + } else if (b == null && "string" != typeof a) { + this.fromByteArray(a); + } else { + this.fromString(a, b); + } + } +} + +// return new, unset BigInteger +function nbi() { + return new BigInteger(null); +} + +// am: Compute w_j += (x*this_i), propagate carries, +// c is initial carry, returns final carry. +// c < 3*dvalue, x < 2*dvalue, this_i < dvalue +// We need to select the fastest one that works in this environment. + +// Alternately, set max digit bits to 28 since some +// browsers slow down when dealing with 32-bit numbers. +function am3(i, x, w, j, c, n) { + var xl = x & 0x3fff, xh = x >> 14; + while (--n >= 0) { + var l = this[i] & 0x3fff; + var h = this[i++] >> 14; + var m = xh * l + h * xl; + l = xl * l + ((m & 0x3fff) << 14) + w[j] + c; + c = (l >> 28) + (m >> 14) + xh * h; + w[j++] = l & 0xfffffff; + } + return c; +} + +// We need to select the fastest one that works in this environment. +//if (j_lm && (navigator.appName == "Microsoft Internet Explorer")) { +// BigInteger.prototype.am = am2; +// dbits = 30; +//} else if (j_lm && (navigator.appName != "Netscape")) { +// BigInteger.prototype.am = am1; +// dbits = 26; +//} else { // Mozilla/Netscape seems to prefer am3 +// BigInteger.prototype.am = am3; +// dbits = 28; +//} + +// For node.js, we pick am3 with max dbits to 28. +BigInteger.prototype.am = am3; +dbits = 28; + +BigInteger.prototype.DB = dbits; +BigInteger.prototype.DM = ((1 << dbits) - 1); +BigInteger.prototype.DV = (1 << dbits); + +var BI_FP = 52; +BigInteger.prototype.FV = Math.pow(2, BI_FP); +BigInteger.prototype.F1 = BI_FP - dbits; +BigInteger.prototype.F2 = 2 * dbits - BI_FP; + +// Digit conversions +var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; +var BI_RC = new Array(); +var rr, vv; +rr = "0".charCodeAt(0); +for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; +rr = "a".charCodeAt(0); +for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; +rr = "A".charCodeAt(0); +for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; + +function int2char(n) { + return BI_RM.charAt(n); +} +function intAt(s, i) { + var c = BI_RC[s.charCodeAt(i)]; + return (c == null) ? -1 : c; +} + +// (protected) copy this to r +function bnpCopyTo(r) { + for (var i = this.t - 1; i >= 0; --i) r[i] = this[i]; + r.t = this.t; + r.s = this.s; +} + +// (protected) set from integer value x, -DV <= x < DV +function bnpFromInt(x) { + this.t = 1; + this.s = (x < 0) ? -1 : 0; + if (x > 0) this[0] = x; + else if (x < -1) this[0] = x + DV; + else this.t = 0; +} + +// return bigint initialized to value +function nbv(i) { + var r = nbi(); + r.fromInt(i); + return r; +} + +// (protected) set from string and radix +function bnpFromString(data, radix, unsigned) { + var k; + switch (radix) { + case 2: + k = 1; + break; + case 4: + k = 2; + break; + case 8: + k = 3; + break; + case 16: + k = 4; + break; + case 32: + k = 5; + break; + case 256: + k = 8; + break; + default: + this.fromRadix(data, radix); + return; + } + + this.t = 0; + this.s = 0; + + var i = data.length; + var mi = false; + var sh = 0; + + while (--i >= 0) { + var x = (k == 8) ? data[i] & 0xff : intAt(data, i); + if (x < 0) { + if (data.charAt(i) == "-") mi = true; + continue; + } + mi = false; + if (sh === 0) + this[this.t++] = x; + else if (sh + k > this.DB) { + this[this.t - 1] |= (x & ((1 << (this.DB - sh)) - 1)) << sh; + this[this.t++] = (x >> (this.DB - sh)); + } + else + this[this.t - 1] |= x << sh; + sh += k; + if (sh >= this.DB) sh -= this.DB; + } + if ((!unsigned) && k == 8 && (data[0] & 0x80) != 0) { + this.s = -1; + if (sh > 0) this[this.t - 1] |= ((1 << (this.DB - sh)) - 1) << sh; + } + this.clamp(); + if (mi) BigInteger.ZERO.subTo(this, this); +} + +function bnpFromByteArray(a, unsigned) { + this.fromString(a, 256, unsigned) +} + +function bnpFromBuffer(a) { + this.fromString(a, 256, true) +} + +// (protected) clamp off excess high words +function bnpClamp() { + var c = this.s & this.DM; + while (this.t > 0 && this[this.t - 1] == c) --this.t; +} + +// (public) return string representation in given radix +function bnToString(b) { + if (this.s < 0) return "-" + this.negate().toString(b); + var k; + if (b == 16) k = 4; + else if (b == 8) k = 3; + else if (b == 2) k = 1; + else if (b == 32) k = 5; + else if (b == 4) k = 2; + else return this.toRadix(b); + var km = (1 << k) - 1, d, m = false, r = "", i = this.t; + var p = this.DB - (i * this.DB) % k; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) > 0) { + m = true; + r = int2char(d); + } + while (i >= 0) { + if (p < k) { + d = (this[i] & ((1 << p) - 1)) << (k - p); + d |= this[--i] >> (p += this.DB - k); + } + else { + d = (this[i] >> (p -= k)) & km; + if (p <= 0) { + p += this.DB; + --i; + } + } + if (d > 0) m = true; + if (m) r += int2char(d); + } + } + return m ? r : "0"; +} + +// (public) -this +function bnNegate() { + var r = nbi(); + BigInteger.ZERO.subTo(this, r); + return r; +} + +// (public) |this| +function bnAbs() { + return (this.s < 0) ? this.negate() : this; +} + +// (public) return + if this > a, - if this < a, 0 if equal +function bnCompareTo(a) { + var r = this.s - a.s; + if (r != 0) return r; + var i = this.t; + r = i - a.t; + if (r != 0) return (this.s < 0) ? -r : r; + while (--i >= 0) if ((r = this[i] - a[i]) != 0) return r; + return 0; +} + +// returns bit length of the integer x +function nbits(x) { + var r = 1, t; + if ((t = x >>> 16) != 0) { + x = t; + r += 16; + } + if ((t = x >> 8) != 0) { + x = t; + r += 8; + } + if ((t = x >> 4) != 0) { + x = t; + r += 4; + } + if ((t = x >> 2) != 0) { + x = t; + r += 2; + } + if ((t = x >> 1) != 0) { + x = t; + r += 1; + } + return r; +} + +// (public) return the number of bits in "this" +function bnBitLength() { + if (this.t <= 0) return 0; + return this.DB * (this.t - 1) + nbits(this[this.t - 1] ^ (this.s & this.DM)); +} + +// (protected) r = this << n*DB +function bnpDLShiftTo(n, r) { + var i; + for (i = this.t - 1; i >= 0; --i) r[i + n] = this[i]; + for (i = n - 1; i >= 0; --i) r[i] = 0; + r.t = this.t + n; + r.s = this.s; +} + +// (protected) r = this >> n*DB +function bnpDRShiftTo(n, r) { + for (var i = n; i < this.t; ++i) r[i - n] = this[i]; + r.t = Math.max(this.t - n, 0); + r.s = this.s; +} + +// (protected) r = this << n +function bnpLShiftTo(n, r) { + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << cbs) - 1; + var ds = Math.floor(n / this.DB), c = (this.s << bs) & this.DM, i; + for (i = this.t - 1; i >= 0; --i) { + r[i + ds + 1] = (this[i] >> cbs) | c; + c = (this[i] & bm) << bs; + } + for (i = ds - 1; i >= 0; --i) r[i] = 0; + r[ds] = c; + r.t = this.t + ds + 1; + r.s = this.s; + r.clamp(); +} + +// (protected) r = this >> n +function bnpRShiftTo(n, r) { + r.s = this.s; + var ds = Math.floor(n / this.DB); + if (ds >= this.t) { + r.t = 0; + return; + } + var bs = n % this.DB; + var cbs = this.DB - bs; + var bm = (1 << bs) - 1; + r[0] = this[ds] >> bs; + for (var i = ds + 1; i < this.t; ++i) { + r[i - ds - 1] |= (this[i] & bm) << cbs; + r[i - ds] = this[i] >> bs; + } + if (bs > 0) r[this.t - ds - 1] |= (this.s & bm) << cbs; + r.t = this.t - ds; + r.clamp(); +} + +// (protected) r = this - a +function bnpSubTo(a, r) { + var i = 0, c = 0, m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] - a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c -= a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while (i < a.t) { + c -= a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c < -1) r[i++] = this.DV + c; + else if (c > 0) r[i++] = c; + r.t = i; + r.clamp(); +} + +// (protected) r = this * a, r != this,a (HAC 14.12) +// "this" should be the larger one if appropriate. +function bnpMultiplyTo(a, r) { + var x = this.abs(), y = a.abs(); + var i = x.t; + r.t = i + y.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < y.t; ++i) r[i + x.t] = x.am(0, y[i], r, i, 0, x.t); + r.s = 0; + r.clamp(); + if (this.s != a.s) BigInteger.ZERO.subTo(r, r); +} + +// (protected) r = this^2, r != this (HAC 14.16) +function bnpSquareTo(r) { + var x = this.abs(); + var i = r.t = 2 * x.t; + while (--i >= 0) r[i] = 0; + for (i = 0; i < x.t - 1; ++i) { + var c = x.am(i, x[i], r, 2 * i, 0, 1); + if ((r[i + x.t] += x.am(i + 1, 2 * x[i], r, 2 * i + 1, c, x.t - i - 1)) >= x.DV) { + r[i + x.t] -= x.DV; + r[i + x.t + 1] = 1; + } + } + if (r.t > 0) r[r.t - 1] += x.am(i, x[i], r, 2 * i, 0, 1); + r.s = 0; + r.clamp(); +} + +// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) +// r != q, this != m. q or r may be null. +function bnpDivRemTo(m, q, r) { + var pm = m.abs(); + if (pm.t <= 0) return; + var pt = this.abs(); + if (pt.t < pm.t) { + if (q != null) q.fromInt(0); + if (r != null) this.copyTo(r); + return; + } + if (r == null) r = nbi(); + var y = nbi(), ts = this.s, ms = m.s; + var nsh = this.DB - nbits(pm[pm.t - 1]); // normalize modulus + if (nsh > 0) { + pm.lShiftTo(nsh, y); + pt.lShiftTo(nsh, r); + } + else { + pm.copyTo(y); + pt.copyTo(r); + } + var ys = y.t; + var y0 = y[ys - 1]; + if (y0 === 0) return; + var yt = y0 * (1 << this.F1) + ((ys > 1) ? y[ys - 2] >> this.F2 : 0); + var d1 = this.FV / yt, d2 = (1 << this.F1) / yt, e = 1 << this.F2; + var i = r.t, j = i - ys, t = (q == null) ? nbi() : q; + y.dlShiftTo(j, t); + if (r.compareTo(t) >= 0) { + r[r.t++] = 1; + r.subTo(t, r); + } + BigInteger.ONE.dlShiftTo(ys, t); + t.subTo(y, y); // "negative" y so we can replace sub with am later + while (y.t < ys) y[y.t++] = 0; + while (--j >= 0) { + // Estimate quotient digit + var qd = (r[--i] == y0) ? this.DM : Math.floor(r[i] * d1 + (r[i - 1] + e) * d2); + if ((r[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out + y.dlShiftTo(j, t); + r.subTo(t, r); + while (r[i] < --qd) r.subTo(t, r); + } + } + if (q != null) { + r.drShiftTo(ys, q); + if (ts != ms) BigInteger.ZERO.subTo(q, q); + } + r.t = ys; + r.clamp(); + if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder + if (ts < 0) BigInteger.ZERO.subTo(r, r); +} + +// (public) this mod a +function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a, null, r); + if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); + return r; +} + +// Modular reduction using "classic" algorithm +function Classic(m) { + this.m = m; +} +function cConvert(x) { + if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; +} +function cRevert(x) { + return x; +} +function cReduce(x) { + x.divRemTo(this.m, null, x); +} +function cMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} +function cSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +Classic.prototype.convert = cConvert; +Classic.prototype.revert = cRevert; +Classic.prototype.reduce = cReduce; +Classic.prototype.mulTo = cMulTo; +Classic.prototype.sqrTo = cSqrTo; + +// (protected) return "-1/this % 2^DB"; useful for Mont. reduction +// justification: +// xy == 1 (mod m) +// xy = 1+km +// xy(2-xy) = (1+km)(1-km) +// x[y(2-xy)] = 1-k^2m^2 +// x[y(2-xy)] == 1 (mod m^2) +// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 +// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. +// JS multiply "overflows" differently from C/C++, so care is needed here. +function bnpInvDigit() { + if (this.t < 1) return 0; + var x = this[0]; + if ((x & 1) === 0) return 0; + var y = x & 3; // y == 1/x mod 2^2 + y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 + y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 + y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y * (2 - x * y % this.DV)) % this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return (y > 0) ? this.DV - y : -y; +} + +// Montgomery reduction +function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp & 0x7fff; + this.mph = this.mp >> 15; + this.um = (1 << (m.DB - 15)) - 1; + this.mt2 = 2 * m.t; +} + +// xR mod m +function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t, r); + r.divRemTo(this.m, null, r); + if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); + return r; +} + +// x/R mod m +function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; +} + +// x = x/R mod m (HAC 14.32) +function montReduce(x) { + while (x.t <= this.mt2) // pad x so am has enough room later + x[x.t++] = 0; + for (var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x[i]*mp mod DV + var j = x[i] & 0x7fff; + var u0 = (j * this.mpl + (((j * this.mph + (x[i] >> 15) * this.mpl) & this.um) << 15)) & x.DM; + // use am to combine the multiply-shift-add into one call + j = i + this.m.t; + x[j] += this.m.am(0, u0, x, i, 0, this.m.t); + // propagate carry + while (x[j] >= x.DV) { + x[j] -= x.DV; + x[++j]++; + } + } + x.clamp(); + x.drShiftTo(this.m.t, x); + if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); +} + +// r = "x^2/R mod m"; x != r +function montSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +// r = "xy/R mod m"; x,y != r +function montMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} + +Montgomery.prototype.convert = montConvert; +Montgomery.prototype.revert = montRevert; +Montgomery.prototype.reduce = montReduce; +Montgomery.prototype.mulTo = montMulTo; +Montgomery.prototype.sqrTo = montSqrTo; + +// (protected) true iff this is even +function bnpIsEven() { + return ((this.t > 0) ? (this[0] & 1) : this.s) === 0; +} + +// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) +function bnpExp(e, z) { + if (e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e) - 1; + g.copyTo(r); + while (--i >= 0) { + z.sqrTo(r, r2); + if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); + else { + var t = r; + r = r2; + r2 = t; + } + } + return z.revert(r); +} + +// Copyright (c) 2005-2009 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Extended JavaScript BN functions, required for RSA private ops. + +// Version 1.1: new BigInteger("0", 10) returns "proper" zero +// Version 1.2: square() API, isProbablePrime fix + +//(public) +function bnClone() { + var r = nbi(); + this.copyTo(r); + return r; +} + +//(public) return value as integer +function bnIntValue() { + if (this.s < 0) { + if (this.t == 1) return this[0] - this.DV; + else if (this.t === 0) return -1; + } + else if (this.t == 1) return this[0]; + else if (this.t === 0) return 0; +// assumes 16 < DB < 32 + return ((this[1] & ((1 << (32 - this.DB)) - 1)) << this.DB) | this[0]; +} + +//(protected) return x s.t. r^x < DV +function bnpChunkSize(r) { + return Math.floor(Math.LN2 * this.DB / Math.log(r)); +} + +//(public) 0 if this === 0, 1 if this > 0 +function bnSigNum() { + if (this.s < 0) return -1; + else if (this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; + else return 1; +} + +//(protected) convert to radix string +function bnpToRadix(b) { + if (b == null) b = 10; + if (this.signum() === 0 || b < 2 || b > 36) return "0"; + var cs = this.chunkSize(b); + var a = Math.pow(b, cs); + var d = nbv(a), y = nbi(), z = nbi(), r = ""; + this.divRemTo(d, y, z); + while (y.signum() > 0) { + r = (a + z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d, y, z); + } + return z.intValue().toString(b) + r; +} + +//(protected) convert from radix string +function bnpFromRadix(s, b) { + this.fromInt(0); + if (b == null) b = 10; + var cs = this.chunkSize(b); + var d = Math.pow(b, cs), mi = false, j = 0, w = 0; + for (var i = 0; i < s.length; ++i) { + var x = intAt(s, i); + if (x < 0) { + if (s.charAt(i) == "-" && this.signum() === 0) mi = true; + continue; + } + w = b * w + x; + if (++j >= cs) { + this.dMultiply(d); + this.dAddOffset(w, 0); + j = 0; + w = 0; + } + } + if (j > 0) { + this.dMultiply(Math.pow(b, j)); + this.dAddOffset(w, 0); + } + if (mi) BigInteger.ZERO.subTo(this, this); +} + +//(public) convert to bigendian byte array +function bnToByteArray() { + var i = this.t, r = new Array(); + r[0] = this.s; + var p = this.DB - (i * this.DB) % 8, d, k = 0; + if (i-- > 0) { + if (p < this.DB && (d = this[i] >> p) != (this.s & this.DM) >> p) + r[k++] = d | (this.s << (this.DB - p)); + while (i >= 0) { + if (p < 8) { + d = (this[i] & ((1 << p) - 1)) << (8 - p); + d |= this[--i] >> (p += this.DB - 8); + } + else { + d = (this[i] >> (p -= 8)) & 0xff; + if (p <= 0) { + p += this.DB; + --i; + } + } + if ((d & 0x80) != 0) d |= -256; + if (k === 0 && (this.s & 0x80) != (d & 0x80)) ++k; + if (k > 0 || d != this.s) r[k++] = d; + } + } + return r; +} + +/** + * return Buffer object + * @param trim {boolean} slice buffer if first element == 0 + * @returns {Buffer} + */ +function bnToBuffer(trimOrSize) { + var res = Buffer.from(this.toByteArray()); + if (trimOrSize === true && res[0] === 0) { + res = res.slice(1); + } else if (_.isNumber(trimOrSize)) { + if (res.length > trimOrSize) { + for (var i = 0; i < res.length - trimOrSize; i++) { + if (res[i] !== 0) { + return null; + } + } + return res.slice(res.length - trimOrSize); + } else if (res.length < trimOrSize) { + var padded = Buffer.alloc(trimOrSize); + padded.fill(0, 0, trimOrSize - res.length); + res.copy(padded, trimOrSize - res.length); + return padded; + } + } + return res; +} + +function bnEquals(a) { + return (this.compareTo(a) == 0); +} +function bnMin(a) { + return (this.compareTo(a) < 0) ? this : a; +} +function bnMax(a) { + return (this.compareTo(a) > 0) ? this : a; +} + +//(protected) r = this op a (bitwise) +function bnpBitwiseTo(a, op, r) { + var i, f, m = Math.min(a.t, this.t); + for (i = 0; i < m; ++i) r[i] = op(this[i], a[i]); + if (a.t < this.t) { + f = a.s & this.DM; + for (i = m; i < this.t; ++i) r[i] = op(this[i], f); + r.t = this.t; + } + else { + f = this.s & this.DM; + for (i = m; i < a.t; ++i) r[i] = op(f, a[i]); + r.t = a.t; + } + r.s = op(this.s, a.s); + r.clamp(); +} + +//(public) this & a +function op_and(x, y) { + return x & y; +} +function bnAnd(a) { + var r = nbi(); + this.bitwiseTo(a, op_and, r); + return r; +} + +//(public) this | a +function op_or(x, y) { + return x | y; +} +function bnOr(a) { + var r = nbi(); + this.bitwiseTo(a, op_or, r); + return r; +} + +//(public) this ^ a +function op_xor(x, y) { + return x ^ y; +} +function bnXor(a) { + var r = nbi(); + this.bitwiseTo(a, op_xor, r); + return r; +} + +//(public) this & ~a +function op_andnot(x, y) { + return x & ~y; +} + +//(public) ~this +function bnNot() { + var r = nbi(); + for (var i = 0; i < this.t; ++i) r[i] = this.DM & ~this[i]; + r.t = this.t; + r.s = ~this.s; + return r; +} + +//return index of lowest 1-bit in x, x < 2^31 +function lbit(x) { + if (x === 0) return -1; + var r = 0; + if ((x & 0xffff) === 0) { + x >>= 16; + r += 16; + } + if ((x & 0xff) === 0) { + x >>= 8; + r += 8; + } + if ((x & 0xf) === 0) { + x >>= 4; + r += 4; + } + if ((x & 3) === 0) { + x >>= 2; + r += 2; + } + if ((x & 1) === 0) ++r; + return r; +} + +//(public) returns index of lowest 1-bit (or -1 if none) +function bnGetLowestSetBit() { + for (var i = 0; i < this.t; ++i) + if (this[i] != 0) return i * this.DB + lbit(this[i]); + if (this.s < 0) return this.t * this.DB; + return -1; +} + +//return number of 1 bits in x +function cbit(x) { + var r = 0; + while (x != 0) { + x &= x - 1; + ++r; + } + return r; +} + +//(protected) r = this + a +function bnpAddTo(a, r) { + var i = 0, c = 0, m = Math.min(a.t, this.t); + while (i < m) { + c += this[i] + a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + if (a.t < this.t) { + c += a.s; + while (i < this.t) { + c += this[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while (i < a.t) { + c += a[i]; + r[i++] = c & this.DM; + c >>= this.DB; + } + c += a.s; + } + r.s = (c < 0) ? -1 : 0; + if (c > 0) r[i++] = c; + else if (c < -1) r[i++] = this.DV + c; + r.t = i; + r.clamp(); +} + +//(public) this + a +function bnAdd(a) { + var r = nbi(); + this.addTo(a, r); + return r; +} + +//(public) this - a +function bnSubtract(a) { + var r = nbi(); + this.subTo(a, r); + return r; +} + +//(public) this * a +function bnMultiply(a) { + var r = nbi(); + this.multiplyTo(a, r); + return r; +} + +// (public) this^2 +function bnSquare() { + var r = nbi(); + this.squareTo(r); + return r; +} + +//(public) this / a +function bnDivide(a) { + var r = nbi(); + this.divRemTo(a, r, null); + return r; +} + +//(public) this % a +function bnRemainder(a) { + var r = nbi(); + this.divRemTo(a, null, r); + return r; +} + +//(protected) this *= n, this >= 0, 1 < n < DV +function bnpDMultiply(n) { + this[this.t] = this.am(0, n - 1, this, 0, 0, this.t); + ++this.t; + this.clamp(); +} + +//(protected) this += n << w words, this >= 0 +function bnpDAddOffset(n, w) { + if (n === 0) return; + while (this.t <= w) this[this.t++] = 0; + this[w] += n; + while (this[w] >= this.DV) { + this[w] -= this.DV; + if (++w >= this.t) this[this.t++] = 0; + ++this[w]; + } +} + +//A "null" reducer +function NullExp() { +} +function nNop(x) { + return x; +} +function nMulTo(x, y, r) { + x.multiplyTo(y, r); +} +function nSqrTo(x, r) { + x.squareTo(r); +} + +NullExp.prototype.convert = nNop; +NullExp.prototype.revert = nNop; +NullExp.prototype.mulTo = nMulTo; +NullExp.prototype.sqrTo = nSqrTo; + +//(public) this^e +function bnPow(e) { + return this.exp(e, new NullExp()); +} + +//(protected) r = lower n words of "this * a", a.t <= n +//"this" should be the larger one if appropriate. +function bnpMultiplyLowerTo(a, n, r) { + var i = Math.min(this.t + a.t, n); + r.s = 0; // assumes a,this >= 0 + r.t = i; + while (i > 0) r[--i] = 0; + var j; + for (j = r.t - this.t; i < j; ++i) r[i + this.t] = this.am(0, a[i], r, i, 0, this.t); + for (j = Math.min(a.t, n); i < j; ++i) this.am(0, a[i], r, i, 0, n - i); + r.clamp(); +} + +//(protected) r = "this * a" without lower n words, n > 0 +//"this" should be the larger one if appropriate. +function bnpMultiplyUpperTo(a, n, r) { + --n; + var i = r.t = this.t + a.t - n; + r.s = 0; // assumes a,this >= 0 + while (--i >= 0) r[i] = 0; + for (i = Math.max(n - this.t, 0); i < a.t; ++i) + r[this.t + i - n] = this.am(n - i, a[i], r, 0, 0, this.t + i - n); + r.clamp(); + r.drShiftTo(1, r); +} + +//Barrett modular reduction +function Barrett(m) { +// setup Barrett + this.r2 = nbi(); + this.q3 = nbi(); + BigInteger.ONE.dlShiftTo(2 * m.t, this.r2); + this.mu = this.r2.divide(m); + this.m = m; +} + +function barrettConvert(x) { + if (x.s < 0 || x.t > 2 * this.m.t) return x.mod(this.m); + else if (x.compareTo(this.m) < 0) return x; + else { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; + } +} + +function barrettRevert(x) { + return x; +} + +//x = x mod m (HAC 14.42) +function barrettReduce(x) { + x.drShiftTo(this.m.t - 1, this.r2); + if (x.t > this.m.t + 1) { + x.t = this.m.t + 1; + x.clamp(); + } + this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3); + this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); + while (x.compareTo(this.r2) < 0) x.dAddOffset(1, this.m.t + 1); + x.subTo(this.r2, x); + while (x.compareTo(this.m) >= 0) x.subTo(this.m, x); +} + +//r = x^2 mod m; x != r +function barrettSqrTo(x, r) { + x.squareTo(r); + this.reduce(r); +} + +//r = x*y mod m; x,y != r +function barrettMulTo(x, y, r) { + x.multiplyTo(y, r); + this.reduce(r); +} + +Barrett.prototype.convert = barrettConvert; +Barrett.prototype.revert = barrettRevert; +Barrett.prototype.reduce = barrettReduce; +Barrett.prototype.mulTo = barrettMulTo; +Barrett.prototype.sqrTo = barrettSqrTo; + +//(public) this^e % m (HAC 14.85) +function bnModPow(e, m) { + var i = e.bitLength(), k, r = nbv(1), z; + if (i <= 0) return r; + else if (i < 18) k = 1; + else if (i < 48) k = 3; + else if (i < 144) k = 4; + else if (i < 768) k = 5; + else k = 6; + if (i < 8) + z = new Classic(m); + else if (m.isEven()) + z = new Barrett(m); + else + z = new Montgomery(m); + +// precomputation + var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; + g[1] = z.convert(this); + if (k > 1) { + var g2 = nbi(); + z.sqrTo(g[1], g2); + while (n <= km) { + g[n] = nbi(); + z.mulTo(g2, g[n - 2], g[n]); + n += 2; + } + } + + var j = e.t - 1, w, is1 = true, r2 = nbi(), t; + i = nbits(e[j]) - 1; + while (j >= 0) { + if (i >= k1) w = (e[j] >> (i - k1)) & km; + else { + w = (e[j] & ((1 << (i + 1)) - 1)) << (k1 - i); + if (j > 0) w |= e[j - 1] >> (this.DB + i - k1); + } + + n = k; + while ((w & 1) === 0) { + w >>= 1; + --n; + } + if ((i -= n) < 0) { + i += this.DB; + --j; + } + if (is1) { // ret == 1, don't bother squaring or multiplying it + g[w].copyTo(r); + is1 = false; + } + else { + while (n > 1) { + z.sqrTo(r, r2); + z.sqrTo(r2, r); + n -= 2; + } + if (n > 0) z.sqrTo(r, r2); else { + t = r; + r = r2; + r2 = t; + } + z.mulTo(r2, g[w], r); + } + + while (j >= 0 && (e[j] & (1 << i)) === 0) { + z.sqrTo(r, r2); + t = r; + r = r2; + r2 = t; + if (--i < 0) { + i = this.DB - 1; + --j; + } + } + } + return z.revert(r); +} + +//(public) gcd(this,a) (HAC 14.54) +function bnGCD(a) { + var x = (this.s < 0) ? this.negate() : this.clone(); + var y = (a.s < 0) ? a.negate() : a.clone(); + if (x.compareTo(y) < 0) { + var t = x; + x = y; + y = t; + } + var i = x.getLowestSetBit(), g = y.getLowestSetBit(); + if (g < 0) return x; + if (i < g) g = i; + if (g > 0) { + x.rShiftTo(g, x); + y.rShiftTo(g, y); + } + while (x.signum() > 0) { + if ((i = x.getLowestSetBit()) > 0) x.rShiftTo(i, x); + if ((i = y.getLowestSetBit()) > 0) y.rShiftTo(i, y); + if (x.compareTo(y) >= 0) { + x.subTo(y, x); + x.rShiftTo(1, x); + } + else { + y.subTo(x, y); + y.rShiftTo(1, y); + } + } + if (g > 0) y.lShiftTo(g, y); + return y; +} + +// protected +BigInteger.prototype.copyTo = bnpCopyTo; +BigInteger.prototype.fromInt = bnpFromInt; +BigInteger.prototype.fromString = bnpFromString; +BigInteger.prototype.fromByteArray = bnpFromByteArray; +BigInteger.prototype.fromBuffer = bnpFromBuffer; +BigInteger.prototype.clamp = bnpClamp; +BigInteger.prototype.dlShiftTo = bnpDLShiftTo; +BigInteger.prototype.drShiftTo = bnpDRShiftTo; +BigInteger.prototype.lShiftTo = bnpLShiftTo; +BigInteger.prototype.rShiftTo = bnpRShiftTo; +BigInteger.prototype.subTo = bnpSubTo; +BigInteger.prototype.multiplyTo = bnpMultiplyTo; +BigInteger.prototype.squareTo = bnpSquareTo; +BigInteger.prototype.divRemTo = bnpDivRemTo; +BigInteger.prototype.invDigit = bnpInvDigit; +BigInteger.prototype.isEven = bnpIsEven; +BigInteger.prototype.exp = bnpExp; + +BigInteger.prototype.chunkSize = bnpChunkSize; +BigInteger.prototype.toRadix = bnpToRadix; +BigInteger.prototype.fromRadix = bnpFromRadix; +BigInteger.prototype.bitwiseTo = bnpBitwiseTo; +BigInteger.prototype.addTo = bnpAddTo; +BigInteger.prototype.dMultiply = bnpDMultiply; +BigInteger.prototype.dAddOffset = bnpDAddOffset; +BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; +BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; + +// public +BigInteger.prototype.toString = bnToString; +BigInteger.prototype.negate = bnNegate; +BigInteger.prototype.abs = bnAbs; +BigInteger.prototype.compareTo = bnCompareTo; +BigInteger.prototype.bitLength = bnBitLength; +BigInteger.prototype.mod = bnMod; + +BigInteger.prototype.clone = bnClone; +BigInteger.prototype.intValue = bnIntValue; +BigInteger.prototype.signum = bnSigNum; +BigInteger.prototype.toByteArray = bnToByteArray; +BigInteger.prototype.toBuffer = bnToBuffer; +BigInteger.prototype.equals = bnEquals; +BigInteger.prototype.min = bnMin; +BigInteger.prototype.max = bnMax; +BigInteger.prototype.and = bnAnd; +BigInteger.prototype.or = bnOr; +BigInteger.prototype.xor = bnXor; +BigInteger.prototype.not = bnNot; +BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; +BigInteger.prototype.add = bnAdd; +BigInteger.prototype.subtract = bnSubtract; +BigInteger.prototype.multiply = bnMultiply; +BigInteger.prototype.divide = bnDivide; +BigInteger.prototype.remainder = bnRemainder; +BigInteger.prototype.modPow = bnModPow; +BigInteger.prototype.pow = bnPow; +BigInteger.prototype.gcd = bnGCD; +BigInteger.int2char = int2char; + +// "constants" +BigInteger.ZERO = nbv(0); +BigInteger.ONE = nbv(1); + +// JSBN-specific extension +BigInteger.prototype.square = bnSquare; + +//BigInteger interfaces not implemented in jsbn: + +//BigInteger(int signum, byte[] magnitude) +//double doubleValue() +//float floatValue() +//int hashCode() +//long longValue() +//static BigInteger valueOf(long val) + +module.exports = BigInteger; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/libs/rsa.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/libs/rsa.js new file mode 100644 index 000000000..23256a300 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/libs/rsa.js @@ -0,0 +1,147 @@ +/* + * RSA Encryption / Decryption with PKCS1 v2 Padding. + * + * ~MIT License~ + * Contributors: + * Tom Wu, rzcoder, frosty00 +*/ + +var _ = require('../utils')._; +var BigInteger = require('./jsbn.js'); +var utils = require('../utils.js'); +var schemes = require('../schemes/schemes.js'); + +//exports.BigInteger = BigInteger; +module.exports.Key = (function () { + /** + * RSA key constructor + * + * n - modulus + * e - publicExponent + * d - privateExponent + * p - prime1 + * q - prime2 + * dmp1 - exponent1 -- d mod (p1) + * dmq1 - exponent2 -- d mod (q-1) + * coeff - coefficient -- (inverse of q) mod p + */ + function RSAKey() { + this.n = null; + this.e = 0; + this.d = null; + this.p = null; + this.q = null; + this.dmp1 = null; + this.dmq1 = null; + this.coeff = null; + } + + RSAKey.prototype.setOptions = function (options) { + var signingSchemeProvider = schemes[options.signingScheme]; + var encryptionSchemeProvider = schemes[options.encryptionScheme]; + + if (signingSchemeProvider === encryptionSchemeProvider) { + this.signingScheme = this.encryptionScheme = encryptionSchemeProvider.makeScheme(this, options); + } else { + this.encryptionScheme = encryptionSchemeProvider.makeScheme(this, options); + this.signingScheme = signingSchemeProvider.makeScheme(this, options); + } + + }; + /** + * Set the private key fields N, e, d and CRT params from buffers + * + * @param N + * @param E + * @param D + * @param P + * @param Q + * @param DP + * @param DQ + * @param C + */ + RSAKey.prototype.setPrivate = function (N, E, D, P, Q, DP, DQ, C) { + if (N && E && D && N.length > 0 && (_.isNumber(E) || E.length > 0) && D.length > 0) { + this.n = new BigInteger(N); + this.e = _.isNumber(E) ? E : utils.get32IntFromBuffer(E, 0); + this.d = new BigInteger(D); + + if (P && Q && DP && DQ && C) { + this.p = new BigInteger(P); + this.q = new BigInteger(Q); + this.dmp1 = new BigInteger(DP); + this.dmq1 = new BigInteger(DQ); + this.coeff = new BigInteger(C); + } else { + // TODO: re-calculate any missing CRT params + } + this.$$recalculateCache(); + } else { + throw Error("Invalid RSA private key"); + } + }; + + /** + * private + * Perform raw private operation on "x": return x^d (mod n) + * + * @param x + * @returns {*} + */ + RSAKey.prototype.$doPrivate = function (x) { + if (this.p || this.q) { + return x.modPow(this.d, this.n); + } + + // TODO: re-calculate any missing CRT params + var xp = x.mod(this.p).modPow(this.dmp1, this.p); + var xq = x.mod(this.q).modPow(this.dmq1, this.q); + + while (xp.compareTo(xq) < 0) { + xp = xp.add(this.p); + } + return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); + }; + + RSAKey.prototype.sign = function (buffer) { + return this.signingScheme.sign.apply(this.signingScheme, arguments); + }; + + /** + * Check if key pair contains private key + */ + RSAKey.prototype.isPrivate = function () { + return this.n && this.e && this.d || false; + }; + + Object.defineProperty(RSAKey.prototype, 'keySize', { + get: function () { + return this.cache.keyBitLength; + } + }); + + Object.defineProperty(RSAKey.prototype, 'encryptedDataLength', { + get: function () { + return this.cache.keyByteLength; + } + }); + + Object.defineProperty(RSAKey.prototype, 'maxMessageLength', { + get: function () { + return this.encryptionScheme.maxMessageLength(); + } + }); + + /** + * Caching key data + */ + RSAKey.prototype.$$recalculateCache = function () { + this.cache = this.cache || {}; + // Bit & byte length + this.cache.keyBitLength = this.n.bitLength(); + this.cache.keyByteLength = (this.cache.keyBitLength + 6) >> 3; + }; + + return RSAKey; +})(); + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/schemes/pkcs1.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/schemes/pkcs1.js new file mode 100644 index 000000000..3578ca186 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/schemes/pkcs1.js @@ -0,0 +1,176 @@ +/** + * PKCS1 padding and signature scheme + */ + +var BigInteger = require('../libs/jsbn'); +var CryptoJS = require('../../crypto-js/crypto-js'); +var constants = { RSA_NO_PADDING: 3 } +require('constants'); +var SIGN_INFO_HEAD = { + md2: Buffer.from('3020300c06082a864886f70d020205000410', 'hex'), + md5: Buffer.from('3020300c06082a864886f70d020505000410', 'hex'), + sha1: Buffer.from('3021300906052b0e03021a05000414', 'hex'), + sha224: Buffer.from('302d300d06096086480165030402040500041c', 'hex'), + sha256: Buffer.from('3031300d060960864801650304020105000420', 'hex'), + sha384: Buffer.from('3041300d060960864801650304020205000430', 'hex'), + sha512: Buffer.from('3051300d060960864801650304020305000440', 'hex'), + ripemd160: Buffer.from('3021300906052b2403020105000414', 'hex'), + rmd160: Buffer.from('3021300906052b2403020105000414', 'hex') +}; + +var SIGN_ALG_TO_HASH_ALIASES = { + 'ripemd160': 'rmd160' +}; + +var DEFAULT_HASH_FUNCTION = 'sha256'; + +module.exports = { + isEncryption: true, + isSignature: true +}; + +module.exports.makeScheme = function (key, options) { + function Scheme(key, options) { + this.key = key; + this.options = options; + } + + Scheme.prototype.maxMessageLength = function () { + if (this.options.encryptionSchemeOptions && this.options.encryptionSchemeOptions.padding == constants.RSA_NO_PADDING) { + return this.key.encryptedDataLength; + } + return this.key.encryptedDataLength - 11; + }; + + /** + * Unpad input Buffer and, if valid, return the Buffer object + * alg: PKCS#1 (type 2, random) + * @param buffer + * @returns {Buffer} + */ + Scheme.prototype.encUnPad = function (buffer, options) { + options = options || {}; + var i = 0; + + if (this.options.encryptionSchemeOptions && this.options.encryptionSchemeOptions.padding == constants.RSA_NO_PADDING) { + //RSA_NO_PADDING treated like JAVA left pad with zero character + var unPad; + if (typeof buffer.lastIndexOf == "function") { //patch for old node version + unPad = buffer.slice(buffer.lastIndexOf('\0') + 1, buffer.length); + } else { + unPad = buffer.slice(String.prototype.lastIndexOf.call(buffer, '\0') + 1, buffer.length); + } + return unPad; + } + + if (buffer.length < 4) { + return null; + } + + /* Type 1: zeros padding for private key decrypt */ + if (options.type === 1) { + if (buffer[0] !== 0 && buffer[1] !== 1) { + return null; + } + i = 3; + while (buffer[i] !== 0) { + if (buffer[i] != 0xFF || ++i >= buffer.length) { + return null; + } + } + } else { + /* random padding for public key decrypt */ + if (buffer[0] !== 0 && buffer[1] !== 2) { + return null; + } + i = 3; + while (buffer[i] !== 0) { + if (++i >= buffer.length) { + return null; + } + } + } + return buffer.slice(i + 1, buffer.length); + }; + + Scheme.prototype.sign = function (buffer) { + var hashAlgorithm = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION; + if (this.options.environment === 'browser') { + hashAlgorithm = SIGN_ALG_TO_HASH_ALIASES[hashAlgorithm] || hashAlgorithm; + var hasher = CryptoJS[hashAlgorithm.toUpperCase()] (buffer.toString()); + var asBuffer = wordArrayToBuffer(hasher) + var paddedHash = this.pkcs1pad(asBuffer, hashAlgorithm); + var res = this.key.$doPrivate(new BigInteger(paddedHash)).toBuffer(this.key.encryptedDataLength); + return res; + } else { + throw new Error ('CCXT only supports browser mode :P'); + } + }; + + /** + * PKCS#1 pad input buffer to max data length + * @param hashBuf + * @param hashAlgorithm + * @returns {*} + */ + Scheme.prototype.pkcs1pad = function (hashBuf, hashAlgorithm) { + var digest = SIGN_INFO_HEAD[hashAlgorithm]; + if (!digest) { + throw Error('Unsupported hash algorithm'); + } + + var data = Buffer.concat([digest, hashBuf]); + + if (data.length + 10 > this.key.encryptedDataLength) { + throw Error('Key is too short for signing algorithm (' + hashAlgorithm + ')'); + } + + var filled = Buffer.alloc(this.key.encryptedDataLength - data.length - 1); + filled.fill(0xff, 0, filled.length - 1); + filled[0] = 1; + filled[filled.length - 1] = 0; + + var res = Buffer.concat([filled, data]); + + return res; + }; + + return new Scheme(key, options); +}; + +// used to convert `CryptoJS` wordArrays into `crypto` hex buffers +function wordToByteArray(word, length) { + var ba = [], + xFF = 0xFF; + if (length > 0) + ba.push(word >>> 24); + if (length > 1) + ba.push((word >>> 16) & xFF); + if (length > 2) + ba.push((word >>> 8) & xFF); + if (length > 3) + ba.push(word & xFF); + + return ba; +} + +function wordArrayToBuffer(wordArray) { + let length = undefined; + if (wordArray.hasOwnProperty("sigBytes") && wordArray.hasOwnProperty("words")) { + length = wordArray.sigBytes; + wordArray = wordArray.words; + } else { + throw Error('Argument not a wordArray') + } + + const result = [] + let bytes = [] + let i = 0; + while (length > 0) { + bytes = wordToByteArray(wordArray[i], Math.min(4, length)); + length -= bytes.length; + result.push(bytes); + i++; + } + return new Buffer.from([].concat.apply([], result), 'hex'); +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/schemes/schemes.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/schemes/schemes.js new file mode 100644 index 000000000..5faf139f2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/schemes/schemes.js @@ -0,0 +1,21 @@ +module.exports = { + pkcs1: require('./pkcs1'), + + /** + * Check if scheme has padding methods + * @param scheme {string} + * @returns {Boolean} + */ + isEncryption: function (scheme) { + return module.exports[scheme] && module.exports[scheme].isEncryption; + }, + + /** + * Check if scheme has sign/verify methods + * @param scheme {string} + * @returns {Boolean} + */ + isSignature: function (scheme) { + return module.exports[scheme] && module.exports[scheme].isSignature; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/utils.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/utils.js new file mode 100644 index 000000000..7d3c693f7 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/node-rsa/utils.js @@ -0,0 +1,98 @@ +/* + * Utils functions + * + */ + +/** + * Break string str each maxLen symbols + * @param str + * @param maxLen + * @returns {string} + */ +module.exports.linebrk = function (str, maxLen) { + var res = ''; + var i = 0; + while (i + maxLen < str.length) { + res += str.substring(i, i + maxLen) + "\n"; + i += maxLen; + } + return res + str.substring(i, str.length); +}; + +/** + * Trying get a 32-bit unsigned integer from the partial buffer + * @param buffer + * @param offset + * @returns {Number} + */ +module.exports.get32IntFromBuffer = function (buffer, offset) { + offset = offset || 0; + var size = 0; + if ((size = buffer.length - offset) > 0) { + if (size >= 4) { + return buffer.readUInt32BE(offset); + } else { + var res = 0; + for (var i = offset + size, d = 0; i > offset; i--, d += 2) { + res += buffer[i - 1] * Math.pow(16, d); + } + return res; + } + } else { + return NaN; + } +}; + +module.exports._ = { + isObject: function (value) { + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); + }, + + isString: function (value) { + return typeof value == 'string' || value instanceof String; + }, + + isNumber: function (value) { + return typeof value == 'number' || !isNaN(parseFloat(value)) && isFinite(value); + }, + + /** + * Returns copy of `obj` without `removeProp` field. + * @param obj + * @param removeProp + * @returns Object + */ + omit: function (obj, removeProp) { + var newObj = {}; + for (var prop in obj) { + if (!obj.hasOwnProperty(prop) || prop === removeProp) { + continue; + } + newObj[prop] = obj[prop]; + } + + return newObj; + } +}; + +/** + * Strips everything around the opening and closing lines, including the lines + * themselves. + */ +module.exports.trimSurroundingText = function (data, opening, closing) { + var trimStartIndex = 0; + var trimEndIndex = data.length; + + var openingBoundaryIndex = data.indexOf(opening); + if (openingBoundaryIndex >= 0) { + trimStartIndex = openingBoundaryIndex + opening.length; + } + + var closingBoundaryIndex = data.indexOf(closing, openingBoundaryIndex); + if (closingBoundaryIndex >= 0) { + trimEndIndex = closingBoundaryIndex; + } + + return data.substring(trimStartIndex, trimEndIndex); +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/formats.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/formats.js new file mode 100644 index 000000000..df4599752 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/formats.js @@ -0,0 +1,18 @@ +'use strict'; + +var replace = String.prototype.replace; +var percentTwenties = /%20/g; + +module.exports = { + 'default': 'RFC3986', + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return value; + } + }, + RFC1738: 'RFC1738', + RFC3986: 'RFC3986' +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/index.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/index.js new file mode 100644 index 000000000..0d6a97dcf --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/index.js @@ -0,0 +1,11 @@ +'use strict'; + +var stringify = require('./stringify'); +var parse = require('./parse'); +var formats = require('./formats'); + +module.exports = { + formats: formats, + parse: parse, + stringify: stringify +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/parse.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/parse.js new file mode 100644 index 000000000..d81628b57 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/parse.js @@ -0,0 +1,242 @@ +'use strict'; + +var utils = require('./utils'); + +var has = Object.prototype.hasOwnProperty; + +var defaults = { + allowDots: false, + allowPrototypes: false, + arrayLimit: 20, + charset: 'utf-8', + charsetSentinel: false, + comma: false, + decoder: utils.decode, + delimiter: '&', + depth: 5, + ignoreQueryPrefix: false, + interpretNumericEntities: false, + parameterLimit: 1000, + parseArrays: true, + plainObjects: false, + strictNullHandling: false +}; + +var interpretNumericEntities = function (str) { + return str.replace(/&#(\d+);/g, function ($0, numberStr) { + return String.fromCharCode(parseInt(numberStr, 10)); + }); +}; + +// This is what browsers will submit when the ✓ character occurs in an +// application/x-www-form-urlencoded body and the encoding of the page containing +// the form is iso-8859-1, or when the submitted form has an accept-charset +// attribute of iso-8859-1. Presumably also with other charsets that do not contain +// the ✓ character, such as us-ascii. +var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓') + +// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded. +var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓') + +var parseValues = function parseQueryStringValues(str, options) { + var obj = {}; + var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str; + var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit; + var parts = cleanStr.split(options.delimiter, limit); + var skipIndex = -1; // Keep track of where the utf8 sentinel was found + var i; + + var charset = options.charset; + if (options.charsetSentinel) { + for (i = 0; i < parts.length; ++i) { + if (parts[i].indexOf('utf8=') === 0) { + if (parts[i] === charsetSentinel) { + charset = 'utf-8'; + } else if (parts[i] === isoSentinel) { + charset = 'iso-8859-1'; + } + skipIndex = i; + i = parts.length; // The eslint settings do not allow break; + } + } + } + + for (i = 0; i < parts.length; ++i) { + if (i === skipIndex) { + continue; + } + var part = parts[i]; + + var bracketEqualsPos = part.indexOf(']='); + var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1; + + var key, val; + if (pos === -1) { + key = options.decoder(part, defaults.decoder, charset); + val = options.strictNullHandling ? null : ''; + } else { + key = options.decoder(part.slice(0, pos), defaults.decoder, charset); + val = options.decoder(part.slice(pos + 1), defaults.decoder, charset); + } + + if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { + val = interpretNumericEntities(val); + } + + if (val && options.comma && val.indexOf(',') > -1) { + val = val.split(','); + } + + if (has.call(obj, key)) { + obj[key] = utils.combine(obj[key], val); + } else { + obj[key] = val; + } + } + + return obj; +}; + +var parseObject = function (chain, val, options) { + var leaf = val; + + for (var i = chain.length - 1; i >= 0; --i) { + var obj; + var root = chain[i]; + + if (root === '[]' && options.parseArrays) { + obj = [].concat(leaf); + } else { + obj = options.plainObjects ? Object.create(null) : {}; + var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; + var index = parseInt(cleanRoot, 10); + if (!options.parseArrays && cleanRoot === '') { + obj = { 0: leaf }; + } else if ( + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) + ) { + obj = []; + obj[index] = leaf; + } else { + obj[cleanRoot] = leaf; + } + } + + leaf = obj; + } + + return leaf; +}; + +var parseKeys = function parseQueryStringKeys(givenKey, val, options) { + if (!givenKey) { + return; + } + + // Transform dot notation to bracket notation + var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey; + + // The regex chunks + + var brackets = /(\[[^[\]]*])/; + var child = /(\[[^[\]]*])/g; + + // Get the parent + + var segment = brackets.exec(key); + var parent = segment ? key.slice(0, segment.index) : key; + + // Stash the parent if it exists + + var keys = []; + if (parent) { + // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties + if (!options.plainObjects && has.call(Object.prototype, parent)) { + if (!options.allowPrototypes) { + return; + } + } + + keys.push(parent); + } + + // Loop through children appending to the array until we hit depth + + var i = 0; + while ((segment = child.exec(key)) !== null && i < options.depth) { + i += 1; + if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) { + if (!options.allowPrototypes) { + return; + } + } + keys.push(segment[1]); + } + + // If there's a remainder, just add whatever is left + + if (segment) { + keys.push('[' + key.slice(segment.index) + ']'); + } + + return parseObject(keys, val, options); +}; + +var normalizeParseOptions = function normalizeParseOptions(opts) { + if (!opts) { + return defaults; + } + + if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') { + throw new TypeError('Decoder has to be a function.'); + } + + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset; + + return { + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes, + arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma, + decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder, + delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter, + depth: typeof opts.depth === 'number' ? opts.depth : defaults.depth, + ignoreQueryPrefix: opts.ignoreQueryPrefix === true, + interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities, + parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit, + parseArrays: opts.parseArrays !== false, + plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (str, opts) { + var options = normalizeParseOptions(opts); + + if (str === '' || str === null || typeof str === 'undefined') { + return options.plainObjects ? Object.create(null) : {}; + } + + var tempObj = typeof str === 'string' ? parseValues(str, options) : str; + var obj = options.plainObjects ? Object.create(null) : {}; + + // Iterate over the keys and setup the new object + + var keys = Object.keys(tempObj); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var newObj = parseKeys(key, tempObj[key], options); + obj = utils.merge(obj, newObj, options); + } + + return utils.compact(obj); +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/stringify.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/stringify.js new file mode 100644 index 000000000..7455049ca --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/stringify.js @@ -0,0 +1,269 @@ +'use strict'; + +var utils = require('./utils'); +var formats = require('./formats'); +var has = Object.prototype.hasOwnProperty; + +var arrayPrefixGenerators = { + brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + return prefix + '[]'; + }, + comma: 'comma', + indices: function indices(prefix, key) { // eslint-disable-line func-name-matching + return prefix + '[' + key + ']'; + }, + repeat: function repeat(prefix) { // eslint-disable-line func-name-matching + return prefix; + } +}; + +var isArray = Array.isArray; +var push = Array.prototype.push; +var pushToArray = function (arr, valueOrArray) { + push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]); +}; + +var toISO = Date.prototype.toISOString; + +var defaults = { + addQueryPrefix: false, + allowDots: false, + charset: 'utf-8', + charsetSentinel: false, + delimiter: '&', + encode: true, + encoder: utils.encode, + encodeValuesOnly: false, + formatter: formats.formatters[formats['default']], + // deprecated + indices: false, + serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching + return toISO.call(date); + }, + skipNulls: false, + strictNullHandling: false +}; + +var stringify = function stringify( // eslint-disable-line func-name-matching + object, + prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset +) { + var obj = object; + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } else if (obj instanceof Date) { + obj = serializeDate(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = obj.join(','); + } + + if (obj === null) { + if (strictNullHandling) { + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix; + } + + obj = ''; + } + + if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { + if (encoder) { + var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); + return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; + } + return [formatter(prefix) + '=' + formatter(String(obj))]; + } + + var values = []; + + if (typeof obj === 'undefined') { + return values; + } + + var objKeys; + if (isArray(filter)) { + objKeys = filter; + } else { + var keys = Object.keys(obj); + objKeys = sort ? keys.sort(sort) : keys; + } + + for (var i = 0; i < objKeys.length; ++i) { + var key = objKeys[i]; + + if (skipNulls && obj[key] === null) { + continue; + } + + if (isArray(obj)) { + pushToArray(values, stringify( + obj[key], + typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); + } else { + pushToArray(values, stringify( + obj[key], + prefix + (allowDots ? '.' + key : '[' + key + ']'), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly, + charset + )); + } + } + + return values; +}; + +var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { + if (!opts) { + return defaults; + } + + if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') { + throw new TypeError('Encoder has to be a function.'); + } + + var charset = opts.charset || defaults.charset; + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + + var format = formats['default']; + if (typeof opts.format !== 'undefined') { + if (!has.call(formats.formatters, opts.format)) { + throw new TypeError('Unknown format option provided.'); + } + format = opts.format; + } + var formatter = formats.formatters[format]; + + var filter = defaults.filter; + if (typeof opts.filter === 'function' || isArray(opts.filter)) { + filter = opts.filter; + } + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, + charset: charset, + charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + formatter: formatter, + serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate, + skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls, + sort: typeof opts.sort === 'function' ? opts.sort : null, + strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling + }; +}; + +module.exports = function (object, opts) { + var obj = object; + var options = normalizeStringifyOptions(opts); + + var objKeys; + var filter; + + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } else if (isArray(options.filter)) { + filter = options.filter; + objKeys = filter; + } + + var keys = []; + + if (typeof obj !== 'object' || obj === null) { + return ''; + } + + var arrayFormat; + if (opts && opts.arrayFormat in arrayPrefixGenerators) { + arrayFormat = opts.arrayFormat; + } else if (opts && 'indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; + } else { + arrayFormat = 'indices'; + } + + var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; + + if (!objKeys) { + objKeys = Object.keys(obj); + } + + if (options.sort) { + objKeys.sort(options.sort); + } + + for (var i = 0; i < objKeys.length; ++i) { + var key = objKeys[i]; + + if (options.skipNulls && obj[key] === null) { + continue; + } + pushToArray(keys, stringify( + obj[key], + key, + generateArrayPrefix, + options.strictNullHandling, + options.skipNulls, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.formatter, + options.encodeValuesOnly, + options.charset + )); + } + + var joined = keys.join(options.delimiter); + var prefix = options.addQueryPrefix === true ? '?' : ''; + + if (options.charsetSentinel) { + if (options.charset === 'iso-8859-1') { + // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark + prefix += 'utf8=%26%2310003%3B&'; + } else { + // encodeURIComponent('✓') + prefix += 'utf8=%E2%9C%93&'; + } + } + + return joined.length > 0 ? prefix + joined : ''; +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/utils.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/utils.js new file mode 100644 index 000000000..1b219cdde --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/static_dependencies/qs/utils.js @@ -0,0 +1,230 @@ +'use strict'; + +var has = Object.prototype.hasOwnProperty; +var isArray = Array.isArray; + +var hexTable = (function () { + var array = []; + for (var i = 0; i < 256; ++i) { + array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); + } + + return array; +}()); + +var compactQueue = function compactQueue(queue) { + while (queue.length > 1) { + var item = queue.pop(); + var obj = item.obj[item.prop]; + + if (isArray(obj)) { + var compacted = []; + + for (var j = 0; j < obj.length; ++j) { + if (typeof obj[j] !== 'undefined') { + compacted.push(obj[j]); + } + } + + item.obj[item.prop] = compacted; + } + } +}; + +var arrayToObject = function arrayToObject(source, options) { + var obj = options && options.plainObjects ? Object.create(null) : {}; + for (var i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +}; + +var merge = function merge(target, source, options) { + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (isArray(target)) { + target.push(source); + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) { + target[source] = true; + } + } else { + return [target, source]; + } + + return target; + } + + if (!target || typeof target !== 'object') { + return [target].concat(source); + } + + var mergeTarget = target; + if (isArray(target) && !isArray(source)) { + mergeTarget = arrayToObject(target, options); + } + + if (isArray(target) && isArray(source)) { + source.forEach(function (item, i) { + if (has.call(target, i)) { + var targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); + } else { + target.push(item); + } + } else { + target[i] = item; + } + }); + return target; + } + + return Object.keys(source).reduce(function (acc, key) { + var value = source[key]; + + if (has.call(acc, key)) { + acc[key] = merge(acc[key], value, options); + } else { + acc[key] = value; + } + return acc; + }, mergeTarget); +}; + +var assign = function assignSingleSource(target, source) { + return Object.keys(source).reduce(function (acc, key) { + acc[key] = source[key]; + return acc; + }, target); +}; + +var decode = function (str, decoder, charset) { + var strWithoutPlus = str.replace(/\+/g, ' '); + if (charset === 'iso-8859-1') { + // unescape never throws, no try...catch needed: + return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape); + } + // utf-8 + try { + return decodeURIComponent(strWithoutPlus); + } catch (e) { + return strWithoutPlus; + } +}; + +var encode = function encode(str, defaultEncoder, charset) { + // This code was originally written by Brian White (mscdex) for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + var string = typeof str === 'string' ? str : String(str); + + if (charset === 'iso-8859-1') { + return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) { + return '%26%23' + parseInt($0.slice(2), 16) + '%3B'; + }); + } + + var out = ''; + for (var i = 0; i < string.length; ++i) { + var c = string.charCodeAt(i); + + if ( + c === 0x2D // - + || c === 0x2E // . + || c === 0x5F // _ + || c === 0x7E // ~ + || (c >= 0x30 && c <= 0x39) // 0-9 + || (c >= 0x41 && c <= 0x5A) // a-z + || (c >= 0x61 && c <= 0x7A) // A-Z + ) { + out += string.charAt(i); + continue; + } + + if (c < 0x80) { + out = out + hexTable[c]; + continue; + } + + if (c < 0x800) { + out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]); + continue; + } + + if (c < 0xD800 || c >= 0xE000) { + out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]); + continue; + } + + i += 1; + c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; + } + + return out; +}; + +var compact = function compact(value) { + var queue = [{ obj: { o: value }, prop: 'o' }]; + var refs = []; + + for (var i = 0; i < queue.length; ++i) { + var item = queue[i]; + var obj = item.obj[item.prop]; + + var keys = Object.keys(obj); + for (var j = 0; j < keys.length; ++j) { + var key = keys[j]; + var val = obj[key]; + if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { + queue.push({ obj: obj, prop: key }); + refs.push(val); + } + } + } + + compactQueue(queue); + + return value; +}; + +var isRegExp = function isRegExp(obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +}; + +var isBuffer = function isBuffer(obj) { + if (!obj || typeof obj !== 'object') { + return false; + } + + return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); +}; + +var combine = function combine(a, b) { + return [].concat(a, b); +}; + +module.exports = { + arrayToObject: arrayToObject, + assign: assign, + combine: combine, + compact: compact, + decode: decode, + encode: encode, + isBuffer: isBuffer, + isRegExp: isRegExp, + merge: merge +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/stex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/stex.js new file mode 100644 index 000000000..aaf34ea4a --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/stex.js @@ -0,0 +1,1802 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, AuthenticationError, ExchangeError, InsufficientFunds, OrderNotFound, PermissionDenied, BadRequest, DDoSProtection } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class stex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'stex', + 'name': 'STEX', // formerly known as stocks.exchange + 'countries': [ 'EE' ], // Estonia + 'rateLimit': 500, // https://help.stex.com/en/articles/2815043-api-3-rate-limits + 'certified': false, + // new metainfo interface + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, // limit orders only + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchFundingFees': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderTrades': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'version': 'v3', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/69680782-03fd0b80-10bd-11ea-909e-7f603500e9cc.jpg', + 'api': 'https://api3.stex.com', + 'www': 'https://www.stex.com', + 'doc': [ + 'https://help.stex.com/en/collections/1593608-api-v3-documentation', + ], + 'fees': 'https://app.stex.com/en/pairs-specification', + 'referral': 'https://app.stex.com?ref=36416021', + }, + 'requiredCredentials': { + 'apiKey': false, + 'secret': false, + 'token': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '30m': '30', + '1h': '60', + '4h': '240', + '12h': '720', + '1d': '1D', // default + }, + 'api': { + 'public': { + 'get': [ + 'currencies', // Available Currencies + 'currencies/{currencyId}', // Get currency info + 'markets', // Available markets + 'pairs-groups', // Available currency pairs groups (as displayed at stex trading page) + 'currency_pairs/list/{code}', // Available currency pairs + 'currency_pairs/group/{currencyPairGroupId}', // Available currency pairs for a given group + 'currency_pairs/{currencyPairId}', // Get currency pair information + 'ticker', // Tickers list for all currency pairs + 'ticker/{currencyPairId}', // Ticker for currency pair + 'trades/{currencyPairId}', // Trades for given currency pair + 'orderbook/{currencyPairId}', // Orderbook for given currency pair + 'chart/{currencyPairId}/{candlesType}', // A list of candles for given currency pair + 'deposit-statuses', // Available Deposit Statuses + 'deposit-statuses/{statusId}', // Get deposit status info + 'withdrawal-statuses', // Available Withdrawal Statuses + 'withdrawal-statuses/{statusId}', // Get status info + 'ping', // Test API is working and get server time + 'mobile-versions', // Shows the official mobile applications data + ], + }, + 'trading': { + 'get': [ + 'fees/{currencyPairId}', // Returns the user's fees for a given currency pair + 'orders', // List your currently open orders + 'orders/{currencyPairId}', // List your currently open orders for given currency pair + 'order/{orderId}', // Get a single order + ], + 'post': [ + 'orders/{currencyPairId}', // Create new order and put it to the orders processing queue + ], + 'delete': [ + 'orders', // Delete all active orders + 'orders/{currencyPairId}', // Delete active orders for given currency pair + 'order/{orderId}', // Cancel order + ], + }, + 'reports': { + 'get': [ + 'orders', // Get past orders + 'orders/{orderId}', // Get specified order details + 'trades/{currencyPairId}', // Get a list of user trades according to request parameters + 'background/{listMode}', // Get reports list for category + 'background/{id}', // Get some report info + 'background/download/{id}', // Get file by id + ], + 'post': [ + 'background/create', // Create new report + ], + 'delete': [ + 'background/{id}', // Remove report by id + ], + }, + 'profile': { + 'get': [ + 'info', // Account information + 'wallets', // Get a list of user wallets + 'wallets/{walletId}', // Single wallet information + 'wallets/address/{walletId}', // Get deposit address for given wallet + 'deposits', // Get a list of deposits made by user + 'deposits/{id}', // Get deposit by id + 'withdrawals', // Get a list of withdrawals made by user + 'withdrawals/{id}', // Get withdrawal by id + 'notifications', // Get notifications + 'favorite/currency_pairs', // Get favorite currency pairs + 'token-scopes', // Get current token scopes + ], + 'post': [ + 'wallets/burn/{walletId}', // Burns the given wallet + 'wallets/{currencyId}', // Create a wallet for given currency + 'wallets/address/{walletId}', // Create new deposit address + 'withdraw', // Create withdrawal request + 'referral/program', // Create referral program + 'referral/insert/{code}', // Insert referral code + 'referral/bonus_transfer/{currencyId}', // Transfer referral bonuses balance to main balance for given currency + ], + 'put': [ + 'profile/favorite/currency_pairs/set', // Set favorite currency pairs + ], + 'delete': [ + 'profile/withdraw/{withdrawalId}', // Cancel unconfirmed withdrawal + ], + }, + 'verification': { + 'get': [ + 'verification/countries', // Countries list, beta + 'verification/stex', // Get information about your KYC, beta + ], + 'post': [ + 'verification/stex', // Update information regarding of your KYC verification, beta + ], + }, + 'settings': { + 'get': [ + 'notifications/{event}', // User event notification settings + 'notifications', // User events notification settings + ], + 'put': [ + 'notifications', // Set notification settings + 'notifications/set', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.002, + 'maker': 0.002, + }, + }, + 'commonCurrencies': { + 'BC': 'Bitcoin Confidential', + 'BITS': 'Bitcoinus', + 'BITSW': 'BITS', + 'BHD': 'Bithold', + }, + 'options': { + 'parseOrderToPrecision': false, + }, + 'exceptions': { + 'exact': { + // {"success":false,"message":"Wrong parameters","errors":{"candleType":["Invalid Candle Type!"]}} + // {"success":false,"message":"Wrong parameters","errors":{"time":["timeStart or timeEnd is less then 1"]}} + 'Wrong parameters': BadRequest, + 'Unauthenticated.': AuthenticationError, // {"message":"Unauthenticated."} + 'Server Error': ExchangeError, // { "message": "Server Error" } + 'This feature is only enabled for users verifies by Cryptonomica': PermissionDenied, // {"success":false,"message":"This feature is only enabled for users verifies by Cryptonomica"} + 'Too Many Attempts.': DDoSProtection, // { "message": "Too Many Attempts." } + }, + 'broad': { + 'Not enough': InsufficientFunds, // {"success":false,"message":"Not enough ETH"} + }, + }, + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "success":true, + // "data":[ + // { + // "id":1, + // "code":"BTC", + // "name":"Bitcoin", + // "active":true, + // "delisted":false, + // "precision":8, + // "minimum_tx_confirmations":1, + // "minimum_withdrawal_amount":"0.00200000", + // "minimum_deposit_amount":"0.00000000", + // "deposit_fee_currency_id":1, + // "deposit_fee_currency_code":"BTC", + // "deposit_fee_const":"0.00000000", + // "deposit_fee_percent":"0.00000000", + // "withdrawal_fee_currency_id":1, + // "withdrawal_fee_currency_code":"BTC", + // "withdrawal_fee_const":"0.00100000", + // "withdrawal_fee_percent":"0.00000000", + // "block_explorer_url":"https:\/\/blockchain.info\/tx\/", + // "protocol_specific_settings":null + // }, + // ] + // } + // + const result = {}; + const currencies = this.safeValue (response, 'data', []); + for (let i = 0; i < currencies.length; i++) { + const currency = currencies[i]; + const id = this.safeString (currency, 'id'); + const numericId = this.safeInteger (currency, 'id'); + // todo: will need to rethink the fees + // to add support for multiple withdrawal/deposit methods and + // differentiated fees for each particular method + const code = this.safeCurrencyCode (this.safeString (currency, 'code')); + const precision = this.safeInteger (currency, 'precision'); + const fee = this.safeFloat (currency, 'withdrawal_fee_const'); // todo: redesign + const active = this.safeValue (currency, 'active', true); + result[code] = { + 'id': id, + 'numericId': numericId, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': this.safeString (currency, 'name'), + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'amount': { 'min': Math.pow (10, -precision), 'max': undefined }, + 'price': { 'min': Math.pow (10, -precision), 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + 'deposit': { + 'min': this.safeFloat (currency, 'minimum_deposit_amount'), + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minimum_withdrawal_amount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + async fetchMarkets (params = {}) { + const request = { + 'code': 'ALL', + }; + const response = await this.publicGetCurrencyPairsListCode (this.extend (request, params)); + // + // { + // "success":true, + // "data":[ + // { + // "id":935, + // "currency_id":662, + // "currency_code":"ABET", + // "currency_name":"Altbet", + // "market_currency_id":1, + // "market_code":"BTC", + // "market_name":"Bitcoin", + // "min_order_amount":"0.00000010", + // "min_buy_price":"0.00000001", + // "min_sell_price":"0.00000001", + // "buy_fee_percent":"0.20000000", + // "sell_fee_percent":"0.20000000", + // "active":true, + // "delisted":false, + // "pair_message":"", + // "currency_precision":8, + // "market_precision":8, + // "symbol":"ABET_BTC", + // "group_name":"BTC", + // "group_id":1 + // } + // ] + // } + // + const result = []; + const markets = this.safeValue (response, 'data', []); + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const numericId = this.safeInteger (market, 'id'); + const baseId = this.safeString (market, 'currency_id'); + const quoteId = this.safeString (market, 'market_currency_id'); + const baseNumericId = this.safeInteger (market, 'currency_id'); + const quoteNumericId = this.safeInteger (market, 'market_currency_id'); + const base = this.safeCurrencyCode (this.safeString (market, 'currency_code')); + const quote = this.safeCurrencyCode (this.safeString (market, 'market_code')); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'currency_precision'), + 'price': this.safeInteger (market, 'market_precision'), + }; + const active = this.safeValue (market, 'active'); + const minBuyPrice = this.safeFloat (market, 'min_buy_price'); + const minSellPrice = this.safeFloat (market, 'min_sell_price'); + const minPrice = Math.max (minBuyPrice, minSellPrice); + const buyFee = this.safeFloat (market, 'buy_fee_percent') / 100; + const sellFee = this.safeFloat (market, 'sell_fee_percent') / 100; + const fee = Math.max (buyFee, sellFee); + result.push ({ + 'id': id, + 'numericId': numericId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'baseNumericId': baseNumericId, + 'quoteNumericId': quoteNumericId, + 'info': market, + 'active': active, + 'maker': fee, + 'taker': fee, + 'precision': precision, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'min_order_amount'), + 'max': undefined, + }, + 'price': { 'min': minPrice, 'max': undefined }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + }; + const response = await this.publicGetTickerCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 2, + // "amount_multiplier": 1, + // "currency_code": "ETH", + // "market_code": "BTC", + // "currency_name": "Ethereum", + // "market_name": "Bitcoin", + // "symbol": "ETH_BTC", + // "group_name": "BTC", + // "group_id": 1, + // "ask": "0.02069998", + // "bid": "0.02028622", + // "last": "0.02049224", + // "open": "0.02059605", + // "low": "0.01977744", + // "high": "0.02097005", + // "volume": "480.43248971", + // "volumeQuote": "23491.29826130", + // "count": "7384", + // "fiatsRate": { + // "USD": 7230.86, + // "EUR": 6590.79, + // "UAH": 173402, + // "AUD": 10595.51, + // "IDR": 101568085, + // "CNY": 50752, + // "KRW": 8452295, + // "JPY": 784607, + // "VND": 167315119, + // "INR": 517596, + // "GBP": 5607.25, + // "CAD": 9602.63, + // "BRL": 30472, + // "RUB": 460718 + // }, + // "timestamp": 1574698235601 + // } + // } + // + const ticker = this.safeValue (response, 'data', {}); + return this.parseTicker (ticker, market); + } + + async fetchTime (params = {}) { + const response = await this.publicGetPing (params); + // + // { + // "success": true, + // "data": { + // "server_datetime": { + // "date": "2019-01-22 15:13:34.233796", + // "timezone_type": 3, + // "timezone": "UTC" + // }, + // "server_timestamp": 1548170014 + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const serverDatetime = this.safeValue (data, 'server_datetime', {}); + return this.parse8601 (this.safeString (serverDatetime, 'date')); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + }; + if (limit !== undefined) { + request['limit_bids'] = limit; // returns all if set to 0, default 100 + request['limit_asks'] = limit; // returns all if set to 0, default 100 + } + const response = await this.publicGetOrderbookCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "ask": [ + // { "currency_pair_id": 2, "amount": "2.17865373", "price": "0.02062917", "amount2": "0.04494382", "count": 1, "cumulative_amount": 2.17865373 }, + // { "currency_pair_id": 2, "amount": "2.27521743", "price": "0.02062918", "amount2": "0.04693587", "count": 1, "cumulative_amount": 4.45387116 }, + // { "currency_pair_id": 2, "amount": "1.26980049", "price": "0.02063170", "amount2": "0.02619814", "count": 1, "cumulative_amount": 5.72367165 }, + // ], + // "bid": [ + // { "currency_pair_id": 2, "amount": "0.00978005", "price": "0.02057000", "amount2": "0.00020118", "count": 1, "cumulative_amount": 0.00978005 }, + // { "currency_pair_id": 2, "amount": "0.00500000", "price": "0.02056000", "amount2": "0.00010280", "count": 1, "cumulative_amount": 0.01478005 }, + // { "currency_pair_id": 2, "amount": "0.77679882", "price": "0.02054001", "amount2": "0.01595546", "count": 1, "cumulative_amount": 0.79157887 }, + // ], + // "ask_total_amount": 2555.749174609999, + // "bid_total_amount": 29.180037330000005 + // } + // } + // + const orderbook = this.safeValue (response, 'data', {}); + return this.parseOrderBook (orderbook, undefined, 'bid', 'ask', 'price', 'amount'); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "id": 2, + // "amount_multiplier": 1, + // "currency_code": "ETH", + // "market_code": "BTC", + // "currency_name": "Ethereum", + // "market_name": "Bitcoin", + // "symbol": "ETH_BTC", + // "group_name": "BTC", + // "group_id": 1, + // "ask": "0.02069998", + // "bid": "0.02028622", + // "last": "0.02049224", + // "open": "0.02059605", + // "low": "0.01977744", + // "high": "0.02097005", + // "volume": "480.43248971", + // "volumeQuote": "23491.29826130", + // "count": "7384", + // "fiatsRate": { + // "USD": 7230.86, + // "EUR": 6590.79, + // "UAH": 173402, + // "AUD": 10595.51, + // "IDR": 101568085, + // "CNY": 50752, + // "KRW": 8452295, + // "JPY": 784607, + // "VND": 167315119, + // "INR": 517596, + // "GBP": 5607.25, + // "CAD": 9602.63, + // "BRL": 30472, + // "RUB": 460718 + // }, + // "timestamp": 1574698235601 + // } + // + const timestamp = this.safeInteger (ticker, 'timestamp'); + const marketId = this.safeString2 (ticker, 'id', 'symbol'); + const symbol = this.safeSymbol (marketId, market, '_'); + const last = this.safeFloat (ticker, 'last'); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let percentage = undefined; + if (last !== undefined) { + if ((open !== undefined) && (open > 0)) { + change = last - open; + percentage = ((100 / open) * last) - 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, // previous day close + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volumeQuote'), + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + parseTickers (tickers, symbols = undefined) { + const result = []; + for (let i = 0; i < tickers.length; i++) { + result.push (this.parseTicker (tickers[i])); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + // + // { + // "success":true, + // "data":[ + // { + // "id":262, + // "amount_multiplier":1, + // "currency_code":"ARDR", + // "market_code":"BTC", + // "currency_name":"ARDOR", + // "market_name":"Bitcoin", + // "symbol":"ARDR_BTC", + // "group_name":"BTC", + // "group_id":1, + // "ask":"0.00000630", + // "bid":"0.00000613", + // "last":"0.00000617", + // "open":"0.00000620", + // "low":"0.00000614", + // "high":"0.00000630", + // "volume":"30.37795305", + // "volumeQuote":"4911487.01996544", + // "count":"710", + // "fiatsRate":{ + // "USD":7230.86, + // "EUR":6590.79, + // "UAH":173402, + // "AUD":10744.52, + // "IDR":101568085, + // "CNY":50752, + // "KRW":8452295, + // "JPY":784607, + // "VND":167315119, + // "INR":517596, + // "GBP":5607.25, + // "CAD":9602.63, + // "BRL":30472, + // "RUB":467358 + // }, + // "timestamp":1574698617304, + // "group_position":1 + // }, + // ] + // } + // + const tickers = this.safeValue (response, 'data', []); + return this.parseTickers (tickers, symbols); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "time": 1566086400000, + // "close": 0.01895, + // "open": 0.01812427, + // "high": 0.0191588, + // "low": 0.01807001, + // "volume": 2588.597813750006 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1d', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + 'candlesType': this.timeframes[timeframe], // default 1d + // 'timeStart': 1574709092, // unix timestamp in seconds, required + // 'timeEnd': 1574709092, // unix timestamp in seconds, required + // 'limit': 100, // default 100, optional + // 'offset' 100, // optional, pagination within timerange + }; + if (limit === undefined) { + limit = 100; + } else { + request['limit'] = limit; + } + const duration = this.parseTimeframe (timeframe); + const timerange = limit * duration; + if (since === undefined) { + request['timeEnd'] = this.seconds (); + request['timeStart'] = request['timeEnd'] - timerange; + } else { + request['timeStart'] = parseInt (since / 1000); + request['timeEnd'] = this.sum (request['timeStart'], timerange); + } + const response = await this.publicGetChartCurrencyPairIdCandlesType (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "time": 1566086400000, + // "close": 0.01895, + // "open": 0.01812427, + // "high": 0.0191588, + // "low": 0.01807001, + // "volume": 2588.597813750006 + // }, + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "id": 35989317, + // "price": "0.02033813", + // "amount": "3.60000000", + // "type": "BUY", + // "timestamp": "1574713503" + // } + // + // private fetchMyTrades, fetchClosedOrder, fetchOrderTrades + // + // { + // "id": 658745, + // "buy_order_id": 6587453, + // "sell_order_id": 6587459, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // + const id = this.safeString (trade, 'id'); + const timestamp = this.safeTimestamp (trade, 'timestamp'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + let symbol = undefined; + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const side = this.safeStringLower2 (trade, 'type', 'trade_type'); + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': undefined, + 'type': undefined, + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + // 'sort': 'ASC', // ASC or DESC, default DESC + // 'from': 1574709092, // unix timestamp, optional + // 'till': 1574709092, // unix timestamp, optional + // 'limit': 100, // default 100, optional + // 'offset': 100, // optional + }; + if (limit !== undefined) { + request['limit'] = limit; // currently limited to 100 or fewer + } + if (since !== undefined) { + request['sort'] = 'ASC'; // needed to make the from param work + request['from'] = parseInt (since / 1000); + } + const response = await this.publicGetTradesCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 35989317, + // "price": "0.02033813", + // "amount": "3.60000000", + // "type": "BUY", + // "timestamp": "1574713503" + // }, + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + // await this.loadAccounts (); + const response = await this.profileGetWallets (params); + // + // { + // "success": true, + // "data": [ + // { + // "id": null, + // "currency_id": 665, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "currency_code": "ORM", + // "currency_name": "Orium", + // "currency_type_id": 5, + // "balance": "0", + // "frozen_balance": "0", + // "bonus_balance": "0", + // "total_balance": "0", + // "protocol_specific_settings": null, + // "rates": { "BTC": "0.00000000020", "USD": "0.00000147" }, + // }, + // { + // "id": null, + // "currency_id": 272, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "currency_code": "USDT", + // "currency_name": "TetherUSD", + // "currency_type_id": 23, + // "balance": "0", + // "frozen_balance": "0", + // "bonus_balance": "0", + // "total_balance": "0", + // "protocol_specific_settings": [ + // { "protocol_name": "OMNI", "protocol_id": 10, "active": true, "withdrawal_fee_currency_id": 272, "withdrawal_fee_const": 10, "withdrawal_fee_percent": 0, "block_explorer_url": "https://omniexplorer.info/search/" }, + // { "protocol_name": "ERC20", "protocol_id": 5, "active": true, "withdrawal_fee_const": 1.2, "withdrawal_fee_percent": 0, "block_explorer_url": "https://etherscan.io/tx/" }, + // { "protocol_name": "TRON", "protocol_id": 24, "active": true, "withdrawal_fee_currency_id": 272, "withdrawal_fee_const": 0.2, "withdrawal_fee_percent": 0, "block_explorer_url": "https://tronscan.org/#/transaction/" } + // ], + // "rates": { "BTC": "0.00013893", "USD": "1" }, + // }, + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'data', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const code = this.safeCurrencyCode (this.safeString (balance, 'currency_id')); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'frozen_balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOrderStatus (status) { + const statuses = { + 'PROCESSING': 'open', + 'PENDING': 'open', + 'PARTIAL': 'open', + 'FINISHED': 'closed', + 'CANCELLED': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder, fetchOpenOrders, fetchClosedOrders, cancelOrder, fetchOrder, fetchClosedOrder + // + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", // missing in fetchClosedOrder + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // // fetchClosedOrder only + // "trades": [ + // { + // "id": 658745, + // "buy_order_id": 658745, + // "sell_order_id": 828680665, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // ], + // // fetchClosedOrder only + // "fees": [ + // { + // "id": 1234567, + // "currency_id": 1, + // "amount": 0.00025, + // "timestamp": "1548149238" + // } + // ] + // } + // + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const marketId = this.safeString2 (order, 'currency_pair_id', 'currency_pair_name'); + const symbol = this.safeSymbol (marketId, market, '_'); + const timestamp = this.safeTimestamp (order, 'timestamp'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'initial_amount'); + const filled = this.safeFloat (order, 'processed_amount'); + let remaining = undefined; + let cost = undefined; + if (filled !== undefined) { + if (amount !== undefined) { + remaining = amount - filled; + if (this.options['parseOrderToPrecision']) { + remaining = parseFloat (this.amountToPrecision (symbol, remaining)); + } + remaining = Math.max (remaining, 0.0); + } + if (price !== undefined) { + if (cost === undefined) { + cost = price * filled; + } + } + } + let type = this.safeString (order, 'original_type'); + if ((type === 'BUY') || (type === 'SELL')) { + type = undefined; + } + const side = this.safeStringLower (order, 'type'); + const rawTrades = this.safeValue (order, 'trades'); + let trades = undefined; + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'symbol': symbol, + 'order': id, + }); + } + const stopPrice = this.safeFloat (order, 'trigger_price'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'trades': trades, + }; + const fees = this.safeValue (order, 'fees'); + if (fees === undefined) { + result['fee'] = undefined; + } else { + const numFees = fees.length; + if (numFees > 0) { + result['fees'] = []; + for (let i = 0; i < fees.length; i++) { + const feeCost = this.safeFloat (fees[i], 'amount'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (fees[i], 'currency_id'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + result['fees'].push ({ + 'cost': feeCost, + 'currency': feeCurrencyCode, + }); + } + } + } else { + result['fee'] = undefined; + } + } + return result; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' createOrder allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + if (type === 'limit') { + type = side; + } + const request = { + 'currencyPairId': market['id'], + 'type': type.toUpperCase (), // 'BUY', 'SELL', 'STOP_LIMIT_BUY', 'STOP_LIMIT_SELL' + 'amount': parseFloat (this.amountToPrecision (symbol, amount)), // required + 'price': parseFloat (this.priceToPrecision (symbol, price)), // required + // 'trigger_price': 123.45 // required for STOP_LIMIT_BUY or STOP_LIMIT_SELL + }; + const response = await this.tradingPostOrdersCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseOrder (data, market); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.tradingGetOrderOrderId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrder (data, market); + } + + async fetchClosedOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.reportsGetOrdersOrderId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 5478965, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.00013800", + // "initial_amount": "1.00000000", + // "type": "BUY", + // "created": "2019-01-22 09:27:17", + // "timestamp": 1548149237, + // "status": "FINISHED", + // "trades": [ + // { + // "id": 658745, + // "buy_order_id": 6587453, + // "sell_order_id": 6587459, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // ], + // "fees": [ + // { + // "id": 1234567, + // "currency_id": 1, + // "amount": 0.00025, + // "timestamp": "1548149238" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + return this.parseOrder (data, market); + } + + async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { + const order = await this.fetchClosedOrder (id, symbol, params); + return order['trades']; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + let method = 'tradingGetOrders'; + const request = { + // 'limit': 100, // default 100 + // 'offset': 100, + }; + if (symbol !== undefined) { + method = 'tradingGetOrdersCurrencyPairId'; + market = this.market (symbol); + request['currencyPairId'] = market['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderId': id, + }; + const response = await this.tradingDeleteOrderOrderId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "put_into_processing_queue": [ + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // ], + // "not_put_into_processing_queue": [ + // { + // "id": 828680665, + // "currency_pair_id": 1, + // "currency_pair_name": "NXT_BTC", + // "price": "0.011384", + // "trigger_price": 0.011385, + // "initial_amount": "13.942", + // "processed_amount": "3.724", + // "type": "SELL", + // "original_type": "STOP_LIMIT_SELL", + // "created": "2019-01-17 10:14:48", + // "timestamp": "1547720088", + // "status": "PARTIAL" + // } + // ], + // "message": "string" + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const acceptedOrders = this.safeValue (data, 'put_into_processing_queue', []); + const rejectedOrders = this.safeValue (data, 'not_put_into_processing_queue', []); + const numAcceptedOrders = acceptedOrders.length; + const numRejectedOrders = rejectedOrders.length; + if (numAcceptedOrders < 1) { + if (numRejectedOrders < 1) { + throw new OrderNotFound (this.id + ' cancelOrder received an empty response: ' + this.json (response)); + } else { + return this.parseOrder (rejectedOrders[0]); + } + } else { + if (numRejectedOrders < 1) { + return this.parseOrder (acceptedOrders[0]); + } else { + throw new OrderNotFound (this.id + ' cancelOrder received an empty response: ' + this.json (response)); + } + } + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let method = 'tradingDeleteOrders'; + if (symbol !== undefined) { + const market = this.market (symbol); + request['currencyPairId'] = market['id']; + method = 'tradingDeleteOrdersCurrencyPairId'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "success":true, + // "data":{ + // "put_into_processing_queue":[], + // "not_put_into_processing_queue":[], + // "message":"Orders operations are handled in processing queue, therefore cancelling is not immediate." + // } + // } + // + return response; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currencyPairId': market['id'], + // 'timeStart': '2019-11-26T19:54:55.901Z', // datetime in iso format + // 'timeEnd': '2019-11-26T19:54:55.901Z', // datetime in iso format + // 'limit': 100, // default 100 + // 'offset': 100, + }; + if (since !== undefined) { + request['timeStart'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.reportsGetTradesCurrencyPairId (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 658745, + // "buy_order_id": 6587453, + // "sell_order_id": 6587459, + // "price": 0.012285, + // "amount": 6.35, + // "trade_type": "SELL", + // "timestamp": "1538737692" + // } + // ] + // } + // + const trades = this.safeValue (response, 'data', []); + return this.parseTrades (trades, market, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currencyId': currency['id'], + // Default value is the value that represents legacy protocol. + // In case of USDT it is 10 as Tether OMNI was the default previously. + // The list of protocols can be obtained from the /public/currencies/{currencyId} + // 'protocol_id': 10, + }; + const response = await this.profilePostWalletsCurrencyId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 45875, + // "currency_id": 1, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "code": "BTC", + // "balance": "0.198752", + // "frozen_balance": "1.5784", + // "bonus_balance": "0.000", + // "deposit_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // }, + // "multi_deposit_addresses": [ + // { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // ], + // "withdrawal_additional_field_name": "Payment ID (optional)", + // "rates": { "BTC": 0.000001 }, + // "protocol_specific_settings": [ + // { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "active": true, + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_const": 0.002, + // "withdrawal_fee_percent": 0, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', {}); + const depositAddress = this.safeValue (data, 'deposit_address', {}); + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'additional_address_parameter'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const balance = await this.fetchBalance (); + const wallets = this.safeValue (balance['info'], 'data', []); + const walletsByCurrencyId = this.indexBy (wallets, 'currency_id'); + const currency = this.currency (code); + const wallet = this.safeValue (walletsByCurrencyId, currency['id']); + if (wallet === undefined) { + throw new ExchangeError (this.id + ' fetchDepositAddress() could not find the wallet id for currency code ' + code + ', try to call createDepositAddress() first'); + } + const walletId = this.safeInteger (wallet, 'id'); + if (walletId === undefined) { + throw new ExchangeError (this.id + ' fetchDepositAddress() could not find the wallet id for currency code ' + code + ', try to call createDepositAddress() first'); + } + const request = { + 'walletId': walletId, + }; + const response = await this.profileGetWalletsWalletId (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 45875, + // "currency_id": 1, + // "delisted": false, + // "disabled": false, + // "disable_deposits": false, + // "code": "BTC", + // "balance": "0.198752", + // "frozen_balance": "1.5784", + // "bonus_balance": "0.000", + // "deposit_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // }, + // "multi_deposit_addresses": [ + // { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // ], + // "withdrawal_additional_field_name": "Payment ID (optional)", + // "rates": { "BTC": 0.000001 }, + // "protocol_specific_settings": [ + // { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "active": true, + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_const": 0.002, + // "withdrawal_fee_percent": 0, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // ] + // } + // } + // + const data = this.safeValue (response, 'data', []); + const depositAddress = this.safeValue (data, 'deposit_address', {}); + const address = this.safeString (depositAddress, 'address'); + const tag = this.safeString (depositAddress, 'additional_address_parameter'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + api + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + headers = { + 'Authorization': 'Bearer ' + this.token, + }; + if (method === 'GET' || method === 'DELETE') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + body = this.json (query); + if (Object.keys (query).length) { + headers['Content-Type'] = 'application/json'; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + parseTransactionStatus (status) { + const statuses = { + 'processing': 'pending', + 'checking by system': 'pending', + 'hodl': 'pending', + 'amount too low': 'failed', + 'not confirmed': 'pending', + 'cancelled by User': 'canceled', + 'approved': 'pending', + 'finished': 'ok', + 'withdrawal error': 'failed', + 'deposit error': 'failed', + 'cancelled by admin': 'canceled', + 'awaiting': 'pending', + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "id": 123654789, + // "currency_id": 1, + // "currency_code": "BTC", + // "deposit_fee_currency_id": 1, + // "deposit_fee_currency_code": "BTC", + // "amount": 0.25, + // "fee": 0.00025, + // "txid": "qwertyuhgfdsasdfgh", + // "protocol_id": 0, + // "deposit_status_id": 1, + // "status": "PROCESSING", + // "status_color": "#BC3D51", + // "created_at": "2018-11-28 12:32:08", + // "timestamp": "1543409389", + // "confirmations": "1 of 2" + // } + // + // fetchWithdrawals + // + // { + // "id": 65899, + // "amount": "0.00600000", + // "currency_id": 1, + // "currency_code": "BTC", + // "fee": "0.00400000", + // "fee_currency_id": 1, + // "fee_currency_code": "BTC", + // "withdrawal_status_id": 1, + // "status": "Not Confirmed", + // "status_color": "#BC3D51", + // "created_at": "2019-01-21 09:36:05", + // "created_ts": "1548063365", + // "updated_at": "2019-01-21 09:36:05", + // "updated_ts": "1548063365", + // "txid": null, + // "protocol_id": 0, + // "withdrawal_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // } + // + const id = this.safeString (transaction, 'id'); + const withdrawalAddress = this.safeValue (transaction, 'withdrawal_address', {}); + const address = this.safeString (withdrawalAddress, 'address'); + const tag = this.safeString (withdrawalAddress, 'additional_address_parameter'); + const currencyId = this.safeString (transaction, 'currency_id'); + let code = undefined; + if (currencyId in this.currencies_by_id) { + currency = this.currencies_by_id[currencyId]; + } else { + code = this.commonCurrencyCode (this.safeString (transaction, 'currency_code')); + } + if ((code === undefined) && (currency !== undefined)) { + code = currency['code']; + } + const type = ('deposit_status_id' in transaction) ? 'deposit' : 'withdrawal'; + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeStringLower (transaction, 'status')); + const timestamp = this.safeTimestamp2 (transaction, 'timestamp', 'created_ts'); + const updated = this.safeTimestamp (transaction, 'updated_ts'); + const txid = this.safeString (transaction, 'txid'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (transaction, 'fee_currency_id', 'deposit_fee_currency_id'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': undefined, + 'address': address, + 'addressTo': address, + 'tagFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currencyId'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timeStart'] = since; + } + const response = await this.profileGetDeposits (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 123654789, + // "currency_id": 1, + // "currency_code": "BTC", + // "deposit_fee_currency_id": 1, + // "deposit_fee_currency_code": "BTC", + // "amount": 0.25, + // "fee": 0.00025, + // "txid": "qwertyuhgfdsasdfgh", + // "protocol_id": 0, + // "deposit_status_id": 1, + // "status": "PROCESSING", + // "status_color": "#BC3D51", + // "created_at": "2018-11-28 12:32:08", + // "timestamp": "1543409389", + // "confirmations": "1 of 2", + // "protocol_specific_settings": { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // } + // ] + // } + // + const deposits = this.safeValue (response, 'data', []); + return this.parseTransactions (deposits, code, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let currency = undefined; + const request = {}; + if (code !== undefined) { + currency = this.currency (code); + request['currencyId'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timeStart'] = since; + } + const response = await this.profileGetWithdrawals (this.extend (request, params)); + // + // { + // "success": true, + // "data": [ + // { + // "id": 65899, + // "amount": "0.00600000", + // "currency_id": 1, + // "currency_code": "BTC", + // "fee": "0.00400000", + // "fee_currency_id": 1, + // "fee_currency_code": "BTC", + // "withdrawal_status_id": 1, + // "status": "Not Confirmed", + // "status_color": "#BC3D51", + // "created_at": "2019-01-21 09:36:05", + // "created_ts": "1548063365", + // "updated_at": "2019-01-21 09:36:05", + // "updated_ts": "1548063365", + // "txid": null, + // "protocol_id": 0, + // "withdrawal_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // }, + // "protocol_specific_settings": { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // } + // ] + // } + // + const withdrawals = this.safeValue (response, 'data', []); + return this.parseTransactions (withdrawals, code, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency_id': currency['id'], + 'amount': parseFloat (this.currencyToPrecision (code, amount)), + 'address': address, + // 'protocol_id': 10, // optional, to be used with multicurrency wallets like USDT + // 'additional_address_parameter': tag, // optional + }; + if (tag !== undefined) { + request['additional_address_parameter'] = tag; + } + const response = await this.profilePostWithdraw (this.extend (request, params)); + // + // { + // "success": true, + // "data": { + // "id": 65899, + // "amount": "0.00600000", + // "currency_id": 1, + // "currency_code": "BTC", + // "fee": "0.00400000", + // "fee_currency_id": 1, + // "fee_currency_code": "BTC", + // "withdrawal_status_id": 1, + // "status": "Not Confirmed", + // "status_color": "#BC3D51", + // "created_at": "2019-01-21 09:36:05", + // "created_ts": "1548063365", + // "updated_at": "2019-01-21 09:36:05", + // "updated_ts": "1548063365", + // "txid": null, + // "protocol_id": 0, + // "withdrawal_address": { + // "address": "0X12WERTYUIIJHGFVBNMJHGDFGHJ765SDFGHJ", + // "address_name": "Address", + // "additional_address_parameter": "qwertyuiopasdfghjkl", + // "additional_address_parameter_name": "Destination Tag", + // "notification": "", + // "protocol_id": 10, + // "protocol_name": "Tether OMNI", + // "supports_new_address_creation": false + // } + // } + // } + // + const data = this.safeValue (response, 'data', {}); + return this.parseTransaction (data, currency); + } + + async fetchFundingFees (codes = undefined, params = {}) { + const response = await this.publicGetCurrencies (params); + // + // { + // "success": true, + // "data": [ + // { + // "id": 1, + // "code": "BTC", + // "name": "Bitcoin", + // "active": true, + // "delisted": false, + // "precision": 8, + // "minimum_tx_confirmations": 24, + // "minimum_withdrawal_amount": "0.009", + // "minimum_deposit_amount": "0.000003", + // "deposit_fee_currency_id": 1, + // "deposit_fee_currency_code": "ETH", + // "deposit_fee_const": "0.00001", + // "deposit_fee_percent": "0", + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_currency_code": "ETH", + // "withdrawal_fee_const": "0.0015", + // "withdrawal_fee_percent": "0", + // "withdrawal_limit": "string", + // "block_explorer_url": "https://blockchain.info/tx/", + // "protocol_specific_settings": [ + // { + // "protocol_name": "Tether OMNI", + // "protocol_id": 10, + // "active": true, + // "withdrawal_fee_currency_id": 1, + // "withdrawal_fee_const": 0.002, + // "withdrawal_fee_percent": 0, + // "block_explorer_url": "https://omniexplorer.info/search/" + // } + // ] + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const withdrawFees = {}; + const depositFees = {}; + for (let i = 0; i < data.length; i++) { + const id = this.safeString (data[i], 'id'); + const code = this.safeCurrencyCode (id); + withdrawFees[code] = this.safeFloat (data[i], 'withdrawal_fee_const'); + depositFees[code] = this.safeFloat (data[i], 'deposit_fee_const'); + } + return { + 'withdraw': withdrawFees, + 'deposit': depositFees, + 'info': response, + }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // {"success":false,"message":"Wrong parameters","errors":{"candleType":["Invalid Candle Type!"]}} + // {"success":false,"message":"Wrong parameters","errors":{"time":["timeStart or timeEnd is less then 1"]}} + // {"success":false,"message":"Not enough ETH"} + // + const success = this.safeValue (response, 'success', false); + if (!success) { + const message = this.safeString (response, 'message'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/surbitcoin.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/surbitcoin.js new file mode 100644 index 000000000..b67310033 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/surbitcoin.js @@ -0,0 +1,32 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const foxbit = require ('./foxbit.js'); + +// --------------------------------------------------------------------------- + +module.exports = class surbitcoin extends foxbit { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'surbitcoin', + 'name': 'SurBitcoin', + 'countries': [ 'VE' ], + 'has': { + 'CORS': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27991511-f0a50194-6481-11e7-99b5-8f02932424cc.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://surbitcoin.com', + 'doc': 'https://blinktrade.com/docs', + }, + 'options': { + 'brokerId': '1', // https://blinktrade.com/docs/#brokers + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/.eslintrc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/.eslintrc.js new file mode 100644 index 000000000..a29b92fbf --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/.eslintrc.js @@ -0,0 +1,43 @@ +'use strict'; + +module.exports = { + "env": { + "node": true, + "mocha": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "script", + }, + "extends": ["eslint:recommended"], + "rules": { + "semi": ["error", "never"], + "no-unused-vars": ["off"], + "quotes": ["off", "single"], + "func-call-spacing": ["error", "always"], + "one-var": "off", + "indent": ["error", 4], + "comma-style": "off", + "no-multi-spaces": "off", + "comma-dangle": "off", + "spaced-comment": "off", + "camelcase": "off", + "padded-blocks": "off", + "multiline-comment-style": "off", + "curly": "off", + "rest-spread-spacing": "off", + "no-multiple-empty-lines": "off", + "no-undef-init": "off", + "no-useless-return": "off", + "no-console": "off", + "operator-linebreak": "off", + "key-spacing": "off", + "brace-style": "off", + "padding-line-between-statements": ["off", + { "blankLine": "always", "prev":"function", "next": "*" }, + { "blankLine": "always", "prev":"directive", "next": "*" }, + { "blankLine": "always", "prev":"*", "next": "cjs-export" }, + ], + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], + }, +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.balance.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.balance.js new file mode 100644 index 000000000..dbd9a8ec4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.balance.js @@ -0,0 +1,65 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , chai = require ('chai') + , assert = chai.assert + +// ---------------------------------------------------------------------------- + +module.exports = (exchange, balance, method) => { + + const currencies = [ + 'USD', + 'USDT', + 'CNY', + 'EUR', + 'BTC', + 'ETH', + 'JPY', + 'LTC', + 'DASH', + 'DOGE', + 'UAH', + 'RUB', + 'XRP', + ] + + // log.yellow (balance) + + assert (typeof balance['total'] === 'object') + assert (typeof balance['free'] === 'object') + assert (typeof balance['used'] === 'object') + + const codes = Object.keys (balance['total']) + for (let i = 0; i < codes.length; i++) { + const code = codes[i] + const total = balance['total'][code] + const free = balance['free'][code] + const used = balance['used'][code] + if ((total !== undefined) && (free !== undefined) && (used !== undefined)) { + assert (total === free + used, 'free and used do not sum to total ' + exchange.id) + } + } + + let result = currencies + .filter ((currency) => (currency in balance) && + (balance[currency]['total'] !== undefined)) + + if (result.length > 0) { + result = result.map ((currency) => currency + ': ' + balance[currency]['total']) + if (exchange.currencies.length > result.length) { + result = result.join (', ') + ' + more...' + } else { + result = result.join (', ') + } + + } else { + + result = 'zero balance' + } + + log (result) + +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.currency.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.currency.js new file mode 100644 index 000000000..f5b93dbbd --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.currency.js @@ -0,0 +1,55 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = (exchange, currency, method) => { + + const format = { + 'id': 'btc', // string literal for referencing within an exchange + 'code': 'BTC', // uppercase string literal of a pair of currencies + //---------------------------------------------------------------------- + // commented temporarily to bring currencies to consistency first + // 'name': 'Bitcoin', // uppercase string, base currency, 3 or more letters + // 'quote': 'USD', // uppercase string, quote currency, 3 or more letters + // 'withdraw': true, // can withdraw + // 'deposit': true, // can deposit + // 'active': true, // can both withdraw & deposit + // 'precision': 8, // number of decimal digits "after the dot" + // 'limits': { // value limits when placing orders on this market + // 'amount': { + // 'min': 0.01, // order amount should be > min + // 'max': 1000, // order amount should be < max + // }, + // 'price': { + // 'min': 0.01, // order price should be > min + // 'max': 1000, // order price should be < max + // }, + // 'cost': { // order cost = price * amount + // 'min': 0.01, // order cost should be > min + // 'max': 1000, // order cost should be < max + // }, + // }, + // 'info': {}, // the original unparsed market info from the exchange + //---------------------------------------------------------------------- + } + + expect (currency).to.deep.include.all.keys (format) + + // expect (currency['precision']).to.not.be.undefined + + // expect (currency['limits']['amount']['min']).to.not.be.undefined + // expect (currency['limits']['price']['min']).to.not.be.undefined + // expect (market['limits']['cost']['min']).to.not.be.undefined + + // log (market) + + return currency +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchBalance.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchBalance.js new file mode 100644 index 000000000..47ec8741f --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchBalance.js @@ -0,0 +1,24 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , testBalance = require ('./test.balance.js') + +// ---------------------------------------------------------------------------- + +module.exports = async (exchange) => { + + if (!(exchange.has.fetchBalance)) { + log (exchange.id.green, ' does not have fetchBalance') + return + } + + log ('fetching balance...') + + const response = await exchange.fetchBalance () + + testBalance (exchange, response) + + return response +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchClosedOrders.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchClosedOrders.js new file mode 100644 index 000000000..9d57a0792 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchClosedOrders.js @@ -0,0 +1,40 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOrder = require ('./test.order.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + if (exchange.has.fetchClosedOrders) { + + // log ('fetching closed orders...') + + const orders = await exchange.fetchClosedOrders (symbol) + + log ('fetched', orders.length.toString ().green, 'closed orders, testing each') + + assert (orders instanceof Array) + + const now = Date.now () + + for (let i = 0; i < orders.length; i++) { + const order = orders[i] + testOrder (exchange, order, symbol, now) + assert (order.status === 'closed' || order.status === 'canceled') + } + + // log (asTable (orders)) + + } else { + + log ('fetching closed orders not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchCurrencies.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchCurrencies.js new file mode 100644 index 000000000..79c018d74 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchCurrencies.js @@ -0,0 +1,38 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testCurrency = require ('./test.currency.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + const skippedExchanges = [ + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchCurrencies...') + return + } + + if (exchange.has.fetchCurrencies) { + + // log ('fetching currencies...') + + const method = 'fetchCurrencies' + const currencies = await exchange[method] () + Object.values (currencies).forEach ((currency) => testCurrency (exchange, currency, method)) + return currencies + + } else { + + log ('fetching currencies not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchDeposits.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchDeposits.js new file mode 100644 index 000000000..0875755b6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchDeposits.js @@ -0,0 +1,39 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTransaction = require ('./test.transaction.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, code) => { + + if (exchange.has.fetchDeposits) { + + // log ('fetching deposits...') + + const transactions = await exchange.fetchDeposits (code) + + log ('fetched', transactions.length.toString ().green, 'deposits, asserting each...') + + assert (transactions instanceof Array) + + const now = Date.now () + + for (let i = 0; i < transactions.length; i++) { + const transaction = transactions[i] + testTransaction (exchange, transaction, code, now) + } + + // log (asTable (transactions)) + + } else { + + log ('fetching deposits not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchFundingFees.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchFundingFees.js new file mode 100644 index 000000000..de16a33bc --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchFundingFees.js @@ -0,0 +1,38 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + const skippedExchanges = [ + 'bibox', // fetchFundingFees should be rewritten to fetchFundingFee + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchFundingFees...') + return + } + + if (exchange.has.fetchFundingFees) { + + // log ('fetching funding fees...') + + const method = 'fetchFundingFees' + const fees = await exchange[method] () + log.green (fees) + return fees + + } else { + + log ('fetching funding fees not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchL2OrderBook.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchL2OrderBook.js new file mode 100644 index 000000000..3989aee25 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchL2OrderBook.js @@ -0,0 +1,31 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOrderBook = require ('./test.orderbook.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + // log (symbol.green, 'fetching order book...') + + const method = 'fetchL2OrderBook' + + if (exchange.has[method]) { + + const orderbook = await exchange[method] (symbol) + testOrderBook (exchange, orderbook, method, symbol) + return orderbook + + } else { + + log (method + '() not supported') + } + +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchLedger.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchLedger.js new file mode 100644 index 000000000..f3226105b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchLedger.js @@ -0,0 +1,47 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testLedgerItem = require ('./test.ledgerItem') + +require ('ansicolor').nice + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, code) => { + + if (exchange.has.fetchLedger) { + + const items = await exchange.fetchLedger (code) + + assert (items instanceof Array) + + log ('fetched', items.length.toString ().green, 'ledger items') + + const now = Date.now () + + for (let i = 0; i < items.length; i++) { + testLedgerItem (exchange, items[i], code, now) + if (i > 0) { + assert (items[i].timestamp >= items[i - 1].timestamp) + } + } + + if (exchange.has.fetchLedgerItem) { + const { id } = items.pop () + let item = await exchange.fetchLedgerItem (id) + if (Array.isArray (item)) { + item = item[0] + } + testLedgerItem (exchange, item, code, now) + } + + } else { + + log ('fetching ledger items not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchMarkets.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchMarkets.js new file mode 100644 index 000000000..b626b92eb --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchMarkets.js @@ -0,0 +1,39 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testMarket = require ('./test.market.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + const skippedExchanges = [ + 'bitforex', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchMarkets...') + return + } + + if (exchange.has.fetchMarkets) { + + // log ('fetching markets...') + + const method = 'fetchMarkets' + const markets = await exchange[method] () + Object.values (markets).forEach ((market) => testMarket (exchange, market, method)) + return markets + + } else { + + log ('fetching markets not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchMyTrades.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchMyTrades.js new file mode 100644 index 000000000..64fae9de8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchMyTrades.js @@ -0,0 +1,51 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTrade = require ('./test.trade.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + const skippedExchanges = [ + 'bitso', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchMyTrades...') + return + } + + if (exchange.has.fetchMyTrades) { + + // log ('fetching my trades...') + + const trades = await exchange.fetchMyTrades (symbol) + + assert (trades instanceof Array) + + log ('fetched', trades.length.toString ().green, 'trades') + + const now = Date.now () + + for (let i = 0; i < trades.length; i++) { + testTrade (exchange, trades[i], symbol, now) + if (i > 0) { + assert (trades[i].timestamp >= trades[i - 1].timestamp) + } + } + + // trades.forEach (trade => log.dim ('-'.repeat (80), "\n", trade)) + // log (asTable (trades)) + + } else { + + log ('fetching my trades not supported') + } +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOHLCV.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOHLCV.js new file mode 100644 index 000000000..0ce38e977 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOHLCV.js @@ -0,0 +1,53 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOHLCV = require ('./test.ohlcv.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + const skippedExchanges = [ + 'btcalpha', // issue with 404 on a documented endpoint https://travis-ci.org/ccxt/ccxt/builds/643930431#L2213 + 'bitmex', // an issue with null values,to be resolved later + 'cex', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchOHLCV...') + return + } + + if (exchange.has.fetchOHLCV) { + + const timeframe = Object.keys (exchange.timeframes || { '1d': '1d' })[0] + const limit = 10 + const duration = exchange.parseTimeframe (timeframe) + const since = exchange.milliseconds () - duration * limit * 1000 - 1000 + + // log (symbol.green, 'fetching OHLCV...') + + const ohlcvs = await exchange.fetchOHLCV (symbol, timeframe, since, limit) + + const now = Date.now () + + for (let i = 0; i < ohlcvs.length; i++) { + const ohlcv = ohlcvs[i] + testOHLCV (exchange, ohlcv, symbol, now) + } + + log (symbol.green, 'fetched', Object.keys (ohlcvs).length.toString ().green, 'OHLCVs') + + return ohlcvs + + } else { + + log ('fetching OHLCV not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOpenOrders.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOpenOrders.js new file mode 100644 index 000000000..21048d4f8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOpenOrders.js @@ -0,0 +1,40 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOrder = require ('./test.order.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + if (exchange.has.fetchOpenOrders) { + + // log ('fetching open orders...') + + const orders = await exchange.fetchOpenOrders (symbol) + + assert (orders instanceof Array) + + log ('fetched', orders.length.toString ().green, 'open orders') + + const now = Date.now () + + for (let i = 0; i < orders.length; i++) { + const order = orders[i] + testOrder (exchange, order, symbol, now) + assert (order.status === 'open') + } + + // log (asTable (orders)) + + } else { + + log ('fetching open orders not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrderBook.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrderBook.js new file mode 100644 index 000000000..5794c816e --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrderBook.js @@ -0,0 +1,32 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOrderBook = require ('./test.orderbook.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + // log (symbol.green, 'fetching order book...') + + const method = 'fetchOrderBook' + + if (exchange.has[method]) { + + const orderbook = await exchange[method] (symbol) + + testOrderBook (exchange, orderbook, method, symbol) + + return orderbook + + } else { + + log (method + '() not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrderBooks.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrderBooks.js new file mode 100644 index 000000000..a09ac40ed --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrderBooks.js @@ -0,0 +1,46 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOrderBook = require ('./test.orderbook.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + const randomSymbols = exchange.symbols.slice ().sort (() => 0.5 - Math.random ()).slice (0, 2) + const customExchangeParams = ([ + 'yobit', + 'tidex', + 'ccex', + 'liqui', + 'dsx', + ]).reduce ((params, id) => ({ ...params, [id]: [randomSymbols], }), {}) + + const args = (exchange.id in customExchangeParams) ? customExchangeParams[exchange.id] : [] + const method = 'fetchOrderBooks' + + if (exchange.has[method]) { + + // log ('fetching order books...') + + let orderbooks = await exchange[method] (...args) + + // log.green (orderbooks) + + Object.entries (orderbooks).forEach (([symbol, orderbook]) => { + testOrderBook (exchange, orderbook, method, symbol) + }) + + // return orderbooks + + } else { + + log (method + '() not supported') + } +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrders.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrders.js new file mode 100644 index 000000000..94ea88933 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchOrders.js @@ -0,0 +1,49 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testOrder = require ('./test.order.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + const skippedExchanges = [ + 'bitmart', + 'rightbtc', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchMyTrades...') + return + } + + if (exchange.has.fetchOrders) { + + // log ('fetching orders...') + + const orders = await exchange.fetchOrders (symbol) + + log ('fetched', orders.length.toString ().green, 'orders, asserting each...') + + assert (orders instanceof Array) + + const now = Date.now () + + for (let i = 0; i < orders.length; i++) { + const order = orders[i] + testOrder (exchange, order, symbol, now) + } + + // log (asTable (orders)) + + } else { + + log ('fetching orders not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchStatus.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchStatus.js new file mode 100644 index 000000000..8d4d4893d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchStatus.js @@ -0,0 +1,37 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + if (exchange.has.fetchStatus) { + + // log ('fetching status...') + + const method = 'fetchStatus' + const status = await exchange[method] () + + const sampleStatus = { + 'status': 'ok', // 'ok', 'shutdown', 'error', 'maintenance' + 'updated': undefined, // integer, last updated timestamp in milliseconds if updated via the API + 'eta': undefined, // when the maintenance or outage is expected to end + 'url': undefined, // a link to a GitHub issue or to an exchange post on the subject + } + + assert.containsAllKeys (status, sampleStatus) + + return status + + } else { + + log ('fetching status not supported') + } +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTicker.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTicker.js new file mode 100644 index 000000000..5cfddc8b6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTicker.js @@ -0,0 +1,56 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTicker = require ('./test.ticker.js') + +// ---------------------------------------------------------------------------- + +const printTickerOneLiner = (ticker, method, symbol) => { + + log (symbol.toString ().green, + method, + ticker['datetime'], + 'bid: ' + (ticker['bid']), + 'ask: ' + (ticker['ask'])) +} + +// ---------------------------------------------------------------------------- + +module.exports = async (exchange, symbol) => { + + const method = 'fetchTicker' + + const skippedExchanges = [ + 'digifinex', + 'currencycom' + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping ' + method + '...') + return + } + + if (exchange.has[method]) { + + // log (symbol.green, 'fetching ticker...') + + const ticker = await exchange.fetchTicker (symbol) + + testTicker (exchange, ticker, method, symbol) + + printTickerOneLiner (ticker, method, symbol) + + return ticker + + } else { + + log (symbol.green, 'fetchTicker () not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTickers.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTickers.js new file mode 100644 index 000000000..ece52b623 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTickers.js @@ -0,0 +1,54 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTicker = require ('./test.ticker.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + const skippedExchanges = [ + 'binance', + 'digifinex', + 'currencycom', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetch all tickers...') + return + } + + if (exchange.has.fetchTickers) { + + // log ('fetching all tickers at once...') + + const method = 'fetchTickers' + let tickers = undefined + + try { + + tickers = await exchange[method] () + log ('fetched all', Object.keys (tickers).length.toString ().green, 'tickers') + + } catch (e) { + + log ('failed to fetch all tickers, fetching multiple tickers at once...') + tickers = await exchange[method] ([ symbol ]) + log ('fetched', Object.keys (tickers).length.toString ().green, 'tickers') + } + + Object.values (tickers).forEach ((ticker) => testTicker (exchange, ticker, method, symbol)) + return tickers + + } else { + + log ('fetching all tickers at once not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTrades.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTrades.js new file mode 100644 index 000000000..59ae85023 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTrades.js @@ -0,0 +1,48 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTrade = require ('./test.trade.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + const skippedExchanges = [ + // 'adara', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchTrades...') + return + } + + if (exchange.has.fetchTrades) { + + // log (symbol.green, 'fetching trades...') + + const trades = await exchange.fetchTrades (symbol) + assert (trades instanceof Array) + log (symbol.green, 'fetched', Object.values (trades).length.toString ().green, 'trades') + const now = Date.now () + for (let i = 0; i < trades.length; i++) { + testTrade (exchange, trades[i], symbol, now) + if (i > 0) { + if (trades[i].timestamp && trades[i - 1].timestamp) { + assert (trades[i].timestamp >= trades[i - 1].timestamp) + } + } + + } + // log (asTable (trades)) + + } else { + + log (symbol.green, 'fetchTrades () not supported'.yellow) + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTradingFees.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTradingFees.js new file mode 100644 index 000000000..fd38d55d0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTradingFees.js @@ -0,0 +1,37 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + const skippedExchanges = [ + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping fetchTradingFees...') + return + } + + if (exchange.has.fetchTradingFees) { + + // log ('fetching trading fees...') + + const method = 'fetchTradingFees' + const fees = await exchange[method] () + log.green ({ 'maker': fees['maker'], 'taker': fees['taker'] }) + return fees + + } else { + + log ('fetching trading fees not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTransactions.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTransactions.js new file mode 100644 index 000000000..96bd20ebb --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchTransactions.js @@ -0,0 +1,39 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTransaction = require ('./test.transaction.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, code) => { + + if (exchange.has.fetchTransactions) { + + // log ('fetching transactions...') + + const transactions = await exchange.fetchTransactions (code) + + log ('fetched', transactions.length.toString ().green, 'transactions, asserting each...') + + assert (transactions instanceof Array) + + const now = Date.now () + + for (let i = 0; i < transactions.length; i++) { + const transaction = transactions[i] + testTransaction (exchange, transaction, code, now) + } + + // log (asTable (transactions)) + + } else { + + log ('fetching transactions not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchWithdrawals.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchWithdrawals.js new file mode 100644 index 000000000..aecac6dee --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.fetchWithdrawals.js @@ -0,0 +1,39 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testTransaction = require ('./test.transaction.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, code) => { + + if (exchange.has.fetchWithdrawals) { + + // log ('fetching deposits...') + + const transactions = await exchange.fetchWithdrawals (code) + + log ('fetched', transactions.length.toString ().green, 'withdrawals, asserting each...') + + assert (transactions instanceof Array) + + const now = Date.now () + + for (let i = 0; i < transactions.length; i++) { + const transaction = transactions[i] + testTransaction (exchange, transaction, code, now) + } + + // log (asTable (transactions)) + + } else { + + log ('fetching withdrawals not supported') + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ledgerItem.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ledgerItem.js new file mode 100644 index 000000000..cece5f4c2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ledgerItem.js @@ -0,0 +1,50 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = (exchange, item, code, now) => { + expect (item).to.be.an ('object') + expect (item).to.have.property ('id') + expect (typeof item.id).to.be.oneOf (['string', 'undefined']) + expect (item).to.have.property ('direction') + expect (item.direction).to.be.oneOf (['in', 'out']) + expect (item).to.have.property ('account') + expect (typeof item.account).to.be.oneOf (['string', 'undefined']) + expect (item).to.have.property ('referenceId') + expect (typeof item.referenceId).to.be.oneOf (['string', 'undefined']) + expect (item).to.have.property ('referenceAccount') + expect (typeof item.referenceAccount).to.be.oneOf (['string', 'undefined']) + expect (item).to.have.property ('type') + // expect (item.type).to.be.oneOf (['trade', 'transaction', 'margin', 'cashback', 'referral', 'transfer', 'fee', /* TODO: add more types here */ ]) + expect (item).to.have.property ('currency') + expect (exchange.currencies).to.have.property (item.currency) + expect (item).to.have.property ('amount') + expect (typeof item.amount).to.be.oneOf (['number', 'undefined']) + expect (item).to.have.property ('before') + expect (typeof item.balanceBefore).to.be.oneOf (['number', 'undefined']) + expect (item).to.have.property ('after') + expect (typeof item.balanceAfter).to.be.oneOf (['number', 'undefined']) + expect (item).to.have.property ('timestamp') + expect (typeof item.timestamp).to.be.oneOf (['number', 'undefined']) + expect (item.timestamp).to.be.gt (1230940800000) + expect (item.timestamp).to.be.lt (now) + expect (item).to.have.property ('datetime') + expect (item.datetime).to.be.equal (exchange.iso8601 (item.timestamp)) + expect (item).to.have.property ('fee') + if (item.fee !== undefined) { + expect (item.fee).to.be.an ('object') + expect (item.fee).to.have.property ('cost') + expect (typeof item.fee.cost).to.be.oneOf (['number', 'undefined']) + expect (item.fee).to.have.property ('currency') + expect (item.fee.currency).to.be.equal (item.currency) + } + expect (item).to.have.property ('info').that.is.an ('object') +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.loadMarkets.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.loadMarkets.js new file mode 100644 index 000000000..b134b227d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.loadMarkets.js @@ -0,0 +1,39 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + , testMarket = require ('./test.market.js') + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange) => { + + const skippedExchanges = [ + 'bitforex', + ] + + if (skippedExchanges.includes (exchange.id)) { + log (exchange.id, 'found in ignored exchanges, skipping loadMarkets...') + return + } + + if (exchange.has.loadMarkets) { + + // log ('loading markets...') + + const method = 'loadMarkets' + const markets = await exchange[method] () + Object.values (markets).forEach ((market) => testMarket (exchange, market, method)) + return markets + + } else { + + log ('loading markets not supported') + } +} + diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.market.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.market.js new file mode 100644 index 000000000..a72ba83ce --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.market.js @@ -0,0 +1,67 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = (exchange, market, method) => { + + const format = { + 'id': 'btcusd', // string literal for referencing within an exchange + 'symbol': 'BTC/USD', // uppercase string literal of a pair of currencies + 'base': 'BTC', // unified uppercase string, base currency, 3 or more letters + 'quote': 'USD', // unified uppercase string, quote currency, 3 or more letters + 'taker': 0.0011, // taker fee, for example, 0.0011 = 0.11% + 'maker': 0.0009, // maker fee, for example, 0.0009 = 0.09% + //--------------------------------------------------------------------- + // commented temporarily to bring markets to consistency first + // 'baseId': 'btc', // exchange-specific base currency id + // 'quoteId': 'usd', // exchange-specific quote currency id + // 'active': true, // boolean, market status + // 'precision': { // number of decimal digits "after the dot" + // 'price': 8, // integer + // 'amount': 8, // integer + // 'cost': 8, // integer + // }, + // 'limits': { // value limits when placing orders on this market + // 'amount': { + // 'min': 0.01, // order amount should be > min + // 'max': 1000, // order amount should be < max + // }, + // 'price': { + // 'min': 0.01, // order price should be > min + // 'max': 1000, // order price should be < max + // }, + // 'cost': { // order cost = price * amount + // 'min': 0.01, // order cost should be > min + // 'max': 1000, // order cost should be < max + // }, + // }, + // 'info': {}, // the original unparsed market info from the exchange + //--------------------------------------------------------------------- + } + + expect (market).to.deep.include.all.keys (format) + expect (market).to.not.have.key ('lot') + + // assert ((market['baseId'] === undefined) || (typeof market['baseId'] === 'string')) + // assert ((market['quoteId'] === undefined) || (typeof market['quoteId'] === 'string')) + + assert ((market['taker'] === undefined) || (typeof market['taker'] === 'number')) + assert ((market['maker'] === undefined) || (typeof market['maker'] === 'number')) + + // expect (market['precision']['amount']).to.not.be.undefined + // expect (market['precision']['price']).to.not.be.undefined + + // expect (market['limits']['amount']['min']).to.not.be.undefined + // expect (market['limits']['price']['min']).to.not.be.undefined + // expect (market['limits']['cost']['min']).to.not.be.undefined + + // log (market) +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ohlcv.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ohlcv.js new file mode 100644 index 000000000..a3bd988db --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ohlcv.js @@ -0,0 +1,36 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const assert = require ('assert'); + +// --------------------------------------------------------------------------- + +function testOHLCV (exchange, ohlcv, symbol, now) { + + const json = exchange.json (ohlcv); + assert (ohlcv); + assert (Array.isArray (ohlcv), json); + const length = ohlcv.length; + assert (length >= 6); + for (let i = 0; i < ohlcv.length; i++) { + assert ((ohlcv[i] === undefined) || (typeof ohlcv[i] === 'number'), json); + } + + assert (ohlcv[0] > 1230940800000, json); // 03 Jan 2009 - first block + assert (ohlcv[0] < 2147483648000, json); // 19 Jan 2038 - int32 overflows + + const skippedExchanges = [ + 'bitmex', // BitMEX API docs: also note the open price is equal to the close price of the previous timeframe bucket. + 'vcc', // same as BitMEX, the open price is equal to the close price of the previous timeframe bucket. + ]; + + if (!exchange.inArray (exchange.id, skippedExchanges)) { + assert ((ohlcv[1] === undefined) || (ohlcv[2] === undefined) || (ohlcv[1] <= ohlcv[2]), 'open > high, ' + exchange.safeString (ohlcv, 1, 'undefined') + ' > ' + exchange.safeString (ohlcv, 2, 'undefined')); // open <= high + } + + assert ((ohlcv[3] === undefined) || (ohlcv[2] === undefined) || (ohlcv[3] <= ohlcv[2]), 'low > high, ' + exchange.safeString (ohlcv, 2, 'undefined') + ' > ' + exchange.safeString (ohlcv, 3, 'undefined')); // low <= high + assert ((ohlcv[3] === undefined) || (ohlcv[4] === undefined) || (ohlcv[3] <= ohlcv[4]), 'low > close, ' + exchange.safeString (ohlcv, 3, 'undefined') + ' > ' + exchange.safeString (ohlcv, 4, 'undefined')); // low <= close +} + +module.exports = testOHLCV; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.order.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.order.js new file mode 100644 index 000000000..d2d674f3e --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.order.js @@ -0,0 +1,62 @@ +'use strict'; + +const assert = require ('assert'); + +function testOrder (exchange, order, symbol, now) { + assert (order); + assert ('id' in order); + assert (typeof order['id'] === 'string'); + assert ('clientOrderId' in order); + assert ((order['clientOrderId'] === undefined) || (typeof order['clientOrderId'] === 'string')); + assert ('timestamp' in order); + assert (typeof order['timestamp'] === 'number'); + assert (order['timestamp'] > 1230940800000); // 03 Jan 2009 - first block + assert (order['timestamp'] < now); + assert ('lastTradeTimestamp' in order); + assert ('datetime' in order); + assert (order['datetime'] === exchange.iso8601 (order['timestamp'])); + assert ('status' in order); + assert ((order['status'] === 'open') || (order['status'] === 'closed') || (order['status'] === 'canceled')); + assert ('symbol' in order); + assert (order['symbol'] === symbol); + assert ('type' in order); + assert ((order['type'] === undefined) || (typeof order['type'] === 'string')); + assert ('timeInForce' in order); + assert ((order['timeInForce'] === undefined) || (typeof order['timeInForce'] === 'string')); + assert ('side' in order); + assert ((order['side'] === 'buy') || (order['side'] === 'sell')); + assert ('price' in order); + assert ((order['price'] === undefined) || (typeof order['price'] === 'number')); + if (order['price'] !== undefined) { + assert (order['price'] > 0); + } + assert ('amount' in order); + assert (typeof order['amount'] === 'number'); + assert (order['amount'] >= 0); + assert ('filled' in order); + if (order['filled'] !== undefined) { + assert (typeof order['filled'] === 'number'); + assert ((order['filled'] >= 0) && (order['filled'] <= order['amount'])); + } + assert ('remaining' in order); + if (order['remaining'] !== undefined) { + assert (typeof order['remaining'] === 'number'); + assert ((order['remaining'] >= 0) && (order['remaining'] <= order['amount'])); + } + assert ('trades' in order); + if (order['trades']) { + assert (Array.isArray (order['trades'])); + } + assert ('fee' in order); + const fee = order['fee']; + if (fee) { + assert (typeof fee['cost'] === 'number'); + if (fee['cost'] !== 0) { + assert (typeof fee['currency'] === 'string'); + } + } + assert ('info' in order); + assert (order['info']); +} + +module.exports = testOrder; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.orderbook.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.orderbook.js new file mode 100644 index 000000000..11d088796 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.orderbook.js @@ -0,0 +1,74 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +// ---------------------------------------------------------------------------- + +const printOrderBookOneLiner = (orderbook, method, symbol) => { + + const bids = orderbook.bids + const asks = orderbook.asks + + log (symbol.toString ().green, + method, + orderbook['nonce'] || orderbook['datetime'], + bids.length, 'bids:', bids[0], + asks.length, 'asks:', asks[0]) +} + +/* ------------------------------------------------------------------------ */ + +module.exports = (exchange, orderbook, method, symbol) => { + + const format = { + 'bids': [], + 'asks': [], + 'timestamp': 1234567890, + 'datetime': '2017-09-01T00:00:00', + 'nonce': 134234234, + // 'info': {}, + } + + expect (orderbook).to.have.all.keys (format) + + const bids = orderbook.bids + const asks = orderbook.asks + + for (let i = 0; i < bids.length; i++) { + if (bids.length > (i + 1)) { + assert (bids[i][0] >= bids[i + 1][0]) + } + assert (typeof bids[i][0] === 'number') + assert (typeof bids[i][1] === 'number') + } + + for (let i = 0; i < asks.length; i++) { + if (asks.length > (i + 1)) { + assert (asks[i][0] <= asks[i + 1][0]) + } + assert (typeof asks[i][0] === 'number') + assert (typeof asks[i][1] === 'number') + } + + if (![ + + 'coinmarketcap', + 'xbtce', + 'upbit', // an orderbook might have a 0-price ask occasionally + + ].includes (exchange.id)) { + + if (bids.length && asks.length) + assert (bids[0][0] <= asks[0][0], + `bids[0][0]: ${bids[0][0]} (of ${bids.length}); asks[0][0]:${asks[0][0]} (of ${asks.length})`) + + } + + printOrderBookOneLiner (orderbook, method, symbol) +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ticker.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ticker.js new file mode 100644 index 000000000..33a86ceaf --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.ticker.js @@ -0,0 +1,99 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = (exchange, ticker, method, symbol) => { + + const format = { + 'symbol': 'ETH/BTC', + 'info': {}, + 'timestamp': 1234567890, + 'datetime': '2017-09-01T00:00:00', + 'high': 1.234, // highest price + 'low': 1.234, // lowest price + 'bid': 1.234, // current best bid (buy) price + 'bidVolume': 1.234, // current best bid (buy) amount (may be missing or undefined) + 'ask': 1.234, // current best ask (sell) price + 'askVolume': 1.234, // current best ask (sell) amount (may be missing or undefined) + 'vwap': 1.234, // volume weighed average price + 'open': 1.234, // opening price + 'close': 1.234, // price of last trade (closing price for current period) + 'last': 1.234, // same as `close`, duplicated for convenience + 'previousClose': 1.234, // closing price for the previous period + 'change': 1.234, // absolute change, `last - open` + 'percentage': 1.234, // relative change, `(change/open) * 100` + 'average': 1.234, // average price, `(last + open) / 2` + 'baseVolume': 1.234, // volume of base currency + 'quoteVolume': 1.234, // volume of quote currency + } + + expect (ticker).to.have.all.keys (format) + + const keys = [ 'datetime', 'timestamp', 'high', 'low', 'bid', 'ask', 'baseVolume', 'quoteVolume', 'vwap' ] + + assert (!('first' in ticker), '`first` field leftover in ' + exchange.id) + assert (ticker['last'] === ticker['close'], '`last` != `close` in ' + exchange.id) + + log (ticker['datetime'], exchange.id, method, ticker['symbol'], ticker['last']) + + keys.forEach ((key) => assert (key in ticker)) + + const { high, low, vwap, baseVolume, quoteVolume } = ticker + + // this assert breaks QuadrigaCX sometimes... still investigating + // if (vwap) + // assert (vwap >= low && vwap <= high) + + /* + if (baseVolume && quoteVolume && high && low) { + assert (quoteVolume >= baseVolume * low) // this assertion breaks therock + assert (quoteVolume <= baseVolume * high) + } + */ + + if (baseVolume && vwap) { + assert (quoteVolume) + } + + if (quoteVolume && vwap) { + assert (baseVolume) + } + + // log (symbol.green, 'ticker', + // ticker['datetime'], + // ... (keys.map (key => + // key + ': ' + ticker[key]))) + + if (![ + + 'coinmarketcap', + 'xbtce', + 'coss', + 'idex', + 'mercado', + 'okex', + 'southxchange', // https://user-images.githubusercontent.com/1294454/59953532-314bea80-9489-11e9-85b3-2a711ca49aa7.png + 'bitmart', + 'gateio', // some ticker bids are greaters than asks + 'timex', + + ].includes (exchange.id)) { + + if (ticker['baseVolume'] || ticker['quoteVolume']) { + if (ticker['bid'] && ticker['ask']) { + assert (ticker['bid'] <= ticker['ask'], (ticker['symbol'] ? (ticker['symbol'] + ' ') : '') + 'ticker bid is greater than ticker ask!') + } + } + + } + + return ticker +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.trade.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.trade.js new file mode 100644 index 000000000..4c0e3827c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.trade.js @@ -0,0 +1,65 @@ +'use strict'; + +const assert = require ('assert'); + +function testTrade (exchange, trade, symbol, now) { + assert (trade); + const sampleTrade = { + 'info': { 'a': 1, 'b': 2, 'c': 3 }, // the original decoded JSON as is + 'id': '12345-67890:09876/54321', // string trade id + 'timestamp': 1502962946216, // Unix timestamp in milliseconds + 'datetime': '2017-08-17 12:42:48.000', // ISO8601 datetime with milliseconds + 'symbol': 'ETH/BTC', // symbol + 'order': '12345-67890:09876/54321', // string order id or undefined/None/null + 'type': 'limit', // order type, 'market', 'limit' or undefined/None/null + 'side': 'buy', // direction of the trade, 'buy' or 'sell' + 'takerOrMaker': 'taker', // string, 'taker' or 'maker' + 'price': 0.06917684, // float price in quote currency + 'amount': 1.5, // amount of base currency + 'cost': 0.10376526, // total cost (including fees), `price * amount` + }; + const keys = Object.keys (sampleTrade); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + assert (key in trade); + } + const fee = ('fee' in trade) ? trade['fee'] : undefined; + const fees = ('fees' in trade) ? trade['fees'] : undefined; + // logical XOR + if (fee || fees) { + assert (!(fee && fees)); + } + if (fee) { + assert (('cost' in fee) && ('currency' in fee)); + } + if (fees) { + assert (Array.isArray (fees)); + for (let i = 0; i < fees.length; i++) { + const fee = fees[i]; + assert (('cost' in fee) && ('currency' in fee)); + } + } + const id = trade['id']; + assert ((id === undefined) || (typeof id === 'string')); + const timestamp = trade['timestamp']; + assert (typeof timestamp === 'number' || timestamp === undefined); + if (timestamp) { + assert (timestamp > 1230940800000); // 03 Jan 2009 - first block + assert (timestamp < 2147483648000); // 19 Jan 2038 - int32 overflows + const adjustedNow = now + 60000; + assert (timestamp < adjustedNow, 'trade.timestamp is greater than or equal to current time: trade: ' + exchange.iso8601 (timestamp) + ' now: ' + exchange.iso8601 (now)); + } + assert (trade['datetime'] === exchange.iso8601 (timestamp)); + assert (trade['symbol'] === symbol, 'trade symbol is not equal to requested symbol: trade: ' + trade['symbol'] + ' requested: ' + symbol); + assert (trade['type'] === undefined || typeof trade['type'] === 'string'); + assert (trade['side'] === undefined || trade['side'] === 'buy' || trade['side'] === 'sell', 'unexpected trade side ' + trade['side']); + assert (trade['order'] === undefined || typeof trade['order'] === 'string'); + assert (typeof trade['price'] === 'number', 'trade.price is not a number'); + assert (trade['price'] > 0); + assert (typeof trade['amount'] === 'number', 'trade.amount is not a number'); + assert (trade['amount'] >= 0); + const takerOrMaker = trade['takerOrMaker']; + assert (takerOrMaker === undefined || takerOrMaker === 'taker' || takerOrMaker === 'maker'); +} + +module.exports = testTrade; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.transaction.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.transaction.js new file mode 100644 index 000000000..c75348cb3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/Exchange/test.transaction.js @@ -0,0 +1,35 @@ +'use strict'; + +// ---------------------------------------------------------------------------- + +const assert = require ('assert'); + +// --------------------------------------------------------------------------- + +function testTransaction (exchange, transaction, code, now) { + assert (transaction); + assert (typeof transaction['id'] === 'string'); + assert (typeof transaction['timestamp'] === 'number'); + assert (transaction['timestamp'] > 1230940800000); // 03 Jan 2009 - first block + assert (transaction['timestamp'] < now); + assert ('updated' in transaction); + assert ('address' in transaction); + assert ('tag' in transaction); + assert ('txid' in transaction); + assert (transaction['datetime'] === exchange.iso8601 (transaction['timestamp'])); + assert ((transaction['status'] === 'ok') || (transaction['status'] === 'pending') || (transaction['status'] === 'canceled')); + assert (transaction['currency'] === code); + assert (typeof transaction['type'] === 'string'); + assert (transaction['type'] === 'deposit' || transaction['type'] === 'withdrawal'); + assert (typeof transaction['amount'] === 'number'); + assert (transaction['amount'] >= 0); + if (transaction['fee']) { + assert (typeof transaction['fee']['cost'] === 'number'); + if (transaction['fee']['cost'] !== 0) { + assert (typeof transaction['fee']['currency'] === 'string'); + } + } + assert (transaction.info); +} + +module.exports = testTransaction; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/.eslintrc b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/.eslintrc new file mode 100644 index 000000000..3f7282366 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/.eslintrc @@ -0,0 +1,39 @@ +{ + "env": { + "node": true, + "mocha": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "script", + }, + "rules": { + "semi": ["error", "never"], + "no-unused-vars": ["off"], + "quotes": ["off", "single"], + "func-call-spacing": ["error", "always"], + "one-var": "off", + "indent": ["warn", 4, { "ignoredNodes": ["ObjectPattern"] }], + "comma-style": "off", + "no-multi-spaces": "off", + "comma-dangle": "off", + "spaced-comment": "off", + "camelcase": "off", + "padded-blocks": "off", + "multiline-comment-style": "off", + "curly": "off", + "rest-spread-spacing": "off", + "no-multiple-empty-lines": "off", + "no-undef-init": "off", + "no-useless-return": "off", + "no-console": "off", + "operator-linebreak": "off", + "key-spacing": "off", + "brace-style": "off", + "padding-line-between-statements": ["off", + {"blankLine": "always", "prev":"function", "next": "*" }, + {"blankLine": "always", "prev":"directive", "next": "*" }, + {"blankLine": "always", "prev":"*", "next": "cjs-export" }, + ], + }, +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.crypto.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.crypto.js new file mode 100644 index 000000000..f29b0339f --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.crypto.js @@ -0,0 +1,118 @@ +const { hash, ecdsa, jwt } = require ('../../../base/functions/crypto') +const { encode } = require ('../../../base/functions/encode') +const Exchange = require ('../../../base/Exchange'); +const assert = require ('assert') + +function equals (a, b) { + // does not check if b has more properties than a + for (const prop of Object.keys (a)) { + if (a[prop] !== b[prop]) { + return false + } + } + return true +} + +const exchange = new Exchange (); + +// --------------------------------------------------------------------------------------------------------------------- + +assert (hash (encode (''), 'sha256', 'hex') === 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'); +assert (hash (encode ('cheese'), 'sha256', 'hex') === '873ac9ffea4dd04fa719e8920cd6938f0c23cd678af330939cff53c3d2855f34'); + +assert (hash (encode (''), 'md5', 'hex') === 'd41d8cd98f00b204e9800998ecf8427e'); +assert (hash (encode ('sexyfish'), 'md5', 'hex') === 'c8a35464aa9d5683585786f44d5889f8'); + +assert (hash (encode (''), 'sha1', 'hex') === 'da39a3ee5e6b4b0d3255bfef95601890afd80709'); +assert (hash (encode ('nutella'), 'sha1', 'hex') === 'b3d60a34b744159793c483b067c56d8affc5111a'); + +// --------------------------------------------------------------------------------------------------------------------- + +const privateKey = '1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a'; + +assert (equals (ecdsa ('1a', privateKey, 'p256', 'sha256'), { + 'r': '3f4537ef9e72240cdefaea19d68fd483b5e10227e89747c58a3b543bfaf9cc46', + 's': '278d65adebd8d52dac34820c02cee0a0e3348f9aee91a21b8e9bfa0a23b40001', + 'v': 1 +})); + + +assert (equals (ecdsa (privateKey, privateKey, 'p256', undefined), { + 'r': '9aadbfe7feca28a8b665731abd8fdd19f8b1be5fcdd1264d111b291a01c8dea2', + 's': '5084765e08e169fbba470ed1f573c9fed8d68a9d883049054b601f66a6648912', + 'v': 0, +})); + +assert (equals (ecdsa ('1a', privateKey, 'secp256k1', 'sha256'), { + 'r': '23dcb2a2a3728a35eb1a35cc01743c4609550d9cceaf2083550f13a9eb135f9f', + 's': '317963fcac18e4ec9f7921b97d7ea0c82a873dd6299cbfb6af016e08ef5ed667', + 'v': 0, +})); + + +assert (equals (ecdsa (privateKey, privateKey, 'secp256k1', undefined), { + 'r': 'b84a36a6fbabd5277ede578448b93d48e70b38efb5b15b1d4e2a298accf938b1', + 's': '66ebfb8221cda925526e699a59cd221bb4cc84bdc563024b1802c4d9e1d8bbe9', + 'v': 1, +})); + +// --------------------------------------------------------------------------------------------------------------------- + +assert (exchange.hashMessage (privateKey) === '0x59ea5d98c3500c3729f95cf98aa91663f498518cc401360df2912742c232207f'); + +assert (equals (exchange.signHash ('0x59ea5d98c3500c3729f95cf98aa91663f498518cc401360df2912742c232207f', privateKey), { + 'r': '0x6f684aa41c02da83dac3039d8805ddbe79a03b1297e247c7742cab8dfc19d341', + 's': '0x62473881674550563cb028ff40a7846fd53620ddf40a20cc1003b8484a109a4a', + 'v': 27 +})); + +assert (equals (exchange.signMessage (privateKey, privateKey), { + 'r': '0x6f684aa41c02da83dac3039d8805ddbe79a03b1297e247c7742cab8dfc19d341', + 's': '0x62473881674550563cb028ff40a7846fd53620ddf40a20cc1003b8484a109a4a', + 'v': 27 +})); + +// --------------------------------------------------------------------------------------------------------------------- + +const pemKeyArray = [ + '-----BEGIN RSA PRIVATE KEY-----', + 'MIIEpAIBAAKCAQEAqUtXQXv2uSm9zPvdJTRpu5/65rBjAoHmIiowAs+u7fY0QP9O', + '+T8CZRQjvZrfPomBiccjCxDo8mm7GkmL67rs9s7VOMQFvpTTtMBeglNSEbXJ3wTt', + '7WM5gD3ZAbhfGtwWdGpcOMX0hV5/d/fiw1zQk0+AooayTryZ7HV+DQcD1sGTYYir', + 'LePUGkKV2NCvKtGOQfRAtuo0ccigMxMvEhWuExCc9Fhd077OpXMc0n7OiFqA2JWN', + 'jlocM9S71Srvw/lDos8a7lGaFUV8gP1CsQY/4qTju6hhsRd4lFujVr5J9FqmDbnv', + 'wQ79SSu6+lT0+ToFdDHiYOorBK2ESFR7EzlyLwIDAQABAoIBAEobQMbZjNbg/sSM', + 'O/HdT6tiDGKPM8gVNLgf34RbhSeFbrpFCDzy6Al3F24YLUEi0CGPmjdt34q93blU', + 'GHvIB5LCV3PR2vHiFAo7ayOBdZtrCEMn1T7lAHaynBu0qW0IiovLQzNW9AKtqv7I', + '8+qw5lyVoKmEbOkqhfaMN/Fb8MJAo3yEVyhyp4EjtFhqDQ9DuHEjp1jGthnMgm+4', + 'qFyELf0DXZSAN+R9IGDqWPyo78lOcPW4pgeLuoLQ6Nn/2JAEGHFt1a7zMEw5yYbR', + 'XB89Bh0dheSmopBN6Heay4YuhnNKua37OlL1/nhKpLGUNE3z/UOXeiUiMeE1C2i+', + 'GjK+xgECgYEA4XH63rJ3VYDHHWUGlWffFWkEpgR4BDEkZ8MyVYQoUjQA2TpCvQ2+', + 'bR7aAIGXhBnRTrIBNu9m4+am0aUfi1hVmVHqt1b2vFgEb9uKuO4U62tCFpeji+Rz', + '8C8QJyu5v5OUWdQZ4EA0d7ljeoTl50g8tpcGsDhnImLt+/jvJuFt8dECgYEAwD0n', + 'rhlkEjKVHxu1xwKeczxYCqqcUV9Quq8HnPh4VJQ77ljfZ57V7sAKrFDi5XfVD9jS', + 'oJPwxjw6za134VobhdvCM6WNs6AJmbR8E/b5kDMbGaZn27vt+/8JfnGaWBqpDi6W', + 'a5xoJOqmcMBQBK1YleZDoY4PLbk+onVKLMfGI/8CgYEAkyUIv8euGdGWpHnm5SdJ', + 'tLi5vv4Vs267uzntJWG/y3+DukTLgIdy7dgAI+pxkVgkg/+syUVSW5eU9CqZPyLl', + 'o8+Sqh2Jp36vTq71iSRj5RA5r3ND3K+8eFzPZzGj6AWUA1lrljFxzV7kLfiF8gH1', + 'FpvWUrhNoGT/vcFJno/uabECgYAecRC5hxfLserfVDoC261PvjyK492BHUDhbxob', + 'h1U2v4qGAdjOxd5Gwm5uPxjPEZzRt5oTB5pXKe5953xWWTiGh/hGyW6ZBTy/9E65', + 'sqBub0lZVHqZ1zamcwqD1WWFkiM3NbVoMQpk3iuhKzMAqpqekiofiSlqKi16+GvY', + 'j4IW7QKBgQCrIUckPZ6IY3ERIN1IL4TIcK2gOJcznp7fLWpC+sv35ya3OhtDXKFt', + 'GjIHmwLuiUNc0iPzA9Rw89W0zIkKWmWcxM88/ithdxh3MEeNDUGSnd4hQ9SECwxB', + 'Wem3eBT7I4VtFYoaTE3/bX1SKfgBdTzIRqWKSDpgBNZg/P2Tc+s11g==', + '-----END RSA PRIVATE KEY-----', +]; + +const pemKey = pemKeyArray.join ("\n"); + +assert (jwt ({'chicken': 'salad'}, encode (pemKey), 'RS256') === 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaGlja2VuIjoic2FsYWQifQ.FSKD5Y6RNzkHTuHdvG3753U7QNZ-u-GUSPfP1FMjEaK0Rr_iyQTSSmHhkdYSFFnmBvrrN_l-UwKwir52WlsgmQm9HYm0kidxbj7fWwrK2E1oe0P7OjupFjv1BZxc5W69WeaHtOPWe28tiHiON1LCnax6HgfI5lcIBsESGIIBZMVeaioQn9gDVwea7JxJvAlrhDIWZowIHTIdCQocXip7g5jREWHeEIuJNug67mwnfAFxCjvTRiTd0Bw6oBwjM3FLya-RyEyWrejQOWSuC8CNWVUHISaSmEyZ7uM6wTi2m_58TaE9mQwlef32OPErPvvBpgL5pZIyQ4ymwrCIFQLBQQ'); +assert (jwt ({'lil': 'xan'}, encode ('betrayed'), 'HS256') === 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsaWwiOiJ4YW4ifQ.md-oFvZagA-NXmZoRNyJOQ7zwK-PWUMmMQ_LI9ZOKaM'); + +// --------------------------------------------------------------------------------------------------------------------- + +assert (exchange.soliditySha3 (['0x63581b9abf2b661da0ba247e0dda1b723dcff5e3', 100, 'lulwat']) === '0x1d3e95c10fc64aee6628ea59284503a4eafc6ff13c541f00753fbd2a66cea0f5'); +assert (exchange.soliditySha3 ([234]) === '0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2'); +assert (exchange.soliditySha3 (['234']) === '0x61c831beab28d67d1bb40b5ae1a11e2757fa842f031a2d0bc94a7867bc5d26c2'); +assert (exchange.soliditySha3 (['Hello!%']) === '0x661136a4267dba9ccdf6bfddb7c00e714de936674c4bdb065a531cf1cb15c7fc'); +assert (exchange.soliditySha3 (['0x407D73d8a49eeb85D32Cf465507dd71d507100c1']) === '0x4e8ebbefa452077428f93c9520d3edd60594ff452a29ac7d2ccc11d47f3ab95b'); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.datetime.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.datetime.js new file mode 100644 index 000000000..3168304f5 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.datetime.js @@ -0,0 +1,62 @@ +'use strict' + +const ccxt = require ('../../../../ccxt'); +const assert = require ('assert'); +const { ROUND_UP, ROUND_DOWN } = require ('../../../base/functions/misc'); + +const exchange = new ccxt.Exchange ({ + 'id': 'regirock', +}); + +// ---------------------------------------------------------------------------- + +assert (exchange.iso8601 (514862627000) === '1986-04-26T01:23:47.000Z'); +assert (exchange.iso8601 (514862627559) === '1986-04-26T01:23:47.559Z'); +assert (exchange.iso8601 (514862627062) === '1986-04-26T01:23:47.062Z'); + +assert (exchange.iso8601 (0) === '1970-01-01T00:00:00.000Z'); + +assert (exchange.iso8601 (-1) === undefined); +assert (exchange.iso8601 () === undefined); +assert (exchange.iso8601 (undefined) === undefined); +assert (exchange.iso8601 ('') === undefined); +assert (exchange.iso8601 ('a') === undefined); +assert (exchange.iso8601 ({}) === undefined); + +// ---------------------------------------------------------------------------- + +assert (exchange.parse8601 ('1986-04-26T01:23:47.000Z') === 514862627000); +assert (exchange.parse8601 ('1986-04-26T01:23:47.559Z') === 514862627559); +assert (exchange.parse8601 ('1986-04-26T01:23:47.062Z') === 514862627062); + +assert (exchange.parse8601 ('1986-04-26T01:23:47.06Z') === 514862627060); +assert (exchange.parse8601 ('1986-04-26T01:23:47.6Z') === 514862627600); + +assert (exchange.parse8601 ('1977-13-13T00:00:00.000Z') === undefined); +assert (exchange.parse8601 ('1986-04-26T25:71:47.000Z') === undefined); + +assert (exchange.parse8601 ('3333') === undefined); +assert (exchange.parse8601 ('Sr90') === undefined); +assert (exchange.parse8601 ('') === undefined); +assert (exchange.parse8601 () === undefined); +assert (exchange.parse8601 (undefined) === undefined); +assert (exchange.parse8601 ({}) === undefined); +assert (exchange.parse8601 (33) === undefined); + +// ---------------------------------------------------------------------------- + +assert (exchange.parseDate ('1986-04-26 00:00:00') === 514857600000); +assert (exchange.parseDate ('1986-04-26T01:23:47.000Z') === 514862627000); +assert (exchange.parseDate ('1986-13-13 00:00:00') === undefined); + + +assert (exchange.roundTimeframe('5m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 13:20:00')); +assert (exchange.roundTimeframe('10m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 13:20:00')); +assert (exchange.roundTimeframe('30m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 13:00:00')); +assert (exchange.roundTimeframe('1d', exchange.parse8601('2019-08-12 13:22:08'), ROUND_DOWN) === exchange.parse8601('2019-08-12 00:00:00')); + +assert (exchange.roundTimeframe('5m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 13:25:00')); +assert (exchange.roundTimeframe('10m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 13:30:00')); +assert (exchange.roundTimeframe('30m', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 13:30:00')); +assert (exchange.roundTimeframe('1h', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-12 14:00:00')); +assert (exchange.roundTimeframe('1d', exchange.parse8601('2019-08-12 13:22:08'), ROUND_UP) === exchange.parse8601('2019-08-13 00:00:00')); diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.generic.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.generic.js new file mode 100644 index 000000000..9fffff635 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.generic.js @@ -0,0 +1,157 @@ +'use strict' + +/* ------------------------------------------------------------------------ */ + +const { deepExtend, groupBy, filterBy, omit, sum, sortBy } = require ('../../../../ccxt') +const { strictEqual: equal, deepEqual } = require ('assert') + +/* ------------------------------------------------------------------------ */ + +it ('deepExtend() works', () => { + + let count = 0 + + const values = [{ + a: 1, + b: 2, + d: { + a: 1, + b: [], + c: { test1: 123, test2: 321 }}, + f: 5, + g: 123, + i: 321, + j: [1, 2], + }, + { + b: 3, + c: 5, + d: { + b: { first: 'one', second: 'two' }, + c: { test2: 222 }}, + e: { one: 1, two: 2 }, + f: [{ 'foo': 'bar' }], + g: (void 0), + h: /abc/g, + i: null, + j: [3, 4] + }] + + const extended = deepExtend (...values) + deepEqual (extended, { + a: 1, + b: 3, + d: { + a: 1, + b: { first: 'one', second: 'two' }, + c: { test1: 123, test2: 222 } + }, + f: [{ 'foo': 'bar' }], + g: undefined, + c: 5, + e: { one: 1, two: 2 }, + h: /abc/g, + i: null, + j: [3, 4] + }) + + deepEqual (deepExtend (undefined, undefined, {'foo': 'bar' }), { 'foo': 'bar' }) +}) + +/* ------------------------------------------------------------------------ */ + +it ('groupBy() works', () => { + + const array = [ + { 'foo': 'a' }, + { 'foo': 'b' }, + { 'foo': 'c' }, + { 'foo': 'b' }, + { 'foo': 'c' }, + { 'foo': 'c' }, + ] + + deepEqual (groupBy (array, 'foo'), { + 'a': [ { 'foo': 'a' } ], + 'b': [ { 'foo': 'b' }, { 'foo': 'b' } ], + 'c': [ { 'foo': 'c' }, { 'foo': 'c' }, { 'foo': 'c' } ], + }) +}) + +/* ------------------------------------------------------------------------ */ + +it ('filterBy() works', () => { + + const array = [ + { 'foo': 'a' }, + { 'foo': undefined }, + { 'foo': 'b' }, + { }, + { 'foo': 'a', 'bar': 'b' }, + { 'foo': 'c' }, + { 'foo': 'd' }, + { 'foo': 'b' }, + { 'foo': 'c' }, + { 'foo': 'c' }, + ] + + deepEqual (filterBy (array, 'foo', 'a'), [ + { 'foo': 'a' }, + { 'foo': 'a', 'bar': 'b' }, + ]) +}) + +/* ------------------------------------------------------------------------ */ + +it ('omit works', () => { + + deepEqual (omit ({ }, 'foo'), {}) + deepEqual (omit ({ foo: 2 }, 'foo'), { }) + deepEqual (omit ({ foo: 2, bar: 3 }, 'foo'), { bar: 3 }) + deepEqual (omit ({ foo: 2, bar: 3 }, ['foo']), { bar: 3 }) + deepEqual (omit ({ foo: 2, bar: 3 }), { foo: 2, bar: 3 }) + deepEqual (omit ({ foo: 2, bar: 3 }, 'foo', 'bar'), {}) + deepEqual (omit ({ foo: 2, bar: 3 }, ['foo'], 'bar'), {}) + deepEqual (omit ({ 5: 2, bar: 3 }, [ 5 ]), { bar: 3 }) + deepEqual (omit ({ 5: 2, bar: 3 }, 5), { bar: 3 }) +}) + +/* ------------------------------------------------------------------------ */ + +it ('sum works', () => { + + equal (undefined, sum ()) + equal (2, sum (2)) + equal (432, sum (2, 30, 400)) + equal (432, sum (2, undefined, [ 88 ], 30, '7', 400, null)) +}) + +/* ------------------------------------------------------------------------ */ + +it ('sortBy works', () => { + + const arr = [{ 'x': 5 }, { 'x': 2 }, { 'x': 4 }, { 'x': 0 }, { 'x': 1 }, { 'x': 3 }] + sortBy (arr, 'x') + + deepEqual (arr, [ + { 'x': 0 }, + { 'x': 1 }, + { 'x': 2 }, + { 'x': 3 }, + { 'x': 4 }, + { 'x': 5 }, + ]) + + deepEqual (sortBy (arr, 'x', true), [ + { 'x': 5 }, + { 'x': 4 }, + { 'x': 3 }, + { 'x': 2 }, + { 'x': 1 }, + { 'x': 0 }, + ]) + + deepEqual (sortBy ([], 'x'), []) +}) + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.number.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.number.js new file mode 100644 index 000000000..f2e5eedd3 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.number.js @@ -0,0 +1,289 @@ +'use strict' + +const { numberToString, decimalToPrecision, ROUND, TRUNCATE, DECIMAL_PLACES, TICK_SIZE, PAD_WITH_ZERO, SIGNIFICANT_DIGITS, toWei, fromWei } = require ('../../../../ccxt'); +const assert = require ('assert'); + +// ---------------------------------------------------------------------------- +// toWei / fromWei + +assert (toWei (1, 18) === '1000000000000000000'); +assert (toWei (1, 17) === '100000000000000000'); +assert (toWei (1, 16) === '10000000000000000'); +assert (toWei ('1', 18) === '1000000000000000000'); +assert (toWei ('1', 17) === '100000000000000000'); +assert (toWei ('1', 16) === '10000000000000000'); +assert (toWei (0, 18) === '0'); +assert (toWei (1, 0) === '1'); +assert (toWei (1, 1) === '10'); +assert (toWei (1.3, 18) === '1300000000000000000'); +assert (toWei ('1.3', 18) === '1300000000000000000'); +assert (toWei (1.999, 17) === '199900000000000000'); +assert (toWei ('1.999', 17) === '199900000000000000'); +assert (toWei ('0.1', 18) === '100000000000000000'); +assert (toWei ('0.01', 18) === '10000000000000000'); +assert (toWei ('0.001', 18) === '1000000000000000'); +assert (toWei (0.1, 18) === '100000000000000000'); +assert (toWei (0.01, 18) === '10000000000000000'); +assert (toWei (0.001, 18) === '1000000000000000'); +assert (toWei ('0.3323340739', 18) === '332334073900000000'); +assert (toWei (0.3323340739, 18) === '332334073900000000'); +assert (toWei ('0.009428', 18) === '9428000000000000'); +assert (toWei (0.009428, 18) === '9428000000000000'); + +// let us test that we get the inverse for all these test +assert (fromWei ('1000000000000000000', 18) === 1.0); +assert (fromWei ('100000000000000000', 17) === 1.0); +assert (fromWei ('10000000000000000', 16) === 1.0); +assert (fromWei (1000000000000000000, 18) === 1.0); +assert (fromWei (100000000000000000, 17) === 1.0); +assert (fromWei (10000000000000000, 16) === 1.0); +assert (fromWei ('1300000000000000000', 18) === 1.3); +assert (fromWei (1300000000000000000, 18) === 1.3); +assert (fromWei ('199900000000000000', 17) === 1.999); +assert (fromWei (199900000000000000, 17) === 1.999); +assert (fromWei ('100000000000000000', 18) === 0.1); +assert (fromWei ('10000000000000000', 18) === 0.01); +assert (fromWei ('1000000000000000', 18) === 0.001); +assert (fromWei (100000000000000000, 18) === 0.1); +assert (fromWei (10000000000000000, 18) === 0.01); +assert (fromWei (1000000000000000, 18) === 0.001); +assert (fromWei ('332334073900000000', 18) === 0.3323340739); +assert (fromWei (332334073900000000, 18) === 0.3323340739); +assert (fromWei ('9428000000000000', 18) === 0.009428); +assert (fromWei (9428000000000000, 18) === 0.009428); + +// ---------------------------------------------------------------------------- +// numberToString + +assert (numberToString (-7.8e-7) === '-0.00000078'); +assert (numberToString (7.8e-7) === '0.00000078'); +assert (numberToString (-17.805e-7) === '-0.0000017805'); +assert (numberToString (17.805e-7) === '0.0000017805'); +assert (numberToString (-7.0005e27) === '-7000500000000000000000000000'); +assert (numberToString (7.0005e27) === '7000500000000000000000000000'); +assert (numberToString (-7.9e27) === '-7900000000000000000000000000'); +assert (numberToString (7.9e27) === '7900000000000000000000000000'); +assert (numberToString (-12.345) === '-12.345'); +assert (numberToString (12.345) === '12.345'); +assert (numberToString (0) === '0'); +assert (numberToString (7.35946e21) === '7359460000000000000000'); +assert (numberToString (0.00000001) === '0.00000001'); +assert (numberToString (1e-7) === '0.0000001'); +assert (numberToString (-1e-7) === '-0.0000001'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionTruncationToNDigitsAfterDot + +assert (decimalToPrecision ('12.3456000', TRUNCATE, 100, DECIMAL_PLACES) === '12.3456'); +assert (decimalToPrecision ('12.3456', TRUNCATE, 100, DECIMAL_PLACES) === '12.3456'); +assert (decimalToPrecision ('12.3456', TRUNCATE, 4, DECIMAL_PLACES) === '12.3456'); +assert (decimalToPrecision ('12.3456', TRUNCATE, 3, DECIMAL_PLACES) === '12.345'); +assert (decimalToPrecision ('12.3456', TRUNCATE, 2, DECIMAL_PLACES) === '12.34'); +assert (decimalToPrecision ('12.3456', TRUNCATE, 1, DECIMAL_PLACES) === '12.3'); +assert (decimalToPrecision ('12.3456', TRUNCATE, 0, DECIMAL_PLACES) === '12'); + +assert (decimalToPrecision ('0.0000001', TRUNCATE, 8, DECIMAL_PLACES) === '0.0000001'); +assert (decimalToPrecision ('0.00000001', TRUNCATE, 8, DECIMAL_PLACES) === '0.00000001'); + +assert (decimalToPrecision ('0.000000000', TRUNCATE, 9, DECIMAL_PLACES, PAD_WITH_ZERO) === '0.000000000'); +assert (decimalToPrecision ('0.000000001', TRUNCATE, 9, DECIMAL_PLACES, PAD_WITH_ZERO) === '0.000000001'); + +assert (decimalToPrecision ('12.3456', TRUNCATE, -1, DECIMAL_PLACES) === '10'); +assert (decimalToPrecision ('123.456', TRUNCATE, -1, DECIMAL_PLACES) === '120'); +assert (decimalToPrecision ('123.456', TRUNCATE, -2, DECIMAL_PLACES) === '100'); +assert (decimalToPrecision ('9.99999', TRUNCATE, -1, DECIMAL_PLACES) === '0'); +assert (decimalToPrecision ('99.9999', TRUNCATE, -1, DECIMAL_PLACES) === '90'); +assert (decimalToPrecision ('99.9999', TRUNCATE, -2, DECIMAL_PLACES) === '0'); + +assert (decimalToPrecision ('0', TRUNCATE, 0, DECIMAL_PLACES) === '0'); +assert (decimalToPrecision ('-0.9', TRUNCATE, 0, DECIMAL_PLACES) === '0'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionTruncationToNSignificantDigits + +assert (decimalToPrecision ('0.000123456700', TRUNCATE, 100, SIGNIFICANT_DIGITS) === '0.0001234567'); +assert (decimalToPrecision ('0.0001234567', TRUNCATE, 100, SIGNIFICANT_DIGITS) === '0.0001234567'); +assert (decimalToPrecision ('0.0001234567', TRUNCATE, 7, SIGNIFICANT_DIGITS) === '0.0001234567'); + +assert (decimalToPrecision ('0.000123456', TRUNCATE, 6, SIGNIFICANT_DIGITS) === '0.000123456'); +assert (decimalToPrecision ('0.000123456', TRUNCATE, 5, SIGNIFICANT_DIGITS) === '0.00012345'); +assert (decimalToPrecision ('0.000123456', TRUNCATE, 2, SIGNIFICANT_DIGITS) === '0.00012'); +assert (decimalToPrecision ('0.000123456', TRUNCATE, 1, SIGNIFICANT_DIGITS) === '0.0001'); + +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 10, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.0000987'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 8, SIGNIFICANT_DIGITS) === '123.00009'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 7, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.0000'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 6, SIGNIFICANT_DIGITS) === '123'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 5, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.00'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 4, SIGNIFICANT_DIGITS) === '123'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 4, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123.0'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 3, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '123'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 2, SIGNIFICANT_DIGITS) === '120'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 1, SIGNIFICANT_DIGITS) === '100'); +assert (decimalToPrecision ('123.0000987654', TRUNCATE, 1, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '100'); + +assert (decimalToPrecision ('1234', TRUNCATE, 5, SIGNIFICANT_DIGITS) === '1234'); +assert (decimalToPrecision ('1234', TRUNCATE, 5, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '1234.0'); +assert (decimalToPrecision ('1234', TRUNCATE, 4, SIGNIFICANT_DIGITS) === '1234'); +assert (decimalToPrecision ('1234', TRUNCATE, 4, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '1234'); +assert (decimalToPrecision ('1234.69', TRUNCATE, 0, SIGNIFICANT_DIGITS) === '0'); +assert (decimalToPrecision ('1234.69', TRUNCATE, 0, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionRoundingToNDigitsAfterDot + +assert (decimalToPrecision ('12.3456000', ROUND, 100, DECIMAL_PLACES) === '12.3456'); +assert (decimalToPrecision ('12.3456', ROUND, 100, DECIMAL_PLACES) === '12.3456'); +assert (decimalToPrecision ('12.3456', ROUND, 4, DECIMAL_PLACES) === '12.3456'); +assert (decimalToPrecision ('12.3456', ROUND, 3, DECIMAL_PLACES) === '12.346'); +assert (decimalToPrecision ('12.3456', ROUND, 2, DECIMAL_PLACES) === '12.35'); +assert (decimalToPrecision ('12.3456', ROUND, 1, DECIMAL_PLACES) === '12.3'); +assert (decimalToPrecision ('12.3456', ROUND, 0, DECIMAL_PLACES) === '12'); + +// a problematic case in PHP +assert (decimalToPrecision ('10000', ROUND, 6, DECIMAL_PLACES) === '10000'); +assert (decimalToPrecision ('0.00003186', ROUND, 8, DECIMAL_PLACES) === '0.00003186'); + +assert (decimalToPrecision ('12.3456', ROUND, -1, DECIMAL_PLACES) === '10'); +assert (decimalToPrecision ('123.456', ROUND, -1, DECIMAL_PLACES) === '120'); +assert (decimalToPrecision ('123.456', ROUND, -2, DECIMAL_PLACES) === '100'); +assert (decimalToPrecision ('9.99999', ROUND, -1, DECIMAL_PLACES) === '10'); +assert (decimalToPrecision ('99.9999', ROUND, -1, DECIMAL_PLACES) === '100'); +assert (decimalToPrecision ('99.9999', ROUND, -2, DECIMAL_PLACES) === '100'); + +assert (decimalToPrecision ('9.999', ROUND, 3, DECIMAL_PLACES) === '9.999'); +assert (decimalToPrecision ('9.999', ROUND, 2, DECIMAL_PLACES) === '10'); +assert (decimalToPrecision ('9.999', ROUND, 2, DECIMAL_PLACES, PAD_WITH_ZERO) === '10.00'); +assert (decimalToPrecision ('99.999', ROUND, 2, DECIMAL_PLACES, PAD_WITH_ZERO) === '100.00'); +assert (decimalToPrecision ('-99.999', ROUND, 2, DECIMAL_PLACES, PAD_WITH_ZERO) === '-100.00'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionRoundingToNSignificantDigits + +assert (decimalToPrecision ('0.000123456700', ROUND, 100, SIGNIFICANT_DIGITS) === '0.0001234567'); +assert (decimalToPrecision ('0.0001234567', ROUND, 100, SIGNIFICANT_DIGITS) === '0.0001234567'); +assert (decimalToPrecision ('0.0001234567', ROUND, 7, SIGNIFICANT_DIGITS) === '0.0001234567'); + +assert (decimalToPrecision ('0.000123456', ROUND, 6, SIGNIFICANT_DIGITS) === '0.000123456'); +assert (decimalToPrecision ('0.000123456', ROUND, 5, SIGNIFICANT_DIGITS) === '0.00012346'); +assert (decimalToPrecision ('0.000123456', ROUND, 4, SIGNIFICANT_DIGITS) === '0.0001235'); +assert (decimalToPrecision ('0.00012', ROUND, 2, SIGNIFICANT_DIGITS) === '0.00012'); +assert (decimalToPrecision ('0.0001', ROUND, 1, SIGNIFICANT_DIGITS) === '0.0001'); + +assert (decimalToPrecision ('123.0000987654', ROUND, 7, SIGNIFICANT_DIGITS) === '123.0001'); +assert (decimalToPrecision ('123.0000987654', ROUND, 6, SIGNIFICANT_DIGITS) === '123'); + +assert (decimalToPrecision ('0.00098765', ROUND, 2, SIGNIFICANT_DIGITS) === '0.00099'); +assert (decimalToPrecision ('0.00098765', ROUND, 2, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0.00099'); + +assert (decimalToPrecision ('0.00098765', ROUND, 1, SIGNIFICANT_DIGITS) === '0.001'); +assert (decimalToPrecision ('0.00098765', ROUND, 10, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0.0009876500000'); + +assert (decimalToPrecision ('0.098765', ROUND, 1, SIGNIFICANT_DIGITS, PAD_WITH_ZERO) === '0.1'); + +assert (decimalToPrecision ('0', ROUND, 0, SIGNIFICANT_DIGITS) === '0'); +assert (decimalToPrecision ('-0.123', ROUND, 0, SIGNIFICANT_DIGITS) === '0'); + +assert (decimalToPrecision ('0.00000044', ROUND, 5, SIGNIFICANT_DIGITS) === '0.00000044'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionRoundingToTickSize + +assert (decimalToPrecision ('0.000123456700', ROUND, 0.00012, TICK_SIZE) === '0.00012'); +assert (decimalToPrecision ('0.0001234567', ROUND, 0.00013, TICK_SIZE) === '0.00013'); +assert (decimalToPrecision ('0.0001234567', TRUNCATE, 0.00013, TICK_SIZE) === '0'); +assert (decimalToPrecision ('101.000123456700', ROUND, 100, TICK_SIZE) === '100'); +assert (decimalToPrecision ('0.000123456700', ROUND, 100, TICK_SIZE) === '0'); +assert (decimalToPrecision ('165', TRUNCATE, 110, TICK_SIZE) === '110'); +assert (decimalToPrecision ('3210', TRUNCATE, 1110, TICK_SIZE) === '2220'); +assert (decimalToPrecision ('165', ROUND, 110, TICK_SIZE) === '220'); +assert (decimalToPrecision ('0.000123456789', ROUND, 0.00000012, TICK_SIZE) === '0.00012348'); +assert (decimalToPrecision ('0.000123456789', TRUNCATE, 0.00000012, TICK_SIZE) === '0.00012336'); +assert (decimalToPrecision ('0.000273398', ROUND, 1e-7, TICK_SIZE) === '0.0002734'); + +assert (decimalToPrecision ('0.01', ROUND, 0.0001, TICK_SIZE, PAD_WITH_ZERO) === '0.0100'); +assert (decimalToPrecision ('0.01', TRUNCATE, 0.0001, TICK_SIZE, PAD_WITH_ZERO) === '0.0100'); + +assert (decimalToPrecision ('-0.000123456789', ROUND, 0.00000012, TICK_SIZE) === '-0.00012348'); +assert (decimalToPrecision ('-0.000123456789', TRUNCATE, 0.00000012, TICK_SIZE) === '-0.00012336'); +assert (decimalToPrecision ('-165', TRUNCATE, 110, TICK_SIZE) === '-110'); +assert (decimalToPrecision ('-165', ROUND, 110, TICK_SIZE) === '-220'); +assert (decimalToPrecision ('-1650', TRUNCATE, 1100, TICK_SIZE) === '-1100'); +assert (decimalToPrecision ('-1650', ROUND, 1100, TICK_SIZE) === '-2200'); + +assert (decimalToPrecision ('0.0006', TRUNCATE, 0.0001, TICK_SIZE) === '0.0006'); +assert (decimalToPrecision ('-0.0006', TRUNCATE, 0.0001, TICK_SIZE) === '-0.0006'); +assert (decimalToPrecision ('0.6', TRUNCATE, 0.2, TICK_SIZE) === '0.6'); +assert (decimalToPrecision ('-0.6', TRUNCATE, 0.2, TICK_SIZE) === '-0.6'); +assert (decimalToPrecision ('1.2', ROUND, 0.4, TICK_SIZE) === '1.2'); +assert (decimalToPrecision ('-1.2', ROUND, 0.4, TICK_SIZE) === '-1.2'); +assert (decimalToPrecision ('1.2', ROUND, 0.02, TICK_SIZE) === '1.2'); +assert (decimalToPrecision ('-1.2', ROUND, 0.02, TICK_SIZE) === '-1.2'); +assert (decimalToPrecision ('44', ROUND, 4.4, TICK_SIZE) === '44'); +assert (decimalToPrecision ('-44', ROUND, 4.4, TICK_SIZE) === '-44'); +assert (decimalToPrecision ('44.00000001', ROUND, 4.4, TICK_SIZE) === '44'); +assert (decimalToPrecision ('-44.00000001', ROUND, 4.4, TICK_SIZE) === '-44'); + +// https://github.com/ccxt/ccxt/issues/6731 +assert (decimalToPrecision ('20', TRUNCATE, 0.00000001, TICK_SIZE) === '20'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionNegativeNumbers + +assert (decimalToPrecision ('-0.123456', TRUNCATE, 5, DECIMAL_PLACES) === '-0.12345'); +assert (decimalToPrecision ('-0.123456', ROUND, 5, DECIMAL_PLACES) === '-0.12346'); + +// ---------------------------------------------------------------------------- +// decimalToPrecision: without dot / trailing dot + +assert (decimalToPrecision ('123', TRUNCATE, 0) === '123'); + +assert (decimalToPrecision ('123', TRUNCATE, 5, DECIMAL_PLACES) === '123'); +assert (decimalToPrecision ('123', TRUNCATE, 5, DECIMAL_PLACES, PAD_WITH_ZERO) === '123.00000'); + +assert (decimalToPrecision ('123.', TRUNCATE, 0, DECIMAL_PLACES) === '123'); +assert (decimalToPrecision ('123.', TRUNCATE, 5, DECIMAL_PLACES, PAD_WITH_ZERO) === '123.00000'); + +assert (decimalToPrecision ('0.', TRUNCATE, 0) === '0'); +assert (decimalToPrecision ('0.', TRUNCATE, 5, DECIMAL_PLACES, PAD_WITH_ZERO) === '0.00000'); + +// ---------------------------------------------------------------------------- +// decimalToPrecision: rounding for equidistant digits + +assert (decimalToPrecision ('1.44', ROUND, 1, DECIMAL_PLACES) === '1.4'); +assert (decimalToPrecision ('1.45', ROUND, 1, DECIMAL_PLACES) === '1.5'); +assert (decimalToPrecision ('1.45', ROUND, 0, DECIMAL_PLACES) === '1'); // not 2 + +// ---------------------------------------------------------------------------- +// negative precision only implemented so far in python +// pretty useless for decimal applications as anything |x| < 5 === 0 +// NO_PADDING and PAD_WITH_ZERO are ignored + +assert (decimalToPrecision ('5', ROUND, -1, DECIMAL_PLACES) === '10'); +assert (decimalToPrecision ('4.999', ROUND, -1, DECIMAL_PLACES) === '0'); +assert (decimalToPrecision ('0.0431531423', ROUND, -1, DECIMAL_PLACES) === '0'); +assert (decimalToPrecision ('-69.3', ROUND, -1, DECIMAL_PLACES) === '-70'); +assert (decimalToPrecision ('5001', ROUND, -4, DECIMAL_PLACES) === '10000'); +assert (decimalToPrecision ('4999.999', ROUND, -4, DECIMAL_PLACES) === '0'); + +assert (decimalToPrecision ('69.3', TRUNCATE, -2, DECIMAL_PLACES) === '0'); +assert (decimalToPrecision ('-69.3', TRUNCATE, -2, DECIMAL_PLACES) === '0'); +assert (decimalToPrecision ('69.3', TRUNCATE, -1, SIGNIFICANT_DIGITS) === '60'); +assert (decimalToPrecision ('-69.3', TRUNCATE, -1, SIGNIFICANT_DIGITS) === '-60'); +assert (decimalToPrecision ('69.3', TRUNCATE, -2, SIGNIFICANT_DIGITS) === '0'); +assert (decimalToPrecision ('1602000000000000000000', TRUNCATE, 3, SIGNIFICANT_DIGITS) === '1600000000000000000000'); + +// ---------------------------------------------------------------------------- +// testDecimalToPrecisionErrorHandling (todo) +// +// throws (() => +// decimalToPrecision ('123456.789', TRUNCATE, -2, DECIMAL_PLACES), +// 'negative precision is not yet supported') +// +// throws (() => +// decimalToPrecision ('foo'), +// "invalid number (contains an illegal character 'f')") +// +// throws (() => +// decimalToPrecision ('0.01', TRUNCATE, -1, TICK_SIZE), +// "TICK_SIZE cant be used with negative numPrecisionDigits") diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.time.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.time.js new file mode 100644 index 000000000..1988410a0 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.time.js @@ -0,0 +1,56 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const { 'strictEqual': equal } = require ('assert') +const { setTimeout_safe, timeout, now, isWindows } = require ('../../../../ccxt') + , approxEqual = (a, b) => equal (true, Math.abs (a - b) <= 20) + , rejectsWith = (msg, x) => + x.then (() => { throw new Error ('not rejected!') }) + .catch ((e) => { equal (e.message, msg) }) + +// these tests break Travis 80% of the time +// TODO: make a more robust test that is not failing on certain machines under certain conditions + +// ---------------------------------------------------------------------------- + +// it ('setTimeout_safe is working', (done) => { +// if (isWindows) { +// // temporarily skip this test with appveyor +// // todo: refix timer functions for windows properly +// done () +// } else { +// const start = now () +// const calls = [] +// const brokenSetTimeout = (done, ms) => { +// calls.push ({ when: now () - start, ms_asked: ms }) +// return setTimeout (done, 100) // simulates a defect setTimeout implementation that sleeps wrong time (100ms always in this test) +// } +// // ask to sleep 250ms +// setTimeout_safe (() => { +// approxEqual (calls[0].ms_asked, 250) +// approxEqual (calls[1].ms_asked, 150) +// approxEqual (calls[2].ms_asked, 50) +// done () +// }, 250, brokenSetTimeout) +// } +// }) + +// ---------------------------------------------------------------------------- + +// it ('setTimeout_safe canceling is working', (done) => { +// const brokenSetTimeout = (done, ms) => setTimeout (done, 100) // simulates a defect setTimeout implementation that sleeps wrong time (100ms always in this test) +// const clear = setTimeout_safe (() => { throw new Error ('shouldnt happen!') }, 250, brokenSetTimeout) +// setTimeout (() => { clear () }, 200) +// setTimeout (() => { done () }, 400) +// }) + +// ---------------------------------------------------------------------------- + +// it ('timeout() is working', async () => { +// equal ('foo', await timeout (200, new Promise (resolve => setTimeout (() => resolve ('foo'), 100)))) +// await rejectsWith ('foo', timeout (100, Promise.reject (new Error ('foo')))) +// await rejectsWith ('timed out', timeout (100, new Promise ((resolve, reject) => setTimeout (() => reject (new Error ('foo')), 200)))) +// }) + +// ---------------------------------------------------------------------------- diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.type.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.type.js new file mode 100644 index 000000000..a32a4fc04 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/functions/test.type.js @@ -0,0 +1,54 @@ +'use strict' + +/* ------------------------------------------------------------------------ */ + +const { safeFloat, safeInteger, safeValue } = require ('../../../../ccxt') +const { strictEqual: equal, deepEqual } = require ('assert') + +/* ------------------------------------------------------------------------ */ + +it ('safeFloat/safeInteger is robust', async () => { + + const $default = {} + + const fns = { safeFloat, safeInteger } + + for (const fn of ['safeFloat', 'safeInteger']) { + + equal (fns[fn] ({ 'x': false }, 'x', $default), $default) + equal (fns[fn] ({ 'x': true }, 'x', $default), $default) + equal (fns[fn] ({ 'x': [] }, 'x', $default), $default) + equal (fns[fn] ({ 'x': [0] }, 'x', $default), $default) + equal (fns[fn] ({ 'x': [1] }, 'x', $default), $default) + equal (fns[fn] ({ 'x': {} }, 'x', $default), $default) + equal (fns[fn] ({ 'x': Number.NaN }, 'x'), undefined) + equal (fns[fn] ({ 'x': Number.POSITIVE_INFINITY }, 'x'), undefined) + equal (fns[fn] ({ 'x': null }, 'x', undefined), undefined) + equal (fns[fn] ({ 'x': null }, 'x', $default), $default) + equal (fns[fn] ({ 'x': '1.0' }, 'x'), 1.0) + equal (fns[fn] ({ 'x': '-1.0' }, 'x'), -1.0) + equal (fns[fn] ({ 'x': 1.0 }, 'x'), 1.0) + equal (fns[fn] ({ 'x': 0 }, 'x'), 0) + equal (fns[fn] ({ 'x': undefined }, 'x', $default), $default) + equal (fns[fn] ({ 'x': "" }, 'x'), undefined) + equal (fns[fn] ({ 'x': "" }, 'x', 0), 0) + equal (fns[fn] ({}, 'x'), undefined) + equal (fns[fn] ({}, 'x', 0), 0) + } + + equal (safeFloat ({ 'x': 1.59999999 }, 'x'), 1.59999999) + equal (safeInteger ({ 'x': 1.59999999 }, 'x'), 1) +}) + +/* ------------------------------------------------------------------------ */ + +it ('safeValue works', () => { + + equal (safeValue ({}, 'foo'), undefined) + equal (safeValue ({}, 'foo', 'bar'), 'bar') + equal (safeValue ({ 'foo': 'bar' }, 'foo'), 'bar') + equal (safeValue ({ 'foo': '' }, 'foo'), '') + equal (safeValue ({ 'foo': 0 }, 'foo'), 0) +}) + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/test.base.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/test.base.js new file mode 100644 index 000000000..0eb7fcffa --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/base/test.base.js @@ -0,0 +1,214 @@ +'use strict' + +/* ------------------------------------------------------------------------ */ + +global.log = require ('ololog') // for easier debugging + +/* ------------------------------------------------------------------------ */ + +const { Exchange, keys, values, unique, index, aggregate } = require ('../../../ccxt') +const { strictEqual: equal, deepEqual } = require ('assert') + +/* ------------------------------------------------------------------------ */ + +describe ('ccxt base code', () => { + +/* ------------------------------------------------------------------------ */ + + require ('./functions/test.generic') + require ('./functions/test.time') + require ('./functions/test.type') + // Some test files are transpiled to other languages and must be kept very simple so we put `it` call externally to them + // Some mocha reporters will swallow `assert` failures if they are outside of `it` clause so we must call `it` + it ('decimalToPrecision() works', () => { + require ('./functions/test.number') + }) + it ('date&time functions work', () => { + require('./functions/test.datetime') + }) + it ('cryptography code works', () => { + require('./functions/test.crypto') + }) + +/* ------------------------------------------------------------------------ */ + + it ('calculateFee() works', () => { + + const price = 100.00 + const amount = 10.00 + const taker = 0.0025 + const maker = 0.0010 + const fees = { taker, maker } + const market = { + 'id': 'foobar', + 'symbol': 'FOO/BAR', + 'base': 'FOO', + 'quote': 'BAR', + 'taker': taker, + 'maker': maker, + 'precision': { + 'amount': 8, + 'price': 8, + }, + } + + const exchange = new Exchange ({ + 'id': 'mock', + 'markets': { + 'FOO/BAR': market, + }, + }) + + Object.keys (fees).forEach (takerOrMaker => { + + const result = exchange.calculateFee (market['symbol'], 'limit', 'sell', amount, price, takerOrMaker, {}) + + deepEqual (result, { + 'type': takerOrMaker, + 'currency': 'BAR', + 'rate': fees[takerOrMaker], + 'cost': fees[takerOrMaker] * amount * price, + }) + }) + }) + +/* ------------------------------------------------------------------------ */ + + it.skip ('exchange config extension works', () => { + + + cost = { min: 0.001, max: 1000 } + precision = { amount: 3 } + const exchange = new binance ({ + 'markets': { + 'ETH/BTC': { limits: { cost }, precision }, + }, + }) + + deepEqual (exchange.markets['ETH/BTC'].limits.cost, cost) + deepEqual (exchange.markets['ETH/BTC'].precision, { price: 6, amount: 3 }) + deepEqual (exchange.markets['ETH/BTC'].symbol, 'ETH/BTC') + }) + +/* ------------------------------------------------------------------------ */ + + it ('aggregate() works', () => { + + const bids = [ + [ 789.1, 123.0 ], + [ 789.100, 123.0 ], + [ 123.0, 456.0 ], + [ 789.0, 123.0 ], + [ 789.10, 123.0 ], + ] + + const asks = [ + [ 123.0, 456.0 ], + [ 789.0, 123.0 ], + [ 789.10, 123.0 ], + ] + + deepEqual (aggregate (bids.sort ()), [ + [ 123.0, 456.0 ], + [ 789.0, 123.0 ], + [ 789.1, 369.0 ], + ]) + + deepEqual (aggregate (asks.sort ()), [ + [ 123.0, 456.0 ], + [ 789.0, 123.0 ], + [ 789.10, 123.0 ], + ]) + + deepEqual (aggregate ([]), []) + }) + +/* ------------------------------------------------------------------------ */ + + it ('parseBalance() works', () => { + + const exchange = new Exchange ({ + 'markets': { + 'ETH/BTC': { 'id': 'ETH/BTC', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', } + } + }) + + const input = { + 'ETH': { 'free': 10, 'used': 10, 'total': 20 }, + 'ZEC': { 'free': 0, 'used': 0, 'total': 0 }, + } + + const expected = { + 'ETH': { 'free': 10, 'used': 10, 'total': 20 }, + 'ZEC': { 'free': 0, 'used': 0, 'total': 0 }, + 'free': { + 'ETH': 10, + 'ZEC': 0, + }, + 'used': { + 'ETH': 10, + 'ZEC': 0, + }, + 'total': { + 'ETH': 20, + 'ZEC': 0, + }, + } + + const actual = exchange.parseBalance (input) + + deepEqual (actual, expected) + }) + +/* ------------------------------------------------------------------------ */ + + it ('camelCase/camel_case property conversion works', () => { + + const exchange = new Exchange ({ 'id': 'mock' }) + + const propsSeenBefore = index ( + ["isNode", "empty", "keys", "values", "extend", "clone", "index", "ordered", "unique", "keysort", "indexBy", "groupBy", "filterBy", "sortBy", "flatten", "pluck", "omit", "sum", "deepExtend", "uuid", "unCamelCase", "capitalize", "isNumber", "isArray", "isObject", "isString", "isStringCoercible", "isDictionary", "hasProps", "prop", "asFloat", "asInteger", "safeFloat", "safeInteger", "safeValue", "safeString", "decimal", "toFixed", "truncate", "truncateToString", "precisionFromString", "stringToBinary", "stringToBase64", "base64ToBinary", "base64ToString", "binaryToString", "binaryConcat", "urlencode", "rawencode", "urlencodeBase64", "hash", "hmac", "jwt", "time", "setTimeout_safe", "sleep", "TimedOut", "timeout", "throttle", "json", "unjson", "aggregate", "is_node", "index_by", "group_by", "filter_by", "sort_by", "deep_extend", "un_camel_case", "is_number", "is_array", "is_object", "is_string", "is_string_coercible", "is_dictionary", "has_props", "as_float", "as_integer", "safe_float", "safe_integer", "safe_value", "safe_string", "to_fixed", "truncate_to_string", "precision_from_string", "string_to_binary", "string_to_base64", "utf16To_base64", "base64To_binary", "base64To_string", "binary_to_string", "binary_concat", "urlencode_base64", "set_timeout_safe", "Timed_out", "encode", "decode", "userAgents", "headers", "proxy", "origin", "iso8601", "parse8601", "milliseconds", "microseconds", "seconds", "id", "enableRateLimit", "rateLimit", "parseJsonResponse", "substituteCommonCurrencyCodes", "parseBalanceFromOpenOrders", "verbose", "debug", "journal", "userAgent", "twofa", "timeframes", "hasPublicAPI", "hasPrivateAPI", "hasCORS", "hasDeposit", "hasFetchBalance", "hasFetchClosedOrders", "hasFetchCurrencies", "hasFetchMyTrades", "hasFetchOHLCV", "hasFetchOpenOrders", "hasFetchOrder", "hasFetchOrderBook", "hasFetchOrders", "hasFetchTicker", "hasFetchTickers", "hasFetchBidsAsks", "hasFetchTrades", "hasWithdraw", "hasCreateOrder", "hasCancelOrder", "apiKey", "secret", "uid", "login", "password", "requiredCredentials", "exceptions", "balance", "orderbooks", "tickers", "fees", "orders", "trades", "currencies", "last_http_response", "last_json_response", "arrayConcat", "market_id", "market_ids", "array_concat", "implode_params", "extract_params", "fetch_balance", "fetch_free_balance", "fetch_used_balance","fetch_total_balance", "fetch_l2_order_book", "fetch_order_book", "fetch_bids_asks", "fetch_tickers", "fetch_ticker", "fetch_trades", "fetch_order", "fetch_orders", "fetch_open_orders", "fetch_closed_orders", "fetch_order_status", "fetch_markets", "load_markets", "set_markets", "parse_balance", "parse_bid_ask", "parse_bids_asks", "parse_order_book", "parse_trades", "parse_orders", "parse_ohlcv", "parse_ohlcvs", "edit_limit_buy_order", "edit_limit_sell_order", "edit_limit_order", "edit_order", "create_limit_buy_order", "create_limit_sell_order", "create_market_buy_order", "create_market_sell_order", "create_order", "calculate_fee", "common_currency_code", "price_to_precision", "amount_to_precision", "amount_to_string", "fee_to_precision", "cost_to_precision", "constructor", "getMarket", "describe", "defaults", "nonce", "encodeURIComponent", "checkRequiredCredentials", "initRestRateLimiter", "defineRestApi", "fetch", "fetch2", "request", "handleErrors", "defaultErrorHandler", "handleRestErrors", "handleRestResponse", "setMarkets", "loadMarkets", "fetchBidsAsks", "fetchTickers", "fetchOrder", "fetchOrders", "fetchOpenOrders", "fetchClosedOrders", "fetchMyTrades", "fetchCurrencies", "fetchMarkets", "fetchOrderStatus", "account", "commonCurrencyCode", "currency", "market", "marketId", "marketIds", "symbol", "extractParams", "implodeParams", "url", "parseBidAsk", "parseBidsAsks", "fetchL2OrderBook", "parseOrderBook", "parseBalance", "fetchPartialBalance", "fetchFreeBalance", "fetchUsedBalance", "fetchTotalBalance", "filterBySinceLimit", "parseTrades", "parseOrders", "filterOrdersBySymbol", "parseOHLCV", "parseOHLCVs", "editLimitBuyOrder", "editLimitSellOrder", "editLimitOrder", "editOrder", "createLimitBuyOrder", "createLimitSellOrder", "createMarketBuyOrder", "createMarketSellOrder", "costToPrecision", "priceToPrecision", "amountToPrecision", "amountToLots", "feeToPrecision", "calculateFee", "Ymd", "YmdHMS"] + ) + + const props = index (Object.getOwnPropertyNames (exchange).concat ( + Object.getOwnPropertyNames (exchange.constructor.prototype))) + + for (const k of Array.from (propsSeenBefore)) + if (this[k] && !props.has (k)) + throw new Error (`missing prop: ${k}`) + + for (const k of Array.from (props)) + if (!propsSeenBefore.has (k)) + log.magenta.noLocate (`+ ${k}`) + + }) + +/* ------------------------------------------------------------------------ */ + + it ('camelCase/camel_case property conversion works #2', () => { + + class Derived extends Exchange {} + const derived = new Derived () + equal (typeof derived.load_markets, 'function') + }) + +/* ------------------------------------------------------------------------ */ + + it ('legacy .hasSomething maps to has.something automatically', () => { + + const exchange = new Exchange ({ + id: 'mock', + has: { + CORS: true, + publicAPI: false, + fetchDepositAddress: 'emulated' + } + }) + + equal (exchange.hasCORS, true) + equal (exchange.hasPublicAPI, false) + equal (exchange.hasFetchDepositAddress, true) + }) +}) + +/* ------------------------------------------------------------------------ */ diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InsufficientFunds.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InsufficientFunds.js new file mode 100644 index 000000000..fb0c3235b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InsufficientFunds.js @@ -0,0 +1,90 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , ccxt = require ('../../../ccxt.js') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ +// will try to place a buy order at the minimum price level on minimum amount possible +// will skip if balance is positive or market limits are not set + +module.exports = async (exchange, symbol, balance) => { + + if (!exchange.has.createOrder) { + log ('createOrder not supported') + return + } + + const markets = await exchange.loadMarkets () + const market = markets[symbol] + if (market.limits === undefined) { + log ('market.limits property is not set, will not test order creation') + return + } + + const { price, amount, cost } = market.limits + + if (price === undefined || amount === undefined || cost === undefined) { + log ('market.limits.[price|amount|cost] property is not set, will not test order creation') + return + } + + let minPrice = price.min + let minAmount = amount.min // will be adjusted co cover minCost if needed + const minCost = cost.min + + if (minPrice === undefined || minAmount === undefined || minCost === undefined) { + log ('min limits are not set, will not test order creation') + return + } + + if (minCost > minPrice * minAmount) { + minAmount = minCost / minPrice + } + + minPrice = exchange.priceToPrecision (symbol, minPrice) + minAmount = exchange.amountToPrecision (symbol, minAmount) + + if (balance === undefined) { + log ('balance is not set, cannot ensure safety, will not test order creation') + return + } + + const { base, quote } = market + if (balance[quote] !== undefined && balance[quote].total > 0) { + log ('balance is not empty, will not test order creation') + return + } + + try { + + log ('creating limit buy order...', symbol, minAmount, minPrice) + + let order = await exchange.createLimitBuyOrder (symbol, minAmount, minPrice) + + log ('order created although it should not had to - cleaning up') + + log (order) + + await exchange.cancelOrder (order.id, symbol) + + assert.fail () + + } catch (e) { + + if (e instanceof ccxt.InsufficientFunds) { + + log ('InsufficientFunds thrown as expected') + + } else { + + log ('InsufficientFunds failed, exception follows:') + throw e + } + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InvalidNonce.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InvalidNonce.js new file mode 100644 index 000000000..53073249c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InvalidNonce.js @@ -0,0 +1,67 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , ccxt = require ('../../../ccxt.js') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + log.green ('AuthenticationError (bad nonce) test...') + + const hasFetchBalance = exchange.has.fetchBalance + const hasFetchMyTrades = exchange.has.fetchMyTrades + const hasFetchOrders = exchange.has.fetchOrders + + if (hasFetchBalance || hasFetchMyTrades || hasFetchOrders) { + + // save the nonce temporarily and replace it with a fake one + const nonce = exchange.nonce + exchange.nonce = () => 1 + + try { + + // check if handleErrors() throws AuthenticationError if an exchange + // responds with an error on a bad nonce + // (still, some exchanges that require nonce silently eat bad nonce w/o an error) + + if (hasFetchBalance) + await exchange.fetchBalance () + else if (hasFetchMyTrades) + await exchange.fetchMyTrades (symbol, 0) + else + await exchange.fetchOrders (symbol) + + // restore the nonce so the caller may proceed in case bad nonce was accepted by an exchange + exchange.nonce = nonce + log.warn (exchange.id + ': AuthenticationError: bad nonce swallowed') + + } catch (e) { + + // restore the nonce so the caller may proceed in case the test failed + exchange.nonce = nonce + + if (e instanceof ccxt.AuthenticationError || e instanceof ccxt.InvalidNonce) { + + // it has thrown the exception as expected + log.green ('AuthenticationError test passed') + + } else { + + // rethrow an unexpected error if any + throw e + } + } + + } else { + + log (exchange.id + ' has no means of testing for bad nonce') + + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InvalidOrder.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InvalidOrder.js new file mode 100644 index 000000000..5d6b0884b --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.InvalidOrder.js @@ -0,0 +1,39 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , ccxt = require ('../../../ccxt.js') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + if (!exchange.has.createOrder) { + log ('createOrder not supported') + return + } + + try { + + await exchange.createLimitBuyOrder (symbol, 0, 0) + assert.fail () + + } catch (e) { + + if (e instanceof ccxt.InvalidOrder) { + + log ('InvalidOrder thrown as expected') + return + + } else { + + log ('InvalidOrder failed, exception follows:') + throw e + } + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.OrderNotFound.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.OrderNotFound.js new file mode 100644 index 000000000..fd75a668c --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/errors/test.OrderNotFound.js @@ -0,0 +1,43 @@ +'use strict' + +// ---------------------------------------------------------------------------- + +const log = require ('ololog') + , ansi = require ('ansicolor').nice + , chai = require ('chai') + , ccxt = require ('../../../ccxt.js') + , expect = chai.expect + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +module.exports = async (exchange, symbol) => { + + if (!exchange.has.createOrder) { + log ('createOrder not supported -> test skipped') + return + } + + let id = 1 + + try { + + await exchange.cancelOrder (id, symbol) + + log ('test failed') + + assert.fail () + + } catch (e) { + + if (e instanceof ccxt.OrderNotFound) { + + log ('OrderNotFound thrown as expected') + + } else { + + log ('OrderNotFound test failed') + throw e + } + } +} \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/test.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/test.js new file mode 100644 index 000000000..52710c534 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/test.js @@ -0,0 +1,413 @@ +'use strict' + +/* ------------------------------------------------------------------------ */ + +const [processPath, , exchangeId = null, exchangeSymbol = null] = process.argv.filter ((x) => !x.startsWith ('--')) +const verbose = process.argv.includes ('--verbose') || false +const debug = process.argv.includes ('--debug') || false + +/* ------------------------------------------------------------------------ */ + +const asTable = require ('as-table') + , log = require ('ololog') + , ansi = require ('ansicolor').nice + , fs = require ('fs') + , ccxt = require ('../../ccxt.js') // eslint-disable-line import/order + , chai = require ('chai') + , assert = chai.assert + +/* ------------------------------------------------------------------------ */ + +const warn = log.bright.yellow.error // .error → stderr + +/* ------------------------------------------------------------------------ */ + +process.on ('uncaughtException', (e) => { log.bright.red.error (e); process.exit (1) }) +process.on ('unhandledRejection', (e) => { log.bright.red.error (e); process.exit (1) }) + +/* ------------------------------------------------------------------------ */ + +log.bright ('\nTESTING', { 'exchange': exchangeId, 'symbol': exchangeSymbol || 'all' }, '\n') + +/* ------------------------------------------------------------------------ */ + +const proxies = [ + '', + 'https://cors-anywhere.herokuapp.com/' +] + +//----------------------------------------------------------------------------- + +const enableRateLimit = true + +const { Agent } = require ('https') + +const httpsAgent = new Agent ({ + 'ecdhCurve': 'auto', +}) + +const timeout = 20000 + +const exchange = new (ccxt)[exchangeId] ({ + httpsAgent, + verbose, + enableRateLimit, + debug, + timeout, +}) + +//----------------------------------------------------------------------------- + +const tests = {} +const properties = Object.keys (exchange.has) +properties + // eslint-disable-next-line no-path-concat + .filter ((property) => fs.existsSync (__dirname + '/Exchange/test.' + property + '.js')) + .forEach ((property) => { + // eslint-disable-next-line global-require, import/no-dynamic-require, no-path-concat + tests[property] = require (__dirname + '/Exchange/test.' + property + '.js') + }) + +const errors = require ('../base/errors.js') + +Object.keys (errors) + // eslint-disable-next-line no-path-concat + .filter ((error) => fs.existsSync (__dirname + '/errors/test.' + error + '.js')) + .forEach ((error) => { + // eslint-disable-next-line global-require, import/no-dynamic-require, no-path-concat + tests[error] = require (__dirname + '/errors/test.' + error + '.js') + }) + +//----------------------------------------------------------------------------- + +const keysGlobal = 'keys.json' +const keysLocal = 'keys.local.json' + +const keysFile = fs.existsSync (keysLocal) ? keysLocal : keysGlobal +// eslint-disable-next-line import/no-dynamic-require, no-path-concat +const settings = require (__dirname + '/../../' + keysFile)[exchangeId] + +if (settings) { + const keys = Object.keys (settings) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (settings[key]) { + settings[key] = ccxt.deepExtend (exchange[key] || {}, settings[key]) + } + } +} + +Object.assign (exchange, settings) + +if (settings && settings.skip) { + log.error.bright ('[Skipped]', { 'exchange': exchangeId, 'symbol': exchangeSymbol || 'all' }) + process.exit () +} + +//----------------------------------------------------------------------------- + +async function testSymbol (exchange, symbol) { + + if (exchange.id !== 'coinmarketcap') { + await tests['loadMarkets'] (exchange) + await tests['fetchCurrencies'] (exchange) + } + + await tests['fetchTicker'] (exchange, symbol) + await tests['fetchTickers'] (exchange, symbol) + await tests['fetchOHLCV'] (exchange, symbol) + await tests['fetchTrades'] (exchange, symbol) + + if (exchange.id === 'coinmarketcap') { + + log (await exchange.fetchTickers ()) + log (await exchange.fetchGlobal ()) + + } else if (exchange.id === 'coinbase') { + + // nothing for now + + } else { + + await tests['fetchOrderBook'] (exchange, symbol) + await tests['fetchL2OrderBook'] (exchange, symbol) + await tests['fetchOrderBooks'] (exchange) + } +} + +//----------------------------------------------------------------------------- + +async function loadExchange (exchange) { + + const markets = await exchange.loadMarkets () + + assert (typeof exchange.markets === 'object', '.markets is not an object') + assert (Array.isArray (exchange.symbols), '.symbols is not an array') + assert (exchange.symbols.length > 0, '.symbols.length <= 0 (less than or equal to zero)') + assert (Object.keys (exchange.markets).length > 0, 'Object.keys (.markets).length <= 0 (less than or equal to zero)') + assert (exchange.symbols.length === Object.keys (exchange.markets).length, 'number of .symbols is not equal to the number of .markets') + + const symbols = [ + 'BTC/CNY', + 'BTC/USD', + 'BTC/USDT', + 'BTC/EUR', + 'BTC/ETH', + 'ETH/BTC', + 'BTC/JPY', + 'ETH/EUR', + 'ETH/JPY', + 'ETH/CNY', + 'ETH/USD', + 'LTC/CNY', + 'DASH/BTC', + 'DOGE/BTC', + 'BTC/AUD', + 'BTC/PLN', + 'USD/SLL', + 'BTC/RUB', + 'BTC/UAH', + 'LTC/BTC', + ] + + let result = exchange.symbols.filter ((symbol) => symbols.indexOf (symbol) >= 0) + + if (result.length > 0) { + if (exchange.symbols.length > result.length) { + result = result.join (', ') + ' + more...' + } else { + result = result.join (', ') + } + } + + log (exchange.symbols.length.toString ().bright.green, 'symbols', result) +} + +//----------------------------------------------------------------------------- + +async function testExchange (exchange) { + + const codes = [ + 'BTC', + 'ETH', + 'XRP', + 'LTC', + 'BCH', + 'EOS', + 'BNB', + 'BSV', + 'USDT', + 'ATOM', + 'BAT', + 'BTG', + 'DASH', + 'DOGE', + 'ETC', + 'IOTA', + 'LSK', + 'MKR', + 'NEO', + 'PAX', + 'QTUM', + 'TRX', + 'TUSD', + 'USD', + 'USDC', + 'WAVES', + 'XEM', + 'XMR', + 'ZEC', + 'ZRX', + ] + + let code = codes[0] + for (let i = 0; i < codes.length; i++) { + if (codes[i] in exchange.currencies) { + code = codes[i] + } + } + + await loadExchange (exchange) + + let symbol = exchange.symbols[0] + const symbols = [ + 'BTC/USD', + 'BTC/USDT', + 'BTC/CNY', + 'BTC/EUR', + 'BTC/ETH', + 'ETH/BTC', + 'ETH/USD', + 'ETH/USDT', + 'BTC/JPY', + 'LTC/BTC', + 'ZRX/WETH', + ] + + for (let i = 0; i < symbols.length; i++) { + const s = symbols[i] + if (exchange.symbols.includes (s)) { + if ('active' in exchange.markets[s]) { + if (exchange.markets[s]['active'] === undefined) { + symbol = s; + } else if (exchange.markets[s]['active']) { + symbol = s; + } + } else { + symbol = s; + } + break + } + } + + if (exchange.id === 'okex') { + // okex has different order creation params for spot and futures markets + // this forces okex to use a spot market until there is a way to test + // several markets per exchange + symbol = 'BTC/USDT' + } + + log.green ('SYMBOL:', symbol) + if ((symbol.indexOf ('.d') < 0)) { + await testSymbol (exchange, symbol) + } + + if (!exchange.privateKey && (!exchange.apiKey || (exchange.apiKey.length < 1))) { + return true + } + + exchange.checkRequiredCredentials () + + if (exchange['has']['signIn']) { + await exchange.signIn () + } + + // move to testnet/sandbox if possible before accessing the balance + // if (exchange.urls['test']) + // exchange.urls['api'] = exchange.urls['test'] + + const balance = await tests['fetchBalance'] (exchange) + + await tests['fetchFundingFees'] (exchange) + await tests['fetchTradingFees'] (exchange) + await tests['fetchStatus'] (exchange) + + await tests['fetchOrders'] (exchange, symbol) + await tests['fetchOpenOrders'] (exchange, symbol) + await tests['fetchClosedOrders'] (exchange, symbol) + await tests['fetchMyTrades'] (exchange, symbol) + + if ('fetchLedger' in tests) { + await tests['fetchLedger'] (exchange, code) + } + + // const code = exchange.markets[symbol]['quote'] + // await tests['fetchTransactions'] (exchange, code) + // await tests['fetchDeposits'] (exchange, code) + // await tests['fetchWithdrawals'] (exchange, code) + + if (exchange.extendedTest) { + + await tests['InvalidNonce'] (exchange, symbol) + await tests['OrderNotFound'] (exchange, symbol) + await tests['InvalidOrder'] (exchange, symbol) + await tests['InsufficientFunds'] (exchange, symbol, balance) // danger zone - won't execute with non-empty balance + } + + // try { + // let marketSellOrder = + // await exchange.createMarketSellOrder (exchange.symbols[0], 1) + // console.log (exchange.id, 'ok', marketSellOrder) + // } catch (e) { + // console.log (exchange.id, 'error', 'market sell', e) + // } + // + // try { + // let marketBuyOrder = await exchange.createMarketBuyOrder (exchange.symbols[0], 1) + // console.log (exchange.id, 'ok', marketBuyOrder) + // } catch (e) { + // console.log (exchange.id, 'error', 'market buy', e) + // } + // + // try { + // let limitSellOrder = await exchange.createLimitSellOrder (exchange.symbols[0], 1, 3000) + // console.log (exchange.id, 'ok', limitSellOrder) + // } catch (e) { + // console.log (exchange.id, 'error', 'limit sell', e) + // } + // + // try { + // let limitBuyOrder = await exchange.createLimitBuyOrder (exchange.symbols[0], 1, 3000) + // console.log (exchange.id, 'ok', limitBuyOrder) + // } catch (e) { + // console.log (exchange.id, 'error', 'limit buy', e) + // } +} + +//----------------------------------------------------------------------------- + +async function tryAllProxies (exchange, proxies) { + + const index = proxies.indexOf (exchange.proxy) + let currentProxy = (index >= 0) ? index : 0 + const maxRetries = proxies.length + + if (settings && ('proxy' in settings)) { + currentProxy = proxies.indexOf (settings.proxy) + } + + for (let numRetries = 0; numRetries < maxRetries; numRetries++) { + + try { + + exchange.proxy = proxies[currentProxy] + + // add random origin for proxies + if (exchange.proxy.length > 0) { + exchange.origin = exchange.uuid () + } + + await testExchange (exchange) + + break + + } catch (e) { + + currentProxy = ++currentProxy % proxies.length + warn ('[' + e.constructor.name + '] ' + e.message.slice (0, 200)) + if (e instanceof ccxt.DDoSProtection) { + continue + } else if (e instanceof ccxt.RequestTimeout) { + continue + } else if (e instanceof ccxt.ExchangeNotAvailable) { + continue + } else if (e instanceof ccxt.AuthenticationError) { + return + } else if (e instanceof ccxt.AuthenticationError) { + return + } else if (e instanceof ccxt.InvalidNonce) { + return + } else { + throw e + } + } + } +} + +//----------------------------------------------------------------------------- + +async function test () { + + if (exchangeSymbol) { + + await loadExchange (exchange) + await testSymbol (exchange, exchangeSymbol) + + } else { + + await tryAllProxies (exchange, proxies) + } + +} + +test () diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/test.timeout_hang.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/test.timeout_hang.js new file mode 100644 index 000000000..41cdb87d8 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/test/test.timeout_hang.js @@ -0,0 +1,12 @@ +// run with `node test_timeout_hang` +// TODO: integrate with CI tests somehow... + +const { timeout } = require ('../base/functions') + +;(async function () { + + await timeout (10000, Promise.resolve ('foo')) + + console.log ('Look ma, no hangs!') // should terminate the process immediately.. + +}) () \ No newline at end of file diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/therock.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/therock.js new file mode 100644 index 000000000..c26b1b8fe --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/therock.js @@ -0,0 +1,1231 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, OrderNotFound, InvalidAddress } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class therock extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'therock', + 'name': 'TheRockTrading', + 'countries': [ 'MT' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': 'emulated', + 'fetchWithdrawals': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg', + 'api': 'https://api.therocktrading.com', + 'www': 'https://therocktrading.com', + 'doc': [ + 'https://api.therocktrading.com/doc/v1/index.html', + 'https://api.therocktrading.com/doc/', + ], + }, + 'api': { + 'public': { + 'get': [ + 'funds', + 'funds/{id}/orderbook', + 'funds/{id}/ticker', + 'funds/{id}/trades', + 'funds/tickers', + ], + }, + 'private': { + 'get': [ + 'balances', + 'balances/{id}', + 'discounts', + 'discounts/{id}', + 'funds', + 'funds/{id}', + 'funds/{id}/trades', + 'funds/{fund_id}/orders', + 'funds/{fund_id}/orders/{id}', + 'funds/{fund_id}/position_balances', + 'funds/{fund_id}/positions', + 'funds/{fund_id}/positions/{id}', + 'transactions', + 'transactions/{id}', + 'withdraw_limits/{id}', + 'withdraw_limits', + ], + 'post': [ + 'atms/withdraw', + 'funds/{fund_id}/orders', + ], + 'delete': [ + 'funds/{fund_id}/orders/{id}', + 'funds/{fund_id}/orders/remove_all', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': { + 'BTC': 0.0005, + 'BCH': 0.0005, + 'PPC': 0.02, + 'ETH': 0.001, + 'ZEC': 0.001, + 'LTC': 0.002, + 'EUR': 2.5, // worst-case scenario: https://therocktrading.com/en/pages/fees + }, + 'deposit': { + 'BTC': 0, + 'BCH': 0, + 'PPC': 0, + 'ETH': 0, + 'ZEC': 0, + 'LTC': 0, + 'EUR': 0, + }, + }, + }, + 'exceptions': { + 'exact': { + 'Request already running': BadRequest, + 'cannot specify multiple address types': BadRequest, + 'Currency is not included in the list': BadRequest, + 'Record not found': OrderNotFound, + }, + 'broad': { + 'before must be greater than after param': BadRequest, + 'must be shorter than 60 days': BadRequest, + 'must be a multiple of (period param) in minutes': BadRequest, + 'Address allocation limit reached for currency': InvalidAddress, + 'is not a valid value for param currency': BadRequest, + ' is invalid': InvalidAddress, + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetFunds (params); + // + // { funds: [ { id: "BTCEUR", + // description: "Trade Bitcoin with Euro", + // type: "currency", + // base_currency: "EUR", + // trade_currency: "BTC", + // buy_fee: 0.2, + // sell_fee: 0.2, + // minimum_price_offer: 0.01, + // minimum_quantity_offer: 0.0005, + // base_currency_decimals: 2, + // trade_currency_decimals: 4, + // leverages: [] }, + // { id: "LTCEUR", + // description: "Trade Litecoin with Euro", + // type: "currency", + // base_currency: "EUR", + // trade_currency: "LTC", + // buy_fee: 0.2, + // sell_fee: 0.2, + // minimum_price_offer: 0.01, + // minimum_quantity_offer: 0.01, + // base_currency_decimals: 2, + // trade_currency_decimals: 2, + // leverages: [] } ] } + // + const markets = this.safeValue (response, 'funds'); + const result = []; + if (markets === undefined) { + throw new ExchangeError (this.id + ' fetchMarkets got an unexpected response'); + } else { + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'id'); + const baseId = this.safeString (market, 'trade_currency'); + const quoteId = this.safeString (market, 'base_currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const buy_fee = this.safeFloat (market, 'buy_fee'); + const sell_fee = this.safeFloat (market, 'sell_fee'); + let taker = Math.max (buy_fee, sell_fee); + taker = taker / 100; + const maker = taker; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': true, + 'maker': maker, + 'taker': taker, + 'precision': { + 'amount': this.safeInteger (market, 'trade_currency_decimals'), + 'price': this.safeInteger (market, 'base_currency_decimals'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minimum_quantity_offer'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (market, 'minimum_price_offer'), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalances (params); + const balances = this.safeValue (response, 'balances', []); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'trading_balance'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': this.marketId (symbol), + }; + const orderbook = await this.publicGetFundsIdOrderbook (this.extend (request, params)); + const timestamp = this.parse8601 (this.safeString (orderbook, 'date')); + return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount'); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.parse8601 (ticker['date']); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': this.safeFloat (ticker, 'close'), // previous day close, if any + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume_traded'), + 'quoteVolume': this.safeFloat (ticker, 'volume'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetFundsTickers (params); + const tickers = this.indexBy (response['tickers'], 'fund_id'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const ticker = await this.publicGetFundsIdTicker (this.extend ({ + 'id': market['id'], + }, params)); + return this.parseTicker (ticker, market); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades, fetchOrder trades + // + // { id: 4493548, + // fund_id: "ETHBTC", + // amount: 0.203, + // price: 0.02783576, + // side: "buy", + // dark: false, + // date: "2018-11-30T08:19:18.236Z" } + // + // fetchMyTrades + // + // { id: 237338, + // fund_id: "BTCEUR", + // amount: 0.348, + // price: 348, + // side: "sell", + // dark: false, + // order_id: 14920648, + // date: "2015-06-03T00:49:49.000Z", + // transactions: [ { id: 2770768, + // date: "2015-06-03T00:49:49.000Z", + // type: "sold_currency_to_fund", + // price: 121.1, + // currency: "EUR" }, + // { id: 2770769, + // date: "2015-06-03T00:49:49.000Z", + // type: "released_currency_to_fund", + // price: 0.348, + // currency: "BTC" }, + // { id: 2770772, + // date: "2015-06-03T00:49:49.000Z", + // type: "paid_commission", + // price: 0.06, + // currency: "EUR", + // trade_id: 440492 } ] } + // + const marketId = this.safeString (trade, 'fund_id'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (trade, 'date')); + const id = this.safeString (trade, 'id'); + const orderId = this.safeString (trade, 'order_id'); + const side = this.safeString (trade, 'side'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + let feeCost = undefined; + const transactions = this.safeValue (trade, 'transactions', []); + const transactionsByType = this.groupBy (transactions, 'type'); + const feeTransactions = this.safeValue (transactionsByType, 'paid_commission', []); + for (let i = 0; i < feeTransactions.length; i++) { + if (feeCost === undefined) { + feeCost = 0; + } + feeCost = this.sum (feeCost, this.safeFloat (feeTransactions[i], 'price')); + } + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + return { + 'info': trade, + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + parseLedgerEntryDirection (direction) { + const directions = { + 'affiliate_earnings': 'in', + 'atm_payment': 'in', + 'bought_currency_from_fund': 'out', + 'bought_shares': 'out', + 'paid_commission': 'out', + 'paypal_payment': 'in', + 'pos_payment': 'in', + 'released_currency_to_fund': 'out', + 'rollover_commission': 'out', + 'sold_currency_to_fund': 'in', + 'sold_shares': 'in', + 'transfer_received': 'in', + 'transfer_sent': 'out', + 'withdraw': 'out', + // commented types will be shown as-is + // 'acquired_currency_from_fund': '', + // 'acquired_insurance': '', + // 'dividend_distributed_to_holders': '', + // 'dividend_from_shares': '', + // 'exposed_position': '', + // 'insurances_reimbursement': '', + // 'lent_currency': '', + // 'linden_lab_assessment': '', + // 'position_transfer_received': '', + // 'return_lent_currency': '', + // 'returned_lent_currency': '', + // 'the_rock_assessment': '', + }; + return this.safeString (directions, direction, direction); + } + + parseLedgerEntryType (type) { + const types = { + 'affiliate_earnings': 'referral', + 'atm_payment': 'transaction', + 'bought_currency_from_fund': 'trade', + 'bought_shares': 'trade', + 'paid_commission': 'fee', + 'paypal_payment': 'transaction', + 'pos_payment': 'transaction', + 'released_currency_to_fund': 'trade', + 'rollover_commission': 'fee', + 'sold_currency_to_fund': 'trade', + 'sold_shares': 'trade', + 'transfer_received': 'transfer', + 'transfer_sent': 'transfer', + 'withdraw': 'transaction', + // commented types will be shown as-is + // 'acquired_currency_from_fund': '', + // 'acquired_insurance': '', + // 'dividend_distributed_to_holders': '', + // 'dividend_from_shares': '', + // 'exposed_position': '', + // 'insurances_reimbursement': '', + // 'lent_currency': '', + // 'linden_lab_assessment': '', + // 'position_transfer_received': '', + // 'return_lent_currency': '', + // 'returned_lent_currency': '', + // 'the_rock_assessment': '', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // withdrawal + // + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // } + // + // deposit + // + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // } + // + // trade fee + // + // { + // "id": 21311221, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "paid_commission", + // "price": 0.0001, + // "fund_id": "BTCEUR", + // "order_id": 12832371, + // "trade_id": 12923212, + // "currency": "BTC", + // "transfer_detail": null + // } + // + const id = this.safeString (item, 'id'); + let referenceId = undefined; + let type = this.safeString (item, 'type'); + const direction = this.parseLedgerEntryDirection (type); + type = this.parseLedgerEntryType (type); + if (type === 'trade' || type === 'fee') { + referenceId = this.safeString (item, 'trade_id'); + } + const currencyId = this.safeString (item, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (item, 'price'); + const timestamp = this.parse8601 (this.safeString (item, 'date')); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': undefined, + 'referenceId': referenceId, + 'referenceAccount': undefined, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': undefined, + 'after': undefined, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'fund_id': 'ETHBTC', // filter by fund symbol + // 'currency': 'BTC', // filter by currency + // 'after': '2015-02-06T08:47:26Z', // filter after a certain timestamp + // 'before': '2015-02-06T08:47:26Z', + // 'type': 'withdraw', + // 'order_id': '12832371', // filter by a specific order ID + // 'trade_id': '12923212', // filter by a specific trade ID + // 'transfer_method': 'bitcoin', // wire_transfer, ripple, greenaddress, bitcoin, litecoin, namecoin, peercoin, dogecoin + // 'transfer_recipient': '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', // filter by a specific recipient (e.g. Bitcoin address, IBAN) + // 'transfer_id': '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa', // filter by a specific transfer ID (e.g. Bitcoin TX hash) + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.privateGetTransactions (this.extend (request, params)); + // + // { + // "transactions": [ + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // }, + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // }, + // { + // "id": 21311221, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "paid_commission", + // "price": 0.0001, + // "fund_id": "BTCEUR", + // "order_id": 12832371, + // "trade_id": 12923212, + // "currency": "BTC", + // "transfer_detail": null + // } + // ], + // "meta": { + // "total_count": 1221, + // "first": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "previous": null, + // "current": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "next": { "page": 2, "href": "https://api.therocktrading.com/v1/transactions?page=2" }, + // "last": { "page": 1221, "href": "https://api.therocktrading.com/v1/transactions?page=1221" } + // } + // } + // + const transactions = this.safeValue (response, 'transactions', []); + return this.parseLedger (transactions, currency, since, limit); + } + + parseTransactionType (type) { + const types = { + 'withdraw': 'withdrawal', + 'atm_payment': 'deposit', + }; + return this.safeString (types, type, type); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchWithdrawals + // + // // fiat + // + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // } + // + // { + // "id": 12564223, + // "date": "2017-08-07T08:13:50.023Z", + // "note": "GB7IDL401573388", + // "type": "withdraw", + // "price": 4345.93, + // "fund_id": null, + // "currency": "EUR", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "id": "EXECUTEDBUTUNCHECKED", + // "method": "wire_transfer", + // "recipient": "GB7IDL401573388", + // "confirmations": 0 + // } + // } + // + // // crypto + // + // { + // id: 20914695, + // date: '2018-02-24T07:13:23.002Z', + // type: 'withdraw', + // price: 2.70883607, + // currency: 'BCH', + // fund_id: null, + // order_id: null, + // trade_id: null, + // note: '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', + // transfer_detail: { + // method: 'bitcoin_cash', + // id: '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa', + // recipient: '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', + // confirmations: 0 + // } + // } + // + // + // fetchDeposits + // + // // fiat + // + // { + // id: 16176632, + // date: '2017-11-20T21:00:13.355Z', + // type: 'atm_payment', + // price: 5000, + // currency: 'EUR', + // fund_id: null, + // order_id: null, + // trade_id: null, + // note: 'Mistral deposit', + // transfer_detail: { + // method: 'wire_transfer', + // id: '972JQ49337DX769T', + // recipient: null, + // confirmations: 0 + // } + // } + // + // // crypto + // + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // } + // + const id = this.safeString (transaction, 'id'); + const type = this.parseTransactionType (this.safeString (transaction, 'type')); + const detail = this.safeValue (transaction, 'transfer_detail', {}); + const method = this.safeString (detail, 'method'); + let txid = undefined; + let address = undefined; + if (method !== undefined) { + if (method !== 'wire_transfer') { + txid = this.safeString (detail, 'id'); + address = this.safeString (detail, 'recipient'); + } + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const amount = this.safeFloat (transaction, 'price'); + const timestamp = this.parse8601 (this.safeString (transaction, 'date')); + const status = 'ok'; + // todo parse tags + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'addressFrom': undefined, + 'addressTo': address, + 'address': address, + 'tagFrom': undefined, + 'tagTo': undefined, + 'tag': undefined, + 'status': status, + 'type': type, + 'updated': undefined, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': undefined, + }; + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'withdraw', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'type': 'atm_payment', + }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'fund_id': 'ETHBTC', // filter by fund symbol + // 'currency': 'BTC', // filter by currency + // 'after': '2015-02-06T08:47:26Z', // filter after a certain timestamp + // 'before': '2015-02-06T08:47:26Z', + // 'type': 'withdraw', + // 'order_id': '12832371', // filter by a specific order ID + // 'trade_id': '12923212', // filter by a specific trade ID + // 'transfer_method': 'bitcoin', // wire_transfer, ripple, greenaddress, bitcoin, litecoin, namecoin, peercoin, dogecoin + // 'transfer_recipient': '1MAHLhJoz9W2ydbRf972WSgJYJ3Ui7aotm', // filter by a specific recipient (e.g. Bitcoin address, IBAN) + // 'transfer_id': '8261949194985b01985006724dca5d6059989e096fa95608271d00dd902327fa', // filter by a specific transfer ID (e.g. Bitcoin TX hash) + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + params = this.extend (request, params); + const response = await this.privateGetTransactions (params); + // + // { + // "transactions": [ + // { + // "id": 21311223, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "withdraw", + // "price": 103.00, + // "currency": "EUR", + // "fund_id": null, + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "wire_transfer", + // "id": "F112DD3", + // "recipient": "IT123456789012", + // "confirmations": 0 + // } + // }, + // { + // "id": 21311222, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "atm_payment", + // "price": 2.01291, + // "currency": "BTC", + // "fund_id": "null", + // "order_id": null, + // "trade_id": null, + // "transfer_detail": { + // "method": "bitcoin", + // "id": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + // "recipient": "mzb3NgX9Dr6jgGAu31L6jsPGB2zkaFxxyf", + // "confirmations": 3 + // } + // }, + // { + // "id": 21311221, + // "date": "2015-06-30T13:55:11.000Z", + // "type": "paid_commission", + // "price": 0.0001, + // "fund_id": "BTCEUR", + // "order_id": 12832371, + // "trade_id": 12923212, + // "currency": "BTC", + // "transfer_detail": null + // } + // ], + // "meta": { + // "total_count": 1221, + // "first": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "previous": null, + // "current": { "page": 1, "href": "https://api.therocktrading.com/v1/transactions?page=1" }, + // "next": { "page": 2, "href": "https://api.therocktrading.com/v1/transactions?page=2" }, + // "last": { "page": 1221, "href": "https://api.therocktrading.com/v1/transactions?page=1221" } + // } + // } + // + const transactions = this.safeValue (response, 'transactions', []); + const transactionTypes = [ 'withdraw', 'atm_payment' ]; + const depositsAndWithdrawals = this.filterByArray (transactions, 'type', transactionTypes, false); + return this.parseTransactions (depositsAndWithdrawals, currency, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'active': 'open', + 'executed': 'closed', + 'deleted': 'canceled', + // don't know what this status means + // 'conditional': '?', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": 4325578, + // "fund_id":"BTCEUR", + // "side":"buy", + // "type":"limit", + // "status":"executed", + // "price":0.0102, + // "amount": 50.0, + // "amount_unfilled": 0.0, + // "conditional_type": null, + // "conditional_price": null, + // "date":"2015-06-03T00:49:48.000Z", + // "close_on": nil, + // "leverage": 1.0, + // "position_id": null, + // "trades": [ + // { + // "id":237338, + // "fund_id":"BTCEUR", + // "amount":50, + // "price":0.0102, + // "side":"buy", + // "dark":false, + // "date":"2015-06-03T00:49:49.000Z" + // } + // ] + // } + // + const id = this.safeString (order, 'id'); + const marketId = this.safeString (order, 'fund_id'); + const symbol = this.safeSymbol (marketId, market); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.parse8601 (this.safeString (order, 'date')); + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'side'); + const amount = this.safeFloat (order, 'amount'); + const remaining = this.safeFloat (order, 'amount_unfilled'); + let filled = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + } + } + const price = this.safeFloat (order, 'price'); + let trades = this.safeValue (order, 'trades'); + let cost = undefined; + let average = undefined; + let lastTradeTimestamp = undefined; + if (trades !== undefined) { + const numTrades = trades.length; + if (numTrades > 0) { + trades = this.parseTrades (trades, market, undefined, undefined, { + 'orderId': id, + }); + // todo: determine the cost and the average price from trades + cost = 0; + filled = 0; + for (let i = 0; i < numTrades; i++) { + const trade = trades[i]; + cost = this.sum (cost, trade['cost']); + filled = this.sum (filled, trade['amount']); + } + if (filled > 0) { + average = cost / filled; + } + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + } else { + cost = 0; + } + } + const stopPrice = this.safeFloat (order, 'conditional_price'); + return { + 'id': id, + 'clientOrderId': undefined, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'average': average, + 'remaining': remaining, + 'fee': undefined, + 'trades': trades, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'active', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + const request = { + 'status': 'executed', + }; + return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'fund_id': market['id'], + // 'after': '2015-02-06T08:47:26Z', + // 'before': '2015-02-06T08:47:26Z' + // 'status': 'active', // 'executed', 'conditional' + // 'side': 'buy', // 'sell' + // 'position_id': 123, // filter orders by margin position id + }; + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.privateGetFundsFundIdOrders (this.extend (request, params)); + // + // { + // orders: [ + // { + // id: 299333648, + // fund_id: 'BTCEUR', + // side: 'sell', + // type: 'limit', + // status: 'executed', + // price: 5821, + // amount: 0.1, + // amount_unfilled: 0, + // conditional_type: null, + // conditional_price: null, + // date: '2018-06-18T17:38:16.129Z', + // close_on: null, + // dark: false, + // leverage: 1, + // position_id: 0 + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + 'fund_id': market['id'], + }; + const response = await this.privatePostFundsFundIdOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + price = 0; + } + const request = { + 'fund_id': this.marketId (symbol), + 'side': side, + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostFundsFundIdOrders (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + 'fund_id': this.marketId (symbol), + }; + const response = await this.privateDeleteFundsFundIdOrdersId (this.extend (request, params)); + return this.parseOrder (response); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + if (limit !== undefined) { + request['per_page'] = limit; // default 25 max 200 + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.privateGetFundsIdTrades (this.extend (request, params)); + // + // { trades: [ { id: 237338, + // fund_id: "BTCEUR", + // amount: 0.348, + // price: 348, + // side: "sell", + // dark: false, + // order_id: 14920648, + // date: "2015-06-03T00:49:49.000Z", + // transactions: [ { id: 2770768, + // date: "2015-06-03T00:49:49.000Z", + // type: "sold_currency_to_fund", + // price: 121.1, + // currency: "EUR" }, + // { id: 2770769, + // date: "2015-06-03T00:49:49.000Z", + // type: "released_currency_to_fund", + // price: 0.348, + // currency: "BTC" }, + // { id: 2770772, + // date: "2015-06-03T00:49:49.000Z", + // type: "paid_commission", + // price: 0.06, + // currency: "EUR", + // trade_id: 440492 } ] } ], + // meta: { total_count: 31, + // first: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=1" }, + // previous: null, + // current: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=1" }, + // next: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=2" }, + // last: { href: "https://api.therocktrading.com/v1/funds/BTCXRP/trades?page=2" } } } + // + return this.parseTrades (response['trades'], market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': market['id'], + }; + if (limit !== undefined) { + request['per_page'] = limit; // default 25 max 200 + } + if (since !== undefined) { + request['after'] = this.iso8601 (since); + } + const response = await this.publicGetFundsIdTrades (this.extend (request, params)); + // + // { trades: [ { id: 4493548, + // fund_id: "ETHBTC", + // amount: 0.203, + // price: 0.02783576, + // side: "buy", + // dark: false, + // date: "2018-11-30T08:19:18.236Z" }, + // { id: 4492926, + // fund_id: "ETHBTC", + // amount: 0.04, + // price: 0.02767034, + // side: "buy", + // dark: false, + // date: "2018-11-30T07:03:03.897Z" } ], + // meta: { total_count: null, + // first: { page: 1, + // href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=1" }, + // previous: null, + // current: { page: 1, + // href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=1" }, + // next: { page: 2, + // href: "https://api.therocktrading.com/v1/funds/ETHBTC/trades?page=2" }, + // last: null } } + // + return this.parseTrades (response['trades'], market, since, limit); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + headers = (headers === undefined) ? {} : headers; + if (api === 'private') { + this.checkRequiredCredentials (); + if (Object.keys (query).length) { + if (method === 'POST') { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } else { + const queryString = this.rawencode (query); + if (queryString.length) { + url += '?' + queryString; + } + } + } + const nonce = this.nonce ().toString (); + const auth = nonce + url; + headers['X-TRT-KEY'] = this.apiKey; + headers['X-TRT-NONCE'] = nonce; + headers['X-TRT-SIGN'] = this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'); + } else if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.rawencode (query); + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { + // "errors": + // [ + // { "message": ":currency is not a valid value for param currency","code": "11","meta": { "key":"currency","value":":currency"} }, + // { "message": "Address allocation limit reached for currency :currency.","code": "13" }, + // { "message": "Request already running", "code": "50"}, + // { "message": "cannot specify multiple address types", "code": "12" }, + // { "message": ":address_type is invalid", "code": "12" } + // ] + // } + // + const errors = this.safeValue (response, 'errors', []); + const numErrors = errors.length; + if (numErrors > 0) { + const feedback = this.id + ' ' + body; + // here we throw the first error we can identify + for (let i = 0; i < numErrors; i++) { + const error = errors[i]; + const message = this.safeString (error, 'message'); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/tidebit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/tidebit.js new file mode 100644 index 000000000..1f2ef5cd9 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/tidebit.js @@ -0,0 +1,542 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, InsufficientFunds, OrderNotFound, ArgumentsRequired } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class tidebit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'tidebit', + 'name': 'TideBit', + 'countries': [ 'HK' ], + 'rateLimit': 1000, + 'version': 'v2', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1', + '5m': '5', + '15m': '15', + '30m': '30', + '1h': '60', + '2h': '120', + '4h': '240', + '12h': '720', + '1d': '1440', + '3d': '4320', + '1w': '10080', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87460811-1e690280-c616-11ea-8652-69f187305add.jpg', + 'api': 'https://www.tidebit.com', + 'www': 'https://www.tidebit.com', + 'doc': [ + 'https://www.tidebit.com/documents/api/guide', + 'https://www.tidebit.com/swagger/#/default', + ], + 'referral': 'http://bit.ly/2IX0LrM', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'tickers', + 'tickers/{market}', + 'timestamp', + 'trades', + 'trades/{market}', + 'order_book', + 'order', + 'k_with_pending_trades', + 'k', + 'depth', + ], + 'post': [], + }, + 'private': { + 'get': [ + 'addresses/{address}', + 'deposits/history', + 'deposits/get_deposit', + 'deposits/deposit_address', + 'historys/orders', + 'historys/vouchers', + 'historys/accounts', + 'historys/snapshots', + 'linkage/get_status', + 'members/me', + 'order', + 'orders', + 'partners/orders/{id}/trades', + 'referral_commissions/get_undeposited', + 'referral_commissions/get_graph_data', + 'trades/my', + 'withdraws/bind_account_list', + 'withdraws/get_withdraw_account', + 'withdraws/fetch_bind_info', + ], + 'post': [ + 'deposits/deposit_cash', + 'favorite_markets/update', + 'order/delete', + 'orders', + 'orders/multi', + 'orders/clear', + 'referral_commissions/deposit', + 'withdraws/apply', + 'withdraws/bind_bank', + 'withdraws/bind_address', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.3 / 100, + 'taker': 0.3 / 100, + }, + 'funding': { + 'tierBased': false, + 'percentage': true, + 'withdraw': {}, // There is only 1% fee on withdrawals to your bank account. + }, + }, + 'exceptions': { + '2002': InsufficientFunds, + '2003': OrderNotFound, + }, + }); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + if ('success' in response) { + if (response['success']) { + const address = this.safeString (response, 'address'); + const tag = this.safeString (response, 'addressTag'); + return { + 'currency': code, + 'address': this.checkAddress (address), + 'tag': tag, + 'info': response, + }; + } + } + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'id'); + const symbol = this.safeString (market, 'name'); + const [ baseId, quoteId ] = symbol.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetMembersMe (params); + const balances = this.safeValue (response, 'accounts'); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 300 + } + request['market'] = market['id']; + const response = await this.publicGetDepth (this.extend (request, params)); + const timestamp = this.safeTimestamp (response, 'timestamp'); + return this.parseOrderBook (response, timestamp); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.safeTimestamp (ticker, 'at'); + ticker = this.safeValue (ticker, 'ticker', {}); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'ask': this.safeFloat (ticker, 'sell'), + 'bidVolume': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'change': undefined, + 'percentage': undefined, + 'previousClose': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const tickers = await this.publicGetTickers (params); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTickersMarket (this.extend (request, params)); + return this.parseTicker (response, market); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + const id = this.safeString (trade, 'id'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'volume'); + const cost = this.safeFloat (trade, 'funds'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1498530360, + // 2700.0, + // 2700.0, + // 2700.0, + // 2700.0, + // 0.01 + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), + this.safeFloat (ohlcv, 1), + this.safeFloat (ohlcv, 2), + this.safeFloat (ohlcv, 3), + this.safeFloat (ohlcv, 4), + this.safeFloat (ohlcv, 5), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 30; // default is 30 + } + const request = { + 'market': market['id'], + 'period': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + request['timestamp'] = parseInt (since / 1000); + } else { + request['timestamp'] = 1800000; + } + const response = await this.publicGetK (this.extend (request, params)); + // + // [ + // [1498530360,2700.0,2700.0,2700.0,2700.0,0.01], + // [1498530420,2700.0,2700.0,2700.0,2700.0,0], + // [1498530480,2700.0,2700.0,2700.0,2700.0,0], + // ] + // + if (response === 'null') { + return []; + } + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'done': 'closed', + 'wait': 'open', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "id": 7, // 唯一的 Order ID + // "side": "sell", // Buy/Sell 代表买单/卖单 + // "price": "3100.0", // 出价 + // "avg_price": "3101.2", // 平均成交价 + // "state": "wait", // 订单的当前状态 [wait,done,cancel] + // // wait 表明订单正在市场上挂单 + // // 是一个active order + // // 此时订单可能部分成交或者尚未成交 + // // done 代表订单已经完全成交 + // // cancel 代表订单已经被撤销 + // "market": "btccny", // 订单参与的交易市场 + // "created_at": "2014-04-18T02:02:33Z", // 下单时间 ISO8601格式 + // "volume": "100.0", // 购买/卖出数量 + // "remaining_volume": "89.8", // 还未成交的数量 remaining_volume 总是小于等于 volume + // // 在订单完全成交时变成 0 + // "executed_volume": "10.2", // 已成交的数量 + // // volume = remaining_volume + executed_volume + // "trades_count": 1, // 订单的成交数 整数值 + // // 未成交的订单为 0 有一笔成交的订单为 1 + // // 通过该字段可以判断订单是否处于部分成交状态 + // "trades": [ // 订单的详细成交记录 参见Trade + // // 注意: 只有某些返回详细订单数据的 API 才会包含 Trade 数据 + // { + // "id": 2, + // "price": "3100.0", + // "volume": "10.2", + // "market": "btccny", + // "created_at": "2014-04-18T02:04:49Z", + // "side": "sell" + // } + // ] + // } + // + const marketId = this.safeString (order, 'market'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + const id = this.safeString (order, 'id'); + const type = this.safeString (order, 'ord_type'); + const side = this.safeString (order, 'side'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + const filled = this.safeFloat (order, 'executed_volume'); + const remaining = this.safeFloat (order, 'remaining_volume'); + let cost = undefined; + if (price !== undefined) { + if (filled !== undefined) { + cost = price * filled; + } + } + const average = this.safeFloat (order, 'avg_price'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'cost': cost, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': average, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'market': this.marketId (symbol), + 'side': side, + 'volume': amount.toString (), + 'ord_type': type, + }; + if (type === 'limit') { + request['price'] = price.toString (); + } + const response = await this.privatePostOrders (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + const result = await this.privatePostOrderDelete (this.extend (request, params)); + const order = this.parseOrder (result); + const status = this.safeString (order, 'status'); + if (status === 'closed' || status === 'canceled') { + throw new OrderNotFound (this.id + ' ' + this.json (order)); + } + return order; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const id = this.safeString (params, 'id'); + if (id === undefined) { + throw new ArgumentsRequired (this.id + ' withdraw() requires an extra `id` param (withdraw account id according to withdraws/bind_account_list endpoint'); + } + const request = { + 'id': id, + 'currency_type': 'coin', // or 'cash' + 'currency': currency['id'], + 'body': amount, + // 'address': address, // they don't allow withdrawing to direct addresses? + }; + if (tag !== undefined) { + request['memo'] = tag; + } + const result = await this.privatePostWithdrawsApply (this.extend (request, params)); + return { + 'info': result, + 'id': undefined, + }; + } + + nonce () { + return this.milliseconds (); + } + + encodeParams (params) { + return this.urlencode (this.keysort (params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const request = '/' + 'api/' + this.version + '/' + this.implodeParams (path, params) + '.json'; + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'] + request; + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce ().toString (); + const sortedByKey = this.keysort (this.extend ({ + 'access_key': this.apiKey, + 'tonce': nonce, + }, params)); + const query = this.urlencode (sortedByKey); + const payload = method + '|' + request + '|' + query; + const signature = this.hmac (this.encode (payload), this.encode (this.secret)); + const suffix = query + '&signature=' + signature; + if (method === 'GET') { + url += '?' + suffix; + } else { + body = suffix; + headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 400) || (response === undefined)) { + const feedback = this.id + ' ' + body; + if (response === undefined) { + throw new ExchangeError (feedback); + } + const error = this.safeValue (response, 'error', {}); + const errorCode = this.safeString (error, 'code'); + this.throwExactlyMatchedException (this.exceptions, errorCode, feedback); + // fallback to default error handler + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/tidex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/tidex.js new file mode 100644 index 000000000..5b2504114 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/tidex.js @@ -0,0 +1,782 @@ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, DDoSProtection, InvalidOrder, AuthenticationError } = require ('./base/errors'); + +module.exports = class tidex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'tidex', + 'name': 'Tidex', + 'countries': [ 'UK' ], + 'rateLimit': 2000, + 'version': '3', + 'userAgent': this.userAgents['chrome'], + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/30781780-03149dc4-a12e-11e7-82bb-313b269d24d4.jpg', + 'api': { + 'web': 'https://gate.tidex.com/api', + 'public': 'https://api.tidex.com/api/3', + 'private': 'https://api.tidex.com/tapi', + }, + 'www': 'https://tidex.com', + 'doc': 'https://tidex.com/exchange/public-api', + 'referral': 'https://tidex.com/exchange/?ref=57f5638d9cd7', + 'fees': [ + 'https://tidex.com/exchange/assets-spec', + 'https://tidex.com/exchange/pairs-spec', + ], + }, + 'api': { + 'web': { + 'get': [ + 'currency', + 'pairs', + 'tickers', + 'orders', + 'ordershistory', + 'trade-data', + 'trade-data/{id}', + ], + }, + 'public': { + 'get': [ + 'info', + 'ticker/{pair}', + 'depth/{pair}', + 'trades/{pair}', + ], + }, + 'private': { + 'post': [ + 'getInfoExt', + 'getInfo', + 'Trade', + 'ActiveOrders', + 'OrderInfo', + 'CancelOrder', + 'TradeHistory', + 'CoinDepositAddress', + 'WithdrawCoin', + 'CreateCoupon', + 'RedeemCoupon', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.1 / 100, + 'maker': 0.1 / 100, + }, + }, + 'commonCurrencies': { + 'DSH': 'DASH', + 'EMGO': 'MGO', + 'MGO': 'WMGO', + }, + 'exceptions': { + 'exact': { + '803': InvalidOrder, // "Count could not be less than 0.001." (selling below minAmount) + '804': InvalidOrder, // "Count could not be more than 10000." (buying above maxAmount) + '805': InvalidOrder, // "price could not be less than X." (minPrice violation on buy & sell) + '806': InvalidOrder, // "price could not be more than X." (maxPrice violation on buy & sell) + '807': InvalidOrder, // "cost could not be less than X." (minCost violation on buy & sell) + '831': InsufficientFunds, // "Not enougth X to create buy order." (buying with balance.quote < order.cost) + '832': InsufficientFunds, // "Not enougth X to create sell order." (selling with balance.base < order.amount) + '833': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) + }, + 'broad': { + 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} + 'invalid api key': AuthenticationError, + 'invalid sign': AuthenticationError, + 'api key dont have trade permission': AuthenticationError, + 'invalid parameter': InvalidOrder, + 'invalid order': InvalidOrder, + 'Requests too often': DDoSProtection, + 'not available': ExchangeNotAvailable, + 'data unavailable': ExchangeNotAvailable, + 'external service unavailable': ExchangeNotAvailable, + }, + }, + 'options': { + 'fetchTickersMaxLength': 2048, + }, + 'orders': {}, // orders cache / emulation + }); + } + + async fetchCurrencies (params = {}) { + const response = await this.webGetCurrency (params); + const result = {}; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + const id = this.safeString (currency, 'symbol'); + const precision = currency['amountPoint']; + const code = this.safeCurrencyCode (id); + let active = currency['visible'] === true; + const canWithdraw = currency['withdrawEnable'] === true; + const canDeposit = currency['depositEnable'] === true; + if (!canWithdraw || !canDeposit) { + active = false; + } + const name = this.safeString (currency, 'name'); + result[code] = { + 'id': id, + 'code': code, + 'name': name, + 'active': active, + 'precision': precision, + 'funding': { + 'withdraw': { + 'active': canWithdraw, + 'fee': currency['withdrawFee'], + }, + 'deposit': { + 'active': canDeposit, + 'fee': 0.0, + }, + }, + 'limits': { + 'amount': { + 'min': undefined, + 'max': Math.pow (10, precision), + }, + 'price': { + 'min': Math.pow (10, -precision), + 'max': Math.pow (10, precision), + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'withdrawMinAmount'), + 'max': undefined, + }, + 'deposit': { + 'min': this.safeFloat (currency, 'depositMinAmount'), + 'max': undefined, + }, + }, + 'info': currency, + }; + } + return result; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInfo (params); + const markets = response['pairs']; + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'decimal_places'), + 'price': this.safeInteger (market, 'decimal_places'), + }; + const limits = { + 'amount': { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }, + 'price': { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }, + 'cost': { + 'min': this.safeFloat (market, 'min_total'), + }, + }; + const hidden = this.safeInteger (market, 'hidden'); + const active = (hidden === 0); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'taker': market['fee'] / 100, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfoExt (params); + const balances = this.safeValue (response, 'return'); + const result = { 'info': balances }; + const funds = this.safeValue (balances, 'funds', {}); + const currencyIds = Object.keys (funds); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (funds, currencyId, {}); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'value'); + account['used'] = this.safeFloat (balance, 'inOrders'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const market_id_in_reponse = (market['id'] in response); + if (!market_id_in_reponse) { + throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); + } + const orderbook = response[market['id']]; + return this.parseOrderBook (orderbook); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join ('-'); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const result = {}; + ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const symbol = this.safeSymbol (id); + result[symbol] = this.parseOrderBook (response[id]); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { high: 0.03497582, + // low: 0.03248474, + // avg: 0.03373028, + // vol: 120.11485715062999, + // vol_cur: 3572.24914074, + // last: 0.0337611, + // buy: 0.0337442, + // sell: 0.03377798, + // updated: 1537522009 } + // + let timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + if (!market['active']) { + timestamp = undefined; + } + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': this.safeFloat (ticker, 'vol_cur'), + 'quoteVolume': this.safeFloat (ticker, 'vol'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = this.ids; + if (symbols === undefined) { + const numIds = ids.length; + ids = ids.join ('-'); + // max URL length is 2048 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); + throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + const response = await this.publicGetTickerPair (this.extend (request, params)); + const result = {}; + const keys = Object.keys (response); + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (response[id], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return tickers[symbol]; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'timestamp'); + let side = this.safeString (trade, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const price = this.safeFloat2 (trade, 'rate', 'price'); + const id = this.safeString2 (trade, 'trade_id', 'tid'); + const orderId = this.safeString (trade, 'order_id'); + const marketId = this.safeString (trade, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const amount = this.safeFloat (trade, 'amount'); + const type = 'limit'; // all trades are still limit trades + let takerOrMaker = undefined; + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const isYourOrder = this.safeValue (trade, 'is_your_order'); + if (isYourOrder !== undefined) { + takerOrMaker = 'taker'; + if (isYourOrder) { + takerOrMaker = 'maker'; + } + if (fee === undefined) { + fee = this.calculateFee (symbol, type, side, amount, price, takerOrMaker); + } + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + return { + 'id': id, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': takerOrMaker, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTradesPair (this.extend (request, params)); + if (Array.isArray (response)) { + const numElements = response.length; + if (numElements === 0) { + return []; + } + } + return this.parseTrades (response[market['id']], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'amount': this.amountToPrecision (symbol, amount), + 'rate': this.priceToPrecision (symbol, price), + }; + price = parseFloat (price); + amount = parseFloat (amount); + const response = await this.privatePostTrade (this.extend (request, params)); + let id = undefined; + let status = 'open'; + let filled = 0.0; + let remaining = amount; + if ('return' in response) { + id = this.safeString (response['return'], 'order_id'); + if (id === '0') { + id = this.safeString (response['return'], 'init_order_id'); + status = 'closed'; + } + filled = this.safeFloat (response['return'], 'received', 0.0); + remaining = this.safeFloat (response['return'], 'remains', amount); + } + const timestamp = this.milliseconds (); + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * filled, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + // 'trades': this.parseTrades (order['trades'], market), + 'info': response, + 'clientOrderId': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'closed', + '2': 'canceled', + '3': 'canceled', // or partially-filled and still open? https://github.com/ccxt/ccxt/issues/1594 + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'timestamp_created'); + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market); + let remaining = undefined; + let amount = undefined; + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if ('start_amount' in order) { + amount = this.safeFloat (order, 'start_amount'); + remaining = this.safeFloat (order, 'amount'); + } else { + remaining = this.safeFloat (order, 'amount'); + } + if (amount !== undefined) { + if (remaining !== undefined) { + filled = amount - remaining; + cost = price * filled; + } + } + const fee = undefined; + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'type': 'limit', + 'timeInForce': undefined, + 'side': this.safeString (order, 'type'), + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + const response = await this.privatePostOrderInfo (this.extend (request, params)); + id = id.toString (); + const result = this.safeValue (response, 'return', {}); + const order = this.safeValue (result, id); + return this.parseOrder (this.extend ({ 'id': id }, order)); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = {}; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostActiveOrders (this.extend (request, params)); + // + // { + // "success":1, + // "return":{ + // "1255468911":{ + // "status":0, + // "pair":"spike_usdt", + // "type":"sell", + // "amount":35028.44256388, + // "rate":0.00199989, + // "timestamp_created":1602684432 + // } + // }, + // "stat":{ + // "isSuccess":true, + // "serverTime":"00:00:00.0000826", + // "time":"00:00:00.0091423", + // "errors":null + // } + // } + // + // it can only return 'open' orders (i.e. no way to fetch 'closed' orders) + const orders = this.safeValue (response, 'return', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + // some derived classes use camelcase notation for request fields + const request = { + // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) + // 'count': 1000, // the number of trades for display numerical, default = 1000 + // 'from_id': trade ID, from which the display starts numerical 0 + // 'end_id': trade ID on which the display ends numerical ∞ + // 'order': 'ASC', // sorting, default = DESC (test result: liqui ignores this field, most recent trade always goes last) + // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) + // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) + // 'pair': 'eth_btc', // default = all markets + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['pair'] = market['id']; + } + if (limit !== undefined) { + request['count'] = parseInt (limit); + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const trades = this.safeValue (response, 'return', []); + return this.parseTrades (trades, market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coinName': currency['id'], + 'amount': parseFloat (amount), + 'address': address, + }; + // no docs on the tag, yet... + if (tag !== undefined) { + throw new ExchangeError (this.id + ' withdraw() does not support the tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.'); + } + const response = await this.privatePostWithdrawCoin (this.extend (request, params)); + return { + 'info': response, + 'id': response['return']['tId'], + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + 'method': path, + }, query)); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } else if (api === 'public') { + url += '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + url += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + if (Object.keys (query).length) { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // 1 - The exchange only returns the integer 'success' key from their private API + // + // { "success": 1, ... } httpCode === 200 + // { "success": 0, ... } httpCode === 200 + // + // 2 - However, derived exchanges can return non-integers + // + // It can be a numeric string + // { "sucesss": "1", ... } + // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) + // + // Or just a string + // { "success": "true", ... } + // { "success": "false", ... }, httpCode >= 200 + // + // Or a boolean + // { "success": true, ... } + // { "success": false, ... }, httpCode >= 200 + // + // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types + // + // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui + // + // To cover points 1, 2, 3 and 4 combined this handler should work like this: + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/timex.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/timex.js new file mode 100644 index 000000000..3937e4660 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/timex.js @@ -0,0 +1,1235 @@ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, PermissionDenied, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, RateLimitExceeded, NotSupported, BadRequest, AuthenticationError } = require ('./base/errors'); + +module.exports = class timex extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'timex', + 'name': 'TimeX', + 'countries': [ 'AU' ], + 'version': 'v1', + 'rateLimit': 1500, + 'has': { + 'cancelOrder': true, + 'cancelOrders': true, + 'CORS': false, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFee': true, // maker fee only + }, + 'timeframes': { + '1m': 'I1', + '5m': 'I5', + '15m': 'I15', + '30m': 'I30', + '1h': 'H1', + '2h': 'H2', + '4h': 'H4', + '6h': 'H6', + '12h': 'H12', + '1d': 'D1', + '1w': 'W1', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/70423869-6839ab00-1a7f-11ea-8f94-13ae72c31115.jpg', + 'api': 'https://plasma-relay-backend.timex.io', + 'www': 'https://timex.io', + 'doc': 'https://docs.timex.io', + 'referral': 'https://timex.io/?refcode=1x27vNkTbP1uwkCck', + }, + 'api': { + 'custody': { + 'get': [ + 'credentials', // Get api key for address + 'credentials/h/{hash}', // Get api key by hash + 'credentials/k/{key}', // Get api key by key + 'credentials/me/address', // Get api key by hash + 'deposit-addresses', // Get deposit addresses list + 'deposit-addresses/h/{hash}', // Get deposit address by hash + ], + }, + 'history': { + 'get': [ + 'orders', // Gets historical orders + 'orders/details', // Gets order details + 'orders/export/csv', // Export orders to csv + 'trades', // Gets historical trades + 'trades/export/csv', // Export trades to csv + ], + }, + 'currencies': { + 'get': [ + 'a/{address}', // Gets currency by address + 'i/{id}', // Gets currency by id + 's/{symbol}', // Gets currency by symbol + ], + 'post': [ + 'perform', // Creates new currency + 'prepare', // Prepare creates new currency + 'remove/perform', // Removes currency by symbol + 's/{symbol}/remove/prepare', // Prepare remove currency by symbol + 's/{symbol}/update/perform', // Prepare update currency by symbol + 's/{symbol}/update/prepare', // Prepare update currency by symbol + ], + }, + 'markets': { + 'get': [ + 'i/{id}', // Gets market by id + 's/{symbol}', // Gets market by symbol + ], + 'post': [ + 'perform', // Creates new market + 'prepare', // Prepare creates new market + 'remove/perform', // Removes market by symbol + 's/{symbol}/remove/prepare', // Prepare remove market by symbol + 's/{symbol}/update/perform', // Prepare update market by symbol + 's/{symbol}/update/prepare', // Prepare update market by symbol + ], + }, + 'public': { + 'get': [ + 'candles', // Gets candles + 'currencies', // Gets all the currencies + 'markets', // Gets all the markets + 'orderbook', // Gets orderbook + 'orderbook/raw', // Gets raw orderbook + 'orderbook/v2', // Gets orderbook v2 + 'tickers', // Gets all the tickers + 'trades', // Gets trades + ], + }, + 'statistics': { + 'get': [ + 'address', // calculateAddressStatistics + ], + }, + 'trading': { + 'get': [ + 'balances', // Get trading balances for all (or selected) currencies + 'fees', // Get trading fee rates for all (or selected) markets + 'orders', // Gets open orders + ], + 'post': [ + 'orders', // Create new order + 'orders/json', // Create orders + ], + 'put': [ + 'orders', // Cancel or update orders + 'orders/json', // Update orders + ], + 'delete': [ + 'orders', // Delete orders + 'orders/json', // Delete orders + ], + }, + 'tradingview': { + 'get': [ + 'config', // Gets config + 'history', // Gets history + 'symbol_info', // Gets symbol info + 'time', // Gets time + ], + }, + }, + 'exceptions': { + 'exact': { + '0': ExchangeError, + '1': NotSupported, + '4000': BadRequest, + '4001': BadRequest, + '4002': InsufficientFunds, + '4003': AuthenticationError, + '4004': AuthenticationError, + '4005': BadRequest, + '4006': BadRequest, + '4007': BadRequest, + '4300': PermissionDenied, + '4100': AuthenticationError, + '4400': OrderNotFound, + '5001': InvalidOrder, + '5002': ExchangeError, + '400': BadRequest, + '401': AuthenticationError, + '403': PermissionDenied, + '404': OrderNotFound, + '429': RateLimitExceeded, + '500': ExchangeError, + '503': ExchangeNotAvailable, + }, + 'broad': { + 'Insufficient': InsufficientFunds, + }, + }, + 'options': { + 'fetchTickers': { + 'period': '1d', + }, + 'fetchTrades': { + 'sort': 'timestamp,asc', + }, + 'fetchMyTrades': { + 'sort': 'timestamp,asc', + }, + 'fetchOpenOrders': { + 'sort': 'createdAt,asc', + }, + 'fetchClosedOrders': { + 'sort': 'createdAt,asc', + }, + 'defaultSort': 'timestamp,asc', + 'defaultSortOrders': 'createdAt,asc', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarkets (params); + // + // [ + // { + // "symbol": "ETHBTC", + // "name": "ETH/BTC", + // "baseCurrency": "ETH", + // "baseTokenAddress": "0x45932db54b38af1f5a57136302eeba66a5975c15", + // "quoteCurrency": "BTC", + // "quoteTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "feeCurrency": "BTC", + // "feeTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "quantityIncrement": "0.0000001", + // "takerFee": "0.005", + // "makerFee": "0.0025", + // "tickSize": "0.00000001", + // "baseMinSize": "0.0001", + // "quoteMinSize": "0.00001", + // "locked": false + // } + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + result.push (this.parseMarket (response[i])); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCurrencies (params); + // + // [ + // { + // "symbol": "BTC", + // "name": "Bitcoin", + // "address": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggb3BhY2l0eT0iMC41IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMwIDUzQzQyLjcwMjUgNTMgNTMgNDIuNzAyNSA1MyAzMEM1MyAxNy4yOTc1IDQyLjcwMjUgNyAzMCA3QzE3LjI5NzUgNyA3IDE3LjI5NzUgNyAzMEM3IDQyLjcwMjUgMTcuMjk3NSA1MyAzMCA1M1pNMzAgNTVDNDMuODA3MSA1NSA1NSA0My44MDcxIDU1IDMwQzU1IDE2LjE5MjkgNDMuODA3MSA1IDMwIDVDMTYuMTkyOSA1IDUgMTYuMTkyOSA1IDMwQzUgNDMuODA3MSAxNi4xOTI5IDU1IDMwIDU1WiIvPgo8cGF0aCBkPSJNNDAuOTQyNSAyNi42NTg1QzQxLjQwMDMgMjMuNjExMyAzOS4wNzA1IDIxLjk3MzIgMzUuODg0OCAyMC44ODA0TDM2LjkxODIgMTYuNzUyNkwzNC4zOTUxIDE2LjEyNjRMMzMuMzg5IDIwLjE0NTVDMzIuNzI1OCAxOS45ODA5IDMyLjA0NDUgMTkuODI1NiAzMS4zNjc1IDE5LjY3MTdMMzIuMzgwOCAxNS42MjYyTDI5Ljg1OTEgMTVMMjguODI1IDE5LjEyNjRDMjguMjc2IDE5LjAwMTkgMjcuNzM3IDE4Ljg3ODggMjcuMjEzOSAxOC43NDkzTDI3LjIxNjggMTguNzM2NEwyMy43MzcyIDE3Ljg3MTJMMjMuMDY2IDIwLjU1NDhDMjMuMDY2IDIwLjU1NDggMjQuOTM4IDIwLjk4MjEgMjQuODk4NSAyMS4wMDg1QzI1LjkyMDQgMjEuMjYyNiAyNi4xMDUgMjEuOTM2IDI2LjA3NDEgMjIuNDY5OUwyNC44OTcgMjcuMTcyNEMyNC45Njc1IDI3LjE5MDMgMjUuMDU4NyAyNy4yMTYgMjUuMTU5MyAyNy4yNTYxQzI1LjA3NTMgMjcuMjM1NCAyNC45ODU0IDI3LjIxMjQgMjQuODkyNyAyNy4xOTAzTDIzLjI0MjggMzMuNzc3OEMyMy4xMTc3IDM0LjA4NjkgMjIuODAwOCAzNC41NTA2IDIyLjA4NjUgMzQuMzc0NkMyMi4xMTE3IDM0LjQxMTEgMjAuMjUyNiAzMy45MTg3IDIwLjI1MjYgMzMuOTE4N0wxOSAzNi43OTQ5TDIyLjI4MzQgMzcuNjFDMjIuODk0MiAzNy43NjI0IDIzLjQ5MjggMzcuOTIyIDI0LjA4MjEgMzguMDcyM0wyMy4wMzggNDIuMjQ3NEwyNS41NTgyIDQyLjg3MzZMMjYuNTkyMyAzOC43NDI5QzI3LjI4MDcgMzguOTI5IDI3Ljk0OSAzOS4xMDA3IDI4LjYwMyAzOS4yNjI0TDI3LjU3MjUgNDMuMzczOEwzMC4wOTU2IDQ0TDMxLjEzOTcgMzkuODMyOEMzNS40NDIyIDQwLjY0MzYgMzguNjc3NCA0MC4zMTY2IDQwLjAzOTIgMzYuNDQxNEM0MS4xMzY1IDMzLjMyMTIgMzkuOTg0NiAzMS41MjEzIDM3LjcyMDkgMzAuMzQ3N0MzOS4zNjk0IDI5Ljk2OTEgNDAuNjExMiAyOC44ODkyIDQwLjk0MjUgMjYuNjU4NVYyNi42NTg1Wk0zNS4xNzc3IDM0LjcwODhDMzQuMzk4IDM3LjgyOSAyOS4xMjI2IDM2LjE0MjIgMjcuNDEyMiAzNS43MTkzTDI4Ljc5NzcgMzAuMTg4MUMzMC41MDgxIDMwLjYxMzIgMzUuOTkyNiAzMS40NTQ4IDM1LjE3NzcgMzQuNzA4OFpNMzUuOTU4MSAyNi42MTM0QzM1LjI0NjcgMjkuNDUxNyAzMC44NTU5IDI4LjAwOTcgMjkuNDMxNiAyNy42NTYxTDMwLjY4NzcgMjIuNjM5NUMzMi4xMTIgMjIuOTkzIDM2LjY5OSAyMy42NTI4IDM1Ljk1ODEgMjYuNjEzNFoiLz4KPC9zdmc+Cg==", + // "background": "transparent", + // "fiatSymbol": "BTC", + // "decimals": 8, + // "tradeDecimals": 20, + // "displayDecimals": 4, + // "crypto": true, + // "depositEnabled": true, + // "withdrawalEnabled": true, + // "transferEnabled": true, + // "buyEnabled": false, + // "purchaseEnabled": false, + // "redeemEnabled": false, + // "active": true, + // "withdrawalFee": "50000000000000000", + // "purchaseCommissions": [] + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const currency = response[i]; + result.push (this.parseCurrency (currency)); + } + return this.indexBy (result, 'code'); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const period = this.safeString (this.options['fetchTickers'], 'period', '1d'); + const request = { + 'period': this.timeframes[period], // I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1 + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ + // { + // "ask": 0.017, + // "bid": 0.016, + // "high": 0.019, + // "last": 0.017, + // "low": 0.015, + // "market": "TIME/ETH", + // "open": 0.016, + // "period": "H1", + // "timestamp": "2018-12-14T20:50:36.134Z", + // "volume": 4.57, + // "volumeQuote": 0.07312 + // } + // ] + // + return this.parseTickers (response, symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const period = this.safeString (this.options['fetchTickers'], 'period', '1d'); + const request = { + 'market': market['id'], + 'period': this.timeframes[period], // I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1 + }; + const response = await this.publicGetTickers (this.extend (request, params)); + // + // [ + // { + // "ask": 0.017, + // "bid": 0.016, + // "high": 0.019, + // "last": 0.017, + // "low": 0.015, + // "market": "TIME/ETH", + // "open": 0.016, + // "period": "H1", + // "timestamp": "2018-12-14T20:50:36.134Z", + // "volume": 4.57, + // "volumeQuote": 0.07312 + // } + // ] + // + const ticker = this.safeValue (response, 0); + return this.parseTicker (ticker, market); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetOrderbookV2 (this.extend (request, params)); + // + // { + // "timestamp":"2019-12-05T00:21:09.538", + // "bid":[ + // { + // "index":"2", + // "price":"0.02024007", + // "baseTokenAmount":"0.0096894", + // "baseTokenCumulativeAmount":"0.0096894", + // "quoteTokenAmount":"0.000196114134258", + // "quoteTokenCumulativeAmount":"0.000196114134258" + // }, + // "ask":[ + // { + // "index":"-3", + // "price":"0.02024012", + // "baseTokenAmount":"0.005", + // "baseTokenCumulativeAmount":"0.005", + // "quoteTokenAmount":"0.0001012006", + // "quoteTokenCumulativeAmount":"0.0001012006" + // }, + // ] + // } + // + const timestamp = this.parse8601 (this.safeString (response, 'timestamp')); + return this.parseOrderBook (response, timestamp, 'bid', 'ask', 'price', 'baseTokenAmount'); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const options = this.safeValue (this.options, 'fetchTrades', {}); + const defaultSort = this.safeValue (options, 'sort', 'timestamp,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'address': 'string', // trade’s member account (?) + // 'cursor': 1234, // int64 (?) + // 'from': this.iso8601 (since), + 'market': market['id'], + // 'page': 0, // results page you want to retrieve 0 .. N + // 'size': limit, // number of records per page, 100 by default + 'sort': sort, // array[string], sorting criteria in the format "property,asc" or "property,desc", default is ascending + // 'till': this.iso8601 (this.milliseconds ()), + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['size'] = limit; // default is 100 + } + const response = await this.publicGetTrades (this.extend (request, query)); + // + // [ + // { + // "id":1, + // "timestamp":"2019-06-25T17:01:50.309", + // "direction":"BUY", + // "price":"0.027", + // "quantity":"0.001" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'period': this.timeframes[timeframe], + }; + // if since and limit are not specified + const duration = this.parseTimeframe (timeframe); + if (since !== undefined) { + request['from'] = this.iso8601 (since); + if (limit !== undefined) { + request['till'] = this.iso8601 (this.sum (since, this.sum (limit, 1) * duration * 1000)); + } + } else if (limit !== undefined) { + const now = this.milliseconds (); + request['till'] = this.iso8601 (now); + request['from'] = this.iso8601 (now - limit * duration * 1000 - 1); + } else { + request['till'] = this.iso8601 (this.milliseconds ()); + } + const response = await this.publicGetCandles (this.extend (request, params)); + // + // [ + // { + // "timestamp":"2019-12-04T23:00:00", + // "open":"0.02024009", + // "high":"0.02024009", + // "low":"0.02024009", + // "close":"0.02024009", + // "volume":"0.00008096036", + // "volumeQuote":"0.004", + // }, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.tradingGetBalances (params); + // + // [ + // {"currency":"BTC","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"AUDT","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"ETH","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"TIME","totalBalance":"0","lockedBalance":"0"}, + // {"currency":"USDT","totalBalance":"0","lockedBalance":"0"} + // ] + // + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['total'] = this.safeFloat (balance, 'totalBalance'); + account['used'] = this.safeFloat (balance, 'lockedBalance'); + result[code] = account; + } + return this.parseBalance (result); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'quantity': this.amountToPrecision (symbol, amount), + 'side': side.toUpperCase (), + // 'clientOrderId': '123', + // 'expireIn': 1575523308, // in seconds + // 'expireTime': 1575523308, // unix timestamp + }; + let query = params; + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + const defaultExpireIn = this.safeInteger (this.options, 'expireIn'); + const expireTime = this.safeValue (params, 'expireTime'); + const expireIn = this.safeValue (params, 'expireIn', defaultExpireIn); + if (expireTime !== undefined) { + request['expireTime'] = expireTime; + } else if (expireIn !== undefined) { + request['expireIn'] = expireIn; + } else { + throw new InvalidOrder (this.id + ' createOrder method requires a expireTime or expireIn param for a ' + type + ' order, you can also set the expireIn exchange-wide option'); + } + query = this.omit (params, [ 'expireTime', 'expireIn' ]); + } else { + request['price'] = 0; + } + const response = await this.tradingPostOrders (this.extend (request, query)); + // + // { + // "orders": [ + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + const order = this.safeValue (orders, 0, {}); + return this.parseOrder (order, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'id': id, + }; + if (amount !== undefined) { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const response = await this.tradingPutOrders (this.extend (request, params)); + // + // { + // "changedOrders": [ + // { + // "newOrder": { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // }, + // "oldId": "string", + // }, + // ], + // "unchangedOrders": [ "string" ], + // } + // + if ('unchangedOrders' in response) { + const orderIds = this.safeValue (response, 'unchangedOrders', []); + const orderId = this.safeString (orderIds, 0); + return { + 'id': orderId, + 'info': response, + }; + } + const orders = this.safeValue (response, 'changedOrders', []); + const firstOrder = this.safeValue (orders, 0, {}); + const order = this.safeValue (firstOrder, 'newOrder', {}); + return this.parseOrder (order, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + return await this.cancelOrders ([ id ], symbol, params); + } + + async cancelOrders (ids, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': ids, + }; + const response = await this.tradingDeleteOrders (this.extend (request, params)); + // + // { + // "changedOrders": [ + // { + // "newOrder": { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // }, + // "oldId": "string", + // }, + // ], + // "unchangedOrders": [ "string" ], + // } + return response; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'orderHash': id, + }; + const response = await this.historyGetOrdersDetails (request); + // + // { + // "order": { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // }, + // "trades": [ + // { + // "fee": "0.3", + // "id": 100, + // "makerOrTaker": "MAKER", + // "makerOrderId": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "takerOrderId": "string", + // "timestamp": "2019-12-05T07:48:26.310Z" + // } + // ] + // } + // + const order = this.safeValue (response, 'order', {}); + const trades = this.safeValue (response, 'trades', []); + return this.parseOrder (this.extend (order, { 'trades': trades })); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchOpenOrders', {}); + const defaultSort = this.safeValue (options, 'sort', 'createdAt,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'clientOrderId': '123', // order’s client id list for filter + // page: 0, // results page you want to retrieve (0 .. N) + 'sort': sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.tradingGetOrders (this.extend (request, query)); + // + // { + // "orders": [ + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchClosedOrders', {}); + const defaultSort = this.safeValue (options, 'sort', 'createdAt,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'clientOrderId': '123', // order’s client id list for filter + // page: 0, // results page you want to retrieve (0 .. N) + 'sort': sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported + 'side': 'BUY', // or 'SELL' + // 'till': this.iso8601 (this.milliseconds ()), + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.historyGetOrders (this.extend (request, query)); + // + // { + // "orders": [ + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // } + // ] + // } + // + const orders = this.safeValue (response, 'orders', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const options = this.safeValue (this.options, 'fetchMyTrades', {}); + const defaultSort = this.safeValue (options, 'sort', 'timestamp,asc'); + const sort = this.safeString (params, 'sort', defaultSort); + const query = this.omit (params, 'sort'); + const request = { + // 'cursorId': 123, // int64 (?) + // 'from': this.iso8601 (since), + // 'makerOrderId': '1234', // maker order hash + // 'owner': '...', // owner address (?) + // 'page': 0, // results page you want to retrieve (0 .. N) + // 'side': 'BUY', // or 'SELL' + // 'size': limit, + 'sort': sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple sort criteria are supported + // 'symbol': market['id'], + // 'takerOrderId': '1234', + // 'till': this.iso8601 (this.milliseconds ()), + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.historyGetTrades (this.extend (request, query)); + // + // { + // "trades": [ + // { + // "fee": "0.3", + // "id": 100, + // "makerOrTaker": "MAKER", + // "makerOrderId": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "takerOrderId": "string", + // "timestamp": "2019-12-08T04:54:11.171Z" + // } + // ] + // } + // + const trades = this.safeValue (response, 'trades', []); + return this.parseTrades (trades, market, since, limit); + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'markets': market['id'], + }; + const response = await this.tradingGetFees (this.extend (request, params)); + // + // [ + // { + // "fee": 0.0075, + // "market": "ETHBTC" + // } + // ] + // + const result = this.safeValue (response, 0, {}); + return { + 'info': response, + 'maker': this.safeFloat (result, 'fee'), + 'taker': undefined, + }; + } + + parseMarket (market) { + // + // { + // "symbol": "ETHBTC", + // "name": "ETH/BTC", + // "baseCurrency": "ETH", + // "baseTokenAddress": "0x45932db54b38af1f5a57136302eeba66a5975c15", + // "quoteCurrency": "BTC", + // "quoteTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "feeCurrency": "BTC", + // "feeTokenAddress": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "quantityIncrement": "0.0000001", + // "takerFee": "0.005", + // "makerFee": "0.0025", + // "tickSize": "0.00000001", + // "baseMinSize": "0.0001", + // "quoteMinSize": "0.00001", + // "locked": false + // } + // + const locked = this.safeValue (market, 'locked'); + const active = !locked; + const id = this.safeString (market, 'symbol'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.precisionFromString (this.safeString (market, 'quantityIncrement')), + 'price': this.precisionFromString (this.safeString (market, 'tickSize')), + }; + const amountIncrement = this.safeFloat (market, 'quantityIncrement'); + const minBase = this.safeFloat (market, 'baseMinSize'); + const minAmount = Math.max (amountIncrement, minBase); + const priceIncrement = this.safeFloat (market, 'tickSize'); + const minCost = this.safeFloat (market, 'quoteMinSize'); + const limits = { + 'amount': { 'min': minAmount, 'max': undefined }, + 'price': { 'min': priceIncrement, 'max': undefined }, + 'cost': { 'min': Math.max (minCost, minAmount * priceIncrement), 'max': undefined }, + }; + const taker = this.safeFloat (market, 'takerFee'); + const maker = this.safeFloat (market, 'makerFee'); + return { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'type': 'spot', + 'active': active, + 'precision': precision, + 'limits': limits, + 'taker': taker, + 'maker': maker, + 'info': market, + }; + } + + parseCurrency (currency) { + // + // { + // "symbol": "BTC", + // "name": "Bitcoin", + // "address": "0x8370fbc6ddec1e18b4e41e72ed943e238458487c", + // "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR...mc+Cg==", + // "background": "transparent", + // "fiatSymbol": "BTC", + // "decimals": 8, + // "tradeDecimals": 20, + // "displayDecimals": 4, + // "crypto": true, + // "depositEnabled": true, + // "withdrawalEnabled": true, + // "transferEnabled": true, + // "buyEnabled": false, + // "purchaseEnabled": false, + // "redeemEnabled": false, + // "active": true, + // "withdrawalFee": "50000000000000000", + // "purchaseCommissions": [] + // } + // + // https://github.com/ccxt/ccxt/issues/6878 + // + // { + // "symbol":"XRP", + // "name":"Ripple", + // "address":"0x0dc8882914f3ddeebf4cec6dc20edb99df3def6c", + // "decimals":6, + // "tradeDecimals":16, + // "depositEnabled":true, + // "withdrawalEnabled":true, + // "transferEnabled":true, + // "active":true + // } + // + const id = this.safeString (currency, 'symbol'); + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'name'); + const precision = this.safeInteger (currency, 'decimals'); + const active = this.safeValue (currency, 'active'); + // const fee = this.safeFloat (currency, 'withdrawalFee'); + const feeString = this.safeString (currency, 'withdrawalFee'); + const tradeDecimals = this.safeInteger (currency, 'tradeDecimals'); + let fee = undefined; + if ((feeString !== undefined) && (tradeDecimals !== undefined)) { + const feeStringLen = feeString.length; + const dotIndex = feeStringLen - tradeDecimals; + if (dotIndex > 0) { + const whole = feeString.slice (0, dotIndex); + const fraction = feeString.slice (-dotIndex); + fee = parseFloat (whole + '.' + fraction); + } else { + let fraction = '.'; + for (let i = 0; i < -dotIndex; i++) { + fraction += '0'; + } + fee = parseFloat (fraction + feeString); + } + } + return { + 'id': code, + 'code': code, + 'info': currency, + 'type': undefined, + 'name': name, + 'active': active, + 'fee': fee, + 'precision': precision, + 'limits': { + 'withdraw': { 'min': fee, 'max': undefined }, + 'amount': { 'min': undefined, 'max': undefined }, + 'price': { 'min': undefined, 'max': undefined }, + 'cost': { 'min': undefined, 'max': undefined }, + }, + }; + } + + parseTickers (rawTickers, symbols = undefined) { + const tickers = []; + for (let i = 0; i < rawTickers.length; i++) { + tickers.push (this.parseTicker (rawTickers[i])); + } + return this.filterByArray (tickers, 'symbol', symbols); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "ask": 0.017, + // "bid": 0.016, + // "high": 0.019, + // "last": 0.017, + // "low": 0.015, + // "market": "TIME/ETH", + // "open": 0.016, + // "period": "H1", + // "timestamp": "2018-12-14T20:50:36.134Z", + // "volume": 4.57, + // "volumeQuote": 0.07312 + // } + // + const marketId = this.safeString (ticker, 'market'); + const symbol = this.safeSymbol (marketId, market, '/'); + const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); + const last = this.safeFloat (ticker, 'last'); + const open = this.safeFloat (ticker, 'open'); + let change = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + } + let percentage = undefined; + if (change !== undefined && open) { + percentage = (change / open) * 100; + } + return { + 'symbol': symbol, + 'info': ticker, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'volumeQuote'), + }; + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades (public) + // + // { + // "id":1, + // "timestamp":"2019-06-25T17:01:50.309", + // "direction":"BUY", + // "price":"0.027", + // "quantity":"0.001" + // } + // + // fetchMyTrades, fetchOrder (private) + // + // { + // "fee": "0.3", + // "id": 100, + // "makerOrTaker": "MAKER", + // "makerOrderId": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "takerOrderId": "string", + // "timestamp": "2019-12-08T04:54:11.171Z" + // } + // + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'quantity'); + const id = this.safeString (trade, 'id'); + const side = this.safeStringLower2 (trade, 'direction', 'side'); + const takerOrMaker = this.safeStringLower (trade, 'makerOrTaker'); + let orderId = undefined; + if (takerOrMaker !== undefined) { + orderId = this.safeString (trade, takerOrMaker + 'OrderId'); + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'fee'); + if (feeCost !== undefined) { + const feeCurrency = (market === undefined) ? undefined : market['quote']; + fee = { + 'cost': feeCost, + 'currency': feeCurrency, + }; + } + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = this.costToPrecision (symbol, amount * price); + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': orderId, + 'type': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'takerOrMaker': takerOrMaker, + 'fee': fee, + }; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "timestamp":"2019-12-04T23:00:00", + // "open":"0.02024009", + // "high":"0.02024009", + // "low":"0.02024009", + // "close":"0.02024009", + // "volume":"0.00008096036", + // "volumeQuote":"0.004", + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'timestamp')), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + parseOrder (order, market = undefined) { + // + // fetchOrder, createOrder, cancelOrder, cancelOrders, fetchOpenOrders, fetchClosedOrders + // + // { + // "cancelledQuantity": "0.3", + // "clientOrderId": "my-order-1", + // "createdAt": "1970-01-01T00:00:00", + // "cursorId": 50, + // "expireTime": "1970-01-01T00:00:00", + // "filledQuantity": "0.3", + // "id": "string", + // "price": "0.017", + // "quantity": "0.3", + // "side": "BUY", + // "symbol": "TIMEETH", + // "type": "LIMIT", + // "updatedAt": "1970-01-01T00:00:00" + // "trades": [], // injected from the outside + // } + // + const id = this.safeString (order, 'id'); + const type = this.safeStringLower (order, 'type'); + const side = this.safeStringLower (order, 'side'); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const timestamp = this.parse8601 (this.safeString (order, 'createdAt')); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'quantity'); + const filled = this.safeFloat (order, 'filledQuantity'); + const canceledQuantity = this.safeFloat (order, 'cancelledQuantity'); + let remaining = undefined; + let status = undefined; + if ((amount !== undefined) && (filled !== undefined)) { + remaining = Math.max (amount - filled, 0.0); + if (filled >= amount) { + status = 'closed'; + } else if ((canceledQuantity !== undefined) && (canceledQuantity > 0)) { + status = 'canceled'; + } else { + status = 'open'; + } + } + const cost = parseFloat (this.costToPrecision (symbol, price * filled)); + const fee = undefined; + let lastTradeTimestamp = undefined; + let trades = undefined; + const rawTrades = this.safeValue (order, 'trades'); + if (rawTrades !== undefined) { + trades = this.parseTrades (rawTrades, market, undefined, undefined, { + 'order': id, + }); + } + if (trades !== undefined) { + const numTrades = trades.length; + if (numTrades > 0) { + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + } + } + const clientOrderId = this.safeString (order, 'clientOrderId'); + return { + 'info': order, + 'id': id, + 'clientOrderId': clientOrderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/' + api + '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencodeWithArrayRepeat (params); + } + if (api !== 'public') { + this.checkRequiredCredentials (); + const auth = this.stringToBase64 (this.apiKey + ':' + this.secret); + const secret = 'Basic ' + this.decode (auth); + headers = { 'authorization': secret }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + if (statusCode >= 400) { + // + // {"error":{"timestamp":"05.12.2019T05:25:43.584+0000","status":"BAD_REQUEST","message":"Insufficient ETH balance. Required: 1, actual: 0.","code":4001}} + // {"error":{"timestamp":"05.12.2019T04:03:25.419+0000","status":"FORBIDDEN","message":"Access denied","code":4300}} + // + const feedback = this.id + ' ' + responseBody; + let error = this.safeValue (response, 'error'); + if (error === undefined) { + error = response; + } + const code = this.safeString2 (error, 'code', 'status'); + const message = this.safeString2 (error, 'message', 'debugMessage'); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/upbit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/upbit.js new file mode 100644 index 000000000..b7665f041 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/upbit.js @@ -0,0 +1,1529 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadRequest, AuthenticationError, InvalidOrder, InsufficientFunds, OrderNotFound, PermissionDenied, AddressPending } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class upbit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'upbit', + 'name': 'Upbit', + 'countries': [ 'KR' ], + 'version': 'v1', + 'rateLimit': 1000, + 'certified': true, + 'pro': true, + // new metainfo interface + 'has': { + 'cancelOrder': true, + 'CORS': true, + 'createDepositAddress': true, + 'createMarketOrder': true, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': false, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchOrders': false, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': 'minutes', + '3m': 'minutes', + '5m': 'minutes', + '15m': 'minutes', + '30m': 'minutes', + '1h': 'minutes', + '4h': 'minutes', + '1d': 'days', + '1w': 'weeks', + '1M': 'months', + }, + 'hostname': 'api.upbit.com', + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg', + 'api': { + 'public': 'https://{hostname}', + 'private': 'https://{hostname}', + }, + 'www': 'https://upbit.com', + 'doc': 'https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C', + 'fees': 'https://upbit.com/service_center/guide', + }, + 'api': { + 'public': { + 'get': [ + 'market/all', + 'candles/{timeframe}', + 'candles/{timeframe}/{unit}', + 'candles/minutes/{unit}', + 'candles/minutes/1', + 'candles/minutes/3', + 'candles/minutes/5', + 'candles/minutes/15', + 'candles/minutes/30', + 'candles/minutes/60', + 'candles/minutes/240', + 'candles/days', + 'candles/weeks', + 'candles/months', + 'trades/ticks', + 'ticker', + 'orderbook', + ], + }, + 'private': { + 'get': [ + 'accounts', + 'orders/chance', + 'order', + 'orders', + 'withdraws', + 'withdraw', + 'withdraws/chance', + 'deposits', + 'deposit', + 'deposits/coin_addresses', + 'deposits/coin_address', + ], + 'post': [ + 'orders', + 'withdraws/coin', + 'withdraws/krw', + 'deposits/generate_coin_address', + ], + 'delete': [ + 'order', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.0025, + 'taker': 0.0025, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'exceptions': { + 'exact': { + 'This key has expired.': AuthenticationError, + 'Missing request parameter error. Check the required parameters!': BadRequest, + 'side is missing, side does not have a valid value': InvalidOrder, + }, + 'broad': { + 'thirdparty_agreement_required': PermissionDenied, + 'out_of_scope': PermissionDenied, + 'order_not_found': OrderNotFound, + 'insufficient_funds': InsufficientFunds, + 'invalid_access_key': AuthenticationError, + 'jwt_verification': AuthenticationError, + 'create_ask_error': ExchangeError, + 'create_bid_error': ExchangeError, + 'volume_too_large': InvalidOrder, + 'invalid_funds': InvalidOrder, + }, + }, + 'options': { + 'createMarketBuyOrderRequiresPrice': true, + 'fetchTickersMaxLength': 4096, // 2048, + 'fetchOrderBooksMaxLength': 4096, // 2048, + 'tradingFeesByQuoteCurrency': { + 'KRW': 0.0005, + }, + }, + 'commonCurrencies': { + 'TON': 'Tokamak Network', + }, + }); + } + + async fetchCurrency (code, params = {}) { + // this method is for retrieving funding fees and limits per currency + // it requires private access and API keys properly set up + await this.loadMarkets (); + const currency = this.currency (code); + return await this.fetchCurrencyById (currency['id'], params); + } + + async fetchCurrencyById (id, params = {}) { + // this method is for retrieving funding fees and limits per currency + // it requires private access and API keys properly set up + const request = { + 'currency': id, + }; + const response = await this.privateGetWithdrawsChance (this.extend (request, params)); + // + // { + // "member_level": { + // "security_level": 3, + // "fee_level": 0, + // "email_verified": true, + // "identity_auth_verified": true, + // "bank_account_verified": true, + // "kakao_pay_auth_verified": false, + // "locked": false, + // "wallet_locked": false + // }, + // "currency": { + // "code": "BTC", + // "withdraw_fee": "0.0005", + // "is_coin": true, + // "wallet_state": "working", + // "wallet_support": [ "deposit", "withdraw" ] + // }, + // "account": { + // "currency": "BTC", + // "balance": "10.0", + // "locked": "0.0", + // "avg_krw_buy_price": "8042000", + // "modified": false + // }, + // "withdraw_limit": { + // "currency": "BTC", + // "minimum": null, + // "onetime": null, + // "daily": "10.0", + // "remaining_daily": "10.0", + // "remaining_daily_krw": "0.0", + // "fixed": null, + // "can_withdraw": true + // } + // } + // + const memberInfo = this.safeValue (response, 'member_level', {}); + const currencyInfo = this.safeValue (response, 'currency', {}); + const withdrawLimits = this.safeValue (response, 'withdraw_limit', {}); + const canWithdraw = this.safeValue (withdrawLimits, 'can_withdraw'); + const walletState = this.safeString (currencyInfo, 'wallet_state'); + const walletLocked = this.safeValue (memberInfo, 'wallet_locked'); + const locked = this.safeValue (memberInfo, 'locked'); + let active = true; + if ((canWithdraw !== undefined) && canWithdraw) { + active = false; + } else if (walletState !== 'working') { + active = false; + } else if ((walletLocked !== undefined) && walletLocked) { + active = false; + } else if ((locked !== undefined) && locked) { + active = false; + } + const maxOnetimeWithdrawal = this.safeFloat (withdrawLimits, 'onetime'); + const maxDailyWithdrawal = this.safeFloat (withdrawLimits, 'daily', maxOnetimeWithdrawal); + const remainingDailyWithdrawal = this.safeFloat (withdrawLimits, 'remaining_daily', maxDailyWithdrawal); + let maxWithdrawLimit = undefined; + if (remainingDailyWithdrawal > 0) { + maxWithdrawLimit = remainingDailyWithdrawal; + } else { + maxWithdrawLimit = maxDailyWithdrawal; + } + const precision = undefined; + const currencyId = this.safeString (currencyInfo, 'code'); + const code = this.safeCurrencyCode (currencyId); + return { + 'info': response, + 'id': currencyId, + 'code': code, + 'name': code, + 'active': active, + 'fee': this.safeFloat (currencyInfo, 'withdraw_fee'), + 'precision': precision, + 'limits': { + 'withdraw': { + 'min': this.safeFloat (withdrawLimits, 'minimum'), + 'max': maxWithdrawLimit, + }, + }, + }; + } + + async fetchMarket (symbol, params = {}) { + // this method is for retrieving trading fees and limits per market + // it requires private access and API keys properly set up + await this.loadMarkets (); + const market = this.market (symbol); + return await this.fetchMarketById (market['id'], params); + } + + async fetchMarketById (id, params = {}) { + // this method is for retrieving trading fees and limits per market + // it requires private access and API keys properly set up + const request = { + 'market': id, + }; + const response = await this.privateGetOrdersChance (this.extend (request, params)); + // + // { bid_fee: "0.0005", + // ask_fee: "0.0005", + // market: { id: "KRW-BTC", + // name: "BTC/KRW", + // order_types: ["limit"], + // order_sides: ["ask", "bid"], + // bid: { currency: "KRW", + // price_unit: null, + // min_total: 1000 }, + // ask: { currency: "BTC", + // price_unit: null, + // min_total: 1000 }, + // max_total: "1000000000.0", + // state: "active" }, + // bid_account: { currency: "KRW", + // balance: "0.0", + // locked: "0.0", + // avg_krw_buy_price: "0", + // modified: false }, + // ask_account: { currency: "BTC", + // balance: "0.00780836", + // locked: "0.0", + // avg_krw_buy_price: "6465564.67", + // modified: false } } + // + const marketInfo = this.safeValue (response, 'market'); + const bid = this.safeValue (marketInfo, 'bid'); + const ask = this.safeValue (marketInfo, 'ask'); + const marketId = this.safeString (marketInfo, 'id'); + const baseId = this.safeString (ask, 'currency'); + const quoteId = this.safeString (bid, 'currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const state = this.safeString (marketInfo, 'state'); + const active = (state === 'active'); + const bidFee = this.safeFloat (response, 'bid_fee'); + const askFee = this.safeFloat (response, 'ask_fee'); + const fee = Math.max (bidFee, askFee); + return { + 'info': response, + 'id': marketId, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': precision, + 'maker': fee, + 'taker': fee, + 'limits': { + 'amount': { + 'min': this.safeFloat (ask, 'min_total'), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (bid, 'min_total'), + 'max': this.safeFloat (marketInfo, 'max_total'), + }, + }, + }; + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetMarketAll (params); + // + // [ { market: "KRW-BTC", + // korean_name: "비트코인", + // english_name: "Bitcoin" }, + // { market: "KRW-DASH", + // korean_name: "대시", + // english_name: "Dash" }, + // { market: "KRW-ETH", + // korean_name: "이더리움", + // english_name: "Ethereum" }, + // { market: "BTC-ETH", + // korean_name: "이더리움", + // english_name: "Ethereum" }, + // ..., + // { market: "BTC-BSV", + // korean_name: "비트코인에스브이", + // english_name: "Bitcoin SV" } ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'market'); + const [ quoteId, baseId ] = id.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': 8, + 'price': 8, + }; + const active = true; + const makerFee = this.safeFloat (this.options['tradingFeesByQuoteCurrency'], quote, this.fees['trading']['maker']); + const takerFee = this.safeFloat (this.options['tradingFeesByQuoteCurrency'], quote, this.fees['trading']['taker']); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'info': market, + 'precision': precision, + 'maker': makerFee, + 'taker': takerFee, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetAccounts (params); + // + // [ { currency: "BTC", + // balance: "0.005", + // locked: "0.0", + // avg_krw_buy_price: "7446000", + // modified: false }, + // { currency: "ETH", + // balance: "0.1", + // locked: "0.0", + // avg_krw_buy_price: "250000", + // modified: false } ] + // + const result = { 'info': response }; + for (let i = 0; i < response.length; i++) { + const balance = response[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'balance'); + account['used'] = this.safeFloat (balance, 'locked'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join (','); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchOrderBooksMaxLength']) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols (' + ids.length.toString () + ' characters) exceeding max URL length (' + this.options['fetchOrderBooksMaxLength'].toString () + ' characters), you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join (','); + } + const request = { + 'markets': ids, + }; + const response = await this.publicGetOrderbook (this.extend (request, params)); + // + // [ { market: "BTC-ETH", + // timestamp: 1542899030043, + // total_ask_size: 109.57065201, + // total_bid_size: 125.74430631, + // orderbook_units: [ { ask_price: 0.02926679, + // bid_price: 0.02919904, + // ask_size: 4.20293961, + // bid_size: 11.65043576 }, + // ..., + // { ask_price: 0.02938209, + // bid_price: 0.0291231, + // ask_size: 0.05135782, + // bid_size: 13.5595 } ] }, + // { market: "KRW-BTC", + // timestamp: 1542899034662, + // total_ask_size: 12.89790974, + // total_bid_size: 4.88395783, + // orderbook_units: [ { ask_price: 5164000, + // bid_price: 5162000, + // ask_size: 2.57606495, + // bid_size: 0.214 }, + // ..., + // { ask_price: 5176000, + // bid_price: 5152000, + // ask_size: 2.752, + // bid_size: 0.4650305 } ] } ] + // + const result = {}; + for (let i = 0; i < response.length; i++) { + const orderbook = response[i]; + const marketId = this.safeString (orderbook, 'market'); + const symbol = this.safeSymbol (marketId, undefined, '-'); + const timestamp = this.safeInteger (orderbook, 'timestamp'); + result[symbol] = { + 'bids': this.sortBy (this.parseBidsAsks (orderbook['orderbook_units'], 'bid_price', 'bid_size'), 0, true), + 'asks': this.sortBy (this.parseBidsAsks (orderbook['orderbook_units'], 'ask_price', 'ask_size'), 0), + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + const orderbooks = await this.fetchOrderBooks ([ symbol ], limit, params); + return this.safeValue (orderbooks, symbol); + } + + parseTicker (ticker, market = undefined) { + // + // { market: "BTC-ETH", + // trade_date: "20181122", + // trade_time: "104543", + // trade_date_kst: "20181122", + // trade_time_kst: "194543", + // trade_timestamp: 1542883543097, + // opening_price: 0.02976455, + // high_price: 0.02992577, + // low_price: 0.02934283, + // trade_price: 0.02947773, + // prev_closing_price: 0.02966, + // change: "FALL", + // change_price: 0.00018227, + // change_rate: 0.0061453136, + // signed_change_price: -0.00018227, + // signed_change_rate: -0.0061453136, + // trade_volume: 1.00000005, + // acc_trade_price: 100.95825586, + // acc_trade_price_24h: 289.58650166, + // acc_trade_volume: 3409.85311036, + // acc_trade_volume_24h: 9754.40510513, + // highest_52_week_price: 0.12345678, + // highest_52_week_date: "2018-02-01", + // lowest_52_week_price: 0.023936, + // lowest_52_week_date: "2017-12-08", + // timestamp: 1542883543813 } + // + const timestamp = this.safeInteger (ticker, 'trade_timestamp'); + const marketId = this.safeString2 (ticker, 'market', 'code'); + const symbol = this.safeSymbol (marketId, market, '-'); + const previous = this.safeFloat (ticker, 'prev_closing_price'); + const last = this.safeFloat (ticker, 'trade_price'); + const change = this.safeFloat (ticker, 'signed_change_price'); + const percentage = this.safeFloat (ticker, 'signed_change_rate'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high_price'), + 'low': this.safeFloat (ticker, 'low_price'), + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'opening_price'), + 'close': last, + 'last': last, + 'previousClose': previous, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'acc_trade_volume_24h'), + 'quoteVolume': this.safeFloat (ticker, 'acc_trade_price_24h'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join (','); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join (','); + } + const request = { + 'markets': ids, + }; + const response = await this.publicGetTicker (this.extend (request, params)); + // + // [ { market: "BTC-ETH", + // trade_date: "20181122", + // trade_time: "104543", + // trade_date_kst: "20181122", + // trade_time_kst: "194543", + // trade_timestamp: 1542883543097, + // opening_price: 0.02976455, + // high_price: 0.02992577, + // low_price: 0.02934283, + // trade_price: 0.02947773, + // prev_closing_price: 0.02966, + // change: "FALL", + // change_price: 0.00018227, + // change_rate: 0.0061453136, + // signed_change_price: -0.00018227, + // signed_change_rate: -0.0061453136, + // trade_volume: 1.00000005, + // acc_trade_price: 100.95825586, + // acc_trade_price_24h: 289.58650166, + // acc_trade_volume: 3409.85311036, + // acc_trade_volume_24h: 9754.40510513, + // highest_52_week_price: 0.12345678, + // highest_52_week_date: "2018-02-01", + // lowest_52_week_price: 0.023936, + // lowest_52_week_date: "2017-12-08", + // timestamp: 1542883543813 } ] + // + const result = {}; + for (let t = 0; t < response.length; t++) { + const ticker = this.parseTicker (response[t]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return this.safeValue (tickers, symbol); + } + + parseTrade (trade, market = undefined) { + // + // fetchTrades + // + // { market: "BTC-ETH", + // trade_date_utc: "2018-11-22", + // trade_time_utc: "13:55:24", + // timestamp: 1542894924397, + // trade_price: 0.02914289, + // trade_volume: 0.20074397, + // prev_closing_price: 0.02966, + // change_price: -0.00051711, + // ask_bid: "ASK", + // sequential_id: 15428949259430000 } + // + // fetchOrder trades + // + // { + // "market": "KRW-BTC", + // "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3", + // "price": "101000.0", + // "volume": "0.77368323", + // "funds": "78142.00623", + // "ask_fee": "117.213009345", + // "bid_fee": "117.213009345", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid", + // } + // + const id = this.safeString2 (trade, 'sequential_id', 'uuid'); + const orderId = undefined; + let timestamp = this.safeInteger (trade, 'timestamp'); + if (timestamp === undefined) { + timestamp = this.parse8601 (this.safeString (trade, 'created_at')); + } + let side = undefined; + const askOrBid = this.safeStringLower2 (trade, 'ask_bid', 'side'); + if (askOrBid === 'ask') { + side = 'sell'; + } else if (askOrBid === 'bid') { + side = 'buy'; + } + let cost = this.safeFloat (trade, 'funds'); + const price = this.safeFloat2 (trade, 'trade_price', 'price'); + const amount = this.safeFloat2 (trade, 'trade_volume', 'volume'); + if (cost === undefined) { + if (amount !== undefined) { + if (price !== undefined) { + cost = price * amount; + } + } + } + const marketId = this.safeString2 (trade, 'market', 'code'); + market = this.safeMarket (marketId, market); + let fee = undefined; + let feeCurrency = undefined; + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + feeCurrency = market['quote']; + } else { + const [ baseId, quoteId ] = marketId.split ('-'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + feeCurrency = quote; + } + const feeCost = this.safeString (trade, askOrBid + '_fee'); + if (feeCost !== undefined) { + fee = { + 'currency': feeCurrency, + 'cost': feeCost, + }; + } + return { + 'id': id, + 'info': trade, + 'order': orderId, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 200; + } + const request = { + 'market': market['id'], + 'count': limit, + }; + const response = await this.publicGetTradesTicks (this.extend (request, params)); + // + // [ { market: "BTC-ETH", + // trade_date_utc: "2018-11-22", + // trade_time_utc: "13:55:24", + // timestamp: 1542894924397, + // trade_price: 0.02914289, + // trade_volume: 0.20074397, + // prev_closing_price: 0.02966, + // change_price: -0.00051711, + // ask_bid: "ASK", + // sequential_id: 15428949259430000 }, + // { market: "BTC-ETH", + // trade_date_utc: "2018-11-22", + // trade_time_utc: "13:03:10", + // timestamp: 1542891790123, + // trade_price: 0.02917, + // trade_volume: 7.392, + // prev_closing_price: 0.02966, + // change_price: -0.00049, + // ask_bid: "ASK", + // sequential_id: 15428917910540000 } ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // market: "BTC-ETH", + // candle_date_time_utc: "2018-11-22T13:47:00", + // candle_date_time_kst: "2018-11-22T22:47:00", + // opening_price: 0.02915963, + // high_price: 0.02915963, + // low_price: 0.02915448, + // trade_price: 0.02915448, + // timestamp: 1542894473674, + // candle_acc_trade_price: 0.0981629437535248, + // candle_acc_trade_volume: 3.36693173, + // unit: 1 + // } + // + return [ + this.parse8601 (this.safeString (ohlcv, 'candle_date_time_utc')), + this.safeFloat (ohlcv, 'opening_price'), + this.safeFloat (ohlcv, 'high_price'), + this.safeFloat (ohlcv, 'low_price'), + this.safeFloat (ohlcv, 'trade_price'), + this.safeFloat (ohlcv, 'candle_acc_trade_volume'), // base volume + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const timeframePeriod = this.parseTimeframe (timeframe); + const timeframeValue = this.timeframes[timeframe]; + if (limit === undefined) { + limit = 200; + } + const request = { + 'market': market['id'], + 'timeframe': timeframeValue, + 'count': limit, + }; + let method = 'publicGetCandlesTimeframe'; + if (timeframeValue === 'minutes') { + const numMinutes = Math.round (timeframePeriod / 60); + request['unit'] = numMinutes; + method += 'Unit'; + } + if (since !== undefined) { + // convert `since` to `to` value + request['to'] = this.iso8601 (this.sum (since, timeframePeriod * limit * 1000)); + } + const response = await this[method] (this.extend (request, params)); + // + // [ + // { + // market: "BTC-ETH", + // candle_date_time_utc: "2018-11-22T13:47:00", + // candle_date_time_kst: "2018-11-22T22:47:00", + // opening_price: 0.02915963, + // high_price: 0.02915963, + // low_price: 0.02915448, + // trade_price: 0.02915448, + // timestamp: 1542894473674, + // candle_acc_trade_price: 0.0981629437535248, + // candle_acc_trade_volume: 3.36693173, + // unit: 1 + // }, + // { + // market: "BTC-ETH", + // candle_date_time_utc: "2018-11-22T10:06:00", + // candle_date_time_kst: "2018-11-22T19:06:00", + // opening_price: 0.0294, + // high_price: 0.02940882, + // low_price: 0.02934283, + // trade_price: 0.02937354, + // timestamp: 1542881219276, + // candle_acc_trade_price: 0.0762597110943884, + // candle_acc_trade_volume: 2.5949617, + // unit: 1 + // } + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + // for market buy it requires the amount of quote currency to spend + if (side === 'buy') { + if (this.options['createMarketBuyOrderRequiresPrice']) { + if (price === undefined) { + throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)"); + } else { + amount = amount * price; + } + } + } + } + let orderSide = undefined; + if (side === 'buy') { + orderSide = 'bid'; + } else if (side === 'sell') { + orderSide = 'ask'; + } else { + throw new InvalidOrder (this.id + ' createOrder allows buy or sell side only!'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'side': orderSide, + }; + if (type === 'limit') { + request['volume'] = this.amountToPrecision (symbol, amount); + request['price'] = this.priceToPrecision (symbol, price); + request['ord_type'] = type; + } else if (type === 'market') { + if (side === 'buy') { + request['ord_type'] = 'price'; + request['price'] = this.priceToPrecision (symbol, amount); + } else if (side === 'sell') { + request['ord_type'] = type; + request['volume'] = this.amountToPrecision (symbol, amount); + } + } + const response = await this.privatePostOrders (this.extend (request, params)); + // + // { + // 'uuid': 'cdd92199-2897-4e14-9448-f923320408ad', + // 'side': 'bid', + // 'ord_type': 'limit', + // 'price': '100.0', + // 'avg_price': '0.0', + // 'state': 'wait', + // 'market': 'KRW-BTC', + // 'created_at': '2018-04-10T15:42:23+09:00', + // 'volume': '0.01', + // 'remaining_volume': '0.01', + // 'reserved_fee': '0.0015', + // 'remaining_fee': '0.0015', + // 'paid_fee': '0.0', + // 'locked': '1.0015', + // 'executed_volume': '0.0', + // 'trades_count': 0 + // } + // + return this.parseOrder (response); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'uuid': id, + }; + const response = await this.privateDeleteOrder (this.extend (request, params)); + // + // { + // "uuid": "cdd92199-2897-4e14-9448-f923320408ad", + // "side": "bid", + // "ord_type": "limit", + // "price": "100.0", + // "state": "wait", + // "market": "KRW-BTC", + // "created_at": "2018-04-10T15:42:23+09:00", + // "volume": "0.01", + // "remaining_volume": "0.01", + // "reserved_fee": "0.0015", + // "remaining_fee": "0.0015", + // "paid_fee": "0.0", + // "locked": "1.0015", + // "executed_volume": "0.0", + // "trades_count": 0 + // } + // + return this.parseOrder (response); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'order_by': 'asc', // 'desc' + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + const response = await this.privateGetDeposits (this.extend (request, params)); + // + // [ + // { + // "type": "deposit", + // "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830", + // "currency": "KRW", + // "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a", + // "state": "ACCEPTED", + // "created_at": "2017-12-08T15:38:02+09:00", + // "done_at": "2017-12-08T15:38:02+09:00", + // "amount": "100000.0", + // "fee": "0.0" + // }, + // ..., + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'state': 'submitting', // 'submitted', 'almost_accepted', 'rejected', 'accepted', 'processing', 'done', 'canceled' + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = limit; // default is 100 + } + const response = await this.privateGetWithdraws (this.extend (request, params)); + // + // [ + // { + // "type": "withdraw", + // "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79", + // "currency": "BTC", + // "txid": null, + // "state": "processing", + // "created_at": "2018-04-13T11:24:01+09:00", + // "done_at": null, + // "amount": "0.01", + // "fee": "0.0", + // "krw_amount": "80420.0" + // }, + // ..., + // ] + // + return this.parseTransactions (response, currency, since, limit); + } + + parseTransactionStatus (status) { + const statuses = { + 'ACCEPTED': 'ok', // deposits + // withdrawals: + 'submitting': 'pending', // 처리 중 + 'submitted': 'pending', // 처리 완료 + 'almost_accepted': 'pending', // 출금대기중 + 'rejected': 'failed', // 거부 + 'accepted': 'pending', // 승인됨 + 'processing': 'pending', // 처리 중 + 'done': 'ok', // 완료 + 'canceled': 'canceled', // 취소됨 + }; + return this.safeString (statuses, status, status); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchDeposits + // + // { + // "type": "deposit", + // "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830", + // "currency": "KRW", + // "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a", + // "state": "ACCEPTED", + // "created_at": "2017-12-08T15:38:02+09:00", + // "done_at": "2017-12-08T15:38:02+09:00", + // "amount": "100000.0", + // "fee": "0.0" + // } + // + // fetchWithdrawals + // + // { + // "type": "withdraw", + // "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79", + // "currency": "BTC", + // "txid": "cd81e9b45df8da29f936836e58c907a106057e454a45767a7b06fcb19b966bba", + // "state": "processing", + // "created_at": "2018-04-13T11:24:01+09:00", + // "done_at": null, + // "amount": "0.01", + // "fee": "0.0", + // "krw_amount": "80420.0" + // } + // + const id = this.safeString (transaction, 'uuid'); + const amount = this.safeFloat (transaction, 'amount'); + const address = undefined; // not present in the data structure received from the exchange + const tag = undefined; // not present in the data structure received from the exchange + const txid = this.safeString (transaction, 'txid'); + const updated = this.parse8601 (this.safeString (transaction, 'done_at')); + const timestamp = this.parse8601 (this.safeString (transaction, 'created_at', updated)); + let type = this.safeString (transaction, 'type'); + if (type === 'withdraw') { + type = 'withdrawal'; + } + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const status = this.parseTransactionStatus (this.safeString (transaction, 'state')); + const feeCost = this.safeFloat (transaction, 'fee'); + return { + 'info': transaction, + 'id': id, + 'currency': code, + 'amount': amount, + 'address': address, + 'tag': tag, + 'status': status, + 'type': type, + 'updated': updated, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': { + 'currency': code, + 'cost': feeCost, + }, + }; + } + + parseOrderStatus (status) { + const statuses = { + 'wait': 'open', + 'done': 'closed', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // { + // "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee", + // "side": "bid", + // "ord_type": "limit", + // "price": "17417000.0", + // "state": "done", + // "market": "KRW-BTC", + // "created_at": "2018-04-05T14:09:14+09:00", + // "volume": "1.0", + // "remaining_volume": "0.0", + // "reserved_fee": "26125.5", + // "remaining_fee": "25974.0", + // "paid_fee": "151.5", + // "locked": "17341974.0", + // "executed_volume": "1.0", + // "trades_count": 2, + // "trades": [ + // { + // "market": "KRW-BTC", + // "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3", + // "price": "101000.0", + // "volume": "0.77368323", + // "funds": "78142.00623", + // "ask_fee": "117.213009345", + // "bid_fee": "117.213009345", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid", + // }, + // { + // "market": "KRW-BTC", + // "uuid": "f73da467-c42f-407d-92fa-e10d86450a20", + // "price": "101000.0", + // "volume": "0.22631677", + // "funds": "22857.99377", + // "ask_fee": "34.286990655", // missing in market orders + // "bid_fee": "34.286990655", // missing in market orders + // "created_at": "2018-04-05T14:09:15+09:00", // missing in market orders + // "side": "bid", + // }, + // ], + // } + // + const id = this.safeString (order, 'uuid'); + let side = this.safeString (order, 'side'); + if (side === 'bid') { + side = 'buy'; + } else { + side = 'sell'; + } + let type = this.safeString (order, 'ord_type'); + const timestamp = this.parse8601 (this.safeString (order, 'created_at')); + const status = this.parseOrderStatus (this.safeString (order, 'state')); + let lastTradeTimestamp = undefined; + let price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'volume'); + const remaining = this.safeFloat (order, 'remaining_volume'); + const filled = this.safeFloat (order, 'executed_volume'); + let cost = undefined; + if (type === 'price') { + type = 'market'; + cost = price; + price = undefined; + } + let average = undefined; + let fee = undefined; + let feeCost = this.safeFloat (order, 'paid_fee'); + const marketId = this.safeString (order, 'market'); + market = this.safeMarket (marketId, market); + let trades = this.safeValue (order, 'trades', []); + trades = this.parseTrades (trades, market, undefined, undefined, { + 'order': id, + 'type': type, + }); + const numTrades = trades.length; + if (numTrades > 0) { + // the timestamp in fetchOrder trades is missing + lastTradeTimestamp = trades[numTrades - 1]['timestamp']; + let getFeesFromTrades = false; + if (feeCost === undefined) { + getFeesFromTrades = true; + feeCost = 0; + } + cost = 0; + for (let i = 0; i < numTrades; i++) { + const trade = trades[i]; + cost = this.sum (cost, trade['cost']); + if (getFeesFromTrades) { + const tradeFee = this.safeValue (trades[i], 'fee', {}); + const tradeFeeCost = this.safeFloat (tradeFee, 'cost'); + if (tradeFeeCost !== undefined) { + feeCost = this.sum (feeCost, tradeFeeCost); + } + } + } + average = cost / filled; + } + if (feeCost !== undefined) { + fee = { + 'currency': market['quote'], + 'cost': feeCost, + }; + } + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': lastTradeTimestamp, + 'symbol': market['symbol'], + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'average': average, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': trades, + }; + return result; + } + + async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'market': this.marketId (symbol), + 'state': state, + // 'page': 1, + // 'order_by': 'asc', + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['market'] = market['id']; + } + const response = await this.privateGetOrders (this.extend (request, params)); + // + // [ + // { + // "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee", + // "side": "bid", + // "ord_type": "limit", + // "price": "17417000.0", + // "state": "done", + // "market": "KRW-BTC", + // "created_at": "2018-04-05T14:09:14+09:00", + // "volume": "1.0", + // "remaining_volume": "0.0", + // "reserved_fee": "26125.5", + // "remaining_fee": "25974.0", + // "paid_fee": "151.5", + // "locked": "17341974.0", + // "executed_volume": "1.0", + // "trades_count":2 + // }, + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('wait', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('done', symbol, since, limit, params); + } + + async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersByState ('cancel', symbol, since, limit, params); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'uuid': id, + }; + const response = await this.privateGetOrder (this.extend (request, params)); + // + // { + // "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee", + // "side": "bid", + // "ord_type": "limit", + // "price": "17417000.0", + // "state": "done", + // "market": "KRW-BTC", + // "created_at": "2018-04-05T14:09:14+09:00", + // "volume": "1.0", + // "remaining_volume": "0.0", + // "reserved_fee": "26125.5", + // "remaining_fee": "25974.0", + // "paid_fee": "151.5", + // "locked": "17341974.0", + // "executed_volume": "1.0", + // "trades_count": 2, + // "trades": [ + // { + // "market": "KRW-BTC", + // "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3", + // "price": "101000.0", + // "volume": "0.77368323", + // "funds": "78142.00623", + // "ask_fee": "117.213009345", + // "bid_fee": "117.213009345", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid" + // }, + // { + // "market": "KRW-BTC", + // "uuid": "f73da467-c42f-407d-92fa-e10d86450a20", + // "price": "101000.0", + // "volume": "0.22631677", + // "funds": "22857.99377", + // "ask_fee": "34.286990655", + // "bid_fee": "34.286990655", + // "created_at": "2018-04-05T14:09:15+09:00", + // "side": "bid" + // } + // ] + // } + // + return this.parseOrder (response); + } + + parseDepositAddresses (addresses) { + const result = {}; + for (let i = 0; i < addresses.length; i++) { + const address = this.parseDepositAddress (addresses[i]); + const code = address['currency']; + result[code] = address; + } + return result; + } + + async fetchDepositAddresses (codes = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.privateGetDepositsCoinAddresses (params); + // + // [ + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // }, + // { + // "currency": "ETH", + // "deposit_address": "0x0d73e0a482b8cf568976d2e8688f4a899d29301c", + // "secondary_address": null + // }, + // { + // "currency": "XRP", + // "deposit_address": "rN9qNpgnBaZwqCg8CvUZRPqCcPPY7wfWep", + // "secondary_address": "3057887915" + // } + // ] + // + return this.parseDepositAddresses (response); + } + + parseDepositAddress (depositAddress, currency = undefined) { + // + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // } + // + const address = this.safeString (depositAddress, 'deposit_address'); + const tag = this.safeString (depositAddress, 'secondary_address'); + const currencyId = this.safeString (depositAddress, 'currency'); + const code = this.safeCurrencyCode (currencyId); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': depositAddress, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const response = await this.privateGetDepositsCoinAddress (this.extend ({ + 'currency': currency['id'], + }, params)); + // + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // } + // + return this.parseDepositAddress (response); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + // https://github.com/ccxt/ccxt/issues/6452 + const response = await this.privatePostDepositsGenerateCoinAddress (this.extend (request, params)); + // + // https://docs.upbit.com/v1.0/reference#%EC%9E%85%EA%B8%88-%EC%A3%BC%EC%86%8C-%EC%83%9D%EC%84%B1-%EC%9A%94%EC%B2%AD + // can be any of the two responses: + // + // { + // "success" : true, + // "message" : "Creating BTC deposit address." + // } + // + // { + // "currency": "BTC", + // "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD", + // "secondary_address": null + // } + // + const message = this.safeString (response, 'message'); + if (message !== undefined) { + throw new AddressPending (this.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress or createDepositAddress one more time later to retrieve the generated address'); + } + return this.parseDepositAddress (response); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'amount': amount, + }; + let method = 'privatePostWithdraws'; + if (code !== 'KRW') { + method += 'Coin'; + request['currency'] = currency['id']; + request['address'] = address; + if (tag !== undefined) { + request['secondary_address'] = tag; + } + } else { + method += 'Krw'; + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "type": "withdraw", + // "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79", + // "currency": "BTC", + // "txid": "ebe6937b-130e-4066-8ac6-4b0e67f28adc", + // "state": "processing", + // "created_at": "2018-04-13T11:24:01+09:00", + // "done_at": null, + // "amount": "0.01", + // "fee": "0.0", + // "krw_amount": "80420.0" + // } + // + return this.parseTransaction (response); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.implodeParams (this.urls['api'][api], { + 'hostname': this.hostname, + }); + url += '/' + this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (method !== 'POST') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + const request = { + 'access_key': this.apiKey, + 'nonce': nonce, + }; + if (Object.keys (query).length) { + const auth = this.urlencode (query); + const hash = this.hash (this.encode (auth), 'sha512'); + request['query_hash'] = hash; + request['query_hash_alg'] = 'SHA512'; + } + const jwt = this.jwt (request, this.encode (this.secret)); + headers = { + 'Authorization': 'Bearer ' + jwt, + }; + if ((method !== 'GET') && (method !== 'DELETE')) { + body = this.json (params); + headers['Content-Type'] = 'application/json'; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + // + // { 'error': { 'message': "Missing request parameter error. Check the required parameters!", 'name': 400 } }, + // { 'error': { 'message': "side is missing, side does not have a valid value", 'name': "validation_error" } }, + // { 'error': { 'message': "개인정보 제 3자 제공 동의가 필요합니다.", 'name': "thirdparty_agreement_required" } }, + // { 'error': { 'message': "권한이 부족합니다.", 'name': "out_of_scope" } }, + // { 'error': { 'message': "주문을 찾지 못했습니다.", 'name': "order_not_found" } }, + // { 'error': { 'message': "주문가능한 금액(ETH)이 부족합니다.", 'name': "insufficient_funds_ask" } }, + // { 'error': { 'message': "주문가능한 금액(BTC)이 부족합니다.", 'name': "insufficient_funds_bid" } }, + // { 'error': { 'message': "잘못된 엑세스 키입니다.", 'name': "invalid_access_key" } }, + // { 'error': { 'message': "Jwt 토큰 검증에 실패했습니다.", 'name': "jwt_verification" } } + // + const error = this.safeValue (response, 'error'); + if (error !== undefined) { + const message = this.safeString (error, 'message'); + const name = this.safeString (error, 'name'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], name, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], name, feedback); + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vaultoro.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vaultoro.js new file mode 100644 index 000000000..abc1ba387 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vaultoro.js @@ -0,0 +1,203 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); + +// --------------------------------------------------------------------------- + +module.exports = class vaultoro extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'vaultoro', + 'name': 'Vaultoro', + 'countries': [ 'CH' ], + 'rateLimit': 1000, + 'version': '1', + 'has': { + 'CORS': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchBalance': true, + 'createOrder': true, + 'cancelOrder': true, + 'fetchTrades': true, + 'fetchTicker': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766880-f205e870-5ee9-11e7-8fe2-0d5b15880752.jpg', + 'api': 'https://api.vaultoro.com', + 'www': 'https://www.vaultoro.com', + 'doc': 'https://api.vaultoro.com', + }, + 'commonCurrencies': { + 'GLD': 'Gold', + }, + 'api': { + 'public': { + 'get': [ + 'bidandask', + 'buyorders', + 'latest', + 'latesttrades', + 'markets', + 'orderbook', + 'sellorders', + 'transactions/day', + 'transactions/hour', + 'transactions/month', + ], + }, + 'private': { + 'get': [ + 'balance', + 'mytrades', + 'orders', + ], + 'post': [ + 'buy/{symbol}/{type}', + 'cancel/{id}', + 'sell/{symbol}/{type}', + 'withdraw', + ], + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const result = []; + const response = await this.publicGetMarkets (params); + const market = this.safeValue (response, 'data'); + const baseId = this.safeString (market, 'MarketCurrency'); + const quoteId = this.safeString (market, 'BaseCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const id = this.safeString (market, 'MarketName'); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + const balances = this.safeValue (response, 'data'); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency_code'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'cash'); + account['used'] = this.safeFloat (balance, 'reserved'); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetOrderbook (params); + const orderbook = { + 'bids': response['data'][0]['b'], + 'asks': response['data'][1]['s'], + }; + return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'Gold_Price', 'Gold_Amount'); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.parse8601 (this.safeString (trade, 'Time')); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const price = this.safeFloat (trade, 'Gold_Price'); + const amount = this.safeFloat (trade, 'Gold_Amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + return { + 'id': undefined, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'order': undefined, + 'type': undefined, + 'side': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.publicGetTransactionsDay (params); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const method = 'privatePost' + this.capitalize (side) + 'SymbolType'; + const request = { + 'symbol': market['quoteId'].toLowerCase (), + 'type': type, + 'gld': amount, + 'price': price || 1, + }; + const response = await this[method] (this.extend (request, params)); + return { + 'info': response, + 'id': response['data']['Order_ID'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id, + }; + return await this.privatePostCancelId (this.extend (request, params)); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/'; + if (api === 'public') { + url += path; + } else { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + url += this.version + '/' + this.implodeParams (path, params); + const query = this.extend ({ + 'nonce': nonce, + 'apikey': this.apiKey, + }, this.omit (params, this.extractParams (path))); + url += '?' + this.urlencode (query); + headers = { + 'Content-Type': 'application/json', + 'X-Signature': this.hmac (this.encode (url), this.encode (this.secret)), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vbtc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vbtc.js new file mode 100644 index 000000000..d547b0458 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vbtc.js @@ -0,0 +1,32 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const foxbit = require ('./foxbit.js'); + +// --------------------------------------------------------------------------- + +module.exports = class vbtc extends foxbit { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'vbtc', + 'name': 'VBTC', + 'countries': [ 'VN' ], + 'has': { + 'CORS': false, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27991481-1f53d1d8-6481-11e7-884e-21d17e7939db.jpg', + 'api': { + 'public': 'https://api.blinktrade.com/api', + 'private': 'https://api.blinktrade.com/tapi', + }, + 'www': 'https://vbtc.exchange', + 'doc': 'https://blinktrade.com/docs', + }, + 'options': { + 'brokerId': '3', // https://blinktrade.com/docs/#brokers + }, + }); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vcc.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vcc.js new file mode 100644 index 000000000..006c36ec6 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/vcc.js @@ -0,0 +1,1338 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, OrderNotFound, InvalidOrder, BadRequest, AuthenticationError, RateLimitExceeded, RequestTimeout, BadSymbol, AddressPending, PermissionDenied, InsufficientFunds } = require ('./base/errors'); +const { ROUND } = require ('./base/functions/number'); + +// --------------------------------------------------------------------------- + +module.exports = class vcc extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'vcc', + 'name': 'VCC Exchange', + 'countries': [ 'VN' ], // Vietnam + 'rateLimit': 1000, + 'version': 'v3', + 'has': { + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createOrder': true, + 'editOrder': false, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': false, + 'fetchTicker': 'emulated', + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFees': false, + 'fetchTransactions': true, + 'fetchWithdrawals': true, + }, + 'timeframes': { + '1m': '60000', + '5m': '300000', + '15m': '900000', + '30m': '1800000', + '1h': '3600000', + '2h': '7200000', + '4h': '14400000', + '6h': '21600000', + '12h': '43200000', + '1d': '86400000', + '1w': '604800000', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/100545356-8427f500-326c-11eb-9539-7d338242d61b.jpg', + 'api': { + 'public': 'https://api.vcc.exchange', + 'private': 'https://api.vcc.exchange', + }, + 'www': 'https://vcc.exchange', + 'doc': [ + 'https://vcc.exchange/api', + ], + 'fees': 'https://support.vcc.exchange/hc/en-us/articles/360016401754', + 'referral': 'https://vcc.exchange?ref=l4xhrH', + }, + 'api': { + 'public': { + 'get': [ + 'summary', + 'exchange_info', + 'assets', // Available Currencies + 'ticker', // Ticker list for all symbols + 'trades/{market_pair}', // Recent trades + 'orderbook/{market_pair}', // Orderbook + 'chart/bars', // Candles + 'tick_sizes', + ], + }, + 'private': { + 'get': [ + 'user', + 'balance', // Get trading balance + 'orders/{order_id}', // Get a single order by order_id + 'orders/open', // Get open orders + 'orders', // Get closed orders + 'orders/trades', // Get trades history + 'deposit-address', // Generate or get deposit address + 'transactions', // Get deposit/withdrawal history + ], + 'post': [ + 'orders', // Create new order + ], + 'put': [ + 'orders/{order_id}/cancel', // Cancel order + 'orders/cancel-by-type', + 'orders/cancel-all', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + }, + 'exceptions': { + 'exact': {}, + 'broad': { + 'limit may not be greater than': BadRequest, // {"message":"The given data was invalid.","errors":{"limit":["The limit may not be greater than 1000."]}} + 'Insufficient balance': InsufficientFunds, // {"message":"Insufficient balance."} + 'Unauthenticated': AuthenticationError, // {"message":"Unauthenticated."} // wrong api key + 'signature is invalid': AuthenticationError, // {"message":"The given data was invalid.","errors":{"signature":["HMAC signature is invalid"]}} + 'Timeout': RequestTimeout, // {"code":504,"message":"Gateway Timeout","description":""} + 'Too many requests': RateLimitExceeded, // {"code":429,"message":"Too many requests","description":"Too many requests"} + 'quantity field is required': InvalidOrder, // {"message":"The given data was invalid.","errors":{"quantity":["The quantity field is required when type is market."]}} + 'price field is required': InvalidOrder, // {"message":"The given data was invalid.","errors":{"price":["The price field is required when type is limit."]}} + 'error_security_level': PermissionDenied, // {"message":"error_security_level"} + 'pair is invalid': BadSymbol, // {"message":"The given data was invalid.","errors":{"coin":["Trading pair is invalid","Trading pair is offline"]}} + // {"message":"The given data was invalid.","errors":{"type":["The selected type is invalid."]}} + // {"message":"The given data was invalid.","errors":{"trade_type":["The selected trade type is invalid."]}} + 'type is invalid': InvalidOrder, + 'Data not found': OrderNotFound, // {"message":"Data not found"} + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetExchangeInfo (params); + // + // { + // "message":null, + // "dataVersion":"4677e56a42f0c29872f3a6e75f5d39d2f07c748c", + // "data":{ + // "timezone":"UTC", + // "serverTime":1605821914333, + // "symbols":[ + // { + // "id":"btcvnd", + // "symbol":"BTC\/VND", + // "coin":"btc", + // "currency":"vnd", + // "baseId":1, + // "quoteId":0, + // "active":true, + // "base_precision":"0.0000010000", + // "quote_precision":"1.0000000000", + // "minimum_quantity":"0.0000010000", + // "minimum_amount":"250000.0000000000", + // "precision":{"price":0,"amount":6,"cost":6}, + // "limits":{ + // "amount":{"min":"0.0000010000"}, + // "price":{"min":"1.0000000000"}, + // "cost":{"min":"250000.0000000000"}, + // }, + // }, + // ], + // }, + // } + // + const data = this.safeValue (response, 'data'); + const markets = this.safeValue (data, 'symbols'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = this.safeValue (markets, i); + const symbol = this.safeString (market, 'symbol'); + const id = symbol.replace ('/', '_'); + const baseId = this.safeString (market, 'coin'); + const quoteId = this.safeString (market, 'currency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const active = this.safeValue (market, 'active'); + const precision = this.safeValue (market, 'precision', {}); + const limits = this.safeValue (market, 'limits', {}); + const amountLimits = this.safeValue (limits, 'amount', {}); + const priceLimits = this.safeValue (limits, 'price', {}); + const costLimits = this.safeValue (limits, 'cost', {}); + const entry = { + 'info': market, + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'precision': { + 'price': this.safeInteger (precision, 'price'), + 'amount': this.safeInteger (precision, 'amount'), + 'cost': this.safeInteger (precision, 'cost'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (amountLimits, 'min'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (priceLimits, 'min'), + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (costLimits, 'min'), + 'max': undefined, + }, + }, + }; + result.push (entry); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetAssets (params); + // + // { + // "message":null, + // "dataVersion":"2514c8012d94ea375018fc13e0b5d4d896e435df", + // "data":{ + // "BTC":{ + // "name":"Bitcoin", + // "unified_cryptoasset_id":1, + // "can_withdraw":1, + // "can_deposit":1, + // "min_withdraw":"0.0011250000", + // "max_withdraw":"100.0000000000", + // "maker_fee":"0.002", + // "taker_fee":"0.002", + // "decimal":8, + // "withdrawal_fee":"0.0006250000", + // }, + // }, + // } + // + const result = {}; + const data = this.safeValue (response, 'data'); + const ids = Object.keys (data); + for (let i = 0; i < ids.length; i++) { + const id = this.safeStringLower (ids, i); + const currency = this.safeValue (data, ids[i]); + const code = this.safeCurrencyCode (id); + const canDeposit = this.safeValue (currency, 'can_deposit'); + const canWithdraw = this.safeValue (currency, 'can_withdraw'); + const active = (canDeposit && canWithdraw); + result[code] = { + 'id': id, + 'code': code, + 'name': this.safeString (currency, 'name'), + 'active': active, + 'fee': this.safeFloat (currency, 'withdrawal_fee'), + 'precision': this.safeInteger (currency, 'decimal'), + 'limits': { + 'withdraw': { + 'min': this.safeFloat (currency, 'min_withdraw'), + 'max': this.safeFloat (currency, 'max_withdraw'), + }, + }, + }; + } + return result; + } + + async fetchTradingFee (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = this.extend ({ + 'symbol': market['id'], + }, this.omit (params, 'symbol')); + const response = await this.privateGetTradingFeeSymbol (request); + // + // { + // takeLiquidityRate: '0.001', + // provideLiquidityRate: '-0.0001' + // } + // + return { + 'info': response, + 'maker': this.safeFloat (response, 'provideLiquidityRate'), + 'taker': this.safeFloat (response, 'takeLiquidityRate'), + }; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetBalance (params); + // + // { + // "message":null, + // "dataVersion":"7168e6c99e90f60673070944d987988eef7d91fa", + // "data":{ + // "vnd":{"balance":0,"available_balance":0}, + // "btc":{"balance":0,"available_balance":0}, + // "eth":{"balance":0,"available_balance":0}, + // }, + // } + // + const data = this.safeValue (response, 'data'); + const result = { 'info': response }; + const currencyIds = Object.keys (data); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (data, currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available_balance'); + account['total'] = this.safeFloat (balance, 'balance'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "low":"415805323.0000000000", + // "high":"415805323.0000000000", + // "open":"415805323.0000000000", + // "close":"415805323.0000000000", + // "time":"1605845940000", + // "volume":"0.0065930000", + // "opening_time":1605845963263, + // "closing_time":1605845963263 + // } + // + return [ + this.safeInteger (ohlcv, 'time'), + this.safeFloat (ohlcv, 'open'), + this.safeFloat (ohlcv, 'high'), + this.safeFloat (ohlcv, 'low'), + this.safeFloat (ohlcv, 'close'), + this.safeFloat (ohlcv, 'volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'currency': market['quoteId'], + 'resolution': this.timeframes[timeframe], + }; + limit = (limit === undefined) ? 100 : limit; + limit = Math.min (100, limit); + const duration = this.parseTimeframe (timeframe); + if (since === undefined) { + const end = this.seconds (); + request['to'] = end; + request['from'] = end - limit * duration; + } else { + const start = parseInt (since / 1000); + request['from'] = start; + request['to'] = this.sum (start, limit * duration); + } + const response = await this.publicGetChartBars (this.extend (request, params)); + // + // [ + // {"low":"415805323.0000000000","high":"415805323.0000000000","open":"415805323.0000000000","close":"415805323.0000000000","time":"1605845940000","volume":"0.0065930000","opening_time":1605845963263,"closing_time":1605845963263}, + // {"low":"416344148.0000000000","high":"416344148.0000000000","open":"415805323.0000000000","close":"416344148.0000000000","time":"1605846000000","volume":"0.0052810000","opening_time":1605846011490,"closing_time":1605846011490}, + // {"low":"416299269.0000000000","high":"417278376.0000000000","open":"416344148.0000000000","close":"417278376.0000000000","time":"1605846060000","volume":"0.0136750000","opening_time":1605846070727,"closing_time":1605846102282}, + // ] + // + return this.parseOHLCVs (response, market, timeframe, since, limit); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_pair': market['id'], + // 'depth': 0, // 0 = full orderbook, 5, 10, 20, 50, 100, 500 + 'level': 2, // 1 = best bidask, 2 = aggregated by price, 3 = no aggregation + }; + if (limit !== undefined) { + if ((limit !== 0) && (limit !== 5) && (limit !== 10) && (limit !== 20) && (limit !== 50) && (limit !== 100) && (limit !== 500)) { + throw new BadRequest (this.id + ' fetchOrderBook limit must be 0, 5, 10, 20, 50, 100, 500 if specified'); + } + request['depth'] = limit; + } + const response = await this.publicGetOrderbookMarketPair (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"376cee43af26deabcd3762ab11a876b6e7a71e82", + // "data":{ + // "bids":[ + // ["413342637.0000000000","0.165089"], + // ["413274576.0000000000","0.03"], + // ["413274574.0000000000","0.03"], + // ], + // "asks":[ + // ["416979125.0000000000","0.122835"], + // ["417248934.0000000000","0.030006"], + // ["417458879.0000000000","0.1517"], + // ], + // "timestamp":"1605841619147" + // } + // } + // + const data = this.safeValue (response, 'data'); + const timestamp = this.safeValue (data, 'timestamp'); + return this.parseOrderBook (data, timestamp, 'bids', 'asks', 0, 1); + } + + parseTicker (ticker, market = undefined) { + // + // { + // "base_id":1, + // "quote_id":0, + // "last_price":"411119457", + // "max_price":"419893173.0000000000", + // "min_price":"401292577.0000000000", + // "open_price":null, + // "base_volume":"10.5915050000", + // "quote_volume":"4367495977.4484430060", + // "isFrozen":0 + // } + // + const timestamp = this.milliseconds (); + const baseVolume = this.safeFloat (ticker, 'base_volume'); + const quoteVolume = this.safeFloat (ticker, 'quote_volume'); + const open = this.safeFloat (ticker, 'open_price'); + const last = this.safeFloat (ticker, 'last_price'); + let change = undefined; + let percentage = undefined; + let average = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + const vwap = this.vwap (baseVolume, quoteVolume); + const symbol = (market === undefined) ? undefined : market['symbol']; + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'max_price'), + 'low': this.safeFloat (ticker, 'min_price'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const response = await this.publicGetTicker (params); + // + // { + // "message":null, + // "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e", + // "data":{ + // "BTC_VND":{ + // "base_id":1, + // "quote_id":0, + // "last_price":"411119457", + // "max_price":"419893173.0000000000", + // "min_price":"401292577.0000000000", + // "open_price":null, + // "base_volume":"10.5915050000", + // "quote_volume":"4367495977.4484430060", + // "isFrozen":0 + // }, + // } + // } + // + const data = this.safeValue (response, 'data'); + const ticker = this.safeValue (data, market['id']); + return this.parseTicker (ticker, market); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + // + // { + // "message":null, + // "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e", + // "data":{ + // "BTC_VND":{ + // "base_id":1, + // "quote_id":0, + // "last_price":"411119457", + // "max_price":"419893173.0000000000", + // "min_price":"401292577.0000000000", + // "open_price":null, + // "base_volume":"10.5915050000", + // "quote_volume":"4367495977.4484430060", + // "isFrozen":0 + // }, + // } + // } + // + const result = {}; + const data = this.safeValue (response, 'data'); + const marketIds = Object.keys (data); + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const market = this.safeMarket (marketId, undefined, '_'); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (data[marketId], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + parseTrade (trade, market = undefined) { + // + // public fetchTrades + // + // { + // "trade_id":181509285, + // "price":"415933022.0000000000", + // "base_volume":"0.0022080000", + // "quote_volume":"918380.1125760000", + // "trade_timestamp":1605842150357, + // "type":"buy", + // } + // + // private fetchMyTrades + // + // { + // "trade_type":"sell", + // "fee":"0.0610578086", + // "id":1483372, + // "created_at":1606581578368, + // "currency":"usdt", + // "coin":"btc", + // "price":"17667.1900000000", + // "quantity":"0.0017280000", + // "amount":"30.5289043200", + // } + // + const timestamp = this.safeInteger2 (trade, 'trade_timestamp', 'created_at'); + const baseId = this.safeStringUpper (trade, 'coin'); + const quoteId = this.safeStringUpper (trade, 'currency'); + let marketId = undefined; + if ((baseId !== undefined) && (quoteId !== undefined)) { + marketId = baseId + '_' + quoteId; + } + market = this.safeMarket (marketId, market, '_'); + const symbol = market['symbol']; + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'base_volume', 'quantity'); + let cost = this.safeFloat2 (trade, 'quote_volume', 'amount'); + if (cost === undefined) { + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + } + const side = this.safeString2 (trade, 'type', 'trade_type'); + const id = this.safeString2 (trade, 'trade_id', 'id'); + const feeCost = this.safeFloat (trade, 'fee'); + let fee = undefined; + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': market['quote'], + }; + } + return { + 'info': trade, + 'id': id, + 'order': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market_pair': market['id'], + // 'type': 'buy', // 'sell' + // 'count': limit, // default 500, max 1000 + }; + if (limit !== undefined) { + request['count'] = Math.min (1000, limit); + } + const response = await this.publicGetTradesMarketPair (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"1f811b533143f739008a3e4ecaaab2ec82ea50d4", + // "data":[ + // { + // "trade_id":181509285, + // "price":"415933022.0000000000", + // "base_volume":"0.0022080000", + // "quote_volume":"918380.1125760000", + // "trade_timestamp":1605842150357, + // "type":"buy", + // }, + // ], + // } + // + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'type': type, // 'deposit', 'withdraw' + // 'start': parseInt (since / 1000), + // 'end': this.seconds (), + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['currency'] = currency['id']; + } + if (limit !== undefined) { + request['limit'] = Math.min (1000, limit); + } + if (since !== undefined) { + request['start'] = since; + } + const response = await this.privateGetTransactions (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"1fdfb0ec85b666871d62fe59d098d01839b05e97", + // "data":{ + // "current_page":1, + // "data":[ + // { + // "id":85391, + // "user_id":253063, + // "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131", + // "internal_transaction_id":null, + // "temp_transaction_id":"2367", + // "currency":"usdt", + // "amount":"30.0000000000", + // "btc_amount":"0.0000000000", + // "usdt_amount":"0.0000000000", + // "fee":"0.0000000000", + // "tx_cost":"0.0000000000", + // "confirmation":0, + // "deposit_code":null, + // "status":"success", + // "bank_name":null, + // "foreign_bank_account":null, + // "foreign_bank_account_holder":null, + // "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44", + // "destination_tag":null, + // "error_detail":null, + // "refunded":"0.0000000000", + // "transaction_date":"2020-11-28", + // "transaction_timestamp":"1606563143.959", + // "created_at":1606563143959, + // "updated_at":1606563143959, + // "transaction_email_timestamp":0, + // "network":null, + // "collect_tx_id":null, + // "collect_id":null + // } + // ], + // "first_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1", + // "from":1, + // "last_page":1, + // "last_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1", + // "next_page_url":null, + // "path":"http:\/\/api.vcc.exchange\/v3\/transactions", + // "per_page":10, + // "prev_page_url":null, + // "to":1, + // "total":1 + // } + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (data, 'data', []); + return this.parseTransactions (data, currency, since, limit); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'type': 'deposit' }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + const request = { 'type': 'withdraw' }; + return await this.fetchTransactions (code, since, limit, this.extend (request, params)); + } + + parseTransaction (transaction, currency = undefined) { + // + // fetchTransactions, fetchDeposits, fetchWithdrawals + // + // { + // "id":85391, + // "user_id":253063, + // "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131", + // "internal_transaction_id":null, + // "temp_transaction_id":"2367", + // "currency":"usdt", + // "amount":"30.0000000000", + // "btc_amount":"0.0000000000", + // "usdt_amount":"0.0000000000", + // "fee":"0.0000000000", + // "tx_cost":"0.0000000000", + // "confirmation":0, + // "deposit_code":null, + // "status":"success", + // "bank_name":null, + // "foreign_bank_account":null, + // "foreign_bank_account_holder":null, + // "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44", + // "destination_tag":null, + // "error_detail":null, + // "refunded":"0.0000000000", + // "transaction_date":"2020-11-28", + // "transaction_timestamp":"1606563143.959", + // "created_at":1606563143959, + // "updated_at":1606563143959, + // "transaction_email_timestamp":0, + // "network":null, + // "collect_tx_id":null, + // "collect_id":null + // } + // + const id = this.safeString (transaction, 'id'); + const timestamp = this.safeInteger (transaction, 'created_at'); + const updated = this.safeInteger (transaction, 'updated_at'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + let amount = this.safeFloat (transaction, 'amount'); + if (amount !== undefined) { + amount = Math.abs (amount); + } + const address = this.safeString (transaction, 'blockchain_address'); + const txid = this.safeString (transaction, 'transaction_id'); + const tag = this.safeString (transaction, 'destination_tag'); + let fee = undefined; + const feeCost = this.safeFloat (transaction, 'fee'); + if (feeCost !== undefined) { + fee = { + 'cost': feeCost, + 'currency': code, + }; + } + const type = amount > 0 ? 'deposit' : 'withdrawal'; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'address': address, + 'addressTo': address, + 'addressFrom': undefined, + 'tag': tag, + 'tagTo': tag, + 'tagFrom': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + 'pending': 'pending', + 'error': 'failed', + 'success': 'ok', + 'cancel': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseTransactionType (type) { + const types = { + 'deposit': 'deposit', + 'withdraw': 'withdrawal', + }; + return this.safeString (types, type, type); + } + + costToPrecision (symbol, cost) { + return this.decimalToPrecision (cost, ROUND, this.markets[symbol]['precision']['cost'], this.precisionMode, this.paddingMode); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'coin': market['baseId'], + 'currency': market['quoteId'], + 'trade_type': side, + 'type': type, + }; + if (type === 'ceiling_market') { + const ceiling = this.safeValue (params, 'ceiling'); + if (ceiling !== undefined) { + request['ceiling'] = this.costToPrecision (symbol, ceiling); + } else if (price !== undefined) { + request['ceiling'] = this.costToPrecision (symbol, amount * price); + } else { + throw new InvalidOrder (this.id + ' createOrder() requires a price argument or a ceiling parameter for ' + type + ' orders'); + } + } else { + request['quantity'] = this.amountToPrecision (symbol, amount); + } + if (type === 'limit') { + request['price'] = this.priceToPrecision (symbol, price); + } + const stopPrice = this.safeValue2 (params, 'stop_price', 'stopPrice'); + if (stopPrice !== undefined) { + request['is_stop'] = 1; + request['stop_condition'] = (side === 'buy') ? 'le' : 'ge'; // ge = greater than or equal, le = less than or equal + request['stop_price'] = this.priceToPrecision (symbol, price); + } + params = this.omit (params, [ 'stop_price', 'stopPrice' ]); + const response = await this.privatePostOrders (this.extend (request, params)); + // + // ceiling_market order + // + // { + // "message":null, + // "dataVersion":"213fc0d433f38307f736cae1cbda4cc310469b7a", + // "data":{ + // "coin":"btc", + // "currency":"usdt", + // "trade_type":"buy", + // "type":"ceiling_market", + // "ceiling":"30", + // "user_id":253063, + // "email":"igor.kroitor@gmail.com", + // "side":"buy", + // "quantity":"0.00172800", + // "status":"pending", + // "fee":0, + // "created_at":1606571333035, + // "updated_at":1606571333035, + // "instrument_symbol":"BTCUSDT", + // "remaining":"0.00172800", + // "fee_rate":"0.002", + // "id":88214435 + // } + // } + // + // limit order + // + // { + // "message":null, + // "dataVersion":"d9b1159d2bcefa2388be156e32ddc7cc324400ee", + // "data":{ + // "id":41230, + // "trade_type":"sell", + // "type":"limit", + // "quantity":"1", + // "price":"14.99", + // "currency":"usdt", + // "coin":"neo", + // "status":"pending", + // "is_stop": "1", + // "stop_price": "13", + // "stop_condition": "ge", + // "fee":0, + // "created_at":1560244052168, + // "updated_at":1560244052168 + // } + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (data, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privatePutOrdersOrderIdCancel (this.extend (request, params)); + return this.parseOrder (response); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + const type = this.safeString (params, 'type'); + const method = (type === undefined) ? 'privatePutOrdersCancelAll' : 'privatePutOrdersCancelByType'; + const request = {}; + if (type !== undefined) { + request['type'] = type; + } + await this.loadMarkets (); + const response = await this[method] (this.extend (request, params)); + // + // { + // "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff", + // "data":{ + // "data":[ + // { + // "id":410, + // "trade_type":"sell", + // "currency":"usdt", + // "coin":"neo", + // "type":"limit", + // "quantity":"1.0000000000", + // "price":"14.9900000000", + // "executed_quantity":"0.0000000000", + // "executed_price":"0.0000000000", + // "fee":"0.0000000000", + // "status":"canceled", + // "created_at":1560244052168, + // "updated_at":1560244052168, + // }, + // ], + // }, + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (response, 'data', []); + return this.parseOrders (data); + } + + parseOrderStatus (status) { + const statuses = { + 'pending': 'open', + 'stopping': 'open', + 'executing': 'open', + 'executed': 'closed', + 'canceled': 'canceled', + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // ceiling_market + // + // { + // "coin":"btc", + // "currency":"usdt", + // "trade_type":"buy", + // "type":"ceiling_market", + // "ceiling":"30", + // "user_id":253063, + // "email":"igor.kroitor@gmail.com", + // "side":"buy", + // "quantity":"0.00172800", + // "status":"pending", + // "fee":0, + // "created_at":1606571333035, + // "updated_at":1606571333035, + // "instrument_symbol":"BTCUSDT", + // "remaining":"0.00172800", + // "fee_rate":"0.002", + // "id":88214435 + // } + // + // limit order + // + // { + // "id":41230, + // "trade_type":"sell", + // "type":"limit", + // "quantity":"1", + // "price":"14.99", + // "currency":"usdt", + // "coin":"neo", + // "status":"pending", + // "is_stop": "1", + // "stop_price": "13", + // "stop_condition": "ge", + // "fee":0, + // "created_at":1560244052168, + // "updated_at":1560244052168 + // } + // + const created = this.safeValue (order, 'created_at'); + const updated = this.safeValue (order, 'updated_at'); + const baseId = this.safeStringUpper (order, 'coin'); + const quoteId = this.safeStringUpper (order, 'currency'); + const marketId = baseId + '_' + quoteId; + market = this.safeMarket (marketId, market, '_'); + const symbol = market['symbol']; + const amount = this.safeFloat (order, 'quantity'); + let filled = this.safeFloat (order, 'executed_quantity'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let cost = this.safeFloat (order, 'ceiling'); + const id = this.safeString (order, 'id'); + let average = undefined; + let price = this.safeFloat (order, 'price'); + // in case of market order + if (!price) { + price = this.safeFloat (order, 'executed_price'); + average = price; + } + let remaining = this.safeFloat (order, 'remaining'); + if ((filled === undefined) && (amount !== undefined) && (remaining !== undefined)) { + filled = Math.max (0, amount - remaining); + } + if (filled !== undefined) { + if ((amount !== undefined) && (remaining === undefined)) { + remaining = Math.max (0, amount - filled); + } + if ((price !== undefined) && (cost === undefined)) { + cost = filled * price; + } + if ((average === undefined) && (cost !== undefined) && (filled > 0)) { + average = cost / filled; + } + } + const type = this.safeString (order, 'type'); + const side = this.safeString (order, 'trade_type'); + const fee = { + 'currency': market['quote'], + 'cost': this.safeFloat (order, 'fee'), + 'rate': this.safeFloat (order, 'fee_rate'), + }; + let lastTradeTimestamp = undefined; + if (updated !== created) { + lastTradeTimestamp = updated; + } + const stopPrice = this.safeFloat (order, 'stopPrice'); + return { + 'id': id, + 'clientOrderId': id, + 'timestamp': created, + 'datetime': this.iso8601 (created), + 'lastTradeTimestamp': lastTradeTimestamp, + 'status': status, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': stopPrice, + 'average': average, + 'amount': amount, + 'cost': cost, + 'filled': filled, + 'remaining': remaining, + 'fee': fee, + 'trades': undefined, + 'info': order, + }; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': id, + }; + const response = await this.privateGetOrdersOrderId (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"57448aa1fb8f227254e8e2e925b3ade8e1e5bbef", + // "data":{ + // "id":88265741, + // "user_id":253063, + // "email":"igor.kroitor@gmail.com", + // "updated_at":1606581578141, + // "created_at":1606581578141, + // "coin":"btc", + // "currency":"usdt", + // "type":"market", + // "trade_type":"sell", + // "executed_price":"17667.1900000000", + // "price":null, + // "executed_quantity":"0.0017280000", + // "quantity":"0.0017280000", + // "fee":"0.0610578086", + // "status":"executed", + // "is_stop":0, + // "stop_condition":null, + // "stop_price":null, + // "ceiling":null + // } + // } + // + const data = this.safeValue (response, 'data'); + return this.parseOrder (data); + } + + async fetchOrdersWithMethod (method, symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'limit': limit, // max 1000 + // 'start_date': since, + // 'end_date': this.milliseconds (), + // 'currency': market['quoteId'], + // 'coin': market['baseId'], + // 'trade_type': 'buy', // or 'sell' + // 'hide_canceled': 0, // 1 to exclude canceled orders + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['coin'] = market['baseId']; + request['currency'] = market['quoteId']; + } + if (since !== undefined) { + request['start_date'] = since; + } + if (limit !== undefined) { + request['limit'] = Math.min (1000, limit); // max 1000 + } + const response = await this[method] (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"89aa11497f23fdd34cf9de9c55acfad863c78780", + // "data":{ + // "current_page":1, + // "data":[ + // { + // "id":88489678, + // "email":"igor.kroitor@gmail.com", + // "updated_at":1606628593567, + // "created_at":1606628593567, + // "coin":"btc", + // "currency":"usdt", + // "type":"limit", + // "trade_type":"buy", + // "executed_price":"0.0000000000", + // "price":"10000.0000000000", + // "executed_quantity":"0.0000000000", + // "quantity":"0.0010000000", + // "fee":"0.0000000000", + // "status":"pending", + // "is_stop":0, + // "stop_condition":null, + // "stop_price":null, + // "ceiling":null, + // }, + // ], + // "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1", + // "from":1, + // "last_page":1, + // "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1", + // "next_page_url":null, + // "path":"http:\/\/api.vcc.exchange\/v3\/orders\/open", + // "per_page":10, + // "prev_page_url":null, + // "to":1, + // "total":1, + // }, + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (data, 'data', []); + return this.parseOrders (data, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetOrdersOpen', symbol, since, limit, params); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchOrdersWithMethod ('privateGetOrders', symbol, since, limit, params); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + // 'page': 1, + // 'limit': limit, // max 1000 + // 'start_date': since, + // 'end_date': this.milliseconds (), + // 'currency': market['quoteId'], + // 'coin': market['baseId'], + // 'trade_type': 'buy', // or 'sell' + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['coin'] = market['baseId']; + request['currency'] = market['quoteId']; + } + if (since !== undefined) { + request['start_date'] = since; + } + if (limit !== undefined) { + request['limit'] = Math.min (1000, limit); // max 1000 + } + const response = await this.privateGetOrdersTrades (this.extend (request, params)); + // + // { + // "message":null, + // "dataVersion":"eb890af684cf84e20044e9a9771b96302e7b8dec", + // "data":{ + // "current_page":1, + // "data":[ + // { + // "trade_type":"sell", + // "fee":"0.0610578086", + // "id":1483372, + // "created_at":1606581578368, + // "currency":"usdt", + // "coin":"btc", + // "price":"17667.1900000000", + // "quantity":"0.0017280000", + // "amount":"30.5289043200", + // }, + // ], + // "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1", + // "from":1, + // "last_page":1, + // "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1", + // "next_page_url":null, + // "path":"http:\/\/api.vcc.exchange\/v3\/orders\/trades", + // "per_page":10, + // "prev_page_url":null, + // "to":2, + // "total":2, + // }, + // } + // + let data = this.safeValue (response, 'data', {}); + data = this.safeValue (data, 'data', []); + return this.parseTrades (data, market, since, limit); + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetDepositAddress (this.extend (request, params)); + // + // { + // "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff", + // "data":{ + // "status": "REQUESTED", + // "blockchain_address": "", + // "currency": "btc" + // } + // } + // + // { + // "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff", + // "data":{ + // "status": "PROVISIONED", + // "blockchain_address": "rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy", + // "blockchain_tag": "920396135", + // "currency": "xrp" + // } + // } + // + const data = this.safeValue (response, 'data'); + const status = this.safeString (data, 'status'); + if (status === 'REQUESTED') { + throw new AddressPending (this.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress one more time later to retrieve the generated address'); + } + const address = this.safeString (data, 'blockchain_address'); + this.checkAddress (address); + const tag = this.safeString (data, 'blockchain_tag'); + const currencyId = this.safeString (data, 'currency'); + return { + 'currency': this.safeCurrencyCode (currencyId), + 'address': address, + 'tag': tag, + 'info': data, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.version + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + if (api === 'private') { + this.checkRequiredCredentials (); + const timestamp = this.milliseconds ().toString (); + if (method !== 'GET') { + body = this.json (query); + } + const auth = method + ' ' + url; + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256'); + headers = { + 'Authorization': 'Bearer ' + this.apiKey, + 'Content-Type': 'application/json', + 'timestamp': timestamp, + 'signature': signature, + }; + } + url = this.urls['api'][api] + '/' + url; + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"message":"Insufficient balance."} + // {"message":"Unauthenticated."} // wrong api key + // {"message":"The given data was invalid.","errors":{"signature":["HMAC signature is invalid"]}} + // {"code":504,"message":"Gateway Timeout","description":""} + // {"code":429,"message":"Too many requests","description":"Too many requests"} + // + const message = this.safeString (response, 'message'); + if (message !== undefined) { + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/wavesexchange.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/wavesexchange.js new file mode 100644 index 000000000..976435981 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/wavesexchange.js @@ -0,0 +1,1666 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, AccountSuspended, ExchangeError, DuplicateOrderId, OrderNotFound, BadSymbol, ExchangeNotAvailable, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class wavesexchange extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'wavesexchange', + 'name': 'Waves.Exchange', + 'countries': ['CH'], // Switzerland + 'rateLimit': 500, + 'certified': true, + 'pro': false, + 'has': { + 'cancelOrder': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '3h': '3h', + '4h': '4h', + '6h': '6h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg', + 'api': { + 'matcher': 'http://matcher.waves.exchange', + 'node': 'https://nodes.waves.exchange', + 'public': 'https://api.wavesplatform.com/v0', + 'private': 'https://api.waves.exchange/v1', + 'forward': 'https://waves.exchange/api/v1/forward/matcher', + 'market': 'https://marketdata.wavesplatform.com/api/v1', + }, + 'doc': 'https://docs.waves.exchange', + 'www': 'https://waves.exchange', + }, + 'api': { + 'matcher': { + 'get': [ + 'matcher', + 'matcher/settings', + 'matcher/settings/rates', + 'matcher/balance/reserved/{publicKey}', + 'matcher/debug/allSnashotOffsets', + 'matcher/debug/currentOffset', + 'matcher/debug/lastOffset', + 'matcher/debug/oldestSnapshotOffset', + 'matcher/orderbook', + 'matcher/orderbook/{amountAsset}/{priceAsset}', + 'matcher/orderbook/{baseId}/{quoteId}/publicKey/{publicKey}', + 'matcher/orderbook/{baseId}/{quoteId}/{orderId}', + 'matcher/orderbook/{baseId}/{quoteId}/info', + 'matcher/orderbook/{baseId}/{quoteId}/status', + 'matcher/orderbook/{baseId}/{quoteId}/tradeableBalance/{address}', + 'matcher/orderbook/{publicKey}', + 'matcher/orderbook/{publicKey}/{orderId}', + 'matcher/orders/{address}', + 'matcher/orders/{address}/{orderId}', + 'matcher/transactions/{orderId}', + ], + 'post': [ + 'matcher/orderbook', + 'matcher/orderbook/market', + 'matcher/orderbook/cancel', + 'matcher/orderbook/{baseId}/{quoteId}/cancel', + 'matcher/debug/saveSnapshots', + 'matcher/orders/{address}/cancel', + 'matcher/orders/cancel/{orderId}', + ], + 'delete': [ + 'matcher/orderbook/{baseId}/{quoteId}', + 'matcher/settings/rates/{assetId}', + ], + 'put': [ + 'matcher/settings/rates/{assetId}', + ], + }, + 'node': { + 'get': [ + 'addresses', + 'addresses/balance/{address}', + 'addresses/balance/{address}/{confirmations}', + 'addresses/balance/details/{address}', + 'addresses/data/{address}', + 'addresses/data/{address}/{key}', + 'addresses/effectiveBalance/{address}', + 'addresses/effectiveBalance/{address}/{confirmations}', + 'addresses/publicKey/{publicKey}', + 'addresses/scriptInfo/{address}', + 'addresses/scriptInfo/{address}/meta', + 'addresses/seed/{address}', + 'addresses/seq/{from}/{to}', + 'addresses/validate/{address}', + 'alias/by-address/{address}', + 'alias/by-alias/{alias}', + 'assets/{assetId}/distribution/{height}/{limit}', + 'assets/balance/{address}', + 'assets/balance/{address}/{assetId}', + 'assets/details/{assetId}', + 'assets/nft/{address}/limit/{limit}', + 'blockchain/rewards', + 'blockchain/rewards/height', + 'blocks/address/{address}/{from}/{to}/', + 'blocks/at/{height}', + 'blocks/delay/{signature}/{blockNum}', + 'blocks/first', + 'blocks/headers/last', + 'blocks/headers/seq/{from}/{to}', + 'blocks/height', + 'blocks/height/{signature}', + 'blocks/last', + 'blocks/seq/{from}/{to}', + 'blocks/signature/{signature}', + 'consensus/algo', + 'consensus/basetarget', + 'consensus/basetarget/{blockId}', + 'consensus/{generatingbalance}/address', + 'consensus/generationsignature', + 'consensus/generationsignature/{blockId}', + 'debug/balances/history/{address}', + 'debug/blocks/{howMany}', + 'debug/configInfo', + 'debug/historyInfo', + 'debug/info', + 'debug/minerInfo', + 'debug/portfolios/{address}', + 'debug/state', + 'debug/stateChanges/address/{address}', + 'debug/stateChanges/info/{id}', + 'debug/stateWaves/{height}', + 'leasing/active/{address}', + 'node/state', + 'node/version', + 'peers/all', + 'peers/blacklisted', + 'peers/connected', + 'peers/suspended', + 'transactions/address/{address}/limit/{limit}', + 'transactions/info/{id}', + 'transactions/status', + 'transactions/unconfirmed', + 'transactions/unconfirmed/info/{id}', + 'transactions/unconfirmed/size', + 'utils/seed', + 'utils/seed/{length}', + 'utils/time', + 'wallet/seed', + ], + 'post': [ + 'addresses', + 'addresses/data/{address}', + 'addresses/sign/{address}', + 'addresses/signText/{address}', + 'addresses/verify/{address}', + 'addresses/verifyText/{address}', + 'debug/blacklist', + 'debug/print', + 'debug/rollback', + 'debug/validate', + 'node/stop', + 'peers/clearblacklist', + 'peers/connect', + 'transactions/broadcast', + 'transactions/calculateFee', + 'tranasctions/sign', + 'transactions/sign/{signerAddress}', + 'tranasctions/status', + 'utils/hash/fast', + 'utils/hash/secure', + 'utils/script/compileCode', + 'utils/script/compileWithImports', + 'utils/script/decompile', + 'utils/script/estimate', + 'utils/sign/{privateKey}', + 'utils/transactionsSerialize', + ], + 'delete': [ + 'addresses/{address}', + 'debug/rollback-to/{signature}', + ], + }, + 'public': { + 'get': [ + 'pairs', + 'candles/{baseId}/{quoteId}', + 'transactions/exchange', + ], + }, + 'private': { + 'get': [ + 'deposit/addresses/{code}', + 'deposit/currencies', + 'withdraw/currencies', + 'withdraw/addresses/{currency}/{address}', + ], + 'post': [ + 'oauth2/token', + ], + }, + 'forward': { + 'get': [ + 'matcher/orders/{address}', // can't get the orders endpoint to work with the matcher api + 'matcher/orders/{address}/{orderId}', + ], + 'post': [ + 'matcher/orders/{wavesAddress}/cancel', + ], + }, + 'market': { + 'get': [ + 'tickers', + ], + }, + }, + 'options': { + 'allowedCandles': 1440, + 'accessToken': undefined, + 'matcherPublicKey': undefined, + 'quotes': undefined, + 'createOrderDefaultExpiry': 2419200000, // 60 * 60 * 24 * 28 * 1000 + 'wavesAddress': undefined, + 'withdrawFeeUSDN': 7420, + 'withdrawFeeWAVES': 100000, + 'wavesPrecision': 8, + }, + 'requiresEddsa': true, + 'exceptions': { + '3147270': InsufficientFunds, // https://github.com/wavesplatform/matcher/wiki/List-of-all-errors + '112': InsufficientFunds, + '4': ExchangeError, + '13': ExchangeNotAvailable, + '14': ExchangeNotAvailable, + '3145733': AccountSuspended, + '3148040': DuplicateOrderId, + '3148801': AuthenticationError, + '9440512': AuthenticationError, + '9440771': BadSymbol, + '9441026': InvalidOrder, + '9441282': InvalidOrder, + '9441286': InvalidOrder, + '9441295': InvalidOrder, + '9441540': InvalidOrder, + '9441542': InvalidOrder, + '106954752': AuthenticationError, + '106954769': AuthenticationError, + '106957828': AuthenticationError, + '106960131': AuthenticationError, + '106981137': AuthenticationError, + '9437193': OrderNotFound, + '1048577': BadRequest, + '1051904': AuthenticationError, + }, + }); + } + + async getQuotes () { + let quotes = this.safeValue (this.options, 'quotes'); + if (quotes) { + return quotes; + } else { + // currencies can have any name because you can create you own token + // as a result someone can create a fake token called BTC + // we use this mapping to determine the real tokens + // https://docs.waves.exchange/en/waves-matcher/matcher-api#asset-pair + const response = await this.matcherGetMatcherSettings (); + // { + // "orderVersions": [ + // 1, + // 2, + // 3 + // ], + // "success": true, + // "matcherPublicKey": "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5", + // "orderFee": { + // "dynamic": { + // "baseFee": 300000, + // "rates": { + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ": 1.22639597, + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH": 0.00989643, + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk": 0.0395674, + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS": 0.00018814, + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8": 26.19721262, + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu": 0.00752978, + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p": 1.84575, + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H": 0.02330273, + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy": 0.00721412, + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3": 0.02659103, + // "WAVES": 1, + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa": 0.03433583 + // } + // } + // }, + // "networkByte": 87, + // "matcherVersion": "2.1.3.5", + // "status": "SimpleResponse", + // "priceAssets": [ + // "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck", + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ", + // "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU", + // "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN", + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", + // "WAVES", + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu", + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy", + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH", + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H", + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3", + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa", + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" + // ] + // } + quotes = {}; + const priceAssets = this.safeValue (response, 'priceAssets'); + for (let i = 0; i < priceAssets.length; i++) { + quotes[priceAssets[i]] = true; + } + this.options['quotes'] = quotes; + return quotes; + } + } + + async fetchMarkets (params = {}) { + const response = await this.marketGetTickers (); + const result = []; + for (let i = 0; i < response.length; i++) { + const entry = response[i]; + const baseId = this.safeString (entry, 'amountAssetID'); + const quoteId = this.safeString (entry, 'priceAssetID'); + const id = baseId + '/' + quoteId; + const marketId = this.safeString (entry, 'symbol'); + const [ base, quote ] = marketId.split ('/'); + const symbol = this.safeCurrencyCode (base) + '/' + this.safeCurrencyCode (quote); + const precision = { + 'amount': this.safeInteger (entry, 'amountAssetDecimals'), + 'price': this.safeInteger (entry, 'priceAssetDecimals'), + }; + result.push ({ + 'symbol': symbol, + 'id': id, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': entry, + 'precision': precision, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = this.extend ({ + 'amountAsset': market['baseId'], + 'priceAsset': market['quoteId'], + }, params); + const response = await this.matcherGetMatcherOrderbookAmountAssetPriceAsset (request); + const timestamp = this.safeInteger (response, 'timestamp'); + const bids = this.parseOrderBookSide (this.safeValue (response, 'bids'), market, limit); + const asks = this.parseOrderBookSide (this.safeValue (response, 'asks'), market, limit); + return { + 'bids': bids, + 'asks': asks, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'nonce': undefined, + }; + } + + parseOrderBookSide (bookSide, market = undefined, limit = undefined) { + const precision = market['precision']; + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const amountPrecision = Math.pow (10, precision['amount']); + const difference = precision['amount'] - precision['price']; + const pricePrecision = Math.pow (10, wavesPrecision - difference); + const result = []; + for (let i = 0; i < bookSide.length; i++) { + const entry = bookSide[i]; + const price = this.safeInteger (entry, 'price', 0) / pricePrecision; + const amount = this.safeInteger (entry, 'amount', 0) / amountPrecision; + if ((limit !== undefined) && (i > limit)) { + break; + } + result.push ([price, amount]); + } + return result; + } + + checkRequiredKeys () { + if (this.apiKey === undefined) { + throw new AuthenticationError (this.id + ' requires apiKey credential'); + } + if (this.secret === undefined) { + throw new AuthenticationError (this.id + ' requires secret credential'); + } + let apiKeyBytes = undefined; + let secretKeyBytes = undefined; + try { + apiKeyBytes = this.base58ToBinary (this.apiKey); + } catch (e) { + throw new AuthenticationError (this.id + ' apiKey must be a base58 encoded public key'); + } + try { + secretKeyBytes = this.base58ToBinary (this.secret); + } catch (e) { + throw new AuthenticationError (this.id + ' secret must be a base58 encoded private key'); + } + const hexApiKeyBytes = this.binaryToBase16 (apiKeyBytes); + const hexSecretKeyBytes = this.binaryToBase16 (secretKeyBytes); + if (hexApiKeyBytes.length !== 64) { + throw new AuthenticationError (this.id + ' apiKey must be a base58 encoded public key'); + } + if (hexSecretKeyBytes.length !== 64) { + throw new AuthenticationError (this.id + ' secret must be a base58 encoded private key'); + } + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + const isCancelOrder = path === 'matcher/orders/{wavesAddress}/cancel'; + path = this.implodeParams (path, params); + let url = this.urls['api'][api] + '/' + path; + let queryString = this.urlencode (query); + if ((api === 'private') || (api === 'forward')) { + headers = { + 'Accept': 'application/json', + }; + const accessToken = this.safeString (this.options, 'accessToken'); + if (accessToken) { + headers['Authorization'] = 'Bearer ' + accessToken; + } + if (method === 'POST') { + headers['content-type'] = 'application/json'; + } else { + headers['content-type'] = 'application/x-www-form-urlencoded'; + } + if (isCancelOrder) { + body = this.json ([query['orderId']]); + queryString = ''; + } + if (queryString.length > 0) { + url += '?' + queryString; + } + } else if (api === 'matcher') { + if (method === 'POST') { + headers = { + 'content-type': 'application/json', + }; + body = this.json (query); + } else { + headers = query; + } + } else { + if (method === 'POST') { + headers = { + 'content-type': 'application/json', + }; + body = this.json (query); + } else { + headers = { + 'content-type': 'application/x-www-form-urlencoded', + }; + if (queryString.length > 0) { + url += '?' + queryString; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + async getAccessToken () { + if (!this.safeString (this.options, 'accessToken')) { + const prefix = 'ffffff01'; + const expiresDelta = 60 * 60 * 24 * 7; + let seconds = this.sum (this.seconds (), expiresDelta); + seconds = seconds.toString (); + const clientId = 'waves.exchange'; + const message = 'W:' + clientId + ':' + seconds; + const messageHex = this.binaryToBase16 (this.stringToBinary (this.encode (message))); + const payload = prefix + messageHex; + const hexKey = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (payload, hexKey, 'ed25519'); + const request = { + 'grant_type': 'password', + 'scope': 'general', + 'username': this.apiKey, + 'password': seconds + ':' + signature, + 'client_id': clientId, + }; + const response = await this.privatePostOauth2Token (request); + // { access_token: 'eyJhbGciOXJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5MTk3NTA1NywiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkOWI5IiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.B-XwexBnUAzbWknVN68RKT0ZP5w6Qk1SKJ8usL3OIwDEzCUUX9PjW-5TQHmiCRcA4oft8lqXEiCwEoNfsblCo_jTpRo518a1vZkIbHQk0-13Dm1K5ewGxfxAwBk0g49odcbKdjl64TN1yM_PO1VtLVuiTeZP-XF-S42Uj-7fcO-r7AulyQLuTE0uo-Qdep8HDCk47rduZwtJOmhFbCCnSgnLYvKWy3CVTeldsR77qxUY-vy8q9McqeP7Id-_MWnsob8vWXpkeJxaEsw1Fke1dxApJaJam09VU8EB3ZJWpkT7V8PdafIrQGeexx3jhKKxo7rRb4hDV8kfpVoCgkvFan', + // token_type: 'bearer', + // refresh_token: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwiYXRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkXWI5IiwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5Mzk2MjI1OCwiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiM2MzZWRlMTktNjI5My00MTNlLWJmMWUtZTRlZDZlYzUzZTgzIiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.gD1Qj0jfqayfZpBvNY0t3ccMyK5hdbT7dY-_5L6LxwV0Knan4ndEtvygxlTOczmJUKtnA4T1r5GBFgNMZTvtViKZIbqZNysEg2OY8UxwDaF4VPeGJLg_QXEnn8wBeBQdyMafh9UQdwD2ci7x-saM4tOAGmncAygfTDxy80201gwDhfAkAGerb9kL00oWzSJScldxu--pNLDBUEHZt52MSEel10HGrzvZkkvvSh67vcQo5TOGb5KG6nh65UdJCwr41AVz4fbQPP-N2Nkxqy0TE_bqVzZxExXgvcS8TS0Z82T3ijJa_ct7B9wblpylBnvmyj3VycUzufD6uy8MUGq32D', + // expires_in: 604798, + // scope: 'general' } + this.options['accessToken'] = this.safeString (response, 'access_token'); + return this.options['accessToken']; + } + } + + parseTicker (ticker, market = undefined) { + // + // { + // "__type":"pair", + // "data":{ + // "firstPrice":0.00012512, + // "lastPrice":0.00012441, + // "low":0.00012167, + // "high":0.00012768, + // "weightedAveragePrice":0.000124710697407246, + // "volume":209554.26356614, + // "quoteVolume":26.1336583539951, + // "volumeWaves":209554.26356614, + // "txsCount":6655 + // }, + // "amountAsset":"WAVES", + // "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" + // } + // + const timestamp = undefined; + const baseId = this.safeString (ticker, 'amountAsset'); + const quoteId = this.safeString (ticker, 'priceAsset'); + let symbol = undefined; + if ((baseId !== undefined) && (quoteId !== undefined)) { + const marketId = baseId + '/' + quoteId; + if (marketId in this.markets_by_id) { + market = this.markets_by_id[marketId]; + } else { + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + } + if ((symbol === undefined) && (market !== undefined)) { + symbol = market['symbol']; + } + const data = this.safeValue (ticker, 'data', {}); + const last = this.safeFloat (data, 'lastPrice'); + const low = this.safeFloat (data, 'low'); + const high = this.safeFloat (data, 'high'); + const vwap = this.safeFloat (data, 'weightedAveragePrice'); + const baseVolume = this.safeFloat (data, 'volume'); + const quoteVolume = this.safeFloat (data, 'quoteVolume'); + const open = this.safeValue (data, 'firstPrice'); + let change = undefined; + let average = undefined; + let percentage = undefined; + if (last !== undefined && open !== undefined) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': high, + 'low': low, + 'bid': undefined, + 'bidVolume': undefined, + 'ask': undefined, + 'askVolume': undefined, + 'vwap': vwap, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pairs': market['id'], + }; + const response = await this.publicGetPairs (this.extend (request, params)); + // + // { + // "__type":"list", + // "data":[ + // { + // "__type":"pair", + // "data":{ + // "firstPrice":0.00012512, + // "lastPrice":0.00012441, + // "low":0.00012167, + // "high":0.00012768, + // "weightedAveragePrice":0.000124710697407246, + // "volume":209554.26356614, + // "quoteVolume":26.1336583539951, + // "volumeWaves":209554.26356614, + // "txsCount":6655 + // }, + // "amountAsset":"WAVES", + // "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const ticker = this.safeValue (data, 0, {}); + return this.parseTicker (ticker, market); + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'baseId': market['baseId'], + 'quoteId': market['quoteId'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + request['timeStart'] = since.toString (); + } else { + const allowedCandles = this.safeInteger (this.options, 'allowedCandles', 1440); + const timeframeUnix = this.parseTimeframe (timeframe) * 1000; + const currentTime = Math.floor (this.milliseconds () / timeframeUnix) * timeframeUnix; + const delta = (allowedCandles - 1) * timeframeUnix; + const timeStart = currentTime - delta; + request['timeStart'] = timeStart.toString (); + } + const response = await this.publicGetCandlesBaseIdQuoteId (this.extend (request, params)); + // + // { + // "__type": "list", + // "data": [ + // { + // "__type": "candle", + // "data": { + // "time": "2020-06-09T14:47:00.000Z", + // "open": 0.0250385, + // "close": 0.0250385, + // "high": 0.0250385, + // "low": 0.0250385, + // "volume": 0.01033012, + // "quoteVolume": 0.00025865, + // "weightedAveragePrice": 0.0250385, + // "maxHeight": 2099399, + // "txsCount": 5, + // "timeClose": "2020-06-09T14:47:59.999Z" + // } + // } + // ] + // } + // + const data = this.safeValue (response, 'data', []); + const result = this.parseOHLCVs (data, market, timeframe, since, limit); + let lastClose = undefined; + const length = result.length; + for (let i = 0; i < result.length; i++) { + const j = length - i - 1; + const entry = result[j]; + const open = entry[1]; + if (open === undefined) { + entry[1] = lastClose; + entry[2] = lastClose; + entry[3] = lastClose; + entry[4] = lastClose; + result[j] = entry; + } + lastClose = entry[4]; + } + return result; + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // __type: 'candle', + // data: { + // time: '2020-06-05T20:46:00.000Z', + // open: 240.573975, + // close: 240.573975, + // high: 240.573975, + // low: 240.573975, + // volume: 0.01278413, + // quoteVolume: 3.075528, + // weightedAveragePrice: 240.573975, + // maxHeight: 2093895, + // txsCount: 5, + // timeClose: '2020-06-05T20:46:59.999Z' + // } + // } + // + const data = this.safeValue (ohlcv, 'data', {}); + return [ + this.parse8601 (this.safeString (data, 'time')), + this.safeFloat (data, 'open'), + this.safeFloat (data, 'high'), + this.safeFloat (data, 'low'), + this.safeFloat (data, 'close'), + this.safeFloat (data, 'volume', 0), + ]; + } + + async fetchDepositAddress (code, params = {}) { + await this.getAccessToken (); + const supportedCurrencies = await this.privateGetDepositCurrencies (); + const currencies = {}; + const items = this.safeValue (supportedCurrencies, 'items', []); + for (let i = 0; i < items.length; i++) { + const entry = items[i]; + const currencyCode = this.safeString (entry, 'id'); + currencies[currencyCode] = true; + } + if (!(code in currencies)) { + const codes = Object.keys (currencies); + throw new ExchangeError (this.id + ' fetch ' + code + ' deposit address not supported. Currency code must be one of ' + codes.toString ()); + } + const request = this.extend ({ + 'code': code, + }, params); + const response = await this.privateGetDepositAddressesCode (request); + // { + // "type": "deposit_addresses", + // "currency": { + // "type": "deposit_currency", + // "id": "ERGO", + // "waves_asset_id": "5dJj4Hn9t2Ve3tRpNGirUHy4yBK6qdJRAJYV21yPPuGz", + // "decimals": 9, + // "status": "active", + // "allowed_amount": { + // "min": 0.001, + // "max": 100000 + // }, + // "fees": { + // "flat": 0, + // "rate": 0 + // } + // }, + // "deposit_addresses": [ + // "9fRAAQjF8Yqg7qicQCL884zjimsRnuwsSavsM1rUdDaoG8mThku" + // ] + // } + const addresses = this.safeValue (response, 'deposit_addresses'); + const address = this.safeString (addresses, 0); + return { + 'address': address, + 'code': code, + 'tag': undefined, + 'info': response, + }; + } + + async getMatcherPublicKey () { + // this method returns a single string + const matcherPublicKey = this.safeString (this.options, 'matcherPublicKey'); + if (matcherPublicKey) { + return matcherPublicKey; + } else { + const response = await this.matcherGetMatcher (); + // remove trailing quotes from string response + this.options['matcherPublicKey'] = response.slice (1, response.length - 1); + return this.options['matcherPublicKey']; + } + } + + getAssetBytes (currencyId) { + if (currencyId === 'WAVES') { + return this.numberToBE (0, 1); + } else { + return this.binaryConcat (this.numberToBE (1, 1), this.base58ToBinary (currencyId)); + } + } + + getAssetId (currencyId) { + if (currencyId === 'WAVES') { + return ''; + } + return currencyId; + } + + priceToPrecision (symbol, price) { + const market = this.markets[symbol]; + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const difference = market['precision']['amount'] - market['precision']['price']; + return parseInt (parseFloat (this.toWei (price, wavesPrecision - difference))); + } + + amountToPrecision (symbol, amount) { + return parseInt (parseFloat (this.toWei (amount, this.markets[symbol]['precision']['amount']))); + } + + currencyToPrecision (currency, amount) { + return parseInt (parseFloat (this.toWei (amount, this.currencies[currency]['precision']))); + } + + currencyFromPrecision (currency, amount) { + return this.fromWei (amount, this.currencies[currency]['precision']); + } + + priceFromPrecision (symbol, price) { + const market = this.markets[symbol]; + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const difference = market['precision']['amount'] - market['precision']['price']; + return this.fromWei (price, wavesPrecision - difference); + } + + getDefaultExpiry () { + const expiry = this.safeInteger (this.options, 'createOrderDefaultExpiry'); + if (expiry) { + return expiry; + } else { + this.options['createOrderDefaultExpiry'] = 60 * 60 * 24 * 28 * 1000; + return this.options['createOrderDefaultExpiry']; + } + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + await this.loadMarkets (); + const market = this.market (symbol); + const matcherPublicKey = await this.getMatcherPublicKey (); + const amountAsset = this.getAssetId (market['baseId']); + const priceAsset = this.getAssetId (market['quoteId']); + amount = this.amountToPrecision (symbol, amount); + price = this.priceToPrecision (symbol, price); + const orderType = (side === 'buy') ? 0 : 1; + const timestamp = this.milliseconds (); + const expiration = this.sum (timestamp, this.getDefaultExpiry ()); + const settings = await this.matcherGetMatcherSettings (); + // { + // "orderVersions": [ + // 1, + // 2, + // 3 + // ], + // "success": true, + // "matcherPublicKey": "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5", + // "orderFee": { + // "dynamic": { + // "baseFee": 300000, + // "rates": { + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ": 1.0257813, + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH": 0.01268146, + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk": 0.05232404, + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS": 0.00023985, + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8": 19.5967716, + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu": 0.00937073, + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p": 2.19825, + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H": 0.03180264, + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy": 0.00996631, + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3": 0.03254476, + // "WAVES": 1, + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa": 0.03703704 + // } + // } + // }, + // "networkByte": 87, + // "matcherVersion": "2.1.4.8", + // "status": "SimpleResponse", + // "priceAssets": [ + // "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck", + // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ", + // "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU", + // "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN", + // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", + // "WAVES", + // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu", + // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy", + // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH", + // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", + // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H", + // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3", + // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa", + // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" + // ] + // } + const orderFee = this.safeValue (settings, 'orderFee'); + const dynamic = this.safeValue (orderFee, 'dynamic'); + const baseMatcherFee = this.safeInteger (dynamic, 'baseFee'); + const wavesMatcherFee = this.currencyFromPrecision ('WAVES', baseMatcherFee); + const rates = this.safeValue (dynamic, 'rates'); + // choose sponsored assets from the list of priceAssets above + const priceAssets = Object.keys (rates); + let matcherFeeAssetId = undefined; + let matcherFee = undefined; + if ('feeAssetId' in params) { + matcherFeeAssetId = params['feeAssetId']; + } else if ('feeAssetId' in this.options) { + matcherFeeAssetId = this.options['feeAssetId']; + } else { + const balances = await this.fetchBalance (); + if (balances['WAVES']['free'] > wavesMatcherFee) { + matcherFeeAssetId = 'WAVES'; + matcherFee = baseMatcherFee; + } else { + for (let i = 0; i < priceAssets.length; i++) { + const assetId = priceAssets[i]; + const code = this.safeCurrencyCode (assetId); + const balance = this.safeValue (this.safeValue (balances, code, {}), 'free'); + const assetFee = rates[assetId] * wavesMatcherFee; + if ((balance !== undefined) && (balance > assetFee)) { + matcherFeeAssetId = assetId; + break; + } + } + } + } + if (matcherFeeAssetId === undefined) { + throw InsufficientFunds (this.id + ' not enough funds to cover the fee, specify feeAssetId in params or options, or buy some WAVES'); + } + if (matcherFee === undefined) { + const wavesPrecision = this.safeInteger (this.options, 'wavesPrecision', 8); + const rate = this.safeFloat (rates, matcherFeeAssetId); + const code = this.safeCurrencyCode (matcherFeeAssetId); + const currency = this.currency (code); + const newPrecison = Math.pow (10, wavesPrecision - currency['precision']); + matcherFee = Math.ceil (rate * baseMatcherFee / newPrecison); + } + const byteArray = [ + this.numberToBE (3, 1), + this.base58ToBinary (this.apiKey), + this.base58ToBinary (matcherPublicKey), + this.getAssetBytes (market['baseId']), + this.getAssetBytes (market['quoteId']), + this.numberToBE (orderType, 1), + this.numberToBE (price, 8), + this.numberToBE (amount, 8), + this.numberToBE (timestamp, 8), + this.numberToBE (expiration, 8), + this.numberToBE (matcherFee, 8), + this.getAssetBytes (matcherFeeAssetId), + ]; + const binary = this.binaryConcatArray (byteArray); + const signature = this.eddsa (this.binaryToBase16 (binary), this.binaryToBase16 (this.base58ToBinary (this.secret)), 'ed25519'); + const assetPair = { + 'amountAsset': amountAsset, + 'priceAsset': priceAsset, + }; + const body = { + 'senderPublicKey': this.apiKey, + 'matcherPublicKey': matcherPublicKey, + 'assetPair': assetPair, + 'orderType': side, + 'price': price, + 'amount': amount, + 'timestamp': timestamp, + 'expiration': expiration, + 'matcherFee': matcherFee, + 'signature': signature, + 'version': 3, + }; + if (matcherFeeAssetId !== 'WAVES') { + body['matcherFeeAssetId'] = matcherFeeAssetId; + } + const response = await this.matcherPostMatcherOrderbook (body); + // { success: true, + // message: + // { version: 3, + // id: 'Do7cDJMf2MJuFyorvxNNuzS42MXSGGEq1r1hGDn1PHiS', + // sender: '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH', + // senderPublicKey: 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: + // { amountAsset: null, + // priceAsset: '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' }, + // orderType: 'sell', + // amount: 1, + // price: 100000000, + // timestamp: 1591593117995, + // expiration: 1594012317995, + // matcherFee: 300000, + // matcherFeeAssetId: null, + // signature: '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq', + // proofs: + // [ '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq' ] }, + // status: 'OrderAccepted' } + const value = this.safeValue (response, 'message'); + return this.parseOrder (value, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + await this.getAccessToken (); + const wavesAddress = await this.getWavesAddress (); + const response = await this.forwardPostMatcherOrdersWavesAddressCancel ({ + 'wavesAddress': wavesAddress, + 'orderId': id, + }); + // { + // "success":true, + // "message":[[{"orderId":"EBpJeGM36KKFz5gTJAUKDBm89V8wqxKipSFBdU35AN3c","success":true,"status":"OrderCanceled"}]], + // "status":"BatchCancelCompleted" + // } + const message = this.safeValue (response, 'message'); + const firstMessage = this.safeValue (message, 0); + const firstOrder = this.safeValue (firstMessage, 0); + const returnedId = this.safeString (firstOrder, 'orderId'); + return { + 'info': response, + 'id': returnedId, + 'clientOrderId': undefined, + 'timestamp': undefined, + 'datetime': undefined, + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': undefined, + 'side': undefined, + 'price': undefined, + 'amount': undefined, + 'cost': undefined, + 'average': undefined, + 'filled': undefined, + 'remaining': undefined, + 'status': undefined, + 'fee': undefined, + 'trades': undefined, + }; + } + + async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrders requires symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const timestamp = this.milliseconds (); + const byteArray = [ + this.base58ToBinary (this.apiKey), + this.numberToBE (timestamp, 8), + ]; + const binary = this.binaryConcatArray (byteArray); + const hexSecret = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (this.binaryToBase16 (binary), hexSecret, 'ed25519'); + const request = { + 'Accept': 'application/json', + 'Timestamp': timestamp.toString (), + 'Signature': signature, + 'publicKey': this.apiKey, + 'baseId': market['baseId'], + 'quoteId': market['quoteId'], + }; + const response = await this.matcherGetMatcherOrderbookBaseIdQuoteIdPublicKeyPublicKey (this.extend (request, params)); + // [ { id: '3KicDeWayY2mdrRoYdCkP3gUAoUZUNT1AA6GAtWuPLfa', + // type: 'sell', + // orderType: 'limit', + // amount: 1, + // fee: 300000, + // price: 100000000, + // timestamp: 1591651254076, + // filled: 0, + // filledFee: 0, + // feeAsset: 'WAVES', + // status: 'Accepted', + // assetPair: + // { amountAsset: null, + // priceAsset: '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS' }, + // avgWeighedPrice: 0 }, ... ] + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.getAccessToken (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const address = await this.getWavesAddress (); + const request = { + 'address': address, + 'activeOnly': true, + }; + const response = await this.forwardGetMatcherOrdersAddress (request); + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.getAccessToken (); + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + } + const address = await this.getWavesAddress (); + const request = { + 'address': address, + 'closedOnly': true, + }; + const response = await this.forwardGetMatcherOrdersAddress (request); + // [ + // { + // "id": "9aXcxvXai73jbAm7tQNnqaQ2PwUjdmWuyjvRTKAHsw4f", + // "type": "buy", + // "orderType": "limit", + // "amount": 23738330, + // "fee": 300000, + // "price": 3828348334, + // "timestamp": 1591926905636, + // "filled": 23738330, + // "filledFee": 300000, + // "feeAsset": "WAVES", + // "status": "Filled", + // "assetPair": { + // "amountAsset": "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", + // "priceAsset": null + // }, + // "avgWeighedPrice": 3828348334 + // }, ... + // ] + return this.parseOrders (response, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'Cancelled': 'canceled', + 'Accepted': 'open', + 'Filled': 'closed', + 'PartiallyFilled': 'open', + }; + return this.safeString (statuses, status, status); + } + + getSymbolFromAssetPair (assetPair) { + // a blank string or null can indicate WAVES + const baseId = this.safeString (assetPair, 'amountAsset', 'WAVES'); + const quoteId = this.safeString (assetPair, 'priceAsset', 'WAVES'); + return this.safeCurrencyCode (baseId) + '/' + this.safeCurrencyCode (quoteId); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // version: 3, + // id: 'BshyeHXDfJmTnjTdBYt371jD4yWaT3JTP6KpjpsiZepS', + // sender: '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH', + // senderPublicKey: 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: { + // amountAsset: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu', + // priceAsset: 'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p' + // }, + // orderType: 'buy', + // amount: 10000, + // price: 400000000, + // timestamp: 1599848586891, + // expiration: 1602267786891, + // matcherFee: 3008, + // matcherFeeAssetId: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu', + // signature: '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy', + // proofs: [ + // '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy' + // ] + // } + // + // fetchClosedOrders + // + // { + // id: '81D9uKk2NfmZzfG7uaJsDtxqWFbJXZmjYvrL88h15fk8', + // type: 'buy', + // orderType: 'limit', + // amount: 30000000000, + // filled: 0, + // price: 1000000, + // fee: 300000, + // filledFee: 0, + // feeAsset: 'WAVES', + // timestamp: 1594303779322, + // status: 'Cancelled', + // assetPair: { + // amountAsset: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu', + // priceAsset: 'WAVES' + // }, + // avgWeighedPrice: 0, + // version: 3 + // } + // + const timestamp = this.safeInteger (order, 'timestamp'); + const side = this.safeString2 (order, 'type', 'orderType'); + let type = 'limit'; + if ('type' in order) { + // fetchOrders + type = this.safeString (order, 'orderType', type); + } + const id = this.safeString (order, 'id'); + let filled = this.safeString (order, 'filled'); + let price = this.safeString (order, 'price'); + let amount = this.safeString (order, 'amount'); + const assetPair = this.safeValue (order, 'assetPair'); + let symbol = undefined; + if (assetPair !== undefined) { + symbol = this.getSymbolFromAssetPair (assetPair); + } else if (market !== undefined) { + symbol = market['symbol']; + } + const amountCurrency = this.safeCurrencyCode (this.safeString (assetPair, 'amountAsset', 'WAVES')); + price = this.priceFromPrecision (symbol, price); + amount = this.currencyFromPrecision (amountCurrency, amount); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + filled = this.currencyFromPrecision (amountCurrency, filled); + let remaining = undefined; + if ((filled !== undefined) && (amount !== undefined)) { + remaining = amount - filled; + } + const average = this.priceFromPrecision (symbol, this.safeString (order, 'avgWeighedPrice')); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + let fee = undefined; + if ('type' in order) { + const currency = this.safeCurrencyCode (this.safeString (order, 'feeAsset')); + fee = { + 'currency': currency, + 'fee': this.currencyFromPrecision (currency, this.safeInteger (order, 'filledFee')), + }; + } else { + const currency = this.safeCurrencyCode (this.safeString (order, 'matcherFeeAssetId', 'WAVES')); + fee = { + 'currency': currency, + 'fee': this.currencyFromPrecision (currency, this.safeInteger (order, 'matcherFee')), + }; + } + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': average, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': fee, + 'trades': undefined, + }; + } + + async getWavesAddress () { + const cachedAddreess = this.safeString (this.options, 'wavesAddress'); + if (cachedAddreess === undefined) { + const request = { + 'publicKey': this.apiKey, + }; + const response = await this.nodeGetAddressesPublicKeyPublicKey (request); + this.options['wavesAddress'] = this.safeString (response, 'address'); + return this.options['wavesAddress']; + } else { + return cachedAddreess; + } + } + + async fetchBalance (params = {}) { + // makes a lot of different requests to get all the data + // in particular: + // fetchMarkets, getWavesAddress, + // getTotalBalance (doesn't include waves), getReservedBalance (doesn't include waves) + // getReservedBalance (includes WAVES) + // I couldn't find another way to get all the data + this.checkRequiredDependencies (); + this.checkRequiredKeys (); + await this.loadMarkets (); + const wavesAddress = await this.getWavesAddress (); + const request = { + 'address': wavesAddress, + }; + const totalBalance = await this.nodeGetAssetsBalanceAddress (request); + // { + // "address": "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH", + // "balances": [ + // { + // "assetId": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "balance": 1177200, + // "reissuable": false, + // "minSponsoredAssetFee": 7420, + // "sponsorBalance": 47492147189709, + // "quantity": 999999999775381400, + // "issueTransaction": { + // "senderPublicKey": "BRnVwSVctnV8pge5vRpsJdWnkjWEJspFb6QvrmZvu3Ht", + // "quantity": 1000000000000000000, + // "fee": 100400000, + // "description": "Neutrino USD", + // "type": 3, + // "version": 2, + // "reissuable": false, + // "script": null, + // "sender": "3PC9BfRwJWWiw9AREE2B3eWzCks3CYtg4yo", + // "feeAssetId": null, + // "chainId": 87, + // "proofs": [ + // "3HNpbVkgP69NWSeb9hGYauiQDaXrRXh3tXFzNsGwsAAXnFrA29SYGbLtziW9JLpXEq7qW1uytv5Fnm5XTUMB2BxU" + // ], + // "assetId": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "decimals": 6, + // "name": "USD-N", + // "id": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + // "timestamp": 1574429393962 + // } + // } + // ] + // } + const balances = this.safeValue (totalBalance, 'balances'); + const result = {}; + for (let i = 0; i < balances.length; i++) { + const entry = balances[i]; + const issueTransaction = this.safeValue (entry, 'issueTransaction'); + const decimals = this.safeInteger (issueTransaction, 'decimals'); + const currencyId = this.safeString (entry, 'assetId'); + const balance = this.safeFloat (entry, 'balance'); + let code = undefined; + if (currencyId in this.currencies_by_id) { + code = this.safeCurrencyCode (currencyId); + result[code] = this.account (); + result[code]['total'] = this.fromWei (balance, decimals); + } + } + const timestamp = this.milliseconds (); + const byteArray = [ + this.base58ToBinary (this.apiKey), + this.numberToBE (timestamp, 8), + ]; + const binary = this.binaryConcatArray (byteArray); + const hexSecret = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (this.binaryToBase16 (binary), hexSecret, 'ed25519'); + const matcherRequest = { + 'publicKey': this.apiKey, + 'signature': signature, + 'timestamp': timestamp.toString (), + }; + const reservedBalance = await this.matcherGetMatcherBalanceReservedPublicKey (matcherRequest); + // { WAVES: 200300000 } + const reservedKeys = Object.keys (reservedBalance); + for (let i = 0; i < reservedKeys.length; i++) { + const currencyId = reservedKeys[i]; + const code = this.safeCurrencyCode (currencyId); + if (!(code in result)) { + result[code] = this.account (); + } + const amount = this.safeFloat (reservedBalance, currencyId); + result[code]['used'] = this.currencyFromPrecision (code, amount); + } + const wavesRequest = { + 'address': wavesAddress, + }; + const wavesTotal = await this.nodeGetAddressesBalanceAddress (wavesRequest); + // { + // "address": "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH", + // "confirmations": 0, + // "balance": 909085978 + // } + result['WAVES'] = this.safeValue (result, 'WAVES', {}); + result['WAVES']['total'] = this.currencyFromPrecision ('WAVES', this.safeFloat (wavesTotal, 'balance')); + const codes = Object.keys (result); + for (let i = 0; i < codes.length; i++) { + const code = codes[i]; + if (this.safeValue (result[code], 'used') === undefined) { + result[code]['used'] = 0.0; + } + } + return this.parseBalance (result); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const address = await this.getWavesAddress (); + const request = { + 'sender': address, + 'amountAsset': market['baseId'], + 'priceAsset': market['quoteId'], + }; + const response = await this.publicGetTransactionsExchange (request); + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'amountAsset': market['baseId'], + 'priceAsset': market['quoteId'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + if (since !== undefined) { + request['timeStart'] = since; + } + const response = await this.publicGetTransactionsExchange (request); + const data = this.safeValue (response, 'data'); + return this.parseTrades (data, market, since, limit); + } + + parseTrade (trade, market = undefined) { + // { __type: 'transaction', + // data: + // { id: 'HSdruioHqvYHeyn9hhyoHdRWPB2bFA8ujeCPZMK6992c', + // timestamp: '2020-06-09T19:34:51.897Z', + // height: 2099684, + // type: 7, + // version: 2, + // proofs: + // [ '26teDHERQgwjjHqEn4REcDotNG8M21xjou3X42XuDuCvrRkQo6aPyrswByH3UrkWG8v27ZAaVNzoxDg4teNcLtde' ], + // fee: 0.003, + // sender: '3PEjHv3JGjcWNpYEEkif2w8NXV4kbhnoGgu', + // senderPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // buyMatcherFee: 0.00299999, + // sellMatcherFee: 0.00299999, + // price: 0.00012003, + // amount: 60.80421562, + // order1: + // { id: 'CBRwP3ar4oMvvpUiGyfxc1syh41488SDi2GkrjuBDegv', + // senderPublicKey: 'DBXSHBz96NFsMu7xh4fi2eT9ZnyxefAHXsMxUayzgC6a', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: [Object], + // orderType: 'buy', + // price: 0.00012003, + // sender: '3PJfFRgVuJ47UY4ckb74EGzEBzkHXtmG1LA', + // amount: 60.80424773, + // timestamp: '2020-06-09T19:34:51.885Z', + // expiration: '2020-06-10T12:31:31.885Z', + // matcherFee: 0.003, + // signature: '4cA3ZAb3XAEEXaFG7caqpto5TRbpR5PkhZpxoNQZ9ZReNvjuJQs5a3THnumv7rcqmVUiVtuHAgk2f67ANcqtKyJ8', + // matcherFeeAssetId: null }, + // order2: + // { id: 'CHJSLQ6dfSPs6gu2mAegrMUcRiDEDqaj2GKfvptMjS3M', + // senderPublicKey: '3RUC4NGFZm9H8VJhSSjJyFLdiE42qNiUagDcZPwjgDf8', + // matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5', + // assetPair: [Object], + // orderType: 'sell', + // price: 0.00012003, + // sender: '3P9vKoQpMZtaSkHKpNh977YY9ZPzTuntLAq', + // amount: 60.80424773, + // timestamp: '2020-06-09T19:34:51.887Z', + // expiration: '2020-06-10T12:31:31.887Z', + // matcherFee: 0.003, + // signature: '3SFyrcqzou2ddZyNisnLYaGhLt5qRjKxH8Nw3s4T5U7CEKGX9DDo8dS27RgThPVGbYF1rYET1FwrWoQ2UFZ6SMTR', + // matcherFeeAssetId: null } } } + const data = this.safeValue (trade, 'data'); + const datetime = this.safeString (data, 'timestamp'); + const timestamp = this.parse8601 (datetime); + const id = this.safeString (data, 'id'); + const price = this.safeFloat (data, 'price'); + const amount = this.safeFloat (data, 'amount'); + const order1 = this.safeValue (data, 'order1'); + const order2 = this.safeValue (data, 'order2'); + let order = undefined; + // order2 arrived after order1 + if (this.safeString (order1, 'senderPublicKey') === this.apiKey) { + order = order1; + } else { + order = order2; + } + let symbol = undefined; + const assetPair = this.safeValue (order, 'assetPair'); + if (assetPair !== undefined) { + symbol = this.getSymbolFromAssetPair (assetPair); + } else if (market !== undefined) { + symbol = market['symbol']; + } + const side = this.safeString (order, 'orderType'); + const orderId = this.safeString (order, 'id'); + let cost = undefined; + if ((price !== undefined) && (amount !== undefined)) { + cost = price * amount; + } + const fee = { + 'cost': this.safeFloat (data, 'fee'), + 'currency': this.safeCurrencyCode (this.safeString (order, 'matcherFeeAssetId', 'WAVES')), + }; + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': datetime, + 'symbol': symbol, + 'id': id, + 'order': orderId, + 'type': undefined, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + const errorCode = this.safeString (response, 'error'); + const success = this.safeValue (response, 'success', true); + const Exception = this.safeValue (this.exceptions, errorCode); + if (Exception !== undefined) { + const message = this.safeString (response, 'message'); + throw new Exception (this.id + ' ' + message); + } + const message = this.safeString (response, 'message'); + if (message === 'Validation Error') { + throw new BadRequest (this.id + ' ' + body); + } + if (!success) { + throw new ExchangeError (this.id + ' ' + body); + } + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + // currently only works for BTC and WAVES + if (code !== 'WAVES') { + const supportedCurrencies = await this.privateGetWithdrawCurrencies (); + const currencies = {}; + const items = this.safeValue (supportedCurrencies, 'items', []); + for (let i = 0; i < items.length; i++) { + const entry = items[i]; + const currencyCode = this.safeString (entry, 'id'); + currencies[currencyCode] = true; + } + if (!(code in currencies)) { + const codes = Object.keys (currencies); + throw new ExchangeError (this.id + ' fetch ' + code + ' withdrawals are not supported. Currency code must be one of ' + codes.toString ()); + } + } + await this.loadMarkets (); + const hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + const set = {}; + for (let i = 0; i < hexChars.length; i++) { + const key = hexChars[i]; + set[key] = true; + } + let isErc20 = true; + const noPrefix = this.remove0xPrefix (address); + const lower = noPrefix.toLowerCase (); + for (let i = 0; i < lower.length; i++) { + const character = lower[i]; + if (!(character in set)) { + isErc20 = false; + break; + } + } + await this.getAccessToken (); + let proxyAddress = undefined; + if (code === 'WAVES' && !isErc20) { + proxyAddress = address; + } else { + const withdrawAddressRequest = { + 'address': address, + 'currency': code, + }; + const withdrawAddress = await this.privateGetWithdrawAddressesCurrencyAddress (withdrawAddressRequest); + const currency = this.safeValue (withdrawAddress, 'currency'); + const allowedAmount = this.safeValue (currency, 'allowed_amount'); + const minimum = this.safeFloat (allowedAmount, 'min'); + if (amount <= minimum) { + throw new BadRequest (this.id + ' ' + code + ' withdraw failed, amount ' + amount.toString () + ' must be greater than the minimum allowed amount of ' + minimum.toString ()); + } + // { + // "type": "withdrawal_addresses", + // "currency": { + // "type": "withdrawal_currency", + // "id": "BTC", + // "waves_asset_id": "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", + // "decimals": 8, + // "status": "active", + // "allowed_amount": { + // "min": 0.001, + // "max": 20 + // }, + // "fees": { + // "flat": 0.001, + // "rate": 0 + // } + // }, + // "proxy_addresses": [ + // "3P3qqmkiLwNHB7x1FeoE8bvkRtULwGpo9ga" + // ] + // } + const proxyAddresses = this.safeValue (withdrawAddress, 'proxy_addresses', []); + proxyAddress = this.safeString (proxyAddresses, 0); + } + const fee = this.safeInteger (this.options, 'withdrawFeeWAVES', 100000); // 0.001 WAVES + const feeAssetId = 'WAVES'; + const type = 4; // transfer + const version = 2; + const amountInteger = this.currencyToPrecision (code, amount); + const currency = this.currency (code); + const timestamp = this.milliseconds (); + const byteArray = [ + this.numberToBE (4, 1), + this.numberToBE (2, 1), + this.base58ToBinary (this.apiKey), + this.getAssetBytes (currency['id']), + this.getAssetBytes (feeAssetId), + this.numberToBE (timestamp, 8), + this.numberToBE (amountInteger, 8), + this.numberToBE (fee, 8), + this.base58ToBinary (proxyAddress), + this.numberToBE (0, 2), + ]; + const binary = this.binaryConcatArray (byteArray); + const hexSecret = this.binaryToBase16 (this.base58ToBinary (this.secret)); + const signature = this.eddsa (this.binaryToBase16 (binary), hexSecret, 'ed25519'); + const request = { + 'senderPublicKey': this.apiKey, + 'amount': amountInteger, + 'fee': fee, + 'type': type, + 'version': version, + 'attachment': '', + 'feeAssetId': this.getAssetId (feeAssetId), + 'proofs': [ + signature, + ], + 'assetId': this.getAssetId (currency['id']), + 'recipient': proxyAddress, + 'timestamp': timestamp, + 'signature': signature, + }; + return await this.nodePostTransactionsBroadcast (request); + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/whitebit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/whitebit.js new file mode 100644 index 000000000..9e4418141 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/whitebit.js @@ -0,0 +1,653 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeNotAvailable, ExchangeError, DDoSProtection, BadSymbol } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class whitebit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'whitebit', + 'name': 'WhiteBit', + 'version': 'v2', + 'countries': [ 'EE' ], + 'rateLimit': 500, + 'has': { + 'cancelOrder': false, + 'CORS': false, + 'createDepositAddress': false, + 'createLimitOrder': false, + 'createMarketOrder': false, + 'createOrder': false, + 'deposit': false, + 'editOrder': false, + 'fetchBalance': false, + 'fetchBidsAsks': false, + 'fetchCurrencies': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOrderBook': true, + 'fetchStatus': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTradingFees': true, + 'privateAPI': false, + 'publicAPI': true, + }, + 'timeframes': { + '1m': '1m', + '3m': '3m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '2h': '2h', + '4h': '4h', + '6h': '6h', + '8h': '8h', + '12h': '12h', + '1d': '1d', + '3d': '3d', + '1w': '1w', + '1M': '1M', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/66732963-8eb7dd00-ee66-11e9-849b-10d9282bb9e0.jpg', + 'api': { + 'web': 'https://whitebit.com/', + 'publicV2': 'https://whitebit.com/api/v2/public', + 'publicV1': 'https://whitebit.com/api/v1/public', + }, + 'www': 'https://www.whitebit.com', + 'doc': 'https://documenter.getpostman.com/view/7473075/Szzj8dgv?version=latest', + 'fees': 'https://whitebit.com/fee-schedule', + 'referral': 'https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963', + }, + 'api': { + 'web': { + 'get': [ + 'v1/healthcheck', + ], + }, + 'publicV1': { + 'get': [ + 'markets', + 'tickers', + 'ticker', + 'symbols', + 'depth/result', + 'history', + 'kline', + ], + }, + 'publicV2': { + 'get': [ + 'markets', + 'ticker', + 'assets', + 'fee', + 'depth/{market}', + 'trades/{market}', + ], + }, + }, + 'fees': { + 'trading': { + 'tierBased': false, + 'percentage': true, + 'taker': 0.001, + 'maker': 0.001, + }, + }, + 'options': { + 'fetchTradesMethod': 'fetchTradesV1', + }, + 'exceptions': { + 'exact': { + '503': ExchangeNotAvailable, // {"response":null,"status":503,"errors":{"message":[""]},"notification":null,"warning":null,"_token":null} + }, + 'broad': { + 'Market is not available': BadSymbol, // {"success":false,"message":{"market":["Market is not available"]},"result":[]} + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.publicV2GetMarkets (params); + // + // { + // "success":true, + // "message":"", + // "result":[ + // { + // "name":"BTC_USD", + // "moneyPrec":"2", + // "stock":"BTC", + // "money":"USD", + // "stockPrec":"6", + // "feePrec":"4", + // "minAmount":"0.001", + // "tradesEnabled":true, + // "minTotal":"0.001" + // } + // ] + // } + // + const markets = this.safeValue (response, 'result'); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'name'); + const baseId = this.safeString (market, 'stock'); + const quoteId = this.safeString (market, 'money'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const active = this.safeValue (market, 'tradesEnabled'); + const entry = { + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': active, + 'precision': { + 'amount': this.safeInteger (market, 'stockPrec'), + 'price': this.safeInteger (market, 'moneyPrec'), + }, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'minAmount'), + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': this.safeFloat (market, 'minTotal'), + 'max': undefined, + }, + }, + }; + result.push (entry); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicV2GetAssets (params); + // + // { + // "success":true, + // "message":"", + // "result":{ + // "BTC":{ + // "id":"4f37bc79-f612-4a63-9a81-d37f7f9ff622", + // "lastUpdateTimestamp":"2019-10-12T04:40:05.000Z", + // "name":"Bitcoin", + // "canWithdraw":true, + // "canDeposit":true, + // "minWithdrawal":"0.001", + // "maxWithdrawal":"0", + // "makerFee":"0.1", + // "takerFee":"0.1" + // } + // } + // } + // + const currencies = this.safeValue (response, 'result'); + const ids = Object.keys (currencies); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = currencies[id]; + // breaks down in Python due to utf8 encoding issues on the exchange side + // const name = this.safeString (currency, 'name'); + const canDeposit = this.safeValue (currency, 'canDeposit', true); + const canWithdraw = this.safeValue (currency, 'canWithdraw', true); + const active = canDeposit && canWithdraw; + const code = this.safeCurrencyCode (id); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, // the original payload + 'name': undefined, // see the comment above + 'active': active, + 'fee': undefined, + 'precision': undefined, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (currency, 'minWithdrawal'), + 'max': this.safeFloat (currency, 'maxWithdrawal'), + }, + }, + }; + } + return result; + } + + async fetchTradingFees (params = {}) { + const response = await this.publicV2GetFee (params); + const fees = this.safeValue (response, 'result'); + return { + 'maker': this.safeFloat (fees, 'makerFee'), + 'taker': this.safeFloat (fees, 'takerFee'), + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + const response = await this.publicV1GetTicker (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result": { + // "bid":"0.021979", + // "ask":"0.021996", + // "open":"0.02182", + // "high":"0.022039", + // "low":"0.02161", + // "last":"0.021987", + // "volume":"2810.267", + // "deal":"61.383565474", + // "change":"0.76", + // }, + // } + // + const ticker = this.safeValue (response, 'result', {}); + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker + // + // { + // "bid":"0.021979", + // "ask":"0.021996", + // "open":"0.02182", + // "high":"0.022039", + // "low":"0.02161", + // "last":"0.021987", + // "volume":"2810.267", + // "deal":"61.383565474", + // "change":"0.76", + // } + // + // fetchTickers v1 + // + // { + // "at":1571022144, + // "ticker": { + // "bid":"0.022024", + // "ask":"0.022042", + // "low":"0.02161", + // "high":"0.022062", + // "last":"0.022036", + // "vol":"2813.503", + // "deal":"61.457279261", + // "change":"0.95" + // } + // } + // + const timestamp = this.safeTimestamp (ticker, 'at', this.milliseconds ()); + ticker = this.safeValue (ticker, 'ticker', ticker); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + const percentage = this.safeFloat (ticker, 'change'); + let change = undefined; + if (percentage !== undefined) { + change = this.numberToString (percentage * 0.01); + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': this.safeFloat (ticker, 'open'), + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'volume'), + 'quoteVolume': this.safeFloat (ticker, 'deal'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicV1GetTickers (params); + // + // { + // "success":true, + // "message":"", + // "result": { + // "ETH_BTC": { + // "at":1571022144, + // "ticker": { + // "bid":"0.022024", + // "ask":"0.022042", + // "low":"0.02161", + // "high":"0.022062", + // "last":"0.022036", + // "vol":"2813.503", + // "deal":"61.457279261", + // "change":"0.95" + // } + // }, + // }, + // } + // + const data = this.safeValue (response, 'result'); + const marketIds = Object.keys (data); + const result = {}; + for (let i = 0; i < marketIds.length; i++) { + const marketId = marketIds[i]; + const market = this.safeMarket (marketId); + const ticker = this.parseTicker (data[marketId], market); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 50, maximum = 100 + } + const response = await this.publicV2GetDepthMarket (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result":{ + // "lastUpdateTimestamp":"2019-10-14T03:15:47.000Z", + // "asks":[ + // ["0.02204","2.03"], + // ["0.022041","2.492"], + // ["0.022042","2.254"], + // ], + // "bids":[ + // ["0.022018","2.327"], + // ["0.022017","1.336"], + // ["0.022015","2.089"], + // ], + // } + // } + // + const result = this.safeValue (response, 'result', {}); + const timestamp = this.parse8601 (this.safeString (result, 'lastUpdateTimestamp')); + return this.parseOrderBook (result, timestamp); + } + + async fetchTradesV1 (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'lastId': 1, // todo add since + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 50, maximum = 10000 + } + const response = await this.publicV1GetHistory (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result":[ + // { + // "id":11887426, + // "type":"buy", + // "time":1571023057.413769, + // "amount":"0.171", + // "price":"0.022052" + // } + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseTrades (result, market, since, limit); + } + + async fetchTradesV2 (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 50, maximum = 10000 + } + const response = await this.publicV2GetTradesMarket (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result": [ + // { + // "tradeId":11903347, + // "price":"0.022044", + // "volume":"0.029", + // "time":"2019-10-14T06:30:57.000Z", + // "isBuyerMaker":false + // }, + // ], + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseTrades (result, market, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + const method = this.safeString (this.options, 'fetchTradesMethod', 'fetchTradesV2'); + return await this[method] (symbol, since, limit, params); + } + + parseTrade (trade, market = undefined) { + // + // fetchTradesV1 + // + // { + // "id":11887426, + // "type":"buy", + // "time":1571023057.413769, + // "amount":"0.171", + // "price":"0.022052" + // } + // + // fetchTradesV2 + // + // { + // "tradeId":11903347, + // "price":"0.022044", + // "volume":"0.029", + // "time":"2019-10-14T06:30:57.000Z", + // "isBuyerMaker":false + // } + // + let timestamp = this.safeValue (trade, 'time'); + if (typeof timestamp === 'string') { + timestamp = this.parse8601 (timestamp); + } else { + timestamp = parseInt (timestamp * 1000); + } + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat2 (trade, 'amount', 'volume'); + const id = this.safeString2 (trade, 'id', 'tradeId'); + let side = this.safeString (trade, 'type'); + if (side === undefined) { + const isBuyerMaker = this.safeValue (trade, 'isBuyerMaker'); + side = isBuyerMaker ? 'buy' : 'sell'; + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + let cost = undefined; + if (amount !== undefined && price !== undefined) { + cost = amount * price; + } + return { + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'id': id, + 'order': undefined, + 'type': undefined, + 'takerOrMaker': undefined, + 'side': side, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'market': market['id'], + 'interval': this.timeframes[timeframe], + }; + if (since !== undefined) { + const maxLimit = 1440; + if (limit === undefined) { + limit = maxLimit; + } + limit = Math.min (limit, maxLimit); + const start = parseInt (since / 1000); + const duration = this.parseTimeframe (timeframe); + const end = this.sum (start, duration * limit); + request['start'] = start; + request['end'] = end; + } + if (limit !== undefined) { + request['limit'] = limit; // max 1440 + } + const response = await this.publicV1GetKline (this.extend (request, params)); + // + // { + // "success":true, + // "message":"", + // "result":[ + // [1591488000,"0.025025","0.025025","0.025029","0.025023","6.181","0.154686629"], + // [1591488060,"0.025028","0.025033","0.025035","0.025026","8.067","0.201921167"], + // [1591488120,"0.025034","0.02505","0.02505","0.025034","20.089","0.503114696"], + // ] + // } + // + const result = this.safeValue (response, 'result', []); + return this.parseOHLCVs (result, market, timeframe, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // [ + // 1591488000, + // "0.025025", + // "0.025025", + // "0.025029", + // "0.025023", + // "6.181", + // "0.154686629" + // ] + // + return [ + this.safeTimestamp (ohlcv, 0), // timestamp + this.safeFloat (ohlcv, 1), // open + this.safeFloat (ohlcv, 3), // high + this.safeFloat (ohlcv, 4), // low + this.safeFloat (ohlcv, 2), // close + this.safeFloat (ohlcv, 5), // volume + ]; + } + + async fetchStatus (params = {}) { + const response = await this.webGetV1Healthcheck (params); + const status = this.safeInteger (response, 'status'); + let formattedStatus = 'ok'; + if (status === 503) { + formattedStatus = 'maintenance'; + } + this.status = this.extend (this.status, { + 'status': formattedStatus, + 'updated': this.milliseconds (), + }); + return this.status; + } + + sign (path, api = 'publicV1', method = 'GET', params = {}, headers = undefined, body = undefined) { + const query = this.omit (params, this.extractParams (path)); + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if ((code === 418) || (code === 429)) { + throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body); + } + if (code === 404) { + throw new ExchangeError (this.id + ' ' + code.toString () + ' endpoint not found'); + } + if (response !== undefined) { + const success = this.safeValue (response, 'success'); + if (!success) { + const feedback = this.id + ' ' + body; + const status = this.safeString (response, 'status'); + if (typeof status === 'string') { + this.throwExactlyMatchedException (this.exceptions['exact'], status, feedback); + } + this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); + throw new ExchangeError (feedback); + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/xbtce.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/xbtce.js new file mode 100644 index 000000000..e0d137f67 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/xbtce.js @@ -0,0 +1,370 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, NotSupported, AuthenticationError } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class xbtce extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'xbtce', + 'name': 'xBTCe', + 'countries': [ 'RU' ], + 'rateLimit': 2000, // responses are cached every 2 seconds + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchMarkets': true, + 'fetchOHLCV': false, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + }, + 'urls': { + 'referral': 'https://xbtce.com/?agent=XX97BTCXXXG687021000B', + 'logo': 'https://user-images.githubusercontent.com/1294454/28059414-e235970c-662c-11e7-8c3a-08e31f78684b.jpg', + 'api': 'https://cryptottlivewebapi.xbtce.net:8443/api', + 'www': 'https://www.xbtce.com', + 'doc': [ + 'https://www.xbtce.com/tradeapi', + 'https://support.xbtce.info/Knowledgebase/Article/View/52/25/xbtce-exchange-api', + ], + }, + 'requiredCredentials': { + 'apiKey': true, + 'secret': true, + 'uid': true, + }, + 'api': { + 'public': { + 'get': [ + 'currency', + 'currency/{filter}', + 'level2', + 'level2/{filter}', + 'quotehistory/{symbol}/{periodicity}/bars/ask', + 'quotehistory/{symbol}/{periodicity}/bars/bid', + 'quotehistory/{symbol}/level2', + 'quotehistory/{symbol}/ticks', + 'symbol', + 'symbol/{filter}', + 'tick', + 'tick/{filter}', + 'ticker', + 'ticker/{filter}', + 'tradesession', + ], + }, + 'private': { + 'get': [ + 'tradeserverinfo', + 'tradesession', + 'currency', + 'currency/{filter}', + 'level2', + 'level2/{filter}', + 'symbol', + 'symbol/{filter}', + 'tick', + 'tick/{filter}', + 'account', + 'asset', + 'asset/{id}', + 'position', + 'position/{id}', + 'trade', + 'trade/{id}', + 'quotehistory/{symbol}/{periodicity}/bars/ask', + 'quotehistory/{symbol}/{periodicity}/bars/ask/info', + 'quotehistory/{symbol}/{periodicity}/bars/bid', + 'quotehistory/{symbol}/{periodicity}/bars/bid/info', + 'quotehistory/{symbol}/level2', + 'quotehistory/{symbol}/level2/info', + 'quotehistory/{symbol}/periodicities', + 'quotehistory/{symbol}/ticks', + 'quotehistory/{symbol}/ticks/info', + 'quotehistory/cache/{symbol}/{periodicity}/bars/ask', + 'quotehistory/cache/{symbol}/{periodicity}/bars/bid', + 'quotehistory/cache/{symbol}/level2', + 'quotehistory/cache/{symbol}/ticks', + 'quotehistory/symbols', + 'quotehistory/version', + ], + 'post': [ + 'trade', + 'tradehistory', + ], + 'put': [ + 'trade', + ], + 'delete': [ + 'trade', + ], + }, + }, + 'commonCurrencies': { + 'DSH': 'DASH', + }, + }); + } + + async fetchMarkets (params = {}) { + const response = await this.privateGetSymbol (params); + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + const id = this.safeString (market, 'Symbol'); + const baseId = this.safeString (market, 'MarginCurrency'); + const quoteId = this.safeString (market, 'ProfitCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = base + '/' + quote; + symbol = market['IsTradeAllowed'] ? symbol : id; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'info': market, + 'active': undefined, + 'precision': this.precision, + 'limits': this.limits, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const balances = await this.privateGetAsset (params); + const result = { 'info': balances }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'Currency'); + const code = this.safeCurrencyCode (currencyId); + const account = { + 'free': this.safeFloat (balance, 'FreeAmount'), + 'used': this.safeFloat (balance, 'LockedAmount'), + 'total': this.safeFloat (balance, 'Amount'), + }; + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'filter': market['id'], + }; + const response = await this.privateGetLevel2Filter (this.extend (request, params)); + const orderbook = response[0]; + const timestamp = this.safeInteger (orderbook, 'Timestamp'); + return this.parseOrderBook (orderbook, timestamp, 'Bids', 'Asks', 'Price', 'Volume'); + } + + parseTicker (ticker, market = undefined) { + let timestamp = 0; + let last = undefined; + if ('LastBuyTimestamp' in ticker) { + if (timestamp < ticker['LastBuyTimestamp']) { + timestamp = ticker['LastBuyTimestamp']; + last = ticker['LastBuyPrice']; + } + } + if ('LastSellTimestamp' in ticker) { + if (timestamp < ticker['LastSellTimestamp']) { + timestamp = ticker['LastSellTimestamp']; + last = ticker['LastSellPrice']; + } + } + if (!timestamp) { + timestamp = this.milliseconds (); + } + let symbol = undefined; + if (market) { + symbol = market['symbol']; + } + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': ticker['DailyBestBuyPrice'], + 'low': ticker['DailyBestSellPrice'], + 'bid': ticker['BestBid'], + 'bidVolume': undefined, + 'ask': ticker['BestAsk'], + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': ticker['DailyTradedTotalVolume'], + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetTicker (params); + const tickers = this.indexBy (response, 'Symbol'); + const ids = Object.keys (tickers); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + let market = undefined; + let symbol = undefined; + if (id in this.markets_by_id) { + market = this.markets_by_id[id]; + symbol = market['symbol']; + } else { + const baseId = id.slice (0, 3); + const quoteId = id.slice (3, 6); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + symbol = base + '/' + quote; + } + const ticker = tickers[id]; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'filter': market['id'], + }; + const response = await this.publicGetTickerFilter (this.extend (request, params)); + const length = response.length; + if (length < 1) { + throw new ExchangeError (this.id + ' fetchTicker returned empty response, xBTCe public API error'); + } + const tickers = this.indexBy (response, 'Symbol'); + const ticker = tickers[market['id']]; + return this.parseTicker (ticker, market); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + // no method for trades? + return await this.privateGetTrade (params); + } + + parseOHLCV (ohlcv, market = undefined) { + return [ + this.safeInteger (ohlcv, 'Timestamp'), + this.safeFloat (ohlcv, 'Open'), + this.safeFloat (ohlcv, 'High'), + this.safeFloat (ohlcv, 'Low'), + this.safeFloat (ohlcv, 'Close'), + this.safeFloat (ohlcv, 'Volume'), + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + // let minutes = parseInt (timeframe / 60); // 1 minute by default + // let periodicity = minutes.toString (); + // await this.loadMarkets (); + // let market = this.market (symbol); + // if (since === undefined) + // since = this.seconds () - 86400 * 7; // last day by defulat + // if (limit === undefined) + // limit = 1000; // default + // let response = await this.privateGetQuotehistorySymbolPeriodicityBarsBid (this.extend ({ + // 'symbol': market['id'], + // 'periodicity': periodicity, + // 'timestamp': since, + // 'count': limit, + // }, params)); + // return this.parseOHLCVs (response['Bars'], market, timeframe, since, limit); + throw new NotSupported (this.id + ' fetchOHLCV is disabled by the exchange'); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'pair': this.marketId (symbol), + 'type': side, + 'amount': amount, + 'rate': price, + }; + const response = await this.privatePostTrade (this.extend (request, params)); + return { + 'info': response, + 'id': response['Id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'Type': 'Cancel', + 'Id': id, + }; + return await this.privateDeleteTrade (this.extend (request, params)); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + if (!this.apiKey) { + throw new AuthenticationError (this.id + ' requires apiKey for all requests, their public API is always busy'); + } + if (!this.uid) { + throw new AuthenticationError (this.id + ' requires uid property for authentication and trading, their public API is always busy'); + } + let url = this.urls['api'] + '/' + this.version; + if (api === 'public') { + url += '/' + api; + } + url += '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + this.checkRequiredCredentials (); + headers = { 'Accept-Encoding': 'gzip, deflate' }; + const nonce = this.nonce ().toString (); + if (method === 'POST') { + if (Object.keys (query).length) { + headers['Content-Type'] = 'application/json'; + body = this.json (query); + } else { + url += '?' + this.urlencode (query); + } + } + let auth = nonce + this.uid + this.apiKey + method + url; + if (body) { + auth += body; + } + const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); + const credentials = this.uid + ':' + this.apiKey + ':' + nonce + ':' + signature; + headers['Authorization'] = 'HMAC ' + credentials; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/xena.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/xena.js new file mode 100644 index 000000000..8be88d342 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/xena.js @@ -0,0 +1,1715 @@ +'use strict'; + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, BadRequest, InsufficientFunds, InvalidAddress, BadSymbol, InvalidOrder } = require ('./base/errors'); + +module.exports = class xena extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'xena', + 'name': 'Xena Exchange', + 'countries': [ 'VC', 'UK' ], + 'rateLimit': 100, + 'certified': true, + 'has': { + 'CORS': false, + 'cancelAllOrders': true, + 'cancelOrder': true, + 'createDepositAddress': true, + 'createOrder': true, + 'editOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchCurrencies': true, + 'fetchDepositAddress': true, + 'fetchDeposits': true, + 'fetchLedger': true, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrderBook': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTime': true, + 'fetchTrades': true, + 'fetchWithdrawals': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg', + 'api': { + 'public': 'https://trading.xena.exchange/api', + 'private': 'https://api.xena.exchange', + }, + 'www': 'https://xena.exchange', + 'doc': 'https://support.xena.exchange/support/solutions/44000808700', + 'fees': 'https://trading.xena.exchange/en/platform-specification/fee-schedule', + }, + 'timeframes': { + '1m': '1m', + '5m': '5m', + '15m': '15m', + '30m': '30m', + '1h': '1h', + '4h': '4h', + '12h': '12h', + '1d': '1d', + '1w': '1w', + }, + 'api': { + 'public': { + 'get': [ + 'common/currencies', + 'common/instruments', + 'common/features', + 'common/commissions', + 'common/news', + 'market-data/candles/{marketId}/{timeframe}', + 'market-data/market-watch', + 'market-data/dom/{symbol}', + 'market-data/candles/{symbol}/{timeframe}', + 'market-data/trades/{symbol}', + 'market-data/server-time', + 'market-data/v2/candles/{symbol}/{timeframe}', + 'market-data/v2/trades/{symbol}', + 'market-data/v2/dom/{symbol}/', + 'market-data/v2/server-time', + ], + }, + 'private': { + 'get': [ + 'trading/accounts/{accountId}/order', + 'trading/accounts/{accountId}/active-orders', + 'trading/accounts/{accountId}/last-order-statuses', + 'trading/accounts/{accountId}/positions', + 'trading/accounts/{accountId}/positions-history', + 'trading/accounts/{accountId}/margin-requirements', + 'trading/accounts', + 'trading/accounts/{accountId}/balance', + 'trading/accounts/{accountId}/trade-history', + // 'trading/accounts/{accountId}/trade-history?symbol=BTC/USDT&client_order_id=EMBB8Veke&trade_id=220143254', + 'transfers/accounts', + 'transfers/accounts/{accountId}', + 'transfers/accounts/{accountId}/deposit-address/{currency}', + 'transfers/accounts/{accountId}/deposits', + 'transfers/accounts/{accountId}/trusted-addresses', + 'transfers/accounts/{accountId}/withdrawals', + 'transfers/accounts/{accountId}/balance-history', + // 'transfers/accounts/{accountId}/balance-history?currency={currency}&from={time}&to={time}&kind={kind}&kind={kind}', + // 'transfers/accounts/{accountId}/balance-history?page={page}&limit={limit}', + // 'transfers/accounts/{accountId}/balance-history?txid=3e1db982c4eed2d6355e276c5bae01a52a27c9cef61574b0e8c67ee05fc26ccf', + ], + 'post': [ + 'trading/order/new', + 'trading/order/heartbeat', + 'trading/order/cancel', + 'trading/order/mass-cancel', + 'trading/order/replace', + 'trading/position/maintenance', + 'transfers/accounts/{accountId}/withdrawals', + 'transfers/accounts/{accountId}/deposit-address/{currency}', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.0005, + 'taker': 0.001, + 'tierBased': true, + 'percentage': true, + }, + 'funding': { + 'tierBased': false, + 'percentage': false, + 'withdraw': {}, + 'deposit': {}, + }, + }, + 'exceptions': { + 'exact': { + 'Validation failed': BadRequest, + 'Unknown derivative symbol': BadSymbol, // {"error":"Unknown derivative symbol"} + 'Unknown account': BadRequest, // {"error":"Unknown account"} + 'Wrong TransactTime': BadRequest, // {"error":"Wrong TransactTime"} + 'ClOrdId is empty': BadRequest, // {"error":"ClOrdId is empty"} + }, + 'broad': { + 'Invalid aggregation ratio or depth': BadRequest, + 'address': InvalidAddress, + 'Money not enough': InsufficientFunds, + 'parse error': BadRequest, + 'Not enough': InsufficientFunds, // {"error":"Not enough free margin"} + }, + }, + 'options': { + 'defaultType': 'margin', // 'margin', + 'accountId': undefined, // '1012838157', + }, + }); + } + + async fetchTime (params = {}) { + const response = await this.publicGetMarketDataV2ServerTime (params); + // + // { + // "msgType":"0", + // "transactTime":1594774454112817637 + // } + // + const transactTime = this.safeInteger (response, 'transactTime'); + return parseInt (transactTime / 1000000); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetCommonInstruments (params); + // + // [ + // { + // "id":"ETHUSD_3M_250920", + // "type":"Margin", + // "marginType":"XenaFuture", + // "symbol":"ETHUSD_3M_250920", + // "baseCurrency":"ETH", + // "quoteCurrency":"USD", + // "settlCurrency":"BTC", + // "tickSize":2, + // "minOrderQuantity":"1", + // "orderQtyStep":"1", + // "limitOrderMaxDistance":"10", + // "priceInputMask":"0000.00", + // "enabled":true, + // "liquidationMaxDistance":"0.01", + // "contractValue":"1", + // "contractCurrency":"BTC", + // "lotSize":"1", + // "tickValue":"0.00000001", // linear contracts only + // "maxOrderQty":"175000", + // "maxPosVolume":"1750000", + // "mark":".ETHUSD_3M_250920", + // "underlying":".ETHUSD_TWAP", + // "openInterest":".ETHUSD_3M_250920_OpenInterest", + // "floatingPL":"BidAsk", // perpetual contracts only + // "addUvmToFreeMargin":"ProfitAndLoss", + // "margin":{ + // "netting":"PositionsAndOrders", + // "rates":[ + // {"maxVolume":"175000","initialRate":"0.05","maintenanceRate":"0.0125"}, + // {"maxVolume":"350000","initialRate":"0.1","maintenanceRate":"0.025"}, + // {"maxVolume":"500000","initialRate":"0.2","maintenanceRate":"0.05"}, + // {"maxVolume":"750000","initialRate":"0.3","maintenanceRate":"0.075"}, + // {"maxVolume":"1050000","initialRate":"0.4","maintenanceRate":"0.1"}, + // {"maxVolume":"1400000","initialRate":"0.5","maintenanceRate":"0.125"}, + // {"maxVolume":"1750000","initialRate":"1","maintenanceRate":"0.25"} + // ], + // "rateMultipliers":{ + // "LimitBuy":"1", + // "LimitSell":"1", + // "Long":"1", + // "MarketBuy":"1", + // "MarketSell":"1", + // "Short":"1", + // "StopBuy":"0", + // "StopSell":"0" + // } + // }, + // "clearing":{"enabled":true,"index":".ETHUSD_3M_250920"}, + // "premium":{"enabled":true,"index":".XBTUSD_Premium_IR_Corrected"}, // perpetual contracts only + // "riskAdjustment":{"enabled":true,"index":".RiskAdjustment_IR"}, + // "expiration":{"enabled":true,"index":".ETHUSD_TWAP"}, // futures only + // "pricePrecision":3, + // "priceRange":{ + // "enabled":true, + // "distance":"0.03", + // "movingBoundary":"0", + // "lowIndex":".ETHUSD_3M_250920_LOWRANGE", + // "highIndex":".ETHUSD_3M_250920_HIGHRANGE" + // }, + // "priceLimits":{ + // "enabled":true, + // "distance":"0.5", + // "movingBoundary":"0", + // "lowIndex":".ETHUSD_3M_250920_LOWLIMIT", + // "highIndex":".ETHUSD_3M_250920_HIGHLIMIT" + // }, + // "inverse":true, // inverse contracts only + // "serie":"ETHUSD", // futures only + // "tradingStartDate":"2020-03-27 07:00:00", + // "expiryDate":"2020-09-25 08:00:00" // futures only + // }, + // { + // "type":"Index", + // "symbol":".ETHUSD_Premium_IR_Corrected", + // "tickSize":6, + // "enabled":true, + // "basis":365 + // }, + // ] + // + const result = []; + for (let i = 0; i < response.length; i++) { + const market = response[i]; + let type = this.safeStringLower (market, 'type'); + const id = this.safeString (market, 'symbol'); + const numericId = this.safeString (market, 'id'); + const marginType = this.safeString (market, 'marginType'); + const baseId = this.safeString (market, 'baseCurrency'); + const quoteId = this.safeString (market, 'quoteCurrency'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + let symbol = id; + if (type === 'margin') { + if (marginType === 'XenaFuture') { + type = 'future'; + } else if (marginType === 'XenaListedPerpetual') { + type = 'swap'; + symbol = base + '/' + quote; + } + } + const future = (type === 'future'); + const swap = (type === 'swap'); + const pricePrecision = this.safeInteger2 (market, 'tickSize', 'pricePrecision'); + const precision = { + 'price': pricePrecision, + 'amount': 0, + }; + const maxCost = this.safeFloat (market, 'maxOrderQty'); + const minCost = this.safeFloat (market, 'minOrderQuantity'); + const limits = { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': minCost, + 'max': maxCost, + }, + }; + const active = this.safeValue (market, 'enabled', false); + const inverse = this.safeValue (market, 'inverse', false); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'numericId': numericId, + 'active': active, + 'type': type, + 'spot': false, + 'future': future, + 'swap': swap, + 'inverse': inverse, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchCurrencies (params = {}) { + const response = await this.publicGetCommonCurrencies (params); + // + // { + // "BAB": { + // "name":"BAB", + // "title":"Bitcoin ABC", + // "blockchain":{ + // "name":"BAB", + // "title":"Bitcoin ABC", + // "deposit":{"confirmations":6}, + // "withdraw":{"confirmations":1}, + // "addressReuseAllowed":false, + // "view":{ + // "uriTemplate":"bitcoinabc:%s?message=Xena Exchange", + // "recommendedFee":"0.00001", + // "transactionUrl":"https://blockchair.com/bitcoin-cash/transaction/${txId}", + // "walletUrl":"https://blockchair.com/bitcoin-cash/address/${walletId}" + // } + // }, + // "precision":5, + // "withdraw":{"minAmount":"0.01","commission":"0.001"}, + // "view":{ + // "color":"#DC7C08", + // "site":"https://www.bitcoinabc.org" + // }, + // "enabled":true + // }, + // } + const ids = Object.keys (response); + const result = {}; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const currency = response[id]; + const code = this.safeCurrencyCode (id); + const name = this.safeString (currency, 'title'); + const precision = this.safeInteger (currency, 'precision'); + const enabled = this.safeValue (currency, 'enabled'); + const active = (enabled === true); + const withdraw = this.safeValue (currency, 'withdraw', {}); + result[code] = { + 'id': id, + 'code': code, + 'info': currency, + 'name': name, + 'active': active, + 'fee': this.safeFloat (withdraw, 'commission'), + 'precision': precision, + 'limits': { + 'amount': { + 'min': undefined, + 'max': undefined, + }, + 'price': { + 'min': undefined, + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + 'withdraw': { + 'min': this.safeFloat (withdraw, 'minAmount'), + 'max': undefined, + }, + }, + }; + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // fetchTicker, fetchTickers + // + // { + // "symbol":".XBTUSD_3M_250920_MID", + // "firstPx":"9337.49", + // "lastPx":"9355.81", + // "highPx":"9579.42", + // "lowPx":"9157.63", + // "buyVolume":"0", + // "sellVolume":"0", + // "bid":"0", + // "ask":"0" + // } + // + const timestamp = this.milliseconds (); + const marketId = this.safeString (ticker, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const last = this.safeFloat (ticker, 'lastPx'); + const open = this.safeFloat (ticker, 'firstPx'); + let percentage = undefined; + let change = undefined; + let average = undefined; + if ((last !== undefined) && (open !== undefined)) { + change = last - open; + average = this.sum (last, open) / 2; + if (open > 0) { + percentage = change / open * 100; + } + } + const buyVolume = this.safeFloat (ticker, 'buyVolume'); + const sellVolume = this.safeFloat (ticker, 'sellVolume'); + const baseVolume = this.sum (buyVolume, sellVolume); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'highPx'), + 'low': this.safeFloat (ticker, 'lowPx'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': open, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': change, + 'percentage': percentage, + 'average': average, + 'baseVolume': baseVolume, + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const tickers = await this.fetchTickers (undefined, params); + if (symbol in tickers) { + return tickers[symbol]; + } + throw new BadSymbol (this.id + ' fetchTicker could not find a ticker with symbol ' + symbol); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const tickers = await this.publicGetMarketDataMarketWatch (params); + // + // [ + // { + // "symbol":".XBTUSD_3M_250920_MID", + // "firstPx":"9337.49", + // "lastPx":"9355.81", + // "highPx":"9579.42", + // "lowPx":"9157.63", + // "buyVolume":"0", + // "sellVolume":"0", + // "bid":"0", + // "ask":"0" + // } + // ] + // + const result = {}; + for (let i = 0; i < tickers.length; i++) { + const ticker = this.parseTicker (tickers[i]); + const symbol = ticker['symbol']; + result[symbol] = ticker; + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'symbol': this.marketId (symbol), + }; + if (limit !== undefined) { + request['depth'] = limit; + } + const response = await this.publicGetMarketDataV2DomSymbol (this.extend (request, params)); + // + // { + // "msgType":"W", + // "mdStreamId":"DOM:XBTUSD:aggregated", + // "lastUpdateTime":1594772683037691997, + // "mdBookType":"2", + // "symbol":"XBTUSD", + // "lowRangePx":"9132.24", + // "highRangePx":"9410.36", + // "lowLimitPx":"9132.24", + // "highLimitPx":"9410.36", + // "clearingPx":"9253.4", + // "bestBid":"9269.8", + // "bestAsk":"9275.9", + // "mdEntry":[ + // {"mdEntryType":"1","mdEntryPx":"9275.9","mdEntrySize":"3000","numberOfOrders":1}, + // {"mdEntryType":"1","mdEntryPx":"9277.7","mdEntrySize":"50000","numberOfOrders":1}, + // {"mdEntryType":"1","mdEntryPx":"9277.8","mdEntrySize":"2000","numberOfOrders":1}, + // {"mdEntryType":"0","mdEntryPx":"9269.8","mdEntrySize":"2000","numberOfOrders":1}, + // {"mdEntryType":"0","mdEntryPx":"9267.9","mdEntrySize":"3000","numberOfOrders":1}, + // {"mdEntryType":"0","mdEntryPx":"9267.8","mdEntrySize":"50000","numberOfOrders":1}, + // ] + // } + // + const mdEntry = this.safeValue (response, 'mdEntry', []); + const mdEntriesByType = this.groupBy (mdEntry, 'mdEntryType'); + const lastUpdateTime = this.safeInteger (response, 'lastUpdateTime'); + const timestamp = parseInt (lastUpdateTime / 1000000); + return this.parseOrderBook (mdEntriesByType, timestamp, '0', '1', 'mdEntryPx', 'mdEntrySize'); + } + + async fetchAccounts (params = {}) { + const response = await this.privateGetTradingAccounts (params); + // + // { + // "accounts": [ + // { "id":8273231, "kind": "Spot" }, + // { "id":10012833469, "kind": "Margin", "currency": "BTC" } + // ] + // } + // + const accounts = this.safeValue (response, 'accounts'); + const result = []; + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + const accountId = this.safeString (account, 'id'); + const currencyId = this.safeString (account, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const type = this.safeStringLower (account, 'kind'); + result.push ({ + 'id': accountId, + 'type': type, + 'currency': code, + 'info': account, + }); + } + return result; + } + + async findAccountByType (type) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountsByType = this.groupBy (this.accounts, 'type'); + const accounts = this.safeValue (accountsByType, type); + if (accounts === undefined) { + throw new ExchangeError (this.id + " findAccountByType() could not find an accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + const numAccounts = accounts.length; + if (numAccounts > 1) { + throw new ExchangeError (this.id + " findAccountByType() found more than one accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes + } + return accounts[0]; + } + + async getAccountId (params) { + await this.loadMarkets (); + await this.loadAccounts (); + const defaultAccountId = this.safeString (this.options, 'accountId'); + const accountId = this.safeString (params, 'accountId', defaultAccountId); + if (accountId !== undefined) { + return accountId; + } + const defaultType = this.safeString (this.options, 'defaultType', 'margin'); + const type = this.safeString (params, 'type', defaultType); + params = this.omit (params, 'type'); + if (type === undefined) { + throw new ArgumentsRequired (this.id + " requires an 'accountId' parameter or a 'type' parameter ('spot' or 'margin')"); + } + const account = await this.findAccountByType (type); + return account['id']; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + }; + const response = await this.privateGetTradingAccountsAccountIdBalance (this.extend (request, params)); + // + // { + // "balances": [ + // {"available":"0","onHold":"0","settled":"0","equity":"0","currency":"BAB","lastUpdated":1564811790485125345}, + // {"available":"0","onHold":"0","settled":"0","equity":"0","currency":"BSV","lastUpdated":1564811790485125345}, + // {"available":"0","onHold":"0","settled":"0","equity":"0","currency":"BTC","lastUpdated":1564811790485125345}, + // ] + // } + // + const result = { 'info': response }; + const balances = this.safeValue (response, 'balances', []); + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + const currencyId = this.safeString (balance, 'currency'); + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'onHold'); + result[code] = account; + } + return this.parseBalance (result); + } + + parseTrade (trade, market = undefined) { + // + // { + // "mdUpdateAction":"0", + // "mdEntryType":"2", + // "mdEntryPx":"9225.16", + // "mdEntrySize":"10000", + // "transactTime":1594728504524977655, + // "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9", + // "aggressorSide":"1" + // } + // + // fetchMyTrades + // + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"xXWKLQVl3", + // "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7", + // "symbol":"ETHUSD", + // "transactTime":1595143349089739000, + // "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4", + // "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1", + // "side":"2", + // "lastQty":"1", + // "lastPx":"234.58", + // "avgPx":"234.58", + // "calculatedCcyLastQty":"0", + // "netMoney":"0", + // "lastLiquidityInd":"2", + // "commission":"0.00000011", + // "commRate":"0.00045", + // "commCurrency":"BTC", + // "positionId":132162662, + // "positionEffect":"C" + // } + // + const id = this.safeString (trade, 'tradeId'); + let timestamp = this.safeInteger (trade, 'transactTime'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp / 1000000); + } + let side = this.safeStringLower2 (trade, 'side', 'aggressorSide'); + if (side === '1') { + side = 'buy'; + } else if (side === '2') { + side = 'sell'; + } + const orderId = this.safeString (trade, 'orderId'); + const marketId = this.safeString (trade, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const price = this.safeFloat2 (trade, 'lastPx', 'mdEntryPx'); + const amount = this.safeFloat2 (trade, 'lastQty', 'mdEntrySize'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + const feeRate = this.safeFloat (trade, 'commRate'); + fee = { + 'cost': feeCost, + 'rate': feeRate, + 'currency': feeCurrencyCode, + }; + } + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'order': orderId, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + }; + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'page': 1, + // 'limit': integer, + // 'from': time, + // 'to': time, + // 'symbol': currency['id'], + // 'trade_id': id, + // 'client_order_id': id, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = since * 1000000; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradingAccountsAccountIdTradeHistory (this.extend (request, params)); + // + // [ + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"xXWKLQVl3", + // "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7", + // "symbol":"ETHUSD", + // "transactTime":1595143349089739000, + // "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4", + // "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1", + // "side":"2", + // "lastQty":"1", + // "lastPx":"234.58", + // "avgPx":"234.58", + // "calculatedCcyLastQty":"0", + // "netMoney":"0", + // "lastLiquidityInd":"2", + // "commission":"0.00000011", + // "commRate":"0.00045", + // "commCurrency":"BTC", + // "positionId":132162662, + // "positionEffect":"C" + // }, + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"3ce8c305-9936-4e97-9206-71ae3ff40305", + // "orderId":"a93c686d-990e-44d9-9cbe-61107744b990", + // "symbol":"ETHUSD", + // "transactTime":1595143315369226000, + // "execId":"1c745881722ad966a4ce71600cd058d59da0d1c3", + // "tradeId":"77f75bd8-27c4-4b1a-a5e8-0d59239ce216", + // "side":"1", + // "lastQty":"1", + // "lastPx":"234.72", + // "avgPx":"234.72", + // "calculatedCcyLastQty":"0", + // "netMoney":"0", + // "lastLiquidityInd":"2", + // "commission":"0.00000011", + // "commRate":"0.00045", + // "commCurrency":"BTC", + // "positionId":132162662, + // "positionEffect":"O" + // } + // ] + // + return this.parseTrades (response, market, since, limit); + } + + parseOHLCV (ohlcv, market = undefined) { + // + // { + // "transactTime":1594784700000000000, + // "firstPx":"9246.3", + // "lastPx":"9232.8", + // "highPx":"9246.3", + // "lowPx":"9232.8", + // "buyVolume":"0", + // "sellVolume":"0" + // } + // + const transactTime = this.safeInteger (ohlcv, 'transactTime'); + const timestamp = parseInt (transactTime / 1000000); + const buyVolume = this.safeFloat (ohlcv, 'buyVolume'); + const sellVolume = this.safeFloat (ohlcv, 'sellVolume'); + const volume = this.sum (buyVolume, sellVolume); + return [ + timestamp, + this.safeFloat (ohlcv, 'firstPx'), + this.safeFloat (ohlcv, 'highPx'), + this.safeFloat (ohlcv, 'lowPx'), + this.safeFloat (ohlcv, 'lastPx'), + volume, + ]; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + 'timeframe': this.timeframes[timeframe], + }; + const durationInSeconds = this.parseTimeframe (timeframe); + const duration = durationInSeconds * 1000; + if (since !== undefined) { + request['from'] = since * 1000000; + if (limit !== undefined) { + request['to'] = this.sum (since, limit * duration) * 1000000; + } + } else { + const now = this.milliseconds (); + // max limit is 1000 + if (limit !== undefined) { + request['from'] = (now - limit * duration) * 1000000; + } + } + const response = await this.publicGetMarketDataV2CandlesSymbolTimeframe (this.extend (request, params)); + // + // { + // "mdEntry":[ + // {"transactTime":1594784700000000000,"firstPx":"9246.3","lastPx":"9232.8","highPx":"9246.3","lowPx":"9232.8","buyVolume":"0","sellVolume":"0"}, + // {"transactTime":1594785600000000000,"firstPx":"9231.8","lastPx":"9227.3","highPx":"9232.8","lowPx":"9227.3","buyVolume":"0","sellVolume":"0"}, + // {"transactTime":1594786500000000000,"firstPx":"9226.3","lastPx":"9230.3","highPx":"9230.3","lowPx":"9220.6","buyVolume":"0","sellVolume":"0"} + // ] + // } + // + const mdEntry = this.safeValue (response, 'mdEntry', []); + return this.parseOHLCVs (mdEntry, market, timeframe, since, limit); + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'symbol': market['id'], + // 'from': this.iso8601 (since), + // 'to': this.iso8601 (this.milliseconds ()), + // 'page': 1, + // 'limit': limit, + }; + if (since !== undefined) { + request['from'] = this.iso8601 (since); + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetMarketDataV2TradesSymbol (this.extend (request, params)); + // + // { + // "msgType":"W", + // "lastUpdateTime":1594737830902223803, + // "symbol":"XBTUSD", + // "mdEntry":[ + // { + // "mdUpdateAction":"0", + // "mdEntryType":"2", + // "mdEntryPx":"9225.16", + // "mdEntrySize":"10000", + // "transactTime":1594728504524977655, + // "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9", + // "aggressorSide":"1" + // }, + // ] + // } + // + const mdEntry = this.safeValue (response, 'mdEntry', []); + return this.parseTrades (mdEntry, market, since, limit); + } + + parseOrderStatus (status) { + const statuses = { + 'A': 'open', // PendingNew + '0': 'open', // New + '1': 'open', // PartiallyFilled + '2': 'closed', // Filled + '6': 'canceled', // PendingCancel + '4': 'canceled', // Cancelled + 'E': 'open', // PendingReplace + '8': 'rejected', // Rejected + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + // + // createOrder + // + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // + const id = this.safeString (order, 'orderId'); + const clientOrderId = this.safeString (order, 'clOrdId'); + const transactTime = this.safeInteger (order, 'transactTime'); + const timestamp = parseInt (transactTime / 1000000); + const status = this.parseOrderStatus (this.safeString (order, 'ordStatus')); + const marketId = this.safeString (order, 'symbol'); + const symbol = this.safeSymbol (marketId, market); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'orderQty'); + const filled = this.safeFloat (order, 'cumQty'); + const remaining = this.safeFloat (order, 'leavesQty'); + let cost = undefined; + let side = this.safeStringLower (order, 'side'); + if (side === '1') { + side = 'buy'; + } else if (side === '1') { + side = 'sell'; + } + let type = this.safeStringLower (order, 'ordType'); + if (type === '1') { + type = 'market'; + } else if (type === '2') { + type = 'limit'; + } else if (type === '3') { + type = 'stop'; + } else if (type === '4') { + type = 'stop-limit'; + } + if (cost === undefined) { + if ((price !== undefined) && (filled !== undefined)) { + cost = price * filled; + } + } + return { + 'id': id, + 'clientOrderId': clientOrderId, + 'info': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'amount': amount, + 'cost': cost, + 'average': undefined, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const orderTypes = { + 'market': '1', + 'limit': '2', + 'stop': '3', + 'stop-limit': '4', + }; + const orderType = this.safeString (orderTypes, type); + if (orderType === undefined) { + throw new InvalidOrder (this.id + ' createOrder does not support order type ' + type + ', supported order types are market, limit, stop, stop-limit'); + } + const orderSides = { + 'buy': '1', + 'sell': '2', + }; + const orderSide = this.safeString (orderSides, side); + if (orderSide === undefined) { + throw new InvalidOrder (this.id + ' createOrder does not support order side ' + side + ', supported order sides are buy, sell'); + } + const market = this.market (symbol); + const request = { + 'account': parseInt (accountId), + 'symbol': market['id'], + 'ordType': orderType, + 'side': orderSide, + 'orderQty': this.amountToPrecision (symbol, amount), + 'transactTime': this.milliseconds () * 1000000, + // 'clOrdId': this.uuid (), // required + // 'price': this.priceToPrecision (symbol, price), // required for limit and stop-limit orders + // 'stopPx': this.priceToPrecision (symbol, stopPx), // required for stop and stop-limit orders + // 'timeInForce': '1', // default '1' = GoodTillCancelled, '3' = ImmediateOrCancel, '4' = FillOrKill + // 'execInst': '0', + // '0' = StayOnOfferSide, maker only, reject instead of aggressive execution + // '9' = PegToOfferSide, maker only, best available level instead of aggressive execution + // 'o' = CancelOnConnectionLoss + // 'positionID': 1013838923, // required when positionEffect == 'C' with hedged accounting + // 'positionEffect': 'O', // 'C' = Close, 'O' = Open, send C along with the positionID if the order must close a position with hedged accounting mode + // 'text': 'comment', // optional + // 'grpID': 'group-identifier', // group identifier for cancel on disconnect orders + }; + if ((type === 'limit') || (type === 'stop-limit')) { + if (price === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a price argument for order type ' + type); + } + request['price'] = this.priceToPrecision (symbol, price); + } + if ((type === 'stop') || (type === 'stop-limit')) { + const stopPx = this.safeFloat (params, 'stopPx'); + if (stopPx === undefined) { + throw new InvalidOrder (this.id + ' createOrder requires a stopPx param for order type ' + type); + } + request['stopPx'] = this.priceToPrecision (symbol, stopPx); + params = this.omit (params, 'stopPx'); + } + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdId', this.uuid ()); + if (clientOrderId !== undefined) { + request['clOrdId'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'clOrdId' ]); + } + const response = await this.privatePostTradingOrderNew (this.extend (request, params)); + // + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // + return this.parseOrder (response, market); + } + + async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const market = this.market (symbol); + const request = { + 'account': parseInt (accountId), + 'clOrdId': this.uuid (), + 'symbol': market['id'], + 'transactTime': this.milliseconds () * 1000000, + // 'origClOrdId': this.uuid (), // one of orderId or origClOrdId is required + // 'orderId': id, + // 'side': '1', // 1 = buy, 2 = sell + // 'execInst': '0', + // '0' = StayOnOfferSide, maker only, reject instead of aggressive execution + // '9' = PegToOfferSide, maker only, best available level instead of aggressive execution + // 'o' = CancelOnConnectionLoss + // 'orderQty': 38 M decimal + // 'price': this.priceToPrecision (symbol, price), // required for limit and stop-limit orders + // 'stopPx': this.priceToPrecision (symbol, stopPx), // required for stop and stop-limit orders + // 'capPrice': this.priceToPrecision (symbol, capPrice), // the price beyond which the order will not move for trailing stop and attempt-zero-loss + // 'pegPriceType': '8', // '8' = TrailingStopPeg, identifies a trailing stop or an attempt-zero-loss order + // 'pegOffsetType': '2', // '2' = BasisPoints, the unit of the distance to the stop price for a trailing stop or an attempt-zero-loss order + // 'pegOffsetValue': 123, // distance to the trailing stop or attempt-zero-loss + }; + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'origClOrdId'); + if (clientOrderId !== undefined) { + request['origClOrdId'] = clientOrderId; + params = this.omit (params, [ 'clientOrderId', 'origClOrdId' ]); + } else { + request['orderId'] = id; + } + if (amount !== undefined) { + request['orderQty'] = this.amountToPrecision (symbol, amount); + } + if (price !== undefined) { + request['price'] = this.priceToPrecision (symbol, price); + } + const stopPx = this.safeFloat (params, 'stopPx'); + if (stopPx !== undefined) { + request['stopPx'] = this.priceToPrecision (symbol, stopPx); + params = this.omit (params, 'stopPx'); + } + const capPrice = this.safeFloat (params, 'capPrice'); + if (capPrice !== undefined) { + request['capPrice'] = this.priceToPrecision (symbol, capPrice); + params = this.omit (params, 'capPrice'); + } + const response = await this.privatePostTradingOrderReplace (this.extend (request, params)); + return this.parseOrder (response, market); + } + + async cancelOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' cancelOrder requires a symbol argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const clientOrderId = this.safeString2 (params, 'clientOrderId', 'origClOrdId'); + params = this.omit (params, [ 'clientOrderId', 'origClOrdId' ]); + const market = this.market (symbol); + const request = { + 'account': parseInt (accountId), + 'symbol': market['id'], + 'clOrdId': this.uuid (), + 'transactTime': this.milliseconds () * 1000000, + }; + if (clientOrderId !== undefined) { + request['origClOrdId'] = clientOrderId; + } else { + request['orderId'] = id; + } + const response = await this.privatePostTradingOrderCancel (this.extend (request, params)); + // + // { + // "msgType":"8", + // "account":1012838158, + // "clOrdId":"0fa3fb55-9dc0-4cfc-a1db-6aa8b7dd2d98", + // "origClOrdId":"3b2878bb-24d8-4922-9d2a-5b8009416677", + // "orderId":"665b418e-9d09-4461-b733-d317f6bff43f", + // "symbol":"ETHUSD", + // "ordType":"2", + // "price":"640", + // "transactTime":1595060080941618739, + // "execId":"c541c0ca437c0e6501c3a50a9d4dc8f575f49972", + // "execType":"6", + // "ordStatus":"6", + // "side":"2", + // "orderQty":"1", + // "leavesQty":"0", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.000032", + // "marginAmtType":"11" + // } + // + return this.parseOrder (response, market); + } + + async cancelAllOrders (symbol = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'account': parseInt (accountId), + 'clOrdId': this.uuid (), + // 'side': '1', // 1 = buy, 2 = sell, optional filter, cancel only orders with the given side + // 'positionEffect': 'C', // C = Close, O = Open, optional filter, cancel only orders with the given positionEffect, applicable only for accounts with hedged accounting + }; + if (symbol !== undefined) { + const market = this.market (symbol); + request['symbol'] = market['id']; + request['massCancelRequestType'] = '1'; // CancelOrdersForASecurity + } else { + request['massCancelRequestType'] = '7'; // CancelAllOrders + } + const response = await this.privatePostTradingOrderMassCancel (this.extend (request, params)); + // + // { + // "msgType":"r", + // "clOrdId":"b3e95759-e43e-4b3a-b664-a4d213e281a7", + // "massActionReportID":"e915b6f4-a7ca-4c5c-b8d6-e39862530248", + // "massCancelResponse":"1", + // "symbol":"ETHUSD", + // "transactTime":1595065630133756426, + // "totalAffectedOrders":2, + // "account":1012838158 + // } + // + return response; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'symbol': market['id'], + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + const response = await this.privateGetTradingAccountsAccountIdActiveOrders (this.extend (request, params)); + // + // [ + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'from': this.iso8601 (since) * 1000000, + // 'to': this.iso8601 (this.milliseconds ()) * 1000000, // max range is 7 days + // 'symbol': market['id'], + // 'limit': 100, + }; + let market = undefined; + if (symbol !== undefined) { + market = this.market (symbol); + request['symbol'] = market['id']; + } + if (since !== undefined) { + request['from'] = this.iso8601 (since) * 1000000; + } + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.privateGetTradingAccountsAccountIdLastOrderStatuses (this.extend (request, params)); + // + // [ + // { + // "msgType":"8", + // "account":1012838720, + // "clOrdId":"XAq0pRQ1g", + // "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35", + // "symbol":"XBTUSD", + // "ordType":"2", + // "price":"9000", + // "transactTime":1593778763271127920, + // "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9", + // "execType":"I", + // "ordStatus":"0", + // "side":"1", + // "orderQty":"1", + // "leavesQty":"1", + // "cumQty":"0", + // "positionEffect":"O", + // "marginAmt":"0.00000556", + // "marginAmtType":"11" + // } + // ] + // + return this.parseOrders (response, market, since, limit); + } + + async createDepositAddress (code, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + const request = { + 'accountId': accountId, + 'currency': currency['id'], + }; + const response = await this.privatePostTransfersAccountsAccountIdDepositAddressCurrency (this.extend (request, params)); + // + // { + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "uri": "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange", + // "allowsRenewal": true + // } + // + const address = this.safeValue (response, 'address'); + const tag = undefined; + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + const request = { + 'accountId': accountId, + 'currency': currency['id'], + }; + const response = await this.privateGetTransfersAccountsAccountIdDepositAddressCurrency (this.extend (request, params)); + // + // { + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "uri": "bitcoin:mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9?message=Xena Exchange", + // "allowsRenewal": true + // } + // + const address = this.safeValue (response, 'address'); + const tag = undefined; + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchTransactionsByType (type, code = undefined, since = undefined, limit = undefined, params = {}) { + if (code === undefined) { + throw new ArgumentsRequired (this.id + ' fetchTransactions() requires a currency `code` argument'); + } + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + 'accountId': accountId, + }; + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const method = 'privateGetTransfersAccountsAccountId' + this.capitalize (type); + const response = await this[method] (this.extend (request, params)); + // + // { + // "withdrawals": [ + // { + // "withdrawalRequestId": 47383243, + // "externalId": "...", // external ID submitted by the client when creating the request + // "status": 1, + // "statusMessage": "Pending confirmation", + // "amount": "10.2", + // "currency": "BTC", + // "lastUpdated": , + // "blockchain": "Bitcoin", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98" + // } + // ] + // } + // + // { + // "deposits": [ + // { + // "currency": "BTC", + // "amount": "1.2", + // "status": 1, + // "statusMessage": "Processing", + // "blockchain": "Bitcoin", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "lastUpdated": + // "confirmations": 2, + // "requiredConfirmations": 6 + // } + // ] + // } + // + // + const transactions = this.safeValue (response, type, []); + return this.parseTransactions (transactions, currency, since, limit); + } + + async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('withdrawals', code, since, limit, params); + } + + async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { + return await this.fetchTransactionsByType ('deposits', code, since, limit, params); + } + + parseTransaction (transaction, currency = undefined) { + // + // withdraw() + // + // { + // "withdrawalRequestId": 47383243, + // "status": 1, + // "statusMessage": "Pending confirmation" + // } + // + // fetchWithdrawals + // + // { + // "withdrawalRequestId": 47383243, + // "externalId": "...", // external ID submitted by the client when creating the request + // "status": 1, + // "statusMessage": "Pending confirmation", + // "amount": "10.2", + // "currency": "BTC", + // "lastUpdated": , + // "blockchain": "Bitcoin", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98" + // } + // + // fetchDeposits + // + // { + // "currency": "BTC", + // "amount": "1.2", + // "status": 1, + // "statusMessage": "Processing", + // "blockchain": "Bitcoin", + // "txId": "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98", + // "address": "mu5GceHFAG38mGRYCFqafe5ZiNKLX3rKk9", + // "lastUpdated": + // "confirmations": 2, + // "requiredConfirmations": 6 + // } + // + const id = this.safeString (transaction, 'withdrawalRequestId'); + const type = (id === undefined) ? 'deposit' : 'withdrawal'; + let updated = this.safeInteger (transaction, 'lastUpdated'); + if (updated !== undefined) { + updated = parseInt (updated / 1000000); + } + const timestamp = undefined; + const txid = this.safeString (transaction, 'txId'); + const currencyId = this.safeString (transaction, 'currency'); + const code = this.safeCurrencyCode (currencyId, currency); + const address = this.safeString (transaction, 'address'); + const addressFrom = undefined; + const addressTo = address; + const amount = this.safeFloat (transaction, 'amount'); + const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); + const fee = undefined; + return { + 'info': transaction, + 'id': id, + 'txid': txid, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'addressFrom': addressFrom, + 'addressTo': addressTo, + 'address': address, + 'tagFrom': undefined, + 'tagTo': undefined, + 'tag': undefined, + 'type': type, + 'amount': amount, + 'currency': code, + 'status': status, + 'updated': updated, + 'fee': fee, + }; + } + + parseTransactionStatus (status) { + const statuses = { + '1': 'pending', // new + '2': 'ok', // completed + '3': 'failed', // duplicate + '4': 'failed', // not enough money + '5': 'pending', // waiting for manual approval from XENA + '100': 'pending', // request is being processed + '101': 'pending', // request is being processed + '102': 'pending', // request is being processed + '103': 'pending', // request is being processed + }; + return this.safeString (statuses, status, status); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const currency = this.currency (code); + let uuid = this.uuid (); + uuid = uuid.split ('-'); + uuid = uuid.join (''); + const request = { + 'currency': currency['id'], + 'accountId': accountId, + 'amount': this.currencyToPrecision (code, amount), + 'address': address, + 'id': uuid, // mandatory external ID (string), used by the client to identify his request + }; + const response = await this.privatePostTransfersAccountsAccountIdWithdrawals (this.extend (request, params)); + // + // { + // "withdrawalRequestId": 47383243, + // "status": 1, + // "statusMessage": "Pending confirmation" + // } + // + return this.parseTransaction (response, currency); + } + + parseLedgerEntryType (type) { + const types = { + 'deposit': 'transaction', + 'withdrawal': 'transaction', + 'internal deposit': 'transfer', + 'internal withdrawal': 'transfer', + 'rebate': 'rebate', + 'reward': 'reward', + }; + return this.safeString (types, type, type); + } + + parseLedgerEntry (item, currency = undefined) { + // + // { + // "accountId":8263118, + // "ts":1551974415000000000, + // "amount":"-1", + // "currency":"BTC", + // "kind":"internal withdrawal", + // "commission":"0", + // "id":96 + // } + // + const id = this.safeString (item, 'id'); + let direction = undefined; + const account = this.safeString (item, 'accountId'); + const referenceId = undefined; + const referenceAccount = undefined; + const type = this.parseLedgerEntryType (this.safeString (item, 'kind')); + const code = this.safeCurrencyCode (this.safeString (item, 'currency'), currency); + let amount = this.safeFloat (item, 'amount'); + if (amount < 0) { + direction = 'out'; + amount = Math.abs (amount); + } else { + direction = 'in'; + } + let timestamp = this.safeInteger (item, 'ts'); + if (timestamp !== undefined) { + timestamp = parseInt (timestamp / 1000000); + } + const fee = { + 'cost': this.safeFloat (item, 'commission'), + 'currency': code, + }; + const before = undefined; + const after = this.safeFloat (item, 'balance'); + const status = 'ok'; + return { + 'info': item, + 'id': id, + 'direction': direction, + 'account': account, + 'referenceId': referenceId, + 'referenceAccount': referenceAccount, + 'type': type, + 'currency': code, + 'amount': amount, + 'before': before, + 'after': after, + 'status': status, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'fee': fee, + }; + } + + async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + await this.loadAccounts (); + const accountId = await this.getAccountId (params); + const request = { + 'accountId': accountId, + // 'page': 1, + // 'limit': 5000, // max 5000 + // 'from': time, + // 'to': time, + // 'symbol': currency['id'], + // 'trade_id': id, + // 'client_order_id': id, + // 'txid': txid, + // 'kind': 'deposit', // 'withdrawal, 'internal deposit', 'internal withdrawal', 'rebate', 'reward' + }; + let currency = undefined; + if (code !== undefined) { + currency = this.currency (code); + request['symbol'] = currency['id']; + } + if (since !== undefined) { + request['from'] = since * 1000000; + } + if (limit !== undefined) { + request['limit'] = limit; // max 5000 + } + const response = await this.privateGetTransfersAccountsAccountIdBalanceHistory (this.extend (request, params)); + // + // [ + // { + // "accountId":8263118, + // "ts":1551974415000000000, + // "amount":"-1", + // "currency":"BTC", + // "kind":"internal withdrawal", + // "commission":"0", + // "id":96 + // }, + // { + // "accountId":8263118, + // "ts":1551964677000000000, + // "amount":"-1", + // "currency":"BTC", + // "kind":"internal deposit", + // "commission":"0", + // "id":95 + // } + // ] + // + return this.parseLedger (response, currency, since, limit); + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api] + '/' + this.implodeParams (path, params); + const query = this.omit (params, this.extractParams (path)); + if (api === 'public') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (api === 'private') { + this.checkRequiredCredentials (); + let nonce = this.nonce (); + // php does not format it properly + // therefore we use string concatenation here + // nonce *= 1000000; + nonce = nonce.toString (); + nonce = nonce + '000000'; // see the comment a few lines above + const payload = 'AUTH' + nonce; + const secret = this.secret.slice (14, 78); + const ecdsa = this.ecdsa (payload, secret, 'p256', 'sha256'); + const signature = ecdsa['r'] + ecdsa['s']; + headers = { + 'X-AUTH-API-KEY': this.apiKey, + 'X-AUTH-API-PAYLOAD': payload, + 'X-AUTH-API-SIGNATURE': signature, + 'X-AUTH-API-NONCE': nonce, + }; + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else if (method === 'POST') { + body = this.json (query); + headers['Content-Type'] = 'application/json'; + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error":"Validation failed","fields":["address"]} + // {"error":"Money not enough. You have only: 0 ETH","fields":["amount"]} + // + if (code >= 400) { + const feedback = this.id + ' ' + this.json (response); + const message = this.safeString (response, 'error'); + const exact = this.exceptions['exact']; + if (message in exact) { + throw new exact[message] (feedback); + } + const broad = this.exceptions['broad']; + const broadKey = this.findBroadlyMatchedKey (broad, body); + if (broadKey !== undefined) { + throw new broad[broadKey] (feedback); + } + throw new ExchangeError (feedback); // unknown message + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/yobit.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/yobit.js new file mode 100644 index 000000000..46fc1d99d --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/yobit.js @@ -0,0 +1,837 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, ArgumentsRequired, ExchangeNotAvailable, InvalidNonce, InsufficientFunds, OrderNotFound, DDoSProtection, InvalidOrder, AuthenticationError, RateLimitExceeded } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class yobit extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'yobit', + 'name': 'YoBit', + 'countries': [ 'RU' ], + 'rateLimit': 3000, // responses are cached every 2 seconds + 'version': '3', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createDepositAddress': true, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchDeposits': false, + 'fetchMarkets': true, + 'fetchMyTrades': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrderBooks': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'fetchTransactions': false, + 'fetchWithdrawals': false, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg', + 'api': { + 'public': 'https://yobit.net/api', + 'private': 'https://yobit.net/tapi', + }, + 'www': 'https://www.yobit.net', + 'doc': 'https://www.yobit.net/en/api/', + 'fees': 'https://www.yobit.net/en/fees/', + }, + 'api': { + 'public': { + 'get': [ + 'depth/{pair}', + 'info', + 'ticker/{pair}', + 'trades/{pair}', + ], + }, + 'private': { + 'post': [ + 'ActiveOrders', + 'CancelOrder', + 'GetDepositAddress', + 'getInfo', + 'OrderInfo', + 'Trade', + 'TradeHistory', + 'WithdrawCoinsToAddress', + ], + }, + }, + 'fees': { + 'trading': { + 'maker': 0.002, + 'taker': 0.002, + }, + 'funding': { + 'withdraw': {}, + }, + }, + 'commonCurrencies': { + 'AIR': 'AirCoin', + 'ANI': 'ANICoin', + 'ANT': 'AntsCoin', // what is this, a coin for ants? + 'ATMCHA': 'ATM', + 'ASN': 'Ascension', + 'AST': 'Astral', + 'ATM': 'Autumncoin', + 'BCC': 'BCH', + 'BCS': 'BitcoinStake', + 'BITS': 'Bitstar', + 'BLN': 'Bulleon', + 'BOT': 'BOTcoin', + 'BON': 'BONES', + 'BPC': 'BitcoinPremium', + 'BTS': 'Bitshares2', + 'CAT': 'BitClave', + 'CBC': 'CryptoBossCoin', + 'CMT': 'CometCoin', + 'COV': 'Coven Coin', + 'COVX': 'COV', + 'CPC': 'Capricoin', + 'CREDIT': 'Creditbit', + 'CS': 'CryptoSpots', + 'DCT': 'Discount', + 'DFT': 'DraftCoin', + 'DGD': 'DarkGoldCoin', + 'DIRT': 'DIRTY', + 'DROP': 'FaucetCoin', + 'DSH': 'DASH', + 'EKO': 'EkoCoin', + 'ENTER': 'ENTRC', + 'EPC': 'ExperienceCoin', + 'ESC': 'EdwardSnowden', + 'EUROPE': 'EUROP', + 'EXT': 'LifeExtension', + 'FUNK': 'FUNKCoin', + 'GCC': 'GlobalCryptocurrency', + 'GEN': 'Genstake', + 'GENE': 'Genesiscoin', + 'GOLD': 'GoldMint', + 'GOT': 'Giotto Coin', + 'HTML5': 'HTML', + 'HYPERX': 'HYPER', + 'ICN': 'iCoin', + 'INSANE': 'INSN', + 'JNT': 'JointCoin', + 'JPC': 'JupiterCoin', + 'KNC': 'KingN Coin', + 'LBTCX': 'LiteBitcoin', + 'LIZI': 'LiZi', + 'LOC': 'LocoCoin', + 'LOCX': 'LOC', + 'LUNYR': 'LUN', + 'LUN': 'LunarCoin', // they just change the ticker if it is already taken + 'MDT': 'Midnight', + 'NAV': 'NavajoCoin', + 'NBT': 'NiceBytes', + 'OMG': 'OMGame', + 'PAC': '$PAC', + 'PLAY': 'PlayCoin', + 'PIVX': 'Darknet', + 'PRS': 'PRE', + 'PUTIN': 'PutinCoin', + 'STK': 'StakeCoin', + 'SUB': 'Subscriptio', + 'PAY': 'EPAY', + 'PLC': 'Platin Coin', + 'RCN': 'RCoin', + 'REP': 'Republicoin', + 'RUR': 'RUB', + 'TTC': 'TittieCoin', + 'UNI': 'Universe', + 'UST': 'Uservice', + 'VOL': 'VolumeCoin', + 'XIN': 'XINCoin', + 'XRA': 'Ratecoin', + }, + 'options': { + // 'fetchTickersMaxLength': 2048, + 'fetchOrdersRequiresSymbol': true, + 'fetchTickersMaxLength': 512, + }, + 'exceptions': { + 'exact': { + '803': InvalidOrder, // "Count could not be less than 0.001." (selling below minAmount) + '804': InvalidOrder, // "Count could not be more than 10000." (buying above maxAmount) + '805': InvalidOrder, // "price could not be less than X." (minPrice violation on buy & sell) + '806': InvalidOrder, // "price could not be more than X." (maxPrice violation on buy & sell) + '807': InvalidOrder, // "cost could not be less than X." (minCost violation on buy & sell) + '831': InsufficientFunds, // "Not enougth X to create buy order." (buying with balance.quote < order.cost) + '832': InsufficientFunds, // "Not enougth X to create sell order." (selling with balance.base < order.amount) + '833': OrderNotFound, // "Order with id X was not found." (cancelling non-existent, closed and cancelled order) + }, + 'broad': { + 'Invalid pair name': ExchangeError, // {"success":0,"error":"Invalid pair name: btc_eth"} + 'invalid api key': AuthenticationError, + 'invalid sign': AuthenticationError, + 'api key dont have trade permission': AuthenticationError, + 'invalid parameter': InvalidOrder, + 'invalid order': InvalidOrder, + 'The given order has already been cancelled': InvalidOrder, + 'Requests too often': DDoSProtection, + 'not available': ExchangeNotAvailable, + 'data unavailable': ExchangeNotAvailable, + 'external service unavailable': ExchangeNotAvailable, + 'Total transaction amount': InvalidOrder, // { "success": 0, "error": "Total transaction amount is less than minimal total: 0.00010000"} + 'The given order has already been closed and cannot be cancelled': InvalidOrder, + 'Insufficient funds': InsufficientFunds, + 'invalid key': AuthenticationError, + 'invalid nonce': InvalidNonce, // {"success":0,"error":"invalid nonce (has already been used)"}' + 'Total order amount is less than minimal amount': InvalidOrder, + 'Rate Limited': RateLimitExceeded, + }, + }, + 'orders': {}, // orders cache / emulation + }); + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfo (params); + // + // { + // "success":1, + // "return":{ + // "funds":{ + // "ltc":22, + // "nvc":423.998, + // "ppc":10, + // }, + // "funds_incl_orders":{ + // "ltc":32, + // "nvc":523.998, + // "ppc":20, + // }, + // "rights":{ + // "info":1, + // "trade":0, + // "withdraw":0 + // }, + // "transaction_count":0, + // "open_orders":1, + // "server_time":1418654530 + // } + // } + // + const balances = this.safeValue (response, 'return', {}); + const result = { 'info': response }; + const free = this.safeValue (balances, 'funds', {}); + const total = this.safeValue (balances, 'funds_incl_orders', {}); + const currencyIds = Object.keys (this.extend (free, total)); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const account = this.account (); + account['free'] = this.safeFloat (free, currencyId); + account['total'] = this.safeFloat (total, currencyId); + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchMarkets (params = {}) { + const response = await this.publicGetInfo (params); + const markets = this.safeValue (response, 'pairs'); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ baseId, quoteId ] = id.split ('_'); + let base = baseId.toUpperCase (); + let quote = quoteId.toUpperCase (); + base = this.safeCurrencyCode (base); + quote = this.safeCurrencyCode (quote); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'decimal_places'), + 'price': this.safeInteger (market, 'decimal_places'), + }; + const amountLimits = { + 'min': this.safeFloat (market, 'min_amount'), + 'max': this.safeFloat (market, 'max_amount'), + }; + const priceLimits = { + 'min': this.safeFloat (market, 'min_price'), + 'max': this.safeFloat (market, 'max_price'), + }; + const costLimits = { + 'min': this.safeFloat (market, 'min_total'), + }; + const limits = { + 'amount': amountLimits, + 'price': priceLimits, + 'cost': costLimits, + }; + const hidden = this.safeInteger (market, 'hidden'); + const active = (hidden === 0); + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': active, + 'taker': market['fee'] / 100, + 'precision': precision, + 'limits': limits, + 'info': market, + }); + } + return result; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; // default = 150, max = 2000 + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const market_id_in_reponse = (market['id'] in response); + if (!market_id_in_reponse) { + throw new ExchangeError (this.id + ' ' + market['symbol'] + ' order book is empty or not available'); + } + const orderbook = response[market['id']]; + return this.parseOrderBook (orderbook); + } + + async fetchOrderBooks (symbols = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let ids = undefined; + if (symbols === undefined) { + ids = this.ids.join ('-'); + // max URL length is 2083 symbols, including http schema, hostname, tld, etc... + if (ids.length > 2048) { + const numIds = this.ids.length; + throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + // 'ignore_invalid': true, + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetDepthPair (this.extend (request, params)); + const result = {}; + ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const symbol = this.safeSymbol (id); + result[symbol] = this.parseOrderBook (response[id]); + } + return result; + } + + parseTicker (ticker, market = undefined) { + // + // { high: 0.03497582, + // low: 0.03248474, + // avg: 0.03373028, + // vol: 120.11485715062999, + // vol_cur: 3572.24914074, + // last: 0.0337611, + // buy: 0.0337442, + // sell: 0.03377798, + // updated: 1537522009 } + // + const timestamp = this.safeTimestamp (ticker, 'updated'); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': this.safeFloat (ticker, 'avg'), + 'baseVolume': this.safeFloat (ticker, 'vol_cur'), + 'quoteVolume': this.safeFloat (ticker, 'vol'), + 'info': ticker, + }; + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + let ids = this.ids; + if (symbols === undefined) { + const numIds = ids.length; + ids = ids.join ('-'); + const maxLength = this.safeInteger (this.options, 'fetchTickersMaxLength', 2048); + // max URL length is 2048 symbols, including http schema, hostname, tld, etc... + if (ids.length > this.options['fetchTickersMaxLength']) { + throw new ArgumentsRequired (this.id + ' has ' + numIds.toString () + ' markets exceeding max URL length for this endpoint (' + maxLength.toString () + ' characters), please, specify a list of symbols of interest in the first argument to fetchTickers'); + } + } else { + ids = this.marketIds (symbols); + ids = ids.join ('-'); + } + const request = { + 'pair': ids, + }; + const tickers = await this.publicGetTickerPair (this.extend (request, params)); + const result = {}; + const keys = Object.keys (tickers); + for (let k = 0; k < keys.length; k++) { + const id = keys[k]; + const ticker = tickers[id]; + const market = this.safeMarket (id); + const symbol = market['symbol']; + result[symbol] = this.parseTicker (ticker, market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + const tickers = await this.fetchTickers ([ symbol ], params); + return tickers[symbol]; + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'timestamp'); + let side = this.safeString (trade, 'type'); + if (side === 'ask') { + side = 'sell'; + } else if (side === 'bid') { + side = 'buy'; + } + const price = this.safeFloat2 (trade, 'rate', 'price'); + const id = this.safeString2 (trade, 'trade_id', 'tid'); + const order = this.safeString (trade, 'order_id'); + const marketId = this.safeString (trade, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const amount = this.safeFloat (trade, 'amount'); + const type = 'limit'; // all trades are still limit trades + let fee = undefined; + const feeCost = this.safeFloat (trade, 'commission'); + if (feeCost !== undefined) { + const feeCurrencyId = this.safeString (trade, 'commissionCurrency'); + const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); + fee = { + 'cost': feeCost, + 'currency': feeCurrencyCode, + }; + } + const isYourOrder = this.safeValue (trade, 'is_your_order'); + if (isYourOrder !== undefined) { + if (fee === undefined) { + fee = this.calculateFee (symbol, type, side, amount, price, 'taker'); + } + } + let cost = undefined; + if (amount !== undefined) { + if (price !== undefined) { + cost = amount * price; + } + } + return { + 'id': id, + 'order': order, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': type, + 'side': side, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': fee, + 'info': trade, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + if (limit !== undefined) { + request['limit'] = limit; + } + const response = await this.publicGetTradesPair (this.extend (request, params)); + if (Array.isArray (response)) { + const numElements = response.length; + if (numElements === 0) { + return []; + } + } + return this.parseTrades (response[market['id']], market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type === 'market') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + 'type': side, + 'amount': this.amountToPrecision (symbol, amount), + 'rate': this.priceToPrecision (symbol, price), + }; + price = parseFloat (price); + amount = parseFloat (amount); + const response = await this.privatePostTrade (this.extend (request, params)); + let id = undefined; + let status = 'open'; + let filled = 0.0; + let remaining = amount; + if ('return' in response) { + id = this.safeString (response['return'], 'order_id'); + if (id === '0') { + id = this.safeString (response['return'], 'init_order_id'); + status = 'closed'; + } + filled = this.safeFloat (response['return'], 'received', 0.0); + remaining = this.safeFloat (response['return'], 'remains', amount); + } + const timestamp = this.milliseconds (); + return { + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': status, + 'symbol': symbol, + 'type': type, + 'side': side, + 'price': price, + 'cost': price * filled, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'fee': undefined, + // 'trades': this.parseTrades (order['trades'], market), + 'info': response, + 'clientOrderId': undefined, + 'average': undefined, + 'trades': undefined, + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'closed', + '2': 'canceled', + '3': 'open', // or partially-filled and canceled? https://github.com/ccxt/ccxt/issues/1594 + }; + return this.safeString (statuses, status, status); + } + + parseOrder (order, market = undefined) { + const id = this.safeString (order, 'id'); + const status = this.parseOrderStatus (this.safeString (order, 'status')); + const timestamp = this.safeTimestamp (order, 'timestamp_created'); + const marketId = this.safeString (order, 'pair'); + const symbol = this.safeSymbol (marketId, market); + const remaining = this.safeFloat (order, 'amount'); + const amount = this.safeFloat (order, 'start_amount'); + const price = this.safeFloat (order, 'rate'); + let filled = undefined; + let cost = undefined; + if (amount !== undefined) { + if (remaining !== undefined) { + filled = Math.max (0, amount - remaining); + cost = price * filled; + } + } + const fee = undefined; + const type = 'limit'; + const side = this.safeString (order, 'type'); + const result = { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'remaining': remaining, + 'filled': filled, + 'status': status, + 'fee': fee, + 'average': undefined, + 'trades': undefined, + }; + return result; + } + + async fetchOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'order_id': parseInt (id), + }; + const response = await this.privatePostOrderInfo (this.extend (request, params)); + id = id.toString (); + const orders = this.safeValue (response, 'return', {}); + return this.parseOrder (this.extend ({ 'id': id }, orders[id])); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument'); + } + await this.loadMarkets (); + const request = {}; + const market = undefined; + if (symbol !== undefined) { + const market = this.market (symbol); + request['pair'] = market['id']; + } + const response = await this.privatePostActiveOrders (this.extend (request, params)); + const orders = this.safeValue (response, 'return', []); + return this.parseOrders (orders, market, since, limit); + } + + async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a `symbol` argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + // some derived classes use camelcase notation for request fields + const request = { + // 'from': 123456789, // trade ID, from which the display starts numerical 0 (test result: liqui ignores this field) + // 'count': 1000, // the number of trades for display numerical, default = 1000 + // 'from_id': trade ID, from which the display starts numerical 0 + // 'end_id': trade ID on which the display ends numerical ∞ + // 'order': 'ASC', // sorting, default = DESC (test result: liqui ignores this field, most recent trade always goes last) + // 'since': 1234567890, // UTC start time, default = 0 (test result: liqui ignores this field) + // 'end': 1234567890, // UTC end time, default = ∞ (test result: liqui ignores this field) + 'pair': market['id'], + }; + if (limit !== undefined) { + request['count'] = parseInt (limit); + } + if (since !== undefined) { + request['since'] = parseInt (since / 1000); + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + const trades = this.safeValue (response, 'return', {}); + const ids = Object.keys (trades); + const result = []; + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + const trade = this.parseTrade (this.extend (trades[id], { + 'trade_id': id, + }), market); + result.push (trade); + } + return this.filterBySymbolSinceLimit (result, symbol, since, limit); + } + + async createDepositAddress (code, params = {}) { + const request = { + 'need_new': 1, + }; + const response = await this.fetchDepositAddress (code, this.extend (request, params)); + const address = this.safeString (response, 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response['info'], + }; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coinName': currency['id'], + 'need_new': 0, + }; + const response = await this.privatePostGetDepositAddress (this.extend (request, params)); + const address = this.safeString (response['return'], 'address'); + this.checkAddress (address); + return { + 'currency': code, + 'address': address, + 'tag': undefined, + 'info': response, + }; + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'coinName': currency['id'], + 'amount': amount, + 'address': address, + }; + // no docs on the tag, yet... + if (tag !== undefined) { + throw new ExchangeError (this.id + ' withdraw() does not support the tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.'); + } + const response = await this.privatePostWithdrawCoinsToAddress (this.extend (request, params)); + return { + 'info': response, + 'id': undefined, + }; + } + + calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { + const market = this.markets[symbol]; + let key = 'quote'; + const rate = market[takerOrMaker]; + let cost = parseFloat (this.costToPrecision (symbol, amount * rate)); + if (side === 'sell') { + cost *= price; + } else { + key = 'base'; + } + return { + 'type': takerOrMaker, + 'currency': market[key], + 'rate': rate, + 'cost': cost, + }; + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + const query = this.omit (params, this.extractParams (path)); + if (api === 'private') { + this.checkRequiredCredentials (); + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'nonce': nonce, + 'method': path, + }, query)); + const signature = this.hmac (this.encode (body), this.encode (this.secret), 'sha512'); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': signature, + }; + } else if (api === 'public') { + url += '/' + this.version + '/' + this.implodeParams (path, params); + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + url += '/' + this.implodeParams (path, params); + if (method === 'GET') { + if (Object.keys (query).length) { + url += '?' + this.urlencode (query); + } + } else { + if (Object.keys (query).length) { + body = this.json (query); + headers = { + 'Content-Type': 'application/json', + }; + } + } + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if ('success' in response) { + // + // 1 - Liqui only returns the integer 'success' key from their private API + // + // { "success": 1, ... } httpCode === 200 + // { "success": 0, ... } httpCode === 200 + // + // 2 - However, exchanges derived from Liqui, can return non-integers + // + // It can be a numeric string + // { "sucesss": "1", ... } + // { "sucesss": "0", ... }, httpCode >= 200 (can be 403, 502, etc) + // + // Or just a string + // { "success": "true", ... } + // { "success": "false", ... }, httpCode >= 200 + // + // Or a boolean + // { "success": true, ... } + // { "success": false, ... }, httpCode >= 200 + // + // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types + // + // 4 - We do not want to copy-paste and duplicate the code of this handler to other exchanges derived from Liqui + // + // To cover points 1, 2, 3 and 4 combined this handler should work like this: + // + let success = this.safeValue (response, 'success', false); + if (typeof success === 'string') { + if ((success === 'true') || (success === '1')) { + success = true; + } else { + success = false; + } + } + if (!success) { + const code = this.safeString (response, 'code'); + const message = this.safeString (response, 'error'); + const feedback = this.id + ' ' + body; + this.throwExactlyMatchedException (this.exceptions['exact'], code, feedback); + this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); + throw new ExchangeError (feedback); // unknown message + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/zaif.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/zaif.js new file mode 100644 index 000000000..b9618184e --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/zaif.js @@ -0,0 +1,488 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { ExchangeError, BadRequest } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class zaif extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'zaif', + 'name': 'Zaif', + 'countries': [ 'JP' ], + 'rateLimit': 2000, + 'version': '1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchClosedOrders': true, + 'fetchMarkets': true, + 'fetchOrderBook': true, + 'fetchOpenOrders': true, + 'fetchTicker': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/27766927-39ca2ada-5eeb-11e7-972f-1b4199518ca6.jpg', + 'api': 'https://api.zaif.jp', + 'www': 'https://zaif.jp', + 'doc': [ + 'https://techbureau-api-document.readthedocs.io/ja/latest/index.html', + 'https://corp.zaif.jp/api-docs', + 'https://corp.zaif.jp/api-docs/api_links', + 'https://www.npmjs.com/package/zaif.jp', + 'https://github.com/you21979/node-zaif', + ], + 'fees': 'https://zaif.jp/fee?lang=en', + }, + 'fees': { + 'trading': { + 'percentage': true, + 'taker': 0.1 / 100, + 'maker': 0, + }, + }, + 'api': { + 'public': { + 'get': [ + 'depth/{pair}', + 'currencies/{pair}', + 'currencies/all', + 'currency_pairs/{pair}', + 'currency_pairs/all', + 'last_price/{pair}', + 'ticker/{pair}', + 'trades/{pair}', + ], + }, + 'private': { + 'post': [ + 'active_orders', + 'cancel_order', + 'deposit_history', + 'get_id_info', + 'get_info', + 'get_info2', + 'get_personal_info', + 'trade', + 'trade_history', + 'withdraw', + 'withdraw_history', + ], + }, + 'ecapi': { + 'post': [ + 'createInvoice', + 'getInvoice', + 'getInvoiceIdsByOrderNumber', + 'cancelInvoice', + ], + }, + 'tlapi': { + 'post': [ + 'get_positions', + 'position_history', + 'active_positions', + 'create_position', + 'change_position', + 'cancel_position', + ], + }, + 'fapi': { + 'get': [ + 'groups/{group_id}', + 'last_price/{group_id}/{pair}', + 'ticker/{group_id}/{pair}', + 'trades/{group_id}/{pair}', + 'depth/{group_id}/{pair}', + ], + }, + }, + 'options': { + // zaif schedule defines several market-specific fees + 'fees': { + 'BTC/JPY': { 'maker': 0, 'taker': 0 }, + 'BCH/JPY': { 'maker': 0, 'taker': 0.3 / 100 }, + 'BCH/BTC': { 'maker': 0, 'taker': 0.3 / 100 }, + 'PEPECASH/JPY': { 'maker': 0, 'taker': 0.01 / 100 }, + 'PEPECASH/BT': { 'maker': 0, 'taker': 0.01 / 100 }, + }, + }, + 'exceptions': { + 'exact': { + 'unsupported currency_pair': BadRequest, // {"error": "unsupported currency_pair"} + }, + 'broad': { + }, + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetCurrencyPairsAll (params); + const result = []; + for (let i = 0; i < markets.length; i++) { + const market = markets[i]; + const id = this.safeString (market, 'currency_pair'); + const name = this.safeString (market, 'name'); + const [ baseId, quoteId ] = name.split ('/'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': -Math.log10 (market['item_unit_step']), + 'price': market['aux_unit_point'], + }; + const fees = this.safeValue (this.options['fees'], symbol, this.fees['trading']); + const taker = fees['taker']; + const maker = fees['maker']; + result.push ({ + 'id': id, + 'symbol': symbol, + 'base': base, + 'quote': quote, + 'baseId': baseId, + 'quoteId': quoteId, + 'active': true, // can trade or not + 'precision': precision, + 'taker': taker, + 'maker': maker, + 'limits': { + 'amount': { + 'min': this.safeFloat (market, 'item_unit_min'), + 'max': undefined, + }, + 'price': { + 'min': this.safeFloat (market, 'aux_unit_min'), + 'max': undefined, + }, + 'cost': { + 'min': undefined, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privatePostGetInfo (params); + const balances = this.safeValue (response, 'return', {}); + const result = { 'info': response }; + const funds = this.safeValue (balances, 'funds', {}); + const currencyIds = Object.keys (funds); + for (let i = 0; i < currencyIds.length; i++) { + const currencyId = currencyIds[i]; + const code = this.safeCurrencyCode (currencyId); + const balance = this.safeValue (funds, currencyId); + const account = { + 'free': balance, + 'used': 0.0, + 'total': balance, + }; + if ('deposit' in balances) { + if (currencyId in balances['deposit']) { + account['total'] = this.safeFloat (balances['deposit'], currencyId); + account['used'] = account['total'] - account['free']; + } + } + result[code] = account; + } + return this.parseBalance (result); + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const response = await this.publicGetDepthPair (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const request = { + 'pair': this.marketId (symbol), + }; + const ticker = await this.publicGetTickerPair (this.extend (request, params)); + const timestamp = this.milliseconds (); + const vwap = this.safeFloat (ticker, 'vwap'); + const baseVolume = this.safeFloat (ticker, 'volume'); + let quoteVolume = undefined; + if (baseVolume !== undefined && vwap !== undefined) { + quoteVolume = baseVolume * vwap; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'bid'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'ask'), + 'askVolume': undefined, + 'vwap': vwap, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': baseVolume, + 'quoteVolume': quoteVolume, + 'info': ticker, + }; + } + + parseTrade (trade, market = undefined) { + let side = this.safeString (trade, 'trade_type'); + side = (side === 'bid') ? 'buy' : 'sell'; + const timestamp = this.safeTimestamp (trade, 'date'); + const id = this.safeString2 (trade, 'id', 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = amount * price; + } + } + const marketId = this.safeString (trade, 'currency_pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + return { + 'id': id, + 'info': trade, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'pair': market['id'], + }; + let response = await this.publicGetTradesPair (this.extend (request, params)); + const numTrades = response.length; + if (numTrades === 1) { + const firstTrade = response[0]; + if (!Object.keys (firstTrade).length) { + response = []; + } + } + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + await this.loadMarkets (); + if (type !== 'limit') { + throw new ExchangeError (this.id + ' allows limit orders only'); + } + const request = { + 'currency_pair': this.marketId (symbol), + 'action': (side === 'buy') ? 'bid' : 'ask', + 'amount': amount, + 'price': price, + }; + const response = await this.privatePostTrade (this.extend (request, params)); + return { + 'info': response, + 'id': response['return']['order_id'].toString (), + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + const request = { + 'order_id': id, + }; + return await this.privatePostCancelOrder (this.extend (request, params)); + } + + parseOrder (order, market = undefined) { + // + // { + // "currency_pair": "btc_jpy", + // "action": "ask", + // "amount": 0.03, + // "price": 56000, + // "timestamp": 1402021125, + // "comment" : "demo" + // } + // + let side = this.safeString (order, 'action'); + side = (side === 'bid') ? 'buy' : 'sell'; + const timestamp = this.safeTimestamp (order, 'timestamp'); + const marketId = this.safeString (order, 'currency_pair'); + const symbol = this.safeSymbol (marketId, market, '_'); + const price = this.safeFloat (order, 'price'); + const amount = this.safeFloat (order, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + const id = this.safeString (order, 'id'); + return { + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'status': 'open', + 'symbol': symbol, + 'type': 'limit', + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'cost': cost, + 'amount': amount, + 'filled': undefined, + 'remaining': undefined, + 'trades': undefined, + 'fee': undefined, + 'info': order, + 'average': undefined, + }; + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // 'is_token': false, + // 'is_token_both': false, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['currency_pair'] = market['id']; + } + const response = await this.privatePostActiveOrders (this.extend (request, params)); + return this.parseOrders (response['return'], market, since, limit); + } + + async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + let market = undefined; + const request = { + // 'from': 0, + // 'count': 1000, + // 'from_id': 0, + // 'end_id': 1000, + // 'order': 'DESC', + // 'since': 1503821051, + // 'end': 1503821051, + // 'is_token': false, + }; + if (symbol !== undefined) { + market = this.market (symbol); + request['currency_pair'] = market['id']; + } + const response = await this.privatePostTradeHistory (this.extend (request, params)); + return this.parseOrders (response['return'], market, since, limit); + } + + async withdraw (code, amount, address, tag = undefined, params = {}) { + this.checkAddress (address); + await this.loadMarkets (); + const currency = this.currency (code); + if (code === 'JPY') { + throw new ExchangeError (this.id + ' withdraw() does not allow ' + code + ' withdrawals'); + } + const request = { + 'currency': currency['id'], + 'amount': amount, + 'address': address, + // 'message': 'Hi!', // XEM and others + // 'opt_fee': 0.003, // BTC and MONA only + }; + if (tag !== undefined) { + request['message'] = tag; + } + const result = await this.privatePostWithdraw (this.extend (request, params)); + return { + 'info': result, + 'id': result['return']['txid'], + 'fee': result['return']['fee'], + }; + } + + nonce () { + const nonce = parseFloat (this.milliseconds () / 1000); + return nonce.toFixed (8); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'] + '/'; + if (api === 'public') { + url += 'api/' + this.version + '/' + this.implodeParams (path, params); + } else if (api === 'fapi') { + url += 'fapi/' + this.version + '/' + this.implodeParams (path, params); + } else { + this.checkRequiredCredentials (); + if (api === 'ecapi') { + url += 'ecapi'; + } else if (api === 'tlapi') { + url += 'tlapi'; + } else { + url += 'tapi'; + } + const nonce = this.nonce (); + body = this.urlencode (this.extend ({ + 'method': path, + 'nonce': nonce, + }, params)); + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Key': this.apiKey, + 'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'), + }; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; + } + // + // {"error": "unsupported currency_pair"} + // + const feedback = this.id + ' ' + body; + const error = this.safeString (response, 'error'); + if (error !== undefined) { + this.throwExactlyMatchedException (this.exceptions['exact'], error, feedback); + this.throwBroadlyMatchedException (this.exceptions['broad'], error, feedback); + throw new ExchangeError (feedback); // unknown message + } + const success = this.safeValue (response, 'success', true); + if (!success) { + throw new ExchangeError (feedback); + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/zb.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/zb.js new file mode 100644 index 000000000..9b4d81872 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/js/zb.js @@ -0,0 +1,653 @@ +'use strict'; + +// --------------------------------------------------------------------------- + +const Exchange = require ('./base/Exchange'); +const { BadRequest, ExchangeError, ArgumentsRequired, AuthenticationError, InsufficientFunds, OrderNotFound, ExchangeNotAvailable, DDoSProtection, InvalidOrder } = require ('./base/errors'); + +// --------------------------------------------------------------------------- + +module.exports = class zb extends Exchange { + describe () { + return this.deepExtend (super.describe (), { + 'id': 'zb', + 'name': 'ZB', + 'countries': [ 'CN' ], + 'rateLimit': 1000, + 'version': 'v1', + 'has': { + 'cancelOrder': true, + 'CORS': false, + 'createMarketOrder': false, + 'createOrder': true, + 'fetchBalance': true, + 'fetchDepositAddress': true, + 'fetchMarkets': true, + 'fetchOHLCV': true, + 'fetchOpenOrders': true, + 'fetchOrder': true, + 'fetchOrderBook': true, + 'fetchOrders': true, + 'fetchTicker': true, + 'fetchTickers': true, + 'fetchTrades': true, + 'withdraw': true, + }, + 'timeframes': { + '1m': '1min', + '3m': '3min', + '5m': '5min', + '15m': '15min', + '30m': '30min', + '1h': '1hour', + '2h': '2hour', + '4h': '4hour', + '6h': '6hour', + '12h': '12hour', + '1d': '1day', + '3d': '3day', + '1w': '1week', + }, + 'exceptions': { + // '1000': 'Successful operation', + '1001': ExchangeError, // 'General error message', + '1002': ExchangeError, // 'Internal error', + '1003': AuthenticationError, // 'Verification does not pass', + '1004': AuthenticationError, // 'Funding security password lock', + '1005': AuthenticationError, // 'Funds security password is incorrect, please confirm and re-enter.', + '1006': AuthenticationError, // 'Real-name certification pending approval or audit does not pass', + '1009': ExchangeNotAvailable, // 'This interface is under maintenance', + '2001': InsufficientFunds, // 'Insufficient CNY Balance', + '2002': InsufficientFunds, // 'Insufficient BTC Balance', + '2003': InsufficientFunds, // 'Insufficient LTC Balance', + '2005': InsufficientFunds, // 'Insufficient ETH Balance', + '2006': InsufficientFunds, // 'Insufficient ETC Balance', + '2007': InsufficientFunds, // 'Insufficient BTS Balance', + '2009': InsufficientFunds, // 'Account balance is not enough', + '3001': OrderNotFound, // 'Pending orders not found', + '3002': InvalidOrder, // 'Invalid price', + '3003': InvalidOrder, // 'Invalid amount', + '3004': AuthenticationError, // 'User does not exist', + '3005': BadRequest, // 'Invalid parameter', + '3006': AuthenticationError, // 'Invalid IP or inconsistent with the bound IP', + '3007': AuthenticationError, // 'The request time has expired', + '3008': OrderNotFound, // 'Transaction records not found', + '3009': InvalidOrder, // 'The price exceeds the limit', + '3011': InvalidOrder, // 'The entrusted price is abnormal, please modify it and place order again', + '4001': ExchangeNotAvailable, // 'API interface is locked or not enabled', + '4002': DDoSProtection, // 'Request too often', + }, + 'urls': { + 'logo': 'https://user-images.githubusercontent.com/1294454/32859187-cd5214f0-ca5e-11e7-967d-96568e2e2bd1.jpg', + 'api': { + 'public': 'http://api.zb.cn/data', // no https for public API + 'private': 'https://trade.zb.cn/api', + }, + 'www': 'https://www.zb.com', + 'doc': 'https://www.zb.com/i/developer', + 'fees': 'https://www.zb.com/i/rate', + }, + 'api': { + 'public': { + 'get': [ + 'markets', + 'ticker', + 'allTicker', + 'depth', + 'trades', + 'kline', + ], + }, + 'private': { + 'get': [ + // spot API + 'order', + 'cancelOrder', + 'getOrder', + 'getOrders', + 'getOrdersNew', + 'getOrdersIgnoreTradeType', + 'getUnfinishedOrdersIgnoreTradeType', + 'getAccountInfo', + 'getUserAddress', + 'getWithdrawAddress', + 'getWithdrawRecord', + 'getChargeRecord', + 'getCnyWithdrawRecord', + 'getCnyChargeRecord', + 'withdraw', + // leverage API + 'getLeverAssetsInfo', + 'getLeverBills', + 'transferInLever', + 'transferOutLever', + 'loan', + 'cancelLoan', + 'getLoans', + 'getLoanRecords', + 'borrow', + 'repay', + 'getRepayments', + ], + }, + }, + 'fees': { + 'funding': { + 'withdraw': { + 'BTC': 0.0001, + 'BCH': 0.0006, + 'LTC': 0.005, + 'ETH': 0.01, + 'ETC': 0.01, + 'BTS': 3, + 'EOS': 1, + 'QTUM': 0.01, + 'HSR': 0.001, + 'XRP': 0.1, + 'USDT': '0.1%', + 'QCASH': 5, + 'DASH': 0.002, + 'BCD': 0, + 'UBTC': 0, + 'SBTC': 0, + 'INK': 20, + 'TV': 0.1, + 'BTH': 0, + 'BCX': 0, + 'LBTC': 0, + 'CHAT': 20, + 'bitCNY': 20, + 'HLC': 20, + 'BTP': 0, + 'BCW': 0, + }, + }, + 'trading': { + 'maker': 0.2 / 100, + 'taker': 0.2 / 100, + }, + }, + 'commonCurrencies': { + 'ENT': 'ENTCash', + }, + }); + } + + async fetchMarkets (params = {}) { + const markets = await this.publicGetMarkets (params); + const keys = Object.keys (markets); + const result = []; + for (let i = 0; i < keys.length; i++) { + const id = keys[i]; + const market = markets[id]; + const [ baseId, quoteId ] = id.split ('_'); + const base = this.safeCurrencyCode (baseId); + const quote = this.safeCurrencyCode (quoteId); + const symbol = base + '/' + quote; + const precision = { + 'amount': this.safeInteger (market, 'amountScale'), + 'price': this.safeInteger (market, 'priceScale'), + }; + result.push ({ + 'id': id, + 'symbol': symbol, + 'baseId': baseId, + 'quoteId': quoteId, + 'base': base, + 'quote': quote, + 'active': true, + 'precision': precision, + 'limits': { + 'amount': { + 'min': Math.pow (10, -precision['amount']), + 'max': undefined, + }, + 'price': { + 'min': Math.pow (10, -precision['price']), + 'max': undefined, + }, + 'cost': { + 'min': 0, + 'max': undefined, + }, + }, + 'info': market, + }); + } + return result; + } + + async fetchBalance (params = {}) { + await this.loadMarkets (); + const response = await this.privateGetGetAccountInfo (params); + // todo: use this somehow + // let permissions = response['result']['base']; + const balances = this.safeValue (response['result'], 'coins'); + const result = { 'info': response }; + for (let i = 0; i < balances.length; i++) { + const balance = balances[i]; + // { enName: "BTC", + // freez: "0.00000000", + // unitDecimal: 8, // always 8 + // cnName: "BTC", + // isCanRecharge: true, // TODO: should use this + // unitTag: "฿", + // isCanWithdraw: true, // TODO: should use this + // available: "0.00000000", + // key: "btc" } + const account = this.account (); + const currencyId = this.safeString (balance, 'key'); + const code = this.safeCurrencyCode (currencyId); + account['free'] = this.safeFloat (balance, 'available'); + account['used'] = this.safeFloat (balance, 'freez'); + result[code] = account; + } + return this.parseBalance (result); + } + + getMarketFieldName () { + return 'market'; + } + + async fetchDepositAddress (code, params = {}) { + await this.loadMarkets (); + const currency = this.currency (code); + const request = { + 'currency': currency['id'], + }; + const response = await this.privateGetGetUserAddress (this.extend (request, params)); + let address = response['message']['datas']['key']; + let tag = undefined; + if (address.indexOf ('_') >= 0) { + const parts = address.split ('_'); + address = parts[0]; // WARNING: MAY BE tag_address INSTEAD OF address_tag FOR SOME CURRENCIES!! + tag = parts[1]; + } + return { + 'currency': code, + 'address': address, + 'tag': tag, + 'info': response, + }; + } + + async fetchOrderBook (symbol, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketFieldName = this.getMarketFieldName (); + const request = {}; + request[marketFieldName] = market['id']; + if (limit !== undefined) { + request['size'] = limit; + } + const response = await this.publicGetDepth (this.extend (request, params)); + return this.parseOrderBook (response); + } + + async fetchTickers (symbols = undefined, params = {}) { + await this.loadMarkets (); + const response = await this.publicGetAllTicker (params); + const result = {}; + const anotherMarketsById = {}; + const marketIds = Object.keys (this.marketsById); + for (let i = 0; i < marketIds.length; i++) { + const tickerId = marketIds[i].replace ('_', ''); + anotherMarketsById[tickerId] = this.marketsById[marketIds[i]]; + } + const ids = Object.keys (response); + for (let i = 0; i < ids.length; i++) { + const market = anotherMarketsById[ids[i]]; + result[market['symbol']] = this.parseTicker (response[ids[i]], market); + } + return this.filterByArray (result, 'symbol', symbols); + } + + async fetchTicker (symbol, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketFieldName = this.getMarketFieldName (); + const request = {}; + request[marketFieldName] = market['id']; + const response = await this.publicGetTicker (this.extend (request, params)); + const ticker = response['ticker']; + return this.parseTicker (ticker, market); + } + + parseTicker (ticker, market = undefined) { + const timestamp = this.milliseconds (); + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + const last = this.safeFloat (ticker, 'last'); + return { + 'symbol': symbol, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'high': this.safeFloat (ticker, 'high'), + 'low': this.safeFloat (ticker, 'low'), + 'bid': this.safeFloat (ticker, 'buy'), + 'bidVolume': undefined, + 'ask': this.safeFloat (ticker, 'sell'), + 'askVolume': undefined, + 'vwap': undefined, + 'open': undefined, + 'close': last, + 'last': last, + 'previousClose': undefined, + 'change': undefined, + 'percentage': undefined, + 'average': undefined, + 'baseVolume': this.safeFloat (ticker, 'vol'), + 'quoteVolume': undefined, + 'info': ticker, + }; + } + + async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + if (limit === undefined) { + limit = 1000; + } + const request = { + 'market': market['id'], + 'type': this.timeframes[timeframe], + 'limit': limit, + }; + if (since !== undefined) { + request['since'] = since; + } + const response = await this.publicGetKline (this.extend (request, params)); + const data = this.safeValue (response, 'data', []); + return this.parseOHLCVs (data, market, timeframe, since, limit); + } + + parseTrade (trade, market = undefined) { + const timestamp = this.safeTimestamp (trade, 'date'); + let side = this.safeString (trade, 'trade_type'); + side = (side === 'bid') ? 'buy' : 'sell'; + const id = this.safeString (trade, 'tid'); + const price = this.safeFloat (trade, 'price'); + const amount = this.safeFloat (trade, 'amount'); + let cost = undefined; + if (price !== undefined) { + if (amount !== undefined) { + cost = price * amount; + } + } + let symbol = undefined; + if (market !== undefined) { + symbol = market['symbol']; + } + return { + 'info': trade, + 'id': id, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'symbol': symbol, + 'type': undefined, + 'side': side, + 'order': undefined, + 'takerOrMaker': undefined, + 'price': price, + 'amount': amount, + 'cost': cost, + 'fee': undefined, + }; + } + + async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { + await this.loadMarkets (); + const market = this.market (symbol); + const marketFieldName = this.getMarketFieldName (); + const request = {}; + request[marketFieldName] = market['id']; + const response = await this.publicGetTrades (this.extend (request, params)); + return this.parseTrades (response, market, since, limit); + } + + async createOrder (symbol, type, side, amount, price = undefined, params = {}) { + if (type !== 'limit') { + throw new InvalidOrder (this.id + ' allows limit orders only'); + } + await this.loadMarkets (); + const request = { + 'price': this.priceToPrecision (symbol, price), + 'amount': this.amountToPrecision (symbol, amount), + 'tradeType': (side === 'buy') ? '1' : '0', + 'currency': this.marketId (symbol), + }; + const response = await this.privateGetOrder (this.extend (request, params)); + return { + 'info': response, + 'id': response['id'], + }; + } + + async cancelOrder (id, symbol = undefined, params = {}) { + await this.loadMarkets (); + const request = { + 'id': id.toString (), + 'currency': this.marketId (symbol), + }; + return await this.privateGetCancelOrder (this.extend (request, params)); + } + + async fetchOrder (id, symbol = undefined, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument'); + } + await this.loadMarkets (); + const request = { + 'id': id.toString (), + 'currency': this.marketId (symbol), + }; + const response = await this.privateGetGetOrder (this.extend (request, params)); + // + // { + // 'total_amount': 0.01, + // 'id': '20180910244276459', + // 'price': 180.0, + // 'trade_date': 1536576744960, + // 'status': 2, + // 'trade_money': '1.96742', + // 'trade_amount': 0.01, + // 'type': 0, + // 'currency': 'eth_usdt' + // } + // + return this.parseOrder (response, undefined); + } + + async fetchOrders (symbol = undefined, since = undefined, limit = 50, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + 'fetchOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'pageIndex': 1, // default pageIndex is 1 + 'pageSize': limit, // default pageSize is 50 + }; + let method = 'privateGetGetOrdersIgnoreTradeType'; + // tradeType 交易类型1/0[buy/sell] + if ('tradeType' in params) { + method = 'privateGetGetOrdersNew'; + } + let response = undefined; + try { + response = await this[method] (this.extend (request, params)); + } catch (e) { + if (e instanceof OrderNotFound) { + return []; + } + throw e; + } + return this.parseOrders (response, market, since, limit); + } + + async fetchOpenOrders (symbol = undefined, since = undefined, limit = 10, params = {}) { + if (symbol === undefined) { + throw new ArgumentsRequired (this.id + 'fetchOpenOrders requires a symbol argument'); + } + await this.loadMarkets (); + const market = this.market (symbol); + const request = { + 'currency': market['id'], + 'pageIndex': 1, // default pageIndex is 1 + 'pageSize': limit, // default pageSize is 10 + }; + let method = 'privateGetGetUnfinishedOrdersIgnoreTradeType'; + // tradeType 交易类型1/0[buy/sell] + if ('tradeType' in params) { + method = 'privateGetGetOrdersNew'; + } + let response = undefined; + try { + response = await this[method] (this.extend (request, params)); + } catch (e) { + if (e instanceof OrderNotFound) { + return []; + } + throw e; + } + return this.parseOrders (response, market, since, limit); + } + + parseOrder (order, market = undefined) { + // + // fetchOrder + // + // { + // 'total_amount': 0.01, + // 'id': '20180910244276459', + // 'price': 180.0, + // 'trade_date': 1536576744960, + // 'status': 2, + // 'trade_money': '1.96742', + // 'trade_amount': 0.01, + // 'type': 0, + // 'currency': 'eth_usdt' + // } + // + let side = this.safeInteger (order, 'type'); + side = (side === 1) ? 'buy' : 'sell'; + const type = 'limit'; // market order is not availalbe in ZB + let timestamp = undefined; + const createDateField = this.getCreateDateField (); + if (createDateField in order) { + timestamp = order[createDateField]; + } + const marketId = this.safeString (order, 'currency'); + const symbol = this.safeSymbol (marketId, market, '_'); + const price = this.safeFloat (order, 'price'); + const filled = this.safeFloat (order, 'trade_amount'); + const amount = this.safeFloat (order, 'total_amount'); + let remaining = undefined; + if (amount !== undefined) { + if (filled !== undefined) { + remaining = amount - filled; + } + } + const cost = this.safeFloat (order, 'trade_money'); + let average = undefined; + const status = this.parseOrderStatus (this.safeString (order, 'status')); + if ((cost !== undefined) && (filled !== undefined) && (filled > 0)) { + average = cost / filled; + } + const id = this.safeString (order, 'id'); + return { + 'info': order, + 'id': id, + 'clientOrderId': undefined, + 'timestamp': timestamp, + 'datetime': this.iso8601 (timestamp), + 'lastTradeTimestamp': undefined, + 'symbol': symbol, + 'type': type, + 'timeInForce': undefined, + 'side': side, + 'price': price, + 'stopPrice': undefined, + 'average': average, + 'cost': cost, + 'amount': amount, + 'filled': filled, + 'remaining': remaining, + 'status': status, + 'fee': undefined, + 'trades': undefined, + }; + } + + parseOrderStatus (status) { + const statuses = { + '0': 'open', + '1': 'canceled', + '2': 'closed', + '3': 'open', // partial + }; + return this.safeString (statuses, status, status); + } + + getCreateDateField () { + return 'trade_date'; + } + + nonce () { + return this.milliseconds (); + } + + sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { + let url = this.urls['api'][api]; + if (api === 'public') { + url += '/' + this.version + '/' + path; + if (Object.keys (params).length) { + url += '?' + this.urlencode (params); + } + } else { + let query = this.keysort (this.extend ({ + 'method': path, + 'accesskey': this.apiKey, + }, params)); + const nonce = this.nonce (); + query = this.keysort (query); + const auth = this.rawencode (query); + const secret = this.hash (this.encode (this.secret), 'sha1'); + const signature = this.hmac (this.encode (auth), this.encode (secret), 'md5'); + const suffix = 'sign=' + signature + '&reqTime=' + nonce.toString (); + url += '/' + path + '?' + auth + '&' + suffix; + } + return { 'url': url, 'method': method, 'body': body, 'headers': headers }; + } + + handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { + if (response === undefined) { + return; // fallback to default error handler + } + if (body[0] === '{') { + const feedback = this.id + ' ' + body; + if ('code' in response) { + const code = this.safeString (response, 'code'); + this.throwExactlyMatchedException (this.exceptions, code, feedback); + if (code !== '1000') { + throw new ExchangeError (feedback); + } + } + // special case for {"result":false,"message":"服务端忙碌"} (a "Busy Server" reply) + const result = this.safeValue (response, 'result'); + if (result !== undefined) { + if (!result) { + const message = this.safeString (response, 'message'); + if (message === '服务端忙碌') { + throw new ExchangeNotAvailable (feedback); + } else { + throw new ExchangeError (feedback); + } + } + } + } + } +}; diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/package.json b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/package.json new file mode 100644 index 000000000..1ba15bbf9 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/package.json @@ -0,0 +1,573 @@ +{ + "_from": "ccxt", + "_id": "ccxt@1.38.72", + "_inBundle": false, + "_integrity": "sha512-ERlYG0wKrzo9ltSlLHvYZ2QC/WG1pMwJfZp+EWsaT27a2FAF+ejUlo6QT+CeSexUb61Y/aIlPwRyFwn6zxhIuA==", + "_location": "/ccxt", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "ccxt", + "name": "ccxt", + "escapedName": "ccxt", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.38.72.tgz", + "_shasum": "8f5a76322f9d84272471a146be604a1978f802ab", + "_spec": "ccxt", + "_where": "c:\\Superalgos\\Projects\\Superalgos\\TS\\Bot-Modules\\Sensor-Bot", + "author": { + "name": "Igor Kroitor", + "email": "igor.kroitor@gmail.com", + "url": "https://github.com/kroitor" + }, + "bugs": { + "url": "https://github.com/ccxt/ccxt/issues" + }, + "bundleDependencies": false, + "collective": { + "type": "opencollective", + "url": "https://opencollective.com/ccxt", + "logo": "https://opencollective.com/ccxt/logo.txt" + }, + "dependencies": {}, + "deprecated": false, + "description": "A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges", + "devDependencies": { + "ansicolor": "1.1.81", + "as-table": "1.0.37", + "asciichart": "1.5.25", + "browserify": "14.5.0", + "chai": "4.1.2", + "chai-as-promised": "7.1.1", + "eslint": "^6.8.0", + "eslint-config-airbnb-base": "14.1.0", + "eslint-plugin-import": "2.20.1", + "mocha": "^8.1.3", + "ololog": "1.1.155" + }, + "engines": { + "node": ">=7.6.0" + }, + "ethereum": "0x26a3CB49578F07000575405a57888681249c35Fd", + "homepage": "https://ccxt.trade", + "keywords": [ + "algorithmic", + "algotrading", + "altcoin", + "altcoins", + "api", + "arbitrage", + "real-time", + "realtime", + "backtest", + "backtesting", + "bitcoin", + "bot", + "btc", + "cny", + "coin", + "coins", + "crypto", + "cryptocurrency", + "crypto currency", + "crypto market", + "currency", + "currencies", + "darkcoin", + "dash", + "digital currency", + "doge", + "dogecoin", + "e-commerce", + "etc", + "eth", + "ether", + "ethereum", + "exchange", + "exchanges", + "eur", + "framework", + "invest", + "investing", + "investor", + "library", + "light", + "litecoin", + "ltc", + "market", + "market data", + "markets", + "merchandise", + "merchant", + "minimal", + "ohlcv", + "order", + "orderbook", + "order book", + "price", + "price data", + "pricefeed", + "private", + "public", + "ripple", + "strategy", + "ticker", + "tickers", + "toolkit", + "trade", + "trader", + "trading", + "usd", + "volume", + "websocket", + "websockets", + "web socket", + "web sockets", + "ws", + "xbt", + "xrp", + "zec", + "zerocoin", + "1Broker", + "1BTCXE", + "ACX", + "acx.io", + "allcoin", + "allcoin.com", + "ANX", + "ANXPro", + "bibox", + "bibox.com", + "Binance", + "binance.com", + "bit2c.co.il", + "Bit2C", + "BitBay", + "BitBays", + "bitcoincoid", + "Bitcoin.co.id", + "Bitfinex", + "bitFLyer", + "bitflyer.jp", + "bithumb", + "bithumb.com", + "bitlish", + "BitMarket", + "BitMEX", + "Bitso", + "Bitstamp", + "Bittrex", + "BL3P", + "Bleutrade", + "bleutrade.com", + "BlinkTrade", + "braziliex", + "braziliex.com", + "BtcBox", + "btcbox.co.jp", + "BTCC", + "BTCChina", + "BTC-e", + "BTCe", + "BTCExchange", + "btcexchange.ph", + "BTC Markets", + "btcmarkets", + "btcmarkets.net", + "BTCTrader", + "btctrader.com", + "btc-trade.com.ua", + "BTC Trade UA", + "BTCTurk", + "btcturk.com", + "BTCX", + "btc-x", + "bter", + "Bter.com", + "BX.in.th", + "ccex", + "C-CEX", + "cex", + "CEX.IO", + "CHBTC", + "ChileBit", + "chilebit.net", + "coincheck", + "CoinExchange", + "coinexchange.io", + "coingi", + "coingi.com", + "CoinMarketCap", + "CoinMate", + "Coinsecure", + "CoinSpot", + "coinspot.com.au", + "Crypto Capital", + "cryptocapital.co", + "DSX", + "dsx.uk", + "EXMO", + "flowBTC", + "flowbtc.com", + "FoxBit", + "foxbit.exchange", + "FYB-SE", + "FYB-SG", + "Gatecoin", + "GDAX", + "Gemini", + "HitBTC", + "Huobi", + "HuobiPRO", + "huobi.pro", + "Independent Reserve", + "independentreserve.com", + "itBit", + "jubi.com", + "Kraken", + "Kucoin", + "Kuna", + "LakeBTC", + "lakebtc.com", + "LiveCoin", + "Liqui", + "liqui.io", + "luno", + "mercado", + "MercadoBitcoin", + "mercadobitcoin.br", + "mixcoins", + "mixcoins.com", + "nova", + "novaexchange", + "novaexchange.com", + "OKCoin", + "OKCoin.com", + "OKCoin.cn", + "OKEX", + "okex.com", + "Paymium", + "Poloniex", + "QuadrigaCX", + "Qryptos", + "QUOINEX", + "Southxchange", + "SurBitcoin", + "surbitcoin.com", + "Tidex", + "tidex.com", + "TheRockTrading", + "UrduBit", + "urdubit.com", + "Vaultoro", + "VBTC", + "vbtc.exchange", + "vbtc.vn", + "VirWoX", + "WEX", + "wex.nz", + "xBTCe", + "xbtce.com", + "YoBit", + "yobit.net", + "YUNBI", + "Zaif", + "ZB", + "1btcxe.com", + "Allcoin", + "anxpro.com", + "anybits.com", + "Anybits", + "bcex.top", + "BCEX", + "Bibox", + "big.one", + "BigONE", + "bitbank.cc", + "bitbank", + "bitbay.net", + "bitfinex.com", + "bitFlyer", + "bitforex.com", + "Bitforex", + "Bithumb", + "bitibu.com", + "Bitibu", + "bitkk.com", + "bitkk", + "bitlish.com", + "Bitlish", + "bitmarket.pl", + "bitmarket.net", + "bitmex.com", + "bitsane.com", + "Bitsane", + "bitso.com", + "bitstamp.net", + "bittrex.com", + "bit-z.com", + "Bit-Z", + "bl3p.eu", + "bitonic.nl", + "Braziliex", + "btc-alpha.com", + "BTC-Alpha", + "btcchina.com", + "btctrade.im", + "BtcTrade.im", + "buda.com", + "Buda", + "bx.in.th", + "c-cex.com", + "cex.io", + "trade.chbtc.com", + "cobinhood.com", + "COBINHOOD", + "coinbase.com", + "Coinbase", + "prime.coinbase.com", + "Coinbase Prime", + "pro.coinbase.com", + "Coinbase Pro", + "coincheck.com", + "coinegg.com", + "CoinEgg", + "coinex.com", + "CoinEx", + "coinfalcon.com", + "CoinFalcon", + "coinfloor.co.uk", + "coinfloor", + "Coingi", + "coinmarketcap.com", + "coinmate.io", + "coinnest.co.kr", + "coinnest", + "coinone.co.kr", + "CoinOne", + "cointiger.pro", + "CoinTiger", + "coolcoin.com", + "CoolCoin", + "coss.io", + "COSS", + "crex24.com", + "CREX24", + "cryptonbtc.com", + "Crypton", + "deribit.com", + "Deribit", + "ethfinex.com", + "Ethfinex", + "exmo.me", + "exx.com", + "EXX", + "fcoin.com", + "FCoin", + "trader.flowbtc.com", + "fybse.se", + "fybsg.com", + "gatecoin.com", + "gate.io", + "Gate.io", + "gdax.com", + "gemini.com", + "getbtc.org", + "GetBTC", + "hadax.com", + "HADAX", + "hitbtc.com", + "Huobi Pro", + "huobi.com.ru", + "Huobi Russia", + "ice3x.com", + "ice3x.co.za", + "ICE3X", + "indodax.com", + "INDODAX", + "itbit.com", + "kkex.com", + "KKEX", + "kraken.com", + "kucoin.com", + "KuCoin", + "kuna.io", + "lbank.info", + "LBank", + "liquid.com", + "Liquid", + "livecoin.net", + "luno.com", + "lykke.com", + "Lykke", + "mercadobitcoin.com.br", + "Mercado Bitcoin", + "MixCoins", + "negociecoins.com.br", + "NegocieCoins", + "Novaexchange", + "okcoin.cn", + "OKCoin CNY", + "okcoin.com", + "OKCoin USD", + "paymium.com", + "poloniex.com", + "quadrigacx.com", + "rightbtc.com", + "RightBTC", + "southxchange.com", + "SouthXchange", + "stronghold.co", + "Stronghold", + "theocean.trade", + "The Ocean", + "therocktrading.com", + "tidebit.com", + "TideBit", + "uex.com", + "UEX", + "upbit.com", + "Upbit", + "vaultoro.com", + "virwox.com", + "yunbi.com", + "zaif.jp", + "zb.com", + "fcoinjp.com", + "FCoinJP", + "binance.je", + "Binance Jersey", + "bequant.io", + "Bequant", + "dx.exchange", + "DX.Exchange", + "oceanex.pro.com", + "OceanEx", + "flowbtc.com.br", + "foxbit.com.br", + "latoken.com", + "Latoken", + "bitmart.com", + "BitMart", + "digifinex.vip", + "DigiFinex", + "idex.market", + "IDEX", + "adara.io", + "Adara", + "binance.us", + "Binance US", + "whitebit.com", + "WhiteBit", + "bitmax.io", + "BitMax", + "bytetrade.com", + "ByteTrade", + "ftx.com", + "FTX", + "{hostname}", + "bw.com", + "stex.com", + "STEX", + "BW", + "timex.io", + "TimeX", + "bitz.com", + "topliq.com", + "TOP.Q", + "hollaex.com", + "HollaEx", + "bybit.com", + "Bybit", + "aofex.com", + "AOFEX", + "byte-trade.com", + "hbtc.com", + "HBTC", + "probit.com", + "ProBit", + "eterbase.com", + "ETERBASE", + "Eterbase", + "qtrade.io", + "qTrade", + "dsxglobal.com", + "bitvavo.com", + "Bitvavo", + "currency.com", + "Currency.com", + "waves.exchange", + "Waves.Exchange", + "phemex.com", + "Phemex", + "huobi.co.jp", + "Huobi Japan", + "digifinex.com", + "bitflyer.com", + "bitpanda.com", + "Bitpanda", + "Bitpanda Pro", + "xena.exchange", + "Xena Exchange", + "bitget.com", + "Bitget", + "idex.io", + "novadax.com.br", + "NovaDAX", + "exchange.ripio.com", + "Ripio", + "huobi.com", + "exchange.bitcoin.com", + "bitcoin.com", + "bibox365.com", + "vcc.exchange", + "VCC Exchange" + ], + "license": "MIT", + "main": "./ccxt.js", + "name": "ccxt", + "repository": { + "type": "git", + "url": "git+https://github.com/ccxt/ccxt.git" + }, + "scripts": { + "browserify": "browserify ./ccxt.browser.js > ./dist/ccxt.browser.js", + "build": "npm run build-without-docs && npm run update-badges", + "build-without-docs": "npm run export-exchanges && npm run vss && npm run copy-python-files && npm run check-js-syntax && npm run browserify && npm run transpile && npm run check-python-syntax && npm run check-php-syntax", + "check-js-syntax": "pwd && eslint --version && eslint \"js/*.js\"", + "check-php-syntax": "php -f php/test/syntax.php", + "check-python-syntax": "cd python && tox -e qa && cd ..", + "check-syntax": "npm run transpile && npm run check-js-syntax && npm run check-python-syntax && npm run check-php-syntax", + "copy-python-files": "npm run copy-python-package && npm run copy-python-license && npm run copy-python-keys && npm run copy-python-readme", + "copy-python-keys": "node build/copy keys.json python/keys.json", + "copy-python-license": "node build/copy LICENSE.txt python/LICENSE.txt", + "copy-python-package": "node build/copy package.json python/package.json", + "copy-python-readme": "node build/copy README.md python/README.md", + "docker": "docker-compose run --rm ccxt", + "export-exchanges": "node build/export-exchanges", + "fast-test": "node run-tests --js", + "git-ignore-generated-files": "node build/git-ignore-generated-files", + "git-unignore-generated-files": "node build/git-ignore-generated-files --unignore", + "lint": "eslint", + "postinstall": "node postinstall", + "test": "npm run build && node run-tests", + "test-base": "npm run test-js-base && npm run test-python-base && npm run test-php-base", + "test-js-base": "mocha js/test/base/test.base.js --reporter ololog/reporter", + "test-php-base": "php -f php/test/decimal_to_precision.php && php -f php/test/test_crypto.php", + "test-python-base": "python python/test/test_decimal_to_precision.py", + "transpile": "node build/transpile", + "update-badges": "node build/update-badges", + "vss": "node build/vss" + }, + "types": "./ccxt.d.ts", + "unpkg": "dist/ccxt.browser.js", + "version": "1.38.72" +} diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/postinstall.js b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/postinstall.js new file mode 100644 index 000000000..20f5b2fd4 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/node_modules/ccxt/postinstall.js @@ -0,0 +1,95 @@ +const fetch = require ('./js/static_dependencies/fetch-ponyfill/fetch-node') ().fetch + +function style(s, style) { + return style + s + '\033[0m' +} + +const colors = { + black: 30, + red: 31, + green: 32, + yellow: 33, + blue: 34, + white: 37, + gray: 90, +} + +let colorFunctions = {} +for (let color of Object.keys (colors)) { + colorFunctions[color] = (s) => console.log (style (s, '\033[' + colors[color].toString () + 'm')) +} + +let ascii = [ + ' ', + ' :Siiiiiiiiiiir rSiiiiiiiiiiS:', + ' r&9hh&&&&&&&A5 SG99h&&&&&&GHr', + ' ;hX32;::::::;, i9X9S:;:::::;,', + ' ;hX9S ihXhr ', + ' ;hX32::::::,:, i9X9i::::::,:.', + ' rG999GGGGGGGAS iG99hGGGGGGGAr', + ' ;2S55SSSSSSS2r r2555SSSSSSS2;', + ' ;2S5s ;2S2r r2SS555555SS2;', + ' rAh&2 sAhAS SAGGh9999GGGAr', + ' .:,::rrrs::::, ,:,,;9X3X:,,:.', + ' &A&H, ,hX33 ', + ' ,;:;;;;;r;;:;, ,hX3X. ', + ' rHGAX sAGA5 :&9h9. ', + ' :Ssir ;isir ,Siii ', + ' ', +] + +let footer = [ + ' ', + ' ---------------------------------------------------', + ' ', + ' You can contribute in crypto directly: ', + ' ', + ' ETH 0x26a3CB49578F07000575405a57888681249c35Fd ', + ' BTC 33RmVRfhK2WZVQR1R83h2e9yXoqRNDvJva ', + ' BCH 1GN9p233TvNcNQFthCgfiHUnj5JRKEc2Ze ', + ' LTC LbT8mkAqQBphc4yxLXEDgYDfEax74et3bP ', + ' ', + ' ---------------------------------------------------', + ' ', + ' Thank you! ', + ' ', +] + +async function getData () { + const collectiveData = await (await fetch ('https://opencollective.com/ccxt.json')).json () + const githubData = await (await fetch ('https://api.github.com/repos/ccxt/ccxt')).json () + + return { + contributors: collectiveData['contributorsCount'].toLocaleString (), + backers: collectiveData['backersCount'].toLocaleString (), + balance: Math.floor (collectiveData['balance'] / 100).toLocaleString (), + budget: Math.floor (collectiveData['yearlyIncome'] / 100).toLocaleString (), + stars: githubData['stargazers_count'].toLocaleString (), + forks: githubData['forks_count'].toLocaleString (), + size: (githubData['size'] / 1000000).toFixed (2) + } +} + +function pad (string) { + const padding = 80 - string.length + const half = Math.floor (padding / 2) + return ' '.repeat (half + (padding % 2)) + string + ' '.repeat (half) +} + +async function main () { + const data = await getData() + + colorFunctions['blue'] (ascii.join ('\n')) + colorFunctions['red'] (pad (`Stars: ${data.stars}`)) + colorFunctions['red'] (pad (`Forks: ${data.forks}`)) + colorFunctions['red'] (pad (`Contributors: ${data.contributors}`)) + colorFunctions['red'] (pad (`Size: ${data.size}MB`)) + colorFunctions['yellow'] ('\n' + pad ('Thanks for installing ccxt 🙏')) + colorFunctions['gray'] (pad ('Please consider donating to our open collective')) + colorFunctions['gray'] (pad ('to help us maintain this package.')) + colorFunctions['yellow'] (pad ('👉 Donate: https://opencollective.com/ccxt/donate 🎉')) + colorFunctions['white'] (pad (`Thanks to our ${data.backers} backers we are operating on an annual budget of $${data.budget}`)) + colorFunctions['yellow'] (footer.join ('\n')) +} + +main() diff --git a/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/package-lock.json b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/package-lock.json new file mode 100644 index 000000000..791fa70c2 --- /dev/null +++ b/Projects/Superalgos/TS/Bot-Modules/Sensor-Bot/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "ccxt": { + "version": "1.38.72", + "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.38.72.tgz", + "integrity": "sha512-ERlYG0wKrzo9ltSlLHvYZ2QC/WG1pMwJfZp+EWsaT27a2FAF+ejUlo6QT+CeSexUb61Y/aIlPwRyFwn6zxhIuA==" + } + } +}