├── .gitignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json ├── src ├── abi │ ├── erc20.json │ └── weth.json ├── contracts │ ├── erc20.d.ts │ ├── types.d.ts │ └── weth.d.ts ├── errors │ └── errors.ts ├── index.ts ├── lib │ ├── ApiHandler.ts │ ├── HydroClient.ts │ ├── HydroWatcher.ts │ └── Web3Handler.ts └── models │ ├── Candle.ts │ ├── Channel.ts │ ├── Fee.ts │ ├── LockedBalance.ts │ ├── Market.ts │ ├── Order.ts │ ├── OrderData.ts │ ├── OrderList.ts │ ├── Orderbook.ts │ ├── PriceLevel.ts │ ├── Ticker.ts │ ├── Trade.ts │ └── TradeList.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/ 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # VS Code settings 61 | .vscode 62 | 63 | # Quick test file directory 64 | .scratch -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "parser": "babylon", 5 | "trailingComma": "es5", 6 | "jsxBracketSameLine": true, 7 | "semi": true, 8 | "singleQuote": true, 9 | "overrides": [ 10 | { 11 | "files": ["*.json", ".eslintrc", ".tslintrc", ".prettierrc", ".tern-project", "*.abi"], 12 | "options": { 13 | "parser": "json", 14 | "tabWidth": 2 15 | } 16 | }, 17 | { 18 | "files": "*.{css,sass,scss,less}", 19 | "options": { 20 | "parser": "css", 21 | "tabWidth": 4 22 | } 23 | }, 24 | { 25 | "files": "*.{ts,tsx}", 26 | "options": { 27 | "parser": "typescript" 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript Client for the Hydro API 2 | 3 | A client written in Typescript for interacting with the Hydro API 4 | 5 | ### New in version 2.x 6 | 7 | - Updated functions to match new Hydro API specs 8 | - Added support for market orders 9 | - Added convenience functions for wrapping eth and approving tokens 10 | 11 | # What is the Hydro Protocol? 12 | 13 | Hydro Protocol is an open-source framework for building decentralized exchanges on Ethereum. Hydro is designed for developers looking to build decentralized exchanges without having to deal with the complexity and expense of designing, deploying, and securing their own smart contracts. 14 | 15 | For more information, please visit https://www.hydroprotocol.io/ 16 | 17 | # What is this Client for? 18 | 19 | The Client is built to provide easy access to the Hydro API. The API is intended to give you full access to the state of the market, and to help you easily create new orders based on that information. Using this API, you can write helpers to visualize the market data however you like, clients to help you create orders more easily, or full on bots that will analyze the incoming data and place orders automatically. 20 | 21 | By default, this Client connects to the DDEX exchange, but is compatible with any exchange running the Hydro API. 22 | 23 | For full API specs, please see the documentation: https://docs.ddex.io/ 24 | 25 | # Getting Started 26 | 27 | To get started, simply install the package through npm: 28 | 29 | ```bash 30 | npm i @hydro-protocol/hydro-client-js 31 | ``` 32 | 33 | Once you've done that there are two main interfaces into the API. 34 | 35 | ## HydroClient 36 | 37 | HydroClient is used to query the API for data. Initialize it with your private key to start making API calls. 38 | 39 | ```javascript 40 | import { HydroClient } from "@hydro-protocol/hydro-client-js"; 41 | 42 | let client = HydroClient.withPrivateKey("0x..."); // Your private key here 43 | 44 | // Get a list of all markets and their details 45 | let markets = await client.listMarkets(); 46 | 47 | // Create, sign, and submit a market order for HOT 48 | let order = await client.createOrder("HOT-WETH", "buy", "market", "0.01", "100"); 49 | ``` 50 | 51 | ### Instantiating a client 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 71 | 72 | 73 | 80 | 83 | 84 | 85 | 94 | 97 | 98 | 99 |
MethodNotes
63 |
 64 |   HydroClient.withoutAuth(
 65 |     options?: HydroClientOptions,
 66 |   )
67 |
69 | Returns an unauthenticated instance of the HydroClient. This instance can query any public API method but will throw if you try to query any private methods. 70 |
74 |
 75 |   HydroClient.withPrivateKey(
 76 |     privatekey: string,
 77 |     options?: HydroClientOptions,
 78 |   )
79 |
81 | Returns an authenticated instance of the HydroClient using the provided Private Key to sign messages and transactions. 82 |
86 |
 87 |   HydroClient.withCustomAuth(
 88 |     address: string,
 89 |     sign: (message: string) => Promise<string>,
 90 |     signTransaction: (tx: Transaction) => Promise<string>,
 91 |     options?: HydroClientOptions,
 92 |   )
93 |
95 | If you are uncomfortable passing your private key directly to the client code, you can implement your own custom authentication using whatever tools you like. You will need to provide:

The address of the wallet

A sign method which returns a Promise resolving to a string representing the signature of the message in hex.

A signTransaction method which returns a Promise resolving to a string representing the signature of the transaction in hex. 96 |
100 | 101 | Each instantiation method takes an options object with the following parameters: 102 | 103 | #### HydroClientOptions 104 | 105 | | Parameter | Type | Notes | 106 | | --------- | ---- | ----- | 107 | | **apiUrl** | **string** | The url to the Hydro API you wish to query. This defaults to the DDEX exchange running on mainnet: https://api.ddex.io/v3/ | 108 | | **web3Url** | **string** | The url to use to query the blockchain. This is required if you wish to use the blockchain helper methods. Recommended to register an account on [Infura](https://infura.io) and use the mainnet url provided to your account. | 109 | 110 | ### Using the HydroClient to query the API 111 | 112 | HydroClient was built to largely mirror the actual Hydro API, so if you see a method in the [API docs](https://docs.ddex.io/) you should be able to find a way to call it from HydroClient. Here is a brief rundown of available API calls. 113 | 114 | #### Public 115 | 116 | These methods do not need a valid signature in order to return data, and can be called without an authenticated HydroClient instance. [Public Rest API](https://docs.ddex.io/#public-rest-api). 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 130 | 133 | 134 | 135 | 138 | 141 | 142 | 143 | 146 | 149 | 150 | 151 | 154 | 157 | 158 | 159 | 162 | 165 | 166 | 167 | 174 | 177 | 178 | 179 | 187 | 190 | 191 | 192 | 199 | 202 | 203 | 204 |
MethodAPI Docs Link
128 |
listMarkets()
129 |
131 | List Markets 132 |
136 |
getMarket(marketId: string)
137 |
139 | Get a Market 140 |
144 |
listTickers()
145 |
147 | List Tickers 148 |
152 |
getTicker(marketId: string)
153 |
155 | Get a Ticker 156 |
160 |
getOrderbook(marketId: string, level?: number)
161 |
163 | Get Orderbook 164 |
168 |
listTrades(
169 |   marketId: string,
170 |   page?: number,
171 |   perPage?: number,
172 | )
173 |
175 | List Trades 176 |
180 |
listCandles(
181 |   marketId: string,
182 |   from: number,
183 |   to: number,
184 |   granularity: number,
185 | )
186 |
188 | List Candles 189 |
193 |
calculateFees(
194 |   marketId: string,
195 |   price: string,
196 |   amount: string,
197 | )
198 |
200 | Calculate Fees 201 |
205 | 206 | #### Private 207 | 208 | These methods return data tied to a specific account, and therefore require an authenticated HydroClient instance. [Private Rest API](https://docs.ddex.io/#private-rest-api). 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 229 | 232 | 233 | 234 | 237 | 240 | 241 | 242 | 252 | 255 | 256 | 257 | 260 | 263 | 264 | 265 | 273 | 276 | 277 | 278 | 281 | 284 | 285 | 286 | 293 | 296 | 297 | 298 | 301 | 304 | 305 | 306 | 309 | 312 | 313 | 314 |
MethodAPI Docs Link
220 |
buildOrder(
221 |   marketId: string,
222 |   side: string,
223 |   orderType: string,
224 |   price: string,
225 |   amount: string,
226 |   expires?: number,
227 | )
228 |
230 | Build Unsigned Order 231 |
235 |
placeOrder(orderId: string, signature: string)
236 |
238 | Place Order 239 |
243 |
createOrder(
244 |   marketId: string,
245 |   side: string,
246 |   orderType: string,
247 |   price: string,
248 |   amount: string,
249 |   expires?: number,
250 | )
251 |
253 | Convenience method. Will build, sign, and place the order in one step. See Build Unsigned Order for parameter details. 254 |
258 |
cancelOrder(orderId: string)
259 |
261 | Cancel Order 262 |
266 |
listOrders(
267 |   marketId?: string,
268 |   status?: string,
269 |   page?: number,
270 |   perPage?: number,
271 | )
272 |
274 | List Orders 275 |
279 |
getOrder(orderId: string)
280 |
282 | Get Order 283 |
287 |
listAccountTrades(
288 |   marketId: string,
289 |   page?: number,
290 |   perPage?: number,
291 | )
292 |
294 | List Account Trades 295 |
299 |
listLockedBalances()
300 |
302 | List Locked Balances 303 |
307 |
getLockedBalance(symbol: string)
308 |
310 | Get Locked Balance 311 |
315 | 316 | #### Blockchain 317 | 318 | These methods do not query the Hydro API directly, but instead perform actions directly against the blockchain. As these actions will be tied to your account, you must use an authenticated HydroClient instance. 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 332 | 335 | 336 | 337 | 340 | 343 | 344 | 345 | 348 | 351 | 352 | 353 | 356 | 359 | 360 | 361 | 364 | 367 | 368 | 369 | 372 | 375 | 376 | 377 |
MethodAPI Docs Link
330 |
getBalance(symbol?: string)
331 |
333 | Returns the balance in your account for the token specified by the given symbol. If no symbol is passed it will return your account's ETH balance. 334 |
338 |
wrapEth(amount: string, wait?: boolean)
339 |
341 | Wraps the specified amount of ETH into WETH for trading on the Hydro API. If wait is true, the Promise will only resolve once the transaction has been confirmed on the blockchain. See Wrapping Ether for details. 342 |
346 |
unwrapEth(amount: string, wait?: boolean)
347 |
349 | Unwraps the specified amount of WETH back into ETH. If wait is true, the Promise will only resolve once the transaction has been confirmed on the blockchain. See Wrapping Ether for details. 350 |
354 |
isTokenEnabled(symbol: string)
355 |
357 | Returns whether this token has been enabled for sale on the Hydro API. 358 |
362 |
enableToken(symbol: string, wait?: boolean)
363 |
365 | Enables this token for sale on the Hydro API. If wait is true, the Promise will only resolve once the transaction has been confirmed on the blockchain. See Enabling Token Trading for details. 366 |
370 |
disableToken(symbol: string, wait?: boolean)
371 |
373 | Disables this token for sale on the Hydro API. If wait is true, the Promise will only resolve once the transaction has been confirmed on the blockchain. See Enabling Token Trading for details. 374 |
378 | 379 | ## HydroWatcher 380 | 381 | Our other API interface is HydroWatcher, which is used to get live updates about the state of the market. It will connect to our websocket endpoint and notify you about any changes to the exchange that you are subscribed to. All of this data is public, so no authentication is required. [Websocket API](https://docs.ddex.io/#websocket). 382 | 383 | ```javascript 384 | import { HydroWatcher } from "@hydro-protocol/hydro-client-js" 385 | 386 | let watcher = new HydroWatcher({ 387 | // Listen for changes to the HOT-WETH ticker and post them to the console. 388 | tickerUpdate: (ticker) => console.log(ticker), 389 | }) 390 | watcher.subscribe("ticker", ["HOT-WETH"]) 391 | ``` 392 | 393 | ### Instantiating a watcher 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 410 | 413 | 414 | 415 |
MethodNotes
405 |
new HydroWatcher(
406 |   listener: HydroListener,
407 |   options?: HydroWatcherOptions,
408 | )
409 |
411 | Returns a HydroWatcher object with which you can use to subscribe to whichever channels you are interested in. 412 |
416 | 417 | Listeners for events must be provided in the HydroWatcher constructor via the **listener** parameter. This is a HydroListener object, which lets you provide callback functions for various events. The object has the following parameters: 418 | 419 | #### HydroListener 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 434 | 439 | 442 | 443 | 444 | 447 | 452 | 455 | 456 | 457 | 460 | 465 | 468 | 469 | 470 | 473 | 479 | 482 | 483 | 484 | 487 | 493 | 496 | 497 | 498 | 501 | 508 | 511 | 512 | 513 | 516 | 523 | 526 | 527 | 528 | 531 | 538 | 541 | 542 | 543 | 546 | 553 | 556 | 557 | 558 | 561 | 568 | 571 | 572 | 573 | 576 | 583 | 586 | 587 | 588 |
ParameterCallback parametersNotes
432 | subscriptionsUpdate 433 | 435 |
(
436 |   channels: Channel[],
437 | )
438 |
440 | Received whenever you subscribe or unsubscribe. Passes the current list of channels and market ids you are watching. 441 |
445 | tickerUpdate 446 | 448 |
(
449 |   ticker: Ticker,
450 | )
451 |
453 | Received when subscribed to a ticker channel and ticker data is updated. 454 |
458 | orderbookSnapshot 459 | 461 |
(
462 |   orderbook: Orderbook,
463 | )
464 |
466 | Received when subscribed to the orderbook channel right after you subscribe. Passes a full snapshot of the current orderbook. 467 |
471 | orderbookUpdate 472 | 474 |
(
475 |   side: Side,
476 |   priceLevel: PriceLevel,
477 | )
478 |
480 | Received when subscribed to the orderbook channel and there are changes to the orderbook. Passes which side the change occurred on, and the new pricing information. 481 |
485 | fullSnapshot 486 | 488 |
(
489 |   orderbook: Orderbook,
490 |   sequence: number,
491 | )
492 |
494 | Received when subscribed to the full channel right after you subscribe. Passes a full snapshot of the current orderbook, and the current sequence number. 495 |
499 | orderReceived 500 | 502 |
(
503 |   order: Order,
504 |   sequence: number,
505 |   time: Date,
506 | )
507 |
509 | Received when subscribed to the full channel and a new order has been created. Passes the order, the current sequence number, and the time the order was created. 510 |
514 | orderOpened 515 | 517 |
(
518 |   order: Order,
519 |   sequence: number,
520 |   time: Date,
521 | )
522 |
524 | Received when subscribed to the full channel and a new order has been created, but not immediately fulfilled. Passes the order, the current sequence number, and the time the order was created. 525 |
529 | orderDone 530 | 532 |
(
533 |   order: Order,
534 |   sequence: number,
535 |   time: Date,
536 | )
537 |
539 | Received when subscribed to the full channel and an order is being taken off the orderbook, either due to being completely fulfilled or because it was cancelled. Passes the order, the current sequence number, and the time the order was removed. 540 |
544 | orderChanged 545 | 547 |
(
548 |   order: Order,
549 |   sequence: number,
550 |   time: Date,
551 | )
552 |
554 | Received when subscribed to the full channel and an order is being updated with new data, usually because it was partially fulfilled. Passes the order, the current sequence number, and the time the order was changed. 555 |
559 | tradeBegin 560 | 562 |
(
563 |   trade: Trade,
564 |   sequence: number,
565 |   time: Date,
566 | )
567 |
569 | Received when subscribed to the full channel and two orders have been matched, creating a trade. Passes the trade, the current sequence number, and the time the trade was created. 570 |
574 | tradeSuccess 575 | 577 |
(
578 |   trade: Trade,
579 |   sequence: number,
580 |   time: Date,
581 | )
582 |
584 | Received when subscribed to the full channel and a trade has been successfully validated on the blockchain. Passes the trade, the current sequence number, and the time the trade was validated. 585 |
589 | 590 | The HydroWatcher constructor takes an options object with the following parameters: 591 | 592 | #### HydroClientOptions 593 | 594 | | Parameter | Type | Notes | 595 | | --------- | ---- | ----- | 596 | | **websocketUrl** | **string** | The websocket url to the Hydro API you wish to query. This defaults to the DDEX exchange running on mainnet: wss://ws.ddex.io/v3 | 597 | 598 | ### Subscribing 599 | 600 | Once you have a HydroWatcher instance defined with callbacks for the events you wish to handle, you must [Subscribe](https://docs.ddex.io/#subscribe) to a channel in order to receive any data. You subscribe by specifying a channel, which defines the type of data you want to receive, and a list of market ids, which filters the events to only include data from those markets. Subscription is handled by the following call: 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 617 | 620 | 621 | 622 |
MethodNotes
612 |
subscribe(
613 |   channel: string,
614 |   marketIds: string[],
615 | )
616 |
618 | Notify the watcher that you wish to recieve data for a channel with the passed set of market ids. 619 |
623 | 624 | ### Unsubscribing 625 | 626 | If you no longer wish to receive data from a channel, you must [Unsubscribe](https://docs.ddex.io/#unsubscribe). Unsubscription is handled by the following call: 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 643 | 646 | 647 | 648 |
MethodNotes
638 |
unsubscribe(
639 |   channel: string,
640 |   marketIds: string[],
641 | )
642 |
644 | Notify the watcher that you no longer wish to recieve data for a channel with the passed set of market ids. 645 |
649 | 650 | ### Channel types 651 | 652 | There are a few different channels you can subscribe to, as defined in the API. 653 | 654 | | Channel | API Docs Link | 655 | | ------ | ----- | 656 | | **ticker** | [Ticker](https://docs.ddex.io/#ticker-channel) - Price updates on a market. | 657 | | **orderbook** | [Orderbook](https://docs.ddex.io/#orderbook-channel) - Aggregated orderbook data for a market. | 658 | | **full** | [Full](https://docs.ddex.io/#full-channel) - Details on all orders and trades for a market. | 659 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hydro-protocol/hydro-client-js", 3 | "version": "2.0.5", 4 | "description": "Javascript SDK for the Hydro API", 5 | "main": "build/index.js", 6 | "types": "build/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "prepare": "tsc" 10 | }, 11 | "dependencies": { 12 | "@babel/runtime": "^7.3.1", 13 | "axios": "^0.18.0", 14 | "bignumber.js": "^6.0.0", 15 | "ethereumjs-tx": "^1.3.7", 16 | "ethereumjs-utils": "^5.2.5", 17 | "path": "^0.12.7", 18 | "web3": "1.0.0-beta.52", 19 | "ws": "^5.1.1" 20 | }, 21 | "devDependencies": { 22 | "@types/ethereumjs-tx": "^1.0.1", 23 | "@types/ethereumjs-util": "^5.2.0", 24 | "@types/node": "^9.4.6", 25 | "@types/web3": "1.0.18", 26 | "@types/ws": "^4.0.2", 27 | "typechain": "0.3.14", 28 | "typescript": "^2.9.2" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/HydroProtocol/hydro-client-js.git" 33 | }, 34 | "keywords": ["hydro", "hydro-protocol", "sdk", "api"], 35 | "author": "Kevin West", 36 | "license": "ISC", 37 | "bugs": { 38 | "url": "https://github.com/HydroProtocol/hydro-client-js/issues" 39 | }, 40 | "homepage": "https://github.com/HydroProtocol/hydro-client-js#readme" 41 | } 42 | -------------------------------------------------------------------------------- /src/abi/erc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "type": "function" 14 | }, 15 | { 16 | "constant": false, 17 | "inputs": [ 18 | { 19 | "name": "_spender", 20 | "type": "address" 21 | }, 22 | { 23 | "name": "_value", 24 | "type": "uint256" 25 | } 26 | ], 27 | "name": "approve", 28 | "outputs": [ 29 | { 30 | "name": "success", 31 | "type": "bool" 32 | } 33 | ], 34 | "payable": false, 35 | "type": "function" 36 | }, 37 | { 38 | "constant": true, 39 | "inputs": [], 40 | "name": "totalSupply", 41 | "outputs": [ 42 | { 43 | "name": "", 44 | "type": "uint256" 45 | } 46 | ], 47 | "payable": false, 48 | "type": "function" 49 | }, 50 | { 51 | "constant": false, 52 | "inputs": [ 53 | { 54 | "name": "_from", 55 | "type": "address" 56 | }, 57 | { 58 | "name": "_to", 59 | "type": "address" 60 | }, 61 | { 62 | "name": "_value", 63 | "type": "uint256" 64 | } 65 | ], 66 | "name": "transferFrom", 67 | "outputs": [ 68 | { 69 | "name": "success", 70 | "type": "bool" 71 | } 72 | ], 73 | "payable": false, 74 | "type": "function" 75 | }, 76 | { 77 | "constant": true, 78 | "inputs": [], 79 | "name": "decimals", 80 | "outputs": [ 81 | { 82 | "name": "", 83 | "type": "uint8" 84 | } 85 | ], 86 | "payable": false, 87 | "type": "function" 88 | }, 89 | { 90 | "constant": true, 91 | "inputs": [], 92 | "name": "version", 93 | "outputs": [ 94 | { 95 | "name": "", 96 | "type": "string" 97 | } 98 | ], 99 | "payable": false, 100 | "type": "function" 101 | }, 102 | { 103 | "constant": true, 104 | "inputs": [ 105 | { 106 | "name": "_owner", 107 | "type": "address" 108 | } 109 | ], 110 | "name": "balanceOf", 111 | "outputs": [ 112 | { 113 | "name": "balance", 114 | "type": "uint256" 115 | } 116 | ], 117 | "payable": false, 118 | "type": "function" 119 | }, 120 | { 121 | "constant": true, 122 | "inputs": [], 123 | "name": "symbol", 124 | "outputs": [ 125 | { 126 | "name": "", 127 | "type": "string" 128 | } 129 | ], 130 | "payable": false, 131 | "type": "function" 132 | }, 133 | { 134 | "constant": false, 135 | "inputs": [ 136 | { 137 | "name": "_to", 138 | "type": "address" 139 | }, 140 | { 141 | "name": "_value", 142 | "type": "uint256" 143 | } 144 | ], 145 | "name": "transfer", 146 | "outputs": [ 147 | { 148 | "name": "success", 149 | "type": "bool" 150 | } 151 | ], 152 | "payable": false, 153 | "type": "function" 154 | }, 155 | { 156 | "constant": false, 157 | "inputs": [ 158 | { 159 | "name": "_spender", 160 | "type": "address" 161 | }, 162 | { 163 | "name": "_value", 164 | "type": "uint256" 165 | }, 166 | { 167 | "name": "_extraData", 168 | "type": "bytes" 169 | } 170 | ], 171 | "name": "approveAndCall", 172 | "outputs": [ 173 | { 174 | "name": "success", 175 | "type": "bool" 176 | } 177 | ], 178 | "payable": false, 179 | "type": "function" 180 | }, 181 | { 182 | "constant": true, 183 | "inputs": [ 184 | { 185 | "name": "_owner", 186 | "type": "address" 187 | }, 188 | { 189 | "name": "_spender", 190 | "type": "address" 191 | } 192 | ], 193 | "name": "allowance", 194 | "outputs": [ 195 | { 196 | "name": "remaining", 197 | "type": "uint256" 198 | } 199 | ], 200 | "payable": false, 201 | "type": "function" 202 | }, 203 | { 204 | "inputs": [ 205 | { 206 | "name": "_initialAmount", 207 | "type": "uint256" 208 | }, 209 | { 210 | "name": "_tokenName", 211 | "type": "string" 212 | }, 213 | { 214 | "name": "_decimalUnits", 215 | "type": "uint8" 216 | }, 217 | { 218 | "name": "_tokenSymbol", 219 | "type": "string" 220 | } 221 | ], 222 | "type": "constructor" 223 | }, 224 | { 225 | "payable": false, 226 | "type": "fallback" 227 | }, 228 | { 229 | "anonymous": false, 230 | "inputs": [ 231 | { 232 | "indexed": true, 233 | "name": "_from", 234 | "type": "address" 235 | }, 236 | { 237 | "indexed": true, 238 | "name": "_to", 239 | "type": "address" 240 | }, 241 | { 242 | "indexed": false, 243 | "name": "_value", 244 | "type": "uint256" 245 | } 246 | ], 247 | "name": "Transfer", 248 | "type": "event" 249 | }, 250 | { 251 | "anonymous": false, 252 | "inputs": [ 253 | { 254 | "indexed": true, 255 | "name": "_owner", 256 | "type": "address" 257 | }, 258 | { 259 | "indexed": true, 260 | "name": "_spender", 261 | "type": "address" 262 | }, 263 | { 264 | "indexed": false, 265 | "name": "_value", 266 | "type": "uint256" 267 | } 268 | ], 269 | "name": "Approval", 270 | "type": "event" 271 | } 272 | ] 273 | -------------------------------------------------------------------------------- /src/abi/weth.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "guy", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "wad", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "src", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "dst", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "wad", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": false, 82 | "inputs": [ 83 | { 84 | "name": "wad", 85 | "type": "uint256" 86 | } 87 | ], 88 | "name": "withdraw", 89 | "outputs": [], 90 | "payable": false, 91 | "stateMutability": "nonpayable", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [], 97 | "name": "decimals", 98 | "outputs": [ 99 | { 100 | "name": "", 101 | "type": "uint8" 102 | } 103 | ], 104 | "payable": false, 105 | "stateMutability": "view", 106 | "type": "function" 107 | }, 108 | { 109 | "constant": true, 110 | "inputs": [ 111 | { 112 | "name": "", 113 | "type": "address" 114 | } 115 | ], 116 | "name": "balanceOf", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "uint256" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": true, 129 | "inputs": [], 130 | "name": "symbol", 131 | "outputs": [ 132 | { 133 | "name": "", 134 | "type": "string" 135 | } 136 | ], 137 | "payable": false, 138 | "stateMutability": "view", 139 | "type": "function" 140 | }, 141 | { 142 | "constant": false, 143 | "inputs": [ 144 | { 145 | "name": "dst", 146 | "type": "address" 147 | }, 148 | { 149 | "name": "wad", 150 | "type": "uint256" 151 | } 152 | ], 153 | "name": "transfer", 154 | "outputs": [ 155 | { 156 | "name": "", 157 | "type": "bool" 158 | } 159 | ], 160 | "payable": false, 161 | "stateMutability": "nonpayable", 162 | "type": "function" 163 | }, 164 | { 165 | "constant": false, 166 | "inputs": [], 167 | "name": "deposit", 168 | "outputs": [], 169 | "payable": true, 170 | "stateMutability": "payable", 171 | "type": "function" 172 | }, 173 | { 174 | "constant": true, 175 | "inputs": [ 176 | { 177 | "name": "", 178 | "type": "address" 179 | }, 180 | { 181 | "name": "", 182 | "type": "address" 183 | } 184 | ], 185 | "name": "allowance", 186 | "outputs": [ 187 | { 188 | "name": "", 189 | "type": "uint256" 190 | } 191 | ], 192 | "payable": false, 193 | "stateMutability": "view", 194 | "type": "function" 195 | }, 196 | { 197 | "payable": true, 198 | "stateMutability": "payable", 199 | "type": "fallback" 200 | }, 201 | { 202 | "anonymous": false, 203 | "inputs": [ 204 | { 205 | "indexed": true, 206 | "name": "src", 207 | "type": "address" 208 | }, 209 | { 210 | "indexed": true, 211 | "name": "guy", 212 | "type": "address" 213 | }, 214 | { 215 | "indexed": false, 216 | "name": "wad", 217 | "type": "uint256" 218 | } 219 | ], 220 | "name": "Approval", 221 | "type": "event" 222 | }, 223 | { 224 | "anonymous": false, 225 | "inputs": [ 226 | { 227 | "indexed": true, 228 | "name": "src", 229 | "type": "address" 230 | }, 231 | { 232 | "indexed": true, 233 | "name": "dst", 234 | "type": "address" 235 | }, 236 | { 237 | "indexed": false, 238 | "name": "wad", 239 | "type": "uint256" 240 | } 241 | ], 242 | "name": "Transfer", 243 | "type": "event" 244 | }, 245 | { 246 | "anonymous": false, 247 | "inputs": [ 248 | { 249 | "indexed": true, 250 | "name": "dst", 251 | "type": "address" 252 | }, 253 | { 254 | "indexed": false, 255 | "name": "wad", 256 | "type": "uint256" 257 | } 258 | ], 259 | "name": "Deposit", 260 | "type": "event" 261 | }, 262 | { 263 | "anonymous": false, 264 | "inputs": [ 265 | { 266 | "indexed": true, 267 | "name": "src", 268 | "type": "address" 269 | }, 270 | { 271 | "indexed": false, 272 | "name": "wad", 273 | "type": "uint256" 274 | } 275 | ], 276 | "name": "Withdrawal", 277 | "type": "event" 278 | } 279 | ] 280 | -------------------------------------------------------------------------------- /src/contracts/erc20.d.ts: -------------------------------------------------------------------------------- 1 | /* Generated by ts-generator ver. 0.0.8 */ 2 | /* tslint:disable */ 3 | 4 | import { Contract, ContractOptions, Options } from "web3-eth-contract"; 5 | import { Block } from "web3-eth"; 6 | import { EventLog } from "web3-core"; 7 | import { EventEmitter } from "events"; 8 | import { Callback, TransactionObject } from "./types"; 9 | 10 | export class erc20 extends Contract { 11 | constructor( 12 | jsonInterface: any[], 13 | address?: string, 14 | options?: ContractOptions 15 | ); 16 | methods: { 17 | balanceOf(_owner: string): TransactionObject; 18 | 19 | allowance(_owner: string, _spender: string): TransactionObject; 20 | 21 | approve( 22 | _spender: string, 23 | _value: number | string 24 | ): TransactionObject; 25 | 26 | transferFrom( 27 | _from: string, 28 | _to: string, 29 | _value: number | string 30 | ): TransactionObject; 31 | 32 | transfer(_to: string, _value: number | string): TransactionObject; 33 | 34 | approveAndCall( 35 | _spender: string, 36 | _value: number | string, 37 | _extraData: (string | number[])[] 38 | ): TransactionObject; 39 | 40 | name(): TransactionObject; 41 | totalSupply(): TransactionObject; 42 | decimals(): TransactionObject; 43 | version(): TransactionObject; 44 | symbol(): TransactionObject; 45 | }; 46 | events: { 47 | Transfer( 48 | options?: { 49 | filter?: object; 50 | fromBlock?: number | string; 51 | topics?: (null | string)[]; 52 | }, 53 | cb?: Callback 54 | ): EventEmitter; 55 | 56 | Approval( 57 | options?: { 58 | filter?: object; 59 | fromBlock?: number | string; 60 | topics?: (null | string)[]; 61 | }, 62 | cb?: Callback 63 | ): EventEmitter; 64 | 65 | allEvents: ( 66 | options?: { 67 | filter?: object; 68 | fromBlock?: number | string; 69 | topics?: (null | string)[]; 70 | }, 71 | cb?: Callback 72 | ) => EventEmitter; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /src/contracts/types.d.ts: -------------------------------------------------------------------------------- 1 | /* Generated by ts-generator ver. 0.0.8 */ 2 | /* tslint:disable */ 3 | 4 | import { Transaction } from "web3-core"; 5 | // @ts-ignore 6 | import PromiEvent from "web3-core-promievent"; 7 | export type Callback = (error: Error, result: T) => void; 8 | export interface TransactionObject { 9 | arguments: any[]; 10 | call(tx?: Transaction): Promise; 11 | send(tx?: Transaction): PromiEvent; 12 | estimateGas(tx?: Transaction): Promise; 13 | encodeABI(): string; 14 | } 15 | -------------------------------------------------------------------------------- /src/contracts/weth.d.ts: -------------------------------------------------------------------------------- 1 | /* Generated by ts-generator ver. 0.0.8 */ 2 | /* tslint:disable */ 3 | 4 | import { Contract, ContractOptions, Options } from "web3-eth-contract"; 5 | import { Block } from "web3-eth"; 6 | import { EventLog } from "web3-core"; 7 | import { EventEmitter } from "events"; 8 | import { Callback, TransactionObject } from "./types"; 9 | 10 | export class weth extends Contract { 11 | constructor( 12 | jsonInterface: any[], 13 | address?: string, 14 | options?: ContractOptions 15 | ); 16 | methods: { 17 | balanceOf(arg0: string): TransactionObject; 18 | 19 | allowance(arg0: string, arg1: string): TransactionObject; 20 | 21 | approve(guy: string, wad: number | string): TransactionObject; 22 | 23 | transferFrom( 24 | src: string, 25 | dst: string, 26 | wad: number | string 27 | ): TransactionObject; 28 | 29 | withdraw(wad: number | string): TransactionObject; 30 | 31 | transfer(dst: string, wad: number | string): TransactionObject; 32 | 33 | deposit(): TransactionObject; 34 | 35 | name(): TransactionObject; 36 | totalSupply(): TransactionObject; 37 | decimals(): TransactionObject; 38 | symbol(): TransactionObject; 39 | }; 40 | events: { 41 | Approval( 42 | options?: { 43 | filter?: object; 44 | fromBlock?: number | string; 45 | topics?: (null | string)[]; 46 | }, 47 | cb?: Callback 48 | ): EventEmitter; 49 | 50 | Transfer( 51 | options?: { 52 | filter?: object; 53 | fromBlock?: number | string; 54 | topics?: (null | string)[]; 55 | }, 56 | cb?: Callback 57 | ): EventEmitter; 58 | 59 | Deposit( 60 | options?: { 61 | filter?: object; 62 | fromBlock?: number | string; 63 | topics?: (null | string)[]; 64 | }, 65 | cb?: Callback 66 | ): EventEmitter; 67 | 68 | Withdrawal( 69 | options?: { 70 | filter?: object; 71 | fromBlock?: number | string; 72 | topics?: (null | string)[]; 73 | }, 74 | cb?: Callback 75 | ): EventEmitter; 76 | 77 | allEvents: ( 78 | options?: { 79 | filter?: object; 80 | fromBlock?: number | string; 81 | topics?: (null | string)[]; 82 | }, 83 | cb?: Callback 84 | ) => EventEmitter; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/errors/errors.ts: -------------------------------------------------------------------------------- 1 | export class ServerError extends Error {} 2 | export class APIError extends Error {} 3 | export class AuthError extends Error {} 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { HydroClient, HydroClientOptions } from './lib/HydroClient'; 2 | import { HydroWatcher, HydroWatcherOptions, HydroListener } from './lib/HydroWatcher'; 3 | 4 | import { Candle } from './models/Candle'; 5 | import { Channel, ChannelName } from './models/Channel'; 6 | import { Fee } from './models/Fee'; 7 | import { LockedBalance } from './models/LockedBalance'; 8 | import { Market } from './models/Market'; 9 | import { Order, OrderType, Side, Status } from './models/Order'; 10 | import { Orderbook, OrderbookLevel } from './models/Orderbook'; 11 | import { OrderData } from './models/OrderData'; 12 | import { PriceLevel } from './models/PriceLevel'; 13 | import { Ticker } from './models/Ticker'; 14 | import { Trade } from './models/Trade'; 15 | import { TradeList } from './models/TradeList'; 16 | 17 | export { 18 | Candle, 19 | Channel, 20 | ChannelName, 21 | Fee, 22 | HydroClient, 23 | HydroClientOptions, 24 | HydroListener, 25 | HydroWatcher, 26 | HydroWatcherOptions, 27 | LockedBalance, 28 | Market, 29 | Order, 30 | Orderbook, 31 | OrderbookLevel, 32 | OrderData, 33 | OrderType, 34 | PriceLevel, 35 | Side, 36 | Status, 37 | Ticker, 38 | Trade, 39 | TradeList, 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/ApiHandler.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from 'axios'; 2 | import { URL } from 'url'; 3 | 4 | import { Account } from './HydroClient'; 5 | 6 | import { ServerError, APIError } from '../errors/errors'; 7 | 8 | export interface ApiHandlerOptions { 9 | apiUrl?: string; 10 | } 11 | 12 | /** 13 | * Handle building requests to the server, including authentication 14 | * in the request header. 15 | */ 16 | export class ApiHandler { 17 | private BASE_MESSAGE: string = 'HYDRO-AUTHENTICATION'; 18 | private API_URL: string = 'https://api.ddex.io/v3/'; 19 | private HEADER: string = 'Hydro-Authentication'; 20 | 21 | private account: Account; 22 | private options?: ApiHandlerOptions; 23 | 24 | constructor(account: Account, options?: ApiHandlerOptions) { 25 | this.account = account; 26 | this.options = options; 27 | } 28 | 29 | /** 30 | * Methods to perform standard get/post/delete requests to the api server. 31 | * Only implemented these three since they are the only ones the api 32 | * currently requires. 33 | */ 34 | public async get(path: string, params?: {}, sign: boolean = false): Promise { 35 | const headers = sign ? await this.getAuthHeaders() : {}; 36 | const res = await axios.get(this.getURL(path), { 37 | params: params, 38 | headers, 39 | }); 40 | return this.handleResponse(res); 41 | } 42 | 43 | public async post(path: string, data?: {}): Promise { 44 | const headers = await this.getAuthHeaders(); 45 | const res = await axios.post(this.getURL(path), data, { headers }); 46 | return this.handleResponse(res); 47 | } 48 | 49 | public async delete(path: string): Promise { 50 | const headers = await this.getAuthHeaders(); 51 | const res = await axios.delete(this.getURL(path), { headers }); 52 | return this.handleResponse(res); 53 | } 54 | 55 | private getURL(path: string): string { 56 | const url = new URL(path, this.getApiUrl()); 57 | return url.toString(); 58 | } 59 | 60 | private async getAuthHeaders(): Promise<{}> { 61 | const message = this.BASE_MESSAGE + '@' + Date.now(); 62 | const signature = await this.account.sign(message); 63 | return { 64 | [this.HEADER]: [this.account.address, message, signature].join('#'), 65 | }; 66 | } 67 | 68 | private getApiUrl(): string { 69 | return this.options && this.options.apiUrl ? this.options.apiUrl : this.API_URL; 70 | } 71 | 72 | private handleResponse(res: AxiosResponse): any { 73 | if (res.status !== 200) { 74 | throw new ServerError('Server Error ' + res.status + ': ' + res.statusText); 75 | } 76 | if (res.data.status !== 0) { 77 | throw new APIError('API Error: ' + res.data.desc); 78 | } 79 | return res.data.data; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/HydroClient.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import EthereumTx from 'ethereumjs-tx'; 3 | import { hashPersonalMessage, ecsign, toRpcSig, toBuffer, privateToAddress } from 'ethereumjs-util'; 4 | import { join } from 'path'; 5 | 6 | import { ApiHandler } from './ApiHandler'; 7 | import { Web3Handler } from './Web3Handler'; 8 | 9 | import { AuthError } from '../errors/errors'; 10 | 11 | import { Candle } from '../models/Candle'; 12 | import { Fee } from '../models/Fee'; 13 | import { LockedBalance } from '../models/LockedBalance'; 14 | import { Market } from '../models/Market'; 15 | import { Order, OrderType, Side, Status } from '../models/Order'; 16 | import { Orderbook, OrderbookLevel } from '../models/Orderbook'; 17 | import { OrderList } from '../models/OrderList'; 18 | import { Ticker } from '../models/Ticker'; 19 | import { TradeList } from '../models/TradeList'; 20 | 21 | export interface HydroClientOptions { 22 | apiUrl?: string; 23 | web3Url?: string; 24 | } 25 | 26 | export class HydroClient { 27 | private account: Account; 28 | private apiHandler: ApiHandler; 29 | private web3Handler: Web3Handler; 30 | 31 | // Cache of token addresses keyed by symbol pulled from the hydro token api 32 | private tokenAddresses: Map; 33 | 34 | private constructor(account: Account, options?: HydroClientOptions) { 35 | this.account = account; 36 | this.apiHandler = new ApiHandler(account, options); 37 | this.web3Handler = new Web3Handler(account, options); 38 | 39 | this.tokenAddresses = new Map(); 40 | } 41 | 42 | /** 43 | * If you only want to make public API calls, no authentication is needed 44 | */ 45 | public static withoutAuth(options?: HydroClientOptions): HydroClient { 46 | const errorFn = (_: any) => { 47 | throw new AuthError('Cannot authenticate without a private key!'); 48 | }; 49 | 50 | const account: Account = { 51 | address: '', 52 | sign: errorFn, 53 | signTransaction: errorFn, 54 | }; 55 | 56 | return new HydroClient(account, options); 57 | } 58 | 59 | /** 60 | * Provide a private key for authentication purposes 61 | * @param privateKey A private key in hex format with the form "0x..." 62 | */ 63 | public static withPrivateKey(privateKey: string, options?: HydroClientOptions): HydroClient { 64 | const pkBuffer: Buffer = toBuffer(privateKey) as Buffer; 65 | let address = '0x' + (privateToAddress(pkBuffer) as Buffer).toString('hex'); 66 | let sign = async (message: string) => { 67 | const shaMessage = hashPersonalMessage(toBuffer(message)); 68 | const ecdsaSignature = ecsign(shaMessage, pkBuffer); 69 | return toRpcSig(ecdsaSignature.v, ecdsaSignature.r, ecdsaSignature.s); 70 | }; 71 | let signTransaction = async (txParams: Transaction) => { 72 | const tx = new EthereumTx(txParams); 73 | tx.sign(pkBuffer); 74 | return '0x' + tx.serialize().toString('hex'); 75 | }; 76 | return new HydroClient({ address, sign, signTransaction }, options); 77 | } 78 | 79 | /** 80 | * If you don't want to supply your private key, or want to integrate with a wallet, provide 81 | * your own function to sign messages and the account you will be using. 82 | * 83 | * @param address The address of the account that will be doing the signing 84 | * @param sign A function that takes the input message and signs it with the private key of the account 85 | * @param signTransaction An async function that takes a transaction object and signs it with the private key of the account 86 | */ 87 | public static withCustomAuth( 88 | address: string, 89 | sign: (message: string) => Promise, 90 | signTransaction: (tx: Transaction) => Promise, 91 | options?: HydroClientOptions 92 | ): HydroClient { 93 | return new HydroClient({ address, sign, signTransaction }, options); 94 | } 95 | 96 | /** 97 | * Public API Calls 98 | * 99 | * These calls do not require any authentication to complete, and will generally give you 100 | * public state about the Hydro API 101 | * 102 | * See https://docs.ddex.io/#public-rest-api 103 | */ 104 | 105 | /** 106 | * Returns all active markets 107 | * 108 | * See https://docs.ddex.io/#list-markets 109 | */ 110 | public async listMarkets(): Promise { 111 | const data = await this.apiHandler.get(join('markets')); 112 | return data.markets.map((market: any) => new Market(market)); 113 | } 114 | 115 | /** 116 | * Returns a specific market 117 | * 118 | * See https://docs.ddex.io/#get-a-market 119 | * 120 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 121 | */ 122 | public async getMarket(marketId: string): Promise { 123 | const data = await this.apiHandler.get(join('markets', marketId)); 124 | return new Market(data.market); 125 | } 126 | 127 | /** 128 | * Returns tickers for all active markets 129 | * 130 | * See https://docs.ddex.io/#list-tickers 131 | */ 132 | public async listTickers(): Promise { 133 | const data = await this.apiHandler.get(join('markets', 'tickers')); 134 | return data.tickers.map((ticker: any) => new Ticker(ticker)); 135 | } 136 | 137 | /** 138 | * Returns ticker for a specific market 139 | * 140 | * See https://docs.ddex.io/#get-a-ticker 141 | * 142 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 143 | */ 144 | public async getTicker(marketId: string): Promise { 145 | const data = await this.apiHandler.get(join('markets', marketId, 'ticker')); 146 | return new Ticker(data.ticker); 147 | } 148 | 149 | /** 150 | * Returns the orderbook for a specific market 151 | * 152 | * See https://docs.ddex.io/#get-orderbook 153 | * 154 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 155 | * @param level (Optional) The amount of detail returned in the orderbook. Default is level ONE. 156 | */ 157 | public async getOrderbook(marketId: string, level?: OrderbookLevel): Promise { 158 | const data = await this.apiHandler.get(join('markets', marketId, 'orderbook'), { 159 | level: level, 160 | }); 161 | return new Orderbook(data.orderBook, level); 162 | } 163 | 164 | /** 165 | * Returns paginated trades for a specific market 166 | * 167 | * See https://docs.ddex.io/#get-trades 168 | * 169 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 170 | * @param page (Optional) Which page to return. Default is page 1. 171 | * @param perPage (Optional) How many results per page. Default is 20. 172 | */ 173 | public async listTrades(marketId: string, page?: number, perPage?: number): Promise { 174 | const data = await this.apiHandler.get(join('markets', marketId, 'trades'), { 175 | page, 176 | perPage, 177 | }); 178 | return new TradeList(data); 179 | } 180 | 181 | /** 182 | * Returns "candles" for building a trading chart for a specific market 183 | * 184 | * See https://docs.ddex.io/#get-candles 185 | * 186 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 187 | * @param from The beginning of the time range as a UNIX timestamp 188 | * @param to The end of the time range as a UNIX timestamp 189 | * @param granularity The width of each candle in seconds 190 | */ 191 | public async listCandles( 192 | marketId: string, 193 | from: number, 194 | to: number, 195 | granularity: number 196 | ): Promise { 197 | const data = await this.apiHandler.get(join('markets', marketId, 'candles'), { 198 | from, 199 | to, 200 | granularity, 201 | }); 202 | return data.candles.map((candle: any) => new Candle(candle)); 203 | } 204 | 205 | /** 206 | * Calculate an estimated fee taken by the exchange given a price and amount for an order 207 | * 208 | * See https://docs.ddex.io/#calculate-fees 209 | * 210 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 211 | * @param price The price of the order 212 | * @param amount The amount of token in the order 213 | */ 214 | public async calculateFees(marketId: string, price: string, amount: string): Promise { 215 | const data = await this.apiHandler.get(join('fees'), { 216 | marketId, 217 | price, 218 | amount, 219 | }); 220 | return new Fee(data); 221 | } 222 | 223 | /** 224 | * Private API Calls 225 | * 226 | * These calls require authentication, meaning you must have a valid trading address 227 | * and the ability to sign requests with that address' private key. 228 | * 229 | * See https://docs.ddex.io/#private-rest-api 230 | */ 231 | 232 | /** 233 | * Build a new order to submit to the exchange 234 | * 235 | * See https://docs.ddex.io/#build-unsigned-order 236 | * 237 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 238 | * @param side Whether this is a "buy" or "sell" order 239 | * @param orderType Whether this is a "limit" or "market" order 240 | * @param price The price of the order 241 | * @param amount The amount of token in the order 242 | * @param expires (Optional) A number of seconds after which this order will expire. Defaults to 0 (no expiration). 243 | */ 244 | public async buildOrder( 245 | marketId: string, 246 | side: Side, 247 | orderType: OrderType, 248 | price: string, 249 | amount: string, 250 | expires: number = 0 251 | ): Promise { 252 | const data = await this.apiHandler.post(join('orders', 'build'), { 253 | marketId, 254 | side, 255 | orderType, 256 | price, 257 | amount, 258 | expires, 259 | }); 260 | return new Order(data.order); 261 | } 262 | 263 | /** 264 | * Submit a signed order to the exchange 265 | * 266 | * See https://docs.ddex.io/#place-order 267 | * 268 | * @param orderId The id of a built order 269 | * @param signature String created by signing the orderId 270 | */ 271 | public async placeOrder(orderId: string, signature: string): Promise { 272 | const data = await this.apiHandler.post(join('orders'), { 273 | orderId, 274 | signature, 275 | method: SignatureMethod.ETH_SIGN, 276 | }); 277 | return new Order(data.order); 278 | } 279 | 280 | /** 281 | * A convenience function that will build an order, sign the order, and then 282 | * immediately place the order on the system using the signing method passed 283 | * in. 284 | * 285 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 286 | * @param side Whether this is a "buy" or "sell" order 287 | * @param orderType Whether this is a "limit" or "market" order 288 | * @param price The price of the order 289 | * @param amount The amount of token in the order 290 | * @param expires (Optional) A number of seconds after which this order will expire. Defaults to 0 (no expiration). 291 | */ 292 | public async createOrder( 293 | marketId: string, 294 | side: Side, 295 | orderType: OrderType, 296 | price: string, 297 | amount: string, 298 | expires: number = 0 299 | ): Promise { 300 | const order = await this.buildOrder(marketId, side, orderType, price, amount, expires); 301 | const signature = await this.account.sign(order.id); 302 | return await this.placeOrder(order.id, signature); 303 | } 304 | 305 | /** 306 | * Cancel an order you have submitted to the exchange 307 | * 308 | * See https://docs.ddex.io/#cancel-order 309 | * 310 | * @param orderId The id of the order you wish to cancel 311 | */ 312 | public async cancelOrder(orderId: string): Promise { 313 | await this.apiHandler.delete(join('orders', orderId)); 314 | } 315 | 316 | /** 317 | * Return paginated orders you have submitted to the exchange 318 | * 319 | * See https://docs.ddex.io/#list-orders 320 | * 321 | * @param marketId (Optional) The id of the market, specified as a trading pair, e.g. "HOT-WETH" 322 | * @param status (Optional) Choose between "pending" or "all" orders 323 | * @param page (Optional) Which page to return. Default is page 1. 324 | * @param perPage (Optional) How many results per page. Default is 20. 325 | */ 326 | public async listOrders( 327 | marketId?: string, 328 | status?: Status, 329 | page?: number, 330 | perPage?: number 331 | ): Promise { 332 | const data = await this.apiHandler.get( 333 | join('orders'), 334 | { marketId, status, page, perPage }, 335 | true 336 | ); 337 | return new OrderList(data); 338 | } 339 | 340 | /** 341 | * Return a specific order you have submitted to the exchange 342 | * 343 | * See https://docs.ddex.io/#get-order 344 | * 345 | * @param orderId The id of the order 346 | */ 347 | public async getOrder(orderId: string): Promise { 348 | const data = await this.apiHandler.get(join('orders', orderId), {}, true); 349 | return new Order(data.order); 350 | } 351 | 352 | /** 353 | * Return paginated list of all trades you have made 354 | * 355 | * See https://docs.ddex.io/#list-account-trades 356 | * 357 | * @param marketId The id of the market, specified as a trading pair, e.g. "HOT-WETH" 358 | * @param page (Optional) Which page to return. Default is page 1. 359 | * @param perPage (Optional) How many results per page. Default is 20. 360 | */ 361 | public async listAccountTrades( 362 | marketId: string, 363 | page?: number, 364 | perPage?: number 365 | ): Promise { 366 | const data = await this.apiHandler.get( 367 | join('markets', marketId, 'trades', 'mine'), 368 | { page, perPage }, 369 | true 370 | ); 371 | return new TradeList(data); 372 | } 373 | 374 | /** 375 | * Return locked balances for each active token 376 | * 377 | * See https://docs.ddex.io/#list-locked-balances 378 | */ 379 | public async listLockedBalances(): Promise { 380 | const data = await this.apiHandler.get(join('account', 'lockedBalances'), {}, true); 381 | return data.lockedBalances.map((lockedBalance: any) => new LockedBalance(lockedBalance)); 382 | } 383 | 384 | /** 385 | * Return a specific locked balance 386 | * 387 | * See https://docs.ddex.io/#get-locked-balance 388 | * 389 | * @param symbol The symbol for the token you want to see your locked balance 390 | */ 391 | public async getLockedBalance(symbol: string): Promise { 392 | const data = await this.apiHandler.get( 393 | join('account', 'lockedBalance'), 394 | { symbol: symbol }, 395 | true 396 | ); 397 | return new LockedBalance(data.lockedBalance); 398 | } 399 | 400 | /** 401 | * Helper Methods (requires auth) 402 | * 403 | * These helper methods don't generally don't call the Hydro API, instead querying the blockchain 404 | * directly. They are useful in helping to wrap/unwrap ETH on your account, and allowing you to 405 | * approve tokens to be traded on the DDEX-1.0 relayer. 406 | * 407 | * To use these methods, you must provide a mainnet endpoint url, like infura, which will be used 408 | * to interact with the blockchain. It is taken in as one of the HydroClient options, as web3_url. 409 | * 410 | * See 411 | * * https://docs.ddex.io/#wrapping-ether 412 | * * https://docs.ddex.io/#enabling-token-trading 413 | */ 414 | 415 | /** 416 | * Query your balance of a token. 417 | * 418 | * @param symbol Symbol of a token you wish to query the balance of. No token returns ETH balance. 419 | * @return Balance in your account for this token. 420 | */ 421 | public async getBalance(symbol?: string): Promise { 422 | let address; 423 | if (symbol) { 424 | address = await this.getTokenAddress(symbol); 425 | } 426 | return this.web3Handler.getBalance(address); 427 | } 428 | 429 | /** 430 | * Wrap a specified amount of ETH from your account into WETH. This is required because the 431 | * DDEX-1.0 relayer can only perform atomic trading between two ERC20 tokens, and unfortunately 432 | * ETH itself does not conform to the ERC20 standard. ETH and WETH are always exchanged at a 1:1 433 | * ratio, so you can wrap and unwrap ETH anytime you like with only the cost of gas. 434 | * 435 | * @param amount The amount of ETH to wrap 436 | * @param wait If true, the promise will only resolve when the transaction is confirmed 437 | * @return Transaction hash 438 | */ 439 | public async wrapEth(amount: string, wait?: boolean): Promise { 440 | const wethAddress = await this.getTokenAddress('WETH'); 441 | return this.web3Handler.wrapEth(wethAddress, amount, wait); 442 | } 443 | 444 | /** 445 | * Unwrap a specified amount of WETH from your account back into ETH. 446 | * 447 | * @param amount The amount of WETH to unwrap 448 | * @param wait If true, the promise will only resolve when the transaction is confirmed 449 | * @return Transaction hash 450 | */ 451 | public async unwrapEth(amount: string, wait?: boolean): Promise { 452 | const wethAddress = await this.getTokenAddress('WETH'); 453 | return this.web3Handler.unwrapEth(wethAddress, amount, wait); 454 | } 455 | 456 | /** 457 | * Determine if this token has a proxy allowance set on the Hydro proxy contract. 458 | * 459 | * @param symbol Symbol of a token you wish to check if it is enabled or diabled for sale. 460 | */ 461 | public async isTokenEnabled(symbol: string): Promise { 462 | const tokenAddress = await this.getTokenAddress(symbol); 463 | const allowance = await this.web3Handler.getAllowance(tokenAddress); 464 | return new BigNumber(allowance).gte(new BigNumber(10).pow(10)); 465 | } 466 | 467 | /** 468 | * Enable token to be sold via Hydro API. This will allow the Hydro proxy contract to send tokens 469 | * of this type on your behalf, allowing atomic trading of tokens between two parties. 470 | * 471 | * @param symbol Symbol of a token you wish to enable for sale via Hydro API 472 | * @param wait If true, the promise will only resolve when the transaction is confirmed 473 | * @return Transaction hash 474 | */ 475 | public async enableToken(symbol: string, wait?: boolean): Promise { 476 | const tokenAddress = await this.getTokenAddress(symbol); 477 | return this.web3Handler.enableToken(tokenAddress, wait); 478 | } 479 | 480 | /** 481 | * Disable token to be sold via Hydro API. The Hydro proxy contract will no longer be able to send 482 | * tokens of this type on your behalf. 483 | * 484 | * @param symbol Symbol of a token you wish to disable for sale via Hydro API 485 | * @param wait If true, the promise will only resolve when the transaction is confirmed 486 | * @return Transaction hash 487 | */ 488 | public async disableToken(symbol: string, wait?: boolean): Promise { 489 | const tokenAddress = await this.getTokenAddress(symbol); 490 | return this.web3Handler.disableToken(tokenAddress, wait); 491 | } 492 | 493 | private async getTokenAddress(token: string): Promise { 494 | if (!this.tokenAddresses.get(token)) { 495 | const data = await this.apiHandler.get(join('tokens', token)); 496 | this.tokenAddresses.set(token, data.token.address); 497 | } 498 | 499 | const address = this.tokenAddresses.get(token); 500 | if (!address) { 501 | throw new Error('Unable to get token address'); 502 | } 503 | 504 | return address; 505 | } 506 | } 507 | 508 | // This SDK only supports EthSign for the moment, so no need to export this. 509 | enum SignatureMethod { 510 | ETH_SIGN, 511 | EIP_712, 512 | } 513 | 514 | export interface Transaction { 515 | nonce?: number; 516 | chainId?: number; 517 | from?: string; 518 | to?: string; 519 | data?: string; 520 | value?: string | number; 521 | gas?: string | number; 522 | gasPrice?: string | number; 523 | } 524 | 525 | export interface Account { 526 | address: string; 527 | sign(message: string): Promise; 528 | signTransaction(tx: Transaction): Promise; 529 | } 530 | -------------------------------------------------------------------------------- /src/lib/HydroWatcher.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from 'ws'; 2 | 3 | import { Channel, ChannelName } from '../models/Channel'; 4 | import { Order, Side } from '../models/Order'; 5 | import { Orderbook, OrderbookLevel } from '../models/Orderbook'; 6 | import { Ticker } from '../models/Ticker'; 7 | import { Trade } from '../models/Trade'; 8 | import { PriceLevel } from '../models/PriceLevel'; 9 | 10 | export interface HydroWatcherOptions { 11 | websocketUrl?: string; 12 | } 13 | 14 | /** 15 | * The Hydro API provides a websocket connection that can push updates 16 | * about the exchange to clients. This watcher lets you create a listener 17 | * function to handle updates, and a subscription function to choose which 18 | * channels you wish to get updates on. 19 | * 20 | * See https://docs.ddex.io/#websocket 21 | */ 22 | export class HydroWatcher { 23 | private static SOCKET_URL: string = 'wss://ws.ddex.io/v3'; 24 | 25 | private listener: HydroListener; 26 | private options?: HydroWatcherOptions; 27 | private socket?: WebSocket; 28 | private messageQueue: string[] = []; 29 | 30 | /** 31 | * Initialize a new watcher with a set of listener functions that will 32 | * be called when a message of that type is received. The watcher will 33 | * not connect to the server until you subscribe to at least one channel. 34 | * 35 | * @param listener Object containing the functions to handle the updates you care about 36 | */ 37 | constructor(listener: HydroListener, options?: HydroWatcherOptions) { 38 | this.listener = listener; 39 | this.options = options; 40 | } 41 | 42 | /** 43 | * Subscribe to a channel and begin receiving updates from the exchange 44 | * for a set of market ids 45 | * 46 | * See https://docs.ddex.io/#subscribe 47 | * 48 | * @param channel The name of the channel you want to subscribe to 49 | * @param marketIds A list of market ids you want updates for 50 | */ 51 | public subscribe(channel: ChannelName, marketIds: string[]) { 52 | this.initIfNeeded(); 53 | this.sendMessage(this.generateMessage('subscribe', channel, marketIds)); 54 | } 55 | 56 | /** 57 | * Unsubscribe to stop receiving updates from the exchange for a particular 58 | * channel/market ids 59 | * 60 | * See https://docs.ddex.io/#unsubscribe 61 | * 62 | * @param channel The name of the channel you want to unsubscribe from 63 | * @param marketIds A list of market ids you no longer want updates for 64 | */ 65 | public unsubscribe(channel: ChannelName, marketIds: string[]) { 66 | this.initIfNeeded(); 67 | this.sendMessage(this.generateMessage('unsubscribe', channel, marketIds)); 68 | } 69 | 70 | private initIfNeeded() { 71 | if (!this.socket || this.socket.readyState === WebSocket.CLOSED) { 72 | this.socket = new WebSocket(this.getWebsocketUrl()); 73 | this.socket.on('message', (message: string) => { 74 | this.receiveMessage(message); 75 | }); 76 | this.socket.on('close', () => { 77 | this.initIfNeeded(); 78 | }); 79 | this.socket.on('open', () => { 80 | while (this.messageQueue.length > 0) { 81 | let message = this.messageQueue.shift(); 82 | this.socket && this.socket.send(message); 83 | } 84 | }); 85 | } 86 | } 87 | 88 | private getWebsocketUrl(): string { 89 | return this.options && this.options.websocketUrl 90 | ? this.options.websocketUrl 91 | : HydroWatcher.SOCKET_URL; 92 | } 93 | 94 | private sendMessage(message: string) { 95 | if (this.socket && this.socket.readyState === WebSocket.OPEN) { 96 | this.socket.send(message); 97 | } else { 98 | this.messageQueue.push(message); 99 | } 100 | } 101 | 102 | private receiveMessage(message: string) { 103 | if (!this.listener) { 104 | return; 105 | } 106 | 107 | const json = JSON.parse(message); 108 | switch (json.type) { 109 | case 'subscriptions': 110 | return ( 111 | this.listener.subscriptionsUpdate && 112 | this.listener.subscriptionsUpdate( 113 | json.channels.map((channel: any) => new Channel(channel)) 114 | ) 115 | ); 116 | case 'ticker': 117 | return this.listener.tickerUpdate && this.listener.tickerUpdate(new Ticker(json)); 118 | case 'level2OrderbookSnapshot': 119 | return ( 120 | this.listener.orderbookSnapshot && this.listener.orderbookSnapshot(new Orderbook(json)) 121 | ); 122 | case 'level2OrderbookUpdate': 123 | return ( 124 | json.changes && 125 | json.changes.forEach((change: any) => { 126 | this.listener.orderbookUpdate && 127 | this.listener.orderbookUpdate(change.side, new PriceLevel(change)); 128 | }) 129 | ); 130 | case 'level3OrderbookSnapshot': 131 | return ( 132 | this.listener.fullSnapshot && 133 | this.listener.fullSnapshot(new Orderbook(json), json.sequence) 134 | ); 135 | case 'receive': 136 | return ( 137 | this.listener.orderReceived && 138 | this.listener.orderReceived(new Order(json), json.sequence, new Date(json.time)) 139 | ); 140 | case 'open': 141 | return ( 142 | this.listener.orderOpened && 143 | this.listener.orderOpened(new Order(json), json.sequence, new Date(json.time)) 144 | ); 145 | case 'done': 146 | return ( 147 | this.listener.orderDone && 148 | this.listener.orderDone(new Order(json), json.sequence, new Date(json.time)) 149 | ); 150 | case 'change': 151 | return ( 152 | this.listener.orderChanged && 153 | this.listener.orderChanged(new Order(json), json.sequence, new Date(json.time)) 154 | ); 155 | case 'trade': 156 | return ( 157 | this.listener.tradeBegin && 158 | this.listener.tradeBegin(new Trade(json), json.sequence, new Date(json.time)) 159 | ); 160 | case 'trade_success': 161 | return ( 162 | this.listener.tradeSuccess && 163 | this.listener.tradeSuccess(new Trade(json), json.sequence, new Date(json.time)) 164 | ); 165 | } 166 | } 167 | 168 | private generateMessage(type: string, channel: ChannelName, marketIds: string[]): string { 169 | return JSON.stringify({ 170 | type: type, 171 | channels: [ 172 | { 173 | name: channel, 174 | marketIds: marketIds, 175 | }, 176 | ], 177 | }); 178 | } 179 | } 180 | 181 | export interface HydroListener { 182 | /** 183 | * Received whenever you subscribe or unsubscribe to tell you your 184 | * current list of channels and market ids you are watching 185 | */ 186 | subscriptionsUpdate?: (channels: Channel[]) => void; 187 | 188 | /** 189 | * Received when subscribed to the 'ticker' channel and ticker 190 | * data is updated 191 | */ 192 | tickerUpdate?: (ticker: Ticker) => void; 193 | 194 | /** 195 | * Received when subscribed to the 'orderbook' channel right 196 | * after you subscribe 197 | */ 198 | orderbookSnapshot?: (orderbook: Orderbook) => void; 199 | 200 | /** 201 | * Received when subscribed to the 'orderbook' channel and there 202 | * are changes to the orderbook 203 | */ 204 | orderbookUpdate?: (side: Side, priceLevel: PriceLevel) => void; 205 | 206 | /** 207 | * Received when subscribed to the 'full' channel right after 208 | * you subscribe 209 | */ 210 | fullSnapshot?: (orderbook: Orderbook, sequence: number) => void; 211 | 212 | /** 213 | * Received when subscribed to the 'full' channel and a new order 214 | * has been created 215 | */ 216 | orderReceived?: (order: Order, sequence: number, time: Date) => void; 217 | 218 | /** 219 | * Received when subscribed to the 'full' channel and a new order 220 | * has been created, but not immediately fulfilled 221 | */ 222 | orderOpened?: (order: Order, sequence: number, time: Date) => void; 223 | 224 | /** 225 | * Received when subscribed to the 'full' channel and an order is 226 | * being taken off the orderbook, either due to being completely 227 | * fulfilled or because it was cancelled 228 | */ 229 | orderDone?: (order: Order, sequence: number, time: Date) => void; 230 | 231 | /** 232 | * Received when subscribed to the 'full' channel and an order is 233 | * being updated with new data, usually because it was partially 234 | * fulfilled 235 | */ 236 | orderChanged?: (order: Order, sequence: number, time: Date) => void; 237 | 238 | /** 239 | * Received when subscribed to the 'full' channel and two orders 240 | * have been matched, creating a trade 241 | */ 242 | tradeBegin?: (trade: Trade, sequence: number, time: Date) => void; 243 | 244 | /** 245 | * Received when subscribed to the 'full' channel and a trade has 246 | * been successfully validated on the blockchain 247 | */ 248 | tradeSuccess?: (trade: Trade, sequence: number, time: Date) => void; 249 | } 250 | -------------------------------------------------------------------------------- /src/lib/Web3Handler.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import Web3 from 'web3'; 3 | 4 | import { Account, Transaction } from './HydroClient'; 5 | 6 | import erc20Abi from '../abi/erc20.json'; 7 | import wethAbi from '../abi/weth.json'; 8 | 9 | import { erc20 } from '../contracts/erc20'; 10 | import { weth } from '../contracts/weth'; 11 | 12 | export interface Web3HandlerOptions { 13 | web3Url?: string; 14 | } 15 | 16 | /** 17 | * Handle building requests to the server, including authentication 18 | * in the request header. 19 | */ 20 | export class Web3Handler { 21 | private PROXY_ADDRESS = '0x74622073a4821dbfd046e9aa2ccf691341a076e1'; 22 | 23 | private account: Account; 24 | private web3?: Web3; 25 | 26 | constructor(account: Account, options?: Web3HandlerOptions) { 27 | this.account = account; 28 | if (options && options.web3Url) { 29 | this.web3 = new Web3(new Web3.providers.HttpProvider(options.web3Url)); 30 | } 31 | } 32 | 33 | public async getBalance(address?: string) { 34 | const web3 = this.getWeb3(); 35 | let balance; 36 | if (address) { 37 | balance = await this.getERC20Contract(address) 38 | .methods.balanceOf(this.account.address) 39 | .call(); 40 | } else { 41 | balance = await web3.eth.getBalance(this.account.address); 42 | } 43 | return web3.utils.fromWei(new BigNumber(balance).toString()).toString(); 44 | } 45 | 46 | public async wrapEth(wethAddress: string, amount: string, wait?: boolean): Promise { 47 | const contract = this.getWethContract(wethAddress); 48 | const data = contract.methods.deposit().encodeABI(); 49 | const web3 = this.getWeb3(); 50 | return this.signAndSendTransaction( 51 | { 52 | data, 53 | to: wethAddress, 54 | value: web3.utils.toHex(web3.utils.toWei(amount)), 55 | }, 56 | wait 57 | ); 58 | } 59 | 60 | public async unwrapEth(wethAddress: string, amount: string, wait?: boolean): Promise { 61 | const contract = this.getWethContract(wethAddress); 62 | const web3 = this.getWeb3(); 63 | const data = contract.methods.withdraw(web3.utils.toWei(amount)).encodeABI(); 64 | return this.signAndSendTransaction( 65 | { 66 | data, 67 | to: wethAddress, 68 | value: web3.utils.toHex(0), 69 | }, 70 | wait 71 | ); 72 | } 73 | 74 | public async getAllowance(address: string): Promise { 75 | return this.getERC20Contract(address) 76 | .methods.allowance(this.account.address, this.PROXY_ADDRESS) 77 | .call(); 78 | } 79 | 80 | public async enableToken(address: string, wait?: boolean): Promise { 81 | const contract = this.getERC20Contract(address); 82 | const data = contract.methods 83 | .approve( 84 | this.PROXY_ADDRESS, 85 | new BigNumber(2) 86 | .pow(256) 87 | .minus(1) 88 | .toFixed() 89 | ) 90 | .encodeABI(); 91 | return this.signAndSendTransaction( 92 | { 93 | data, 94 | to: address, 95 | value: 0, 96 | }, 97 | wait 98 | ); 99 | } 100 | 101 | public async disableToken(address: string, wait?: boolean): Promise { 102 | const contract = this.getERC20Contract(address); 103 | const data = contract.methods.approve(this.PROXY_ADDRESS, 0).encodeABI(); 104 | return this.signAndSendTransaction( 105 | { 106 | data, 107 | to: address, 108 | value: 0, 109 | }, 110 | wait 111 | ); 112 | } 113 | 114 | private getERC20Contract(address: string) { 115 | const web3 = this.getWeb3(); 116 | // @ts-ignore 117 | return new web3.eth.Contract(erc20Abi, address) as erc20; 118 | } 119 | 120 | private getWethContract(address: string) { 121 | const web3 = this.getWeb3(); 122 | // @ts-ignore 123 | return new web3.eth.Contract(wethAbi, address) as weth; 124 | } 125 | 126 | private getWeb3(): Web3 { 127 | if (!this.web3) { 128 | throw new Error('No web3 url provided, cannot use mainnet functions!'); 129 | } 130 | 131 | return this.web3; 132 | } 133 | 134 | private async signAndSendTransaction(tx: Transaction, wait?: boolean): Promise { 135 | const web3 = this.getWeb3(); 136 | tx = await this.constructTransaction(tx); 137 | const sig = await this.account.signTransaction(tx); 138 | return new Promise(async (res, rej) => { 139 | const p = web3.eth 140 | .sendSignedTransaction(sig) 141 | .once('transactionHash', hash => { 142 | if (!wait) { 143 | res(hash); 144 | } 145 | }) 146 | .once('confirmation', (_, receipt) => { 147 | if (wait) { 148 | res(receipt.transactionHash); 149 | } 150 | }) 151 | .on('error', error => rej(error.message)); 152 | }); 153 | } 154 | 155 | private async constructTransaction(tx: Transaction): Promise { 156 | const web3 = this.getWeb3(); 157 | const from = this.account.address; 158 | 159 | const [chainId, gasPrice, nonce] = await Promise.all([ 160 | web3.eth.net.getId(), 161 | web3.eth.getGasPrice(), 162 | web3.eth.getTransactionCount(from), 163 | ]); 164 | 165 | tx = { 166 | ...tx, 167 | chainId, 168 | from, 169 | gas: 100000000, 170 | gasPrice: web3.utils.toHex(gasPrice), 171 | nonce, 172 | }; 173 | 174 | const gas = await web3.eth.estimateGas(tx); 175 | 176 | return { ...tx, gas: web3.utils.toHex(gas) }; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/models/Candle.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * Represents a "candle" on a trading chart, meant to graph trading price and 5 | * volume over time. Each candle is a specific period of time in the history 6 | * of the exchange, and contains data about price fluctuations and volume 7 | * of token traded during that period. 8 | */ 9 | export class Candle { 10 | /** 11 | * The amount of token traded over this time period 12 | */ 13 | readonly volume: BigNumber; 14 | 15 | /** 16 | * The price at the very beginning of this time period 17 | */ 18 | readonly open: BigNumber; 19 | 20 | /** 21 | * The price at the very end of this time period 22 | */ 23 | readonly close: BigNumber; 24 | 25 | /** 26 | * The highest price reached during this time period 27 | */ 28 | readonly high: BigNumber; 29 | 30 | /** 31 | * The lowest price reached during this time period 32 | */ 33 | readonly low: BigNumber; 34 | 35 | /** 36 | * The beginning of the time range 37 | */ 38 | readonly time: Date; 39 | 40 | constructor(json: any) { 41 | this.volume = json.volume ? new BigNumber(json.volume) : new BigNumber("0"); 42 | this.open = json.open ? new BigNumber(json.open) : new BigNumber("0"); 43 | this.close = json.close ? new BigNumber(json.close) : new BigNumber("0"); 44 | this.high = json.high ? new BigNumber(json.high) : new BigNumber("0"); 45 | this.low = json.low ? new BigNumber(json.low) : new BigNumber("0"); 46 | this.time = new Date(json.time); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/models/Channel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Description of a websocket channel you are listening for updates on. 3 | */ 4 | export class Channel { 5 | /** 6 | * The name of a subscription channel 7 | */ 8 | readonly name: ChannelName; 9 | 10 | /** 11 | * Which market IDs are included in this subscription 12 | */ 13 | readonly marketIds: string[]; 14 | 15 | constructor(json: any) { 16 | this.name = json.name; 17 | this.marketIds = json.marketIds; 18 | } 19 | } 20 | 21 | /** 22 | * See https://docs.ddex.io/#websocket 23 | */ 24 | export type ChannelName = 'ticker' | 'orderbook' | 'full'; 25 | -------------------------------------------------------------------------------- /src/models/Fee.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * A representation of the fees incurred when making an order on the exchange 5 | */ 6 | export class Fee { 7 | /** 8 | * The total amount being charged as a fee 9 | */ 10 | readonly asMakerFeeRate: BigNumber; 11 | 12 | /** 13 | * The percentage of the order that will be taken as a fee 14 | */ 15 | readonly asMakerTotalFeeAmount: BigNumber; 16 | 17 | /** 18 | * The total amount being charged as a fee 19 | */ 20 | readonly asMakerTradeFeeAmount: BigNumber; 21 | 22 | /** 23 | * The total amount being charged as a fee 24 | */ 25 | readonly asTakerFeeRate: BigNumber; 26 | 27 | /** 28 | * The percentage of the order that will be taken as a fee 29 | */ 30 | readonly asTakerTotalFeeAmount: BigNumber; 31 | 32 | /** 33 | * The total amount being charged as a fee 34 | */ 35 | readonly asTakerTradeFeeAmount: BigNumber; 36 | 37 | /** 38 | * The total amount being charged as a fee 39 | */ 40 | readonly gasFeeAmount: BigNumber; 41 | 42 | constructor(json: any) { 43 | this.asMakerFeeRate = json.asMakerFeeRate 44 | ? new BigNumber(json.asMakerFeeRate) 45 | : new BigNumber("0"); 46 | this.asMakerTotalFeeAmount = json.asMakerTotalFeeAmount 47 | ? new BigNumber(json.asMakerTotalFeeAmount) 48 | : new BigNumber("0"); 49 | this.asMakerTradeFeeAmount = json.asMakerTradeFeeAmount 50 | ? new BigNumber(json.asMakerTradeFeeAmount) 51 | : new BigNumber("0"); 52 | this.asTakerFeeRate = json.asTakerFeeRate 53 | ? new BigNumber(json.asTakerFeeRate) 54 | : new BigNumber("0"); 55 | this.asTakerTotalFeeAmount = json.asTakerTotalFeeAmount 56 | ? new BigNumber(json.asTakerTotalFeeAmount) 57 | : new BigNumber("0"); 58 | this.asTakerTradeFeeAmount = json.asTakerTradeFeeAmount 59 | ? new BigNumber(json.asTakerTradeFeeAmount) 60 | : new BigNumber("0"); 61 | this.gasFeeAmount = json.gasFeeAmount 62 | ? new BigNumber(json.gasFeeAmount) 63 | : new BigNumber("0"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/models/LockedBalance.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * A representation of how much of a specific token is currently listed in all 5 | * of the orders you have in the exchange. 6 | */ 7 | export class LockedBalance { 8 | /** 9 | * The symbol of the token 10 | */ 11 | readonly symbol: string; 12 | 13 | /** 14 | * The amount of token that is tied up in orders 15 | */ 16 | readonly amount: BigNumber; 17 | 18 | /** 19 | * Last time this balance was updated 20 | */ 21 | readonly updatedAt: Date; 22 | 23 | constructor(json: any) { 24 | this.symbol = json.symbol; 25 | this.amount = json.amount ? new BigNumber(json.amount) : new BigNumber("0"); 26 | this.updatedAt = new Date(json.updatedAt); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/models/Market.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | import { OrderType } from "./Order"; 3 | 4 | /** 5 | * A representation of a market on the exchange, including the various 6 | * limitations of the market. 7 | */ 8 | export class Market { 9 | /** 10 | * A token pair representing this market, e.g. "HOT-WETH" 11 | */ 12 | readonly id: string; 13 | 14 | /** 15 | * The symbol of the quote token 16 | */ 17 | readonly quoteToken: string; 18 | 19 | /** 20 | * The number of decimals used by the quote token 21 | */ 22 | readonly quoteTokenDecimals: number; 23 | 24 | /** 25 | * The contract address of the quote token 26 | */ 27 | readonly quoteTokenAddress: string; 28 | 29 | /** 30 | * The symbol of the base token 31 | */ 32 | readonly baseToken: string; 33 | 34 | /** 35 | * The number of decimals used by the base token 36 | */ 37 | readonly baseTokenDecimals: number; 38 | 39 | /** 40 | * The contract address of the base token 41 | */ 42 | readonly baseTokenAddress: string; 43 | 44 | /** 45 | * The minimum amount of the quote token you can specify in an order 46 | */ 47 | readonly minOrderSize: BigNumber; 48 | 49 | /** 50 | * The number of significant digits allowed for the price of an order 51 | */ 52 | readonly pricePrecision: number; 53 | 54 | /** 55 | * The maximum number of decimal places that can be used to specify the price of an order 56 | */ 57 | readonly priceDecimals: number; 58 | 59 | /** 60 | * The maximum number of decimal places that can be used to specify the amount of an order 61 | */ 62 | readonly amountDecimals: number; 63 | 64 | /** 65 | * The base fee rate that will be used for the order maker 66 | */ 67 | readonly asMakerFeeRate: BigNumber; 68 | 69 | /** 70 | * The base fee rate that will be used for the order taker 71 | */ 72 | readonly asTakerFeeRate: BigNumber; 73 | 74 | /** 75 | * The amount that will be charged for gas on orders 76 | */ 77 | readonly gasFeeAmount: BigNumber; 78 | 79 | /** 80 | * List of order types supported by this market 81 | */ 82 | readonly supportedOrderTypes: OrderType[]; 83 | 84 | constructor(json: any) { 85 | this.id = json.id; 86 | 87 | this.quoteToken = json.quoteToken; 88 | this.quoteTokenDecimals = json.quoteTokenDecimals; 89 | this.quoteTokenAddress = json.quoteTokenAddress; 90 | 91 | this.baseToken = json.baseToken; 92 | this.baseTokenDecimals = json.baseTokenDecimals; 93 | this.baseTokenAddress = json.baseTokenAddress; 94 | 95 | this.minOrderSize = json.minOrderSize 96 | ? new BigNumber(json.minOrderSize) 97 | : new BigNumber("0"); 98 | 99 | this.pricePrecision = json.pricePrecision; 100 | this.priceDecimals = json.priceDecimals; 101 | this.amountDecimals = json.amountDecimals; 102 | 103 | this.asMakerFeeRate = json.asMakerFeeRate 104 | ? new BigNumber(json.asMakerFeeRate) 105 | : new BigNumber("0"); 106 | this.asTakerFeeRate = json.asTakerFeeRate 107 | ? new BigNumber(json.asTakerFeeRate) 108 | : new BigNumber("0"); 109 | 110 | this.gasFeeAmount = json.gasFeeAmount 111 | ? new BigNumber(json.gasFeeAmount) 112 | : new BigNumber("0"); 113 | 114 | this.supportedOrderTypes = json.supportedOrderTypes; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/models/Order.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { OrderData } from './OrderData'; 3 | 4 | /** 5 | * Data used to represent an order on the exchange. The values in this class 6 | * may not be set to useful values depending on the source, for example if 7 | * you are looking at data of an order made by someone else it will only contain 8 | * a minimal amount of data, and orders that come through from websocket events 9 | * will also be more stripped down. You can expect the class to contain full 10 | * data whenever creating or querying for your own orders. 11 | */ 12 | export class Order { 13 | /** 14 | * The id of the order on the exchange 15 | */ 16 | readonly id: string; 17 | 18 | /** 19 | * Which version of hydro was used to construct this order 20 | */ 21 | readonly version: string; 22 | 23 | /** 24 | * Which market this order exists on, e.g. "HOT-WETH" 25 | */ 26 | readonly marketId: string; 27 | 28 | /** 29 | * The type of order being made, market or limit 30 | */ 31 | readonly type: OrderType; 32 | 33 | /** 34 | * Current status of the order 35 | */ 36 | readonly status: string; 37 | 38 | /** 39 | * Whether this is a "buy" or a "sell" order 40 | */ 41 | readonly side: Side; 42 | 43 | /** 44 | * The account that made this order 45 | */ 46 | readonly account: string; 47 | 48 | /** 49 | * Data used by 0x to construct an order. 50 | * 51 | * See https://0xproject.com/wiki#Create,-Validate,-Fill-Order 52 | */ 53 | readonly data?: OrderData; 54 | 55 | /** 56 | * The amount of token specified in this order 57 | */ 58 | readonly amount: BigNumber; 59 | 60 | /** 61 | * The price of the order 62 | */ 63 | readonly price: BigNumber; 64 | 65 | /** 66 | * The average price used for the order so far. This can differ from price when taking into 67 | * account things like price improvement or market orders. 68 | */ 69 | readonly averagePrice: BigNumber; 70 | 71 | /** 72 | * The fee rate that will be used for the order maker 73 | */ 74 | readonly makerFeeRate: BigNumber; 75 | 76 | /** 77 | * The fee rate that will be used for the order maker 78 | */ 79 | readonly takerFeeRate: BigNumber; 80 | 81 | /** 82 | * The rebate rate that will be applied for the order maker 83 | */ 84 | readonly makerRebateRate: BigNumber; 85 | 86 | /** 87 | * The amount of gas that will be charged for this order 88 | */ 89 | readonly gasFeeAmount: BigNumber; 90 | 91 | /** 92 | * The amount of the the order that is still available to be filled 93 | */ 94 | readonly availableAmount: BigNumber; 95 | 96 | /** 97 | * The amount of the order that is currently being filled by a trade, 98 | * but has not been verified on the blockchain yet 99 | */ 100 | readonly pendingAmount: BigNumber; 101 | 102 | /** 103 | * The amount of the order that was remaining when the order was 104 | * cancelled 105 | */ 106 | readonly canceledAmount: BigNumber; 107 | 108 | /** 109 | * The amount of the order that has been filled by a trade 110 | */ 111 | readonly confirmedAmount: BigNumber; 112 | 113 | /** 114 | * Time the order was created 115 | */ 116 | readonly createdAt?: Date; 117 | 118 | constructor(json: any) { 119 | this.id = json.id || json.orderId; 120 | this.version = json.version; 121 | this.marketId = json.marketId; 122 | this.type = json.type && json.type.toUpperCase ? json.type.toUpperCase() : 'limit'; 123 | this.status = json.status; 124 | this.side = json.side && json.side.toUpperCase ? json.side.toUpperCase() : 'buy'; 125 | this.account = json.account; 126 | 127 | if (json.json) { 128 | this.data = new OrderData(json.json); 129 | } 130 | 131 | this.amount = json.amount ? new BigNumber(json.amount) : new BigNumber('0'); 132 | this.price = json.price ? new BigNumber(json.price) : new BigNumber('0'); 133 | this.averagePrice = json.averagePrice ? new BigNumber(json.averagePrice) : new BigNumber('0'); 134 | 135 | this.makerFeeRate = json.makerFeeRate ? new BigNumber(json.makerFeeRate) : new BigNumber('0'); 136 | this.takerFeeRate = json.takerFeeRate ? new BigNumber(json.takerFeeRate) : new BigNumber('0'); 137 | this.makerRebateRate = json.makerRebateRate 138 | ? new BigNumber(json.makerRebateRate) 139 | : new BigNumber('0'); 140 | this.gasFeeAmount = json.gasFeeAmount ? new BigNumber(json.gasFeeAmount) : new BigNumber('0'); 141 | 142 | let availableAmount = json.availableAmount || json.newAvailableAmount; 143 | this.availableAmount = availableAmount ? new BigNumber(availableAmount) : new BigNumber('0'); 144 | this.pendingAmount = json.pendingAmount 145 | ? new BigNumber(json.pendingAmount) 146 | : new BigNumber('0'); 147 | this.canceledAmount = json.canceledAmount 148 | ? new BigNumber(json.canceledAmount) 149 | : new BigNumber('0'); 150 | this.confirmedAmount = json.confirmedAmount 151 | ? new BigNumber(json.confirmedAmount) 152 | : new BigNumber('0'); 153 | 154 | this.createdAt = json.createdAt ? new Date(json.createdAt) : undefined; 155 | } 156 | } 157 | 158 | export type OrderType = 'limit' | 'market'; 159 | export type Side = 'buy' | 'sell'; 160 | export type Status = 'pending' | 'all'; 161 | -------------------------------------------------------------------------------- /src/models/OrderData.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { OrderType, Side } from './Order'; 3 | 4 | /** 5 | * Data used by 0x to construct an order. 6 | * 7 | * See https://0xproject.com/wiki#Create,-Validate,-Fill-Order 8 | */ 9 | export class OrderData { 10 | /** 11 | * The address of the order creator 12 | */ 13 | readonly trader: string; 14 | 15 | /** 16 | * The address of the relayer in charge of matching orders 17 | */ 18 | readonly relayer: string; 19 | 20 | /** 21 | * The address of the base token 22 | */ 23 | readonly baseToken: string; 24 | 25 | /** 26 | * The address of quote token 27 | */ 28 | readonly quoteToken: string; 29 | 30 | /** 31 | * The amount of base token to be filled by this order 32 | */ 33 | readonly baseTokenAmount: BigNumber; 34 | 35 | /** 36 | * The amount of quote token related to the base token. This essentially determines price. 37 | */ 38 | readonly quoteTokenAmount: BigNumber; 39 | 40 | /** 41 | * The amount of gas in base token charged by the relayer to cover ethereum gas costs 42 | */ 43 | readonly gasTokenAmount: BigNumber; 44 | 45 | /** 46 | * A data field containing a compact representation of various properties of the order 47 | */ 48 | readonly data: string; 49 | 50 | constructor(json: any) { 51 | this.trader = json.trader; 52 | this.relayer = json.relayer; 53 | this.baseToken = json.baseToken; 54 | this.quoteToken = json.quoteToken; 55 | 56 | this.baseTokenAmount = json.baseTokenAmount 57 | ? new BigNumber(json.baseTokenAmount) 58 | : new BigNumber('0'); 59 | this.quoteTokenAmount = json.quoteTokenAmount 60 | ? new BigNumber(json.quoteTokenAmount) 61 | : new BigNumber('0'); 62 | this.gasTokenAmount = json.gasTokenAmount 63 | ? new BigNumber(json.gasTokenAmount) 64 | : new BigNumber('0'); 65 | 66 | this.data = json.data; 67 | } 68 | 69 | /* Methods to pull information from the compact data field */ 70 | 71 | /** 72 | * Which version of Hydro was used to construct this order 73 | */ 74 | public getVersion(): number { 75 | return this.sliceData(0, 1); 76 | } 77 | 78 | /** 79 | * Whether this is a buy or sell order 80 | */ 81 | public getSide(): Side { 82 | const side = this.sliceData(1, 1); 83 | return side === 0 ? 'buy' : 'sell'; 84 | } 85 | 86 | /** 87 | * Whether this is a limit or market order 88 | */ 89 | public getType(): OrderType { 90 | const type = this.sliceData(2, 1); 91 | return type === 0 ? 'limit' : 'market'; 92 | } 93 | 94 | /** 95 | * Return a Date object representing when this order will expire 96 | */ 97 | public getExpiration(): Date { 98 | const expiration = this.sliceData(3, 5); 99 | return new Date(expiration * 1000); 100 | } 101 | 102 | /** 103 | * Return a BigNumber representing the rate for the fee the maker will pay 104 | */ 105 | public getMakerFeeRate(): BigNumber { 106 | const makerFeeRate = this.sliceData(8, 2); 107 | return new BigNumber(makerFeeRate).div(100000); 108 | } 109 | 110 | /** 111 | * Return a BigNumber representing the rate for the fee the taker will pay 112 | */ 113 | public getTakerFeeRate(): BigNumber { 114 | const takerFeeRate = this.sliceData(10, 2); 115 | return new BigNumber(takerFeeRate).div(100000); 116 | } 117 | 118 | /** 119 | * Return a BigNumber representing the rate for the rebate the maker will get 120 | */ 121 | public getMakerRebateRate(): BigNumber { 122 | const makerRebateRate = this.sliceData(12, 2); 123 | return new BigNumber(makerRebateRate).div(100000); 124 | } 125 | 126 | /** 127 | * Returns decimal representation of the data at a certain offset with a certain size. 128 | * Offet will not include the prepended 0x, and both parameters will be defined by bytes, or a 129 | * 2 digit hex representation. For example in the string 0x12345678, passing (0, 1) would get the 130 | * hex value 12, convert it to decimal and return 18. Passing (1, 2) would get the hex value 3456 131 | * and return 13398. 132 | * 133 | * @param offset The number of bytes to offset into the data field 134 | * @param size The number of bytes to grab at that offset 135 | * @return The decimal representation of the hex found 136 | */ 137 | private sliceData(offset: number, size: number): number { 138 | const additionalOffset = this.data.startsWith('0x') ? 2 : 0; 139 | const hex = this.data.substr(offset * 2 + additionalOffset, size * 2); 140 | return parseInt(hex, 16); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/models/OrderList.ts: -------------------------------------------------------------------------------- 1 | import { Order } from "./Order"; 2 | 3 | /** 4 | * A paginated list of orders 5 | */ 6 | export class OrderList { 7 | /** 8 | * The total number of pages available on the server 9 | */ 10 | readonly totalPages: number; 11 | 12 | /** 13 | * The current page this list represents 14 | */ 15 | readonly currentPage: number; 16 | 17 | /** 18 | * A list of orders 19 | */ 20 | readonly orders: Order[]; 21 | 22 | constructor(json: any) { 23 | this.totalPages = json.totalPages; 24 | this.currentPage = json.currentPage; 25 | this.orders = json.orders.map((order: any) => new Order(order)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/models/Orderbook.ts: -------------------------------------------------------------------------------- 1 | import { Order } from './Order'; 2 | import { PriceLevel } from './PriceLevel'; 3 | 4 | export class Orderbook { 5 | /** 6 | * The market this orderbook exists on, e.g. "HOT-WETH" 7 | */ 8 | readonly marketId: string; 9 | 10 | /** 11 | * List of bids (buy orders) 12 | */ 13 | readonly bids: (PriceLevel | Order)[]; 14 | 15 | /** 16 | * List of asks (sell orders) 17 | */ 18 | readonly asks: (PriceLevel | Order)[]; 19 | 20 | constructor(json: any, level?: OrderbookLevel) { 21 | this.marketId = json.marketId; 22 | 23 | if (level === 3) { 24 | this.bids = json.bids.map((bid: any) => new Order(bid)); 25 | this.asks = json.asks.map((ask: any) => new Order(ask)); 26 | } else { 27 | this.bids = json.bids.map((bid: any) => new PriceLevel(bid)); 28 | this.asks = json.asks.map((ask: any) => new PriceLevel(ask)); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Each level represents a different amount of detail returned in 35 | * the orderbook. 36 | * 37 | * Level 1 returns only the best bid/ask prices 38 | * 39 | * Level 2 returns the top 50 bids and asks, with orders at the 40 | * same price being aggregated together 41 | * 42 | * Level 3 returns the full non-aggregated orderbook, with each 43 | * individual order id, and it's price and amount. 44 | * 45 | * See https://docs.ddex.io/#get-orderbook 46 | */ 47 | export type OrderbookLevel = 1 | 2 | 3; 48 | -------------------------------------------------------------------------------- /src/models/PriceLevel.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * An aggregated level in the orderbook, containing the total 5 | * amount of token available to purchase at a certain price 6 | */ 7 | export class PriceLevel { 8 | /** 9 | * The price of the tokens 10 | */ 11 | readonly price: BigNumber; 12 | 13 | /** 14 | * The total aggregated amount of tokens available for this price 15 | */ 16 | readonly amount: BigNumber; 17 | 18 | constructor(json: any) { 19 | this.price = json.price ? new BigNumber(json.price) : new BigNumber("0"); 20 | this.amount = json.amount ? new BigNumber(json.amount) : new BigNumber("0"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/models/Ticker.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * Contains the latest market data about a specific market 5 | */ 6 | export class Ticker { 7 | /** 8 | * The market this ticker represents, e.g. "HOT-WETH" 9 | */ 10 | readonly marketId: string; 11 | 12 | /** 13 | * The price of the last trade on this market 14 | */ 15 | readonly price: BigNumber; 16 | 17 | /** 18 | * The 24 hour volume of this market 19 | */ 20 | readonly volume: BigNumber; 21 | 22 | /** 23 | * The lowest current ask price on this market 24 | */ 25 | readonly ask: BigNumber; 26 | 27 | /** 28 | * The highest current bid price on this market 29 | */ 30 | readonly bid: BigNumber; 31 | 32 | /** 33 | * The lowest price in the last 24 hours 34 | */ 35 | readonly low: BigNumber; 36 | 37 | /** 38 | * The highest price in the last 24 hours 39 | */ 40 | readonly high: BigNumber; 41 | 42 | /** 43 | * The last time this ticker was updated 44 | */ 45 | readonly updatedAt: Date; 46 | 47 | constructor(json: any) { 48 | this.marketId = json.marketId; 49 | this.price = json.price ? new BigNumber(json.price) : new BigNumber("0"); 50 | this.volume = json.volume ? new BigNumber(json.volume) : new BigNumber("0"); 51 | this.ask = json.ask ? new BigNumber(json.ask) : new BigNumber("0"); 52 | this.bid = json.bid ? new BigNumber(json.bid) : new BigNumber("0"); 53 | this.low = json.low ? new BigNumber(json.low) : new BigNumber("0"); 54 | this.high = json.high ? new BigNumber(json.high) : new BigNumber("0"); 55 | this.updatedAt = new Date(json.updatedAt || json.time); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/models/Trade.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | 3 | /** 4 | * Data used to represent a trade on the exchange. The values in this class 5 | * may not be set to useful values depending on the source, for example if 6 | * you are looking at data of a trade made by someone else it will only contain 7 | * a minimal amount of data, and orders that come through from websocket events 8 | * will also be more stripped down. You can expect the class to contain full 9 | * data whenever querying for your own trades. 10 | */ 11 | export class Trade { 12 | /** 13 | * The market this trade exists on, e.g. "HOT-WETH" 14 | */ 15 | readonly marketId: string; 16 | 17 | /** 18 | * Current status of the trade 19 | */ 20 | readonly status: string; 21 | 22 | /** 23 | * The id of this trade on the blockchain 24 | */ 25 | readonly transactionId: string; 26 | 27 | /** 28 | * The id of the order created by the maker 29 | */ 30 | readonly makerOrderId: string; 31 | 32 | /** 33 | * The id of the order created by the taker 34 | */ 35 | readonly takerOrderId: string; 36 | 37 | /** 38 | * The address of the maker 39 | */ 40 | readonly maker: string; 41 | 42 | /** 43 | * The address of the taker 44 | */ 45 | readonly taker: string; 46 | 47 | /** 48 | * The address of the buyer 49 | */ 50 | readonly buyer: string; 51 | 52 | /** 53 | * The address of the seller 54 | */ 55 | readonly seller: string; 56 | 57 | /** 58 | * The amount of tokens that were traded 59 | */ 60 | readonly amount: BigNumber; 61 | 62 | /** 63 | * The price of the tokens that were traded 64 | */ 65 | readonly price: BigNumber; 66 | 67 | /** 68 | * The price the taker was willing to pay 69 | */ 70 | readonly takerPrice: BigNumber; 71 | 72 | /** 73 | * The fee charged for this trade 74 | */ 75 | readonly feeAmount: BigNumber; 76 | 77 | /** 78 | * The time the two orders making the trade were matched 79 | */ 80 | readonly createdAt: Date; 81 | 82 | /** 83 | * The time the trade was verified on the blockchain 84 | */ 85 | readonly executedAt: Date; 86 | 87 | constructor(json: any) { 88 | this.marketId = json.marketId; 89 | this.status = json.status; 90 | 91 | this.transactionId = json.transactionId; 92 | this.makerOrderId = json.makerOrderId; 93 | this.takerOrderId = json.takerOrderId; 94 | 95 | this.maker = json.maker; 96 | this.taker = json.taker; 97 | this.buyer = json.buyer; 98 | this.seller = json.seller; 99 | 100 | this.amount = json.amount ? new BigNumber(json.amount) : new BigNumber("0"); 101 | this.price = json.price ? new BigNumber(json.price) : new BigNumber("0"); 102 | this.takerPrice = json.takerPrice 103 | ? new BigNumber(json.takerPrice) 104 | : new BigNumber("0"); 105 | this.feeAmount = json.feeAmount 106 | ? new BigNumber(json.feeAmount) 107 | : new BigNumber("0"); 108 | 109 | this.createdAt = new Date(json.createdAt); 110 | this.executedAt = new Date(json.executedAt); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/models/TradeList.ts: -------------------------------------------------------------------------------- 1 | import { Trade } from "./Trade"; 2 | 3 | /** 4 | * A paginated list of trades 5 | */ 6 | export class TradeList { 7 | /** 8 | * The total number of pages available on the server 9 | */ 10 | readonly totalPages: number; 11 | 12 | /** 13 | * The current page this list represents 14 | */ 15 | readonly currentPage: number; 16 | 17 | /** 18 | * A list of trades 19 | */ 20 | readonly trades: Trade[]; 21 | 22 | constructor(json: any) { 23 | this.totalPages = json.totalPages; 24 | this.currentPage = json.currentPage; 25 | this.trades = json.trades.map((trade: any) => new Trade(trade)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./build", 7 | "strict": true, 8 | "lib": ["es2015"], 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | --------------------------------------------------------------------------------