├── web.js ├── node.js ├── README.md ├── src ├── config │ ├── index.js │ ├── config.js │ └── config.test.js ├── api │ ├── index.js │ ├── api-utils.js │ ├── api-utils.test.js │ ├── market-api.js │ └── trade-api.js ├── utils │ ├── asserts.js │ ├── utils.js │ ├── request.js │ ├── constans.js │ ├── serializers.js │ └── jsdocsModels.js ├── ws │ ├── streams │ │ ├── user-data-stream.js │ │ ├── user-history-stream.js │ │ ├── constants.js │ │ ├── base-user-stream.js │ │ ├── generic-pairs-stream.js │ │ ├── candlestick-stream.js │ │ └── base-stream.js │ └── the-ocean-websockets.js ├── ledger │ └── ledger.js ├── the-ocean.js ├── the-ocean-market-data.js ├── auth │ └── auth.js ├── the-ocean-wallet.js └── the-ocean-trade.js ├── .npmignore ├── test ├── the-ocean-wallet.test.js ├── the-ocean-trade.test.js ├── the-ocean-market-data.test.js └── the-ocean.test.js ├── .gitignore ├── .babelrc ├── LICENSE ├── CHANGELOG.md └── package.json /web.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/the-ocean.umd.js') 2 | -------------------------------------------------------------------------------- /node.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/the-ocean.commonjs2.js') 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The-ocean 2 | Please refer to our documentation at https://docs.theocean.trade/ 3 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import config from './config' 2 | 3 | module.exports = { 4 | config 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docker 2 | integration 3 | .gitlab-ci.yml 4 | docker-compose.yml 5 | index.html 6 | webpack.config.js -------------------------------------------------------------------------------- /test/the-ocean-wallet.test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | describe('Wallet', () => { 4 | it.skip('runs', () => { 5 | 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | lib/ 4 | dist/ 5 | .temp/ 6 | npm-debug.log 7 | .env 8 | # yarn.lock 9 | yarn-error.log 10 | .DS_Store -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import market from './market-api' 2 | import trade from './trade-api' 3 | 4 | module.exports = { 5 | market, 6 | trade 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/asserts.js: -------------------------------------------------------------------------------- 1 | class Assert { 2 | static isString (value) { 3 | return typeof value === 'string' 4 | } 5 | } 6 | 7 | export { Assert } 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": "current", 6 | "browsers": [ 7 | "last 2 versions" 8 | ] 9 | } 10 | }] 11 | ], 12 | "plugins": [ 13 | "transform-object-rest-spread", 14 | "transform-runtime" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/ws/streams/user-data-stream.js: -------------------------------------------------------------------------------- 1 | import BaseUserStream from './base-user-stream' 2 | import { CHANNEL } from './constants' 3 | 4 | const debug = require('debug')('the-ocean:stream:user_data') 5 | 6 | export default class UserDataStream extends BaseUserStream { 7 | constructor (io) { 8 | super(io, CHANNEL.USER_DATA, debug) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ws/streams/user-history-stream.js: -------------------------------------------------------------------------------- 1 | import BaseUserStream from './base-user-stream' 2 | import { CHANNEL } from './constants' 3 | 4 | const debug = require('debug')('the-ocean:stream:user-history') 5 | 6 | export default class UserHistoryStream extends BaseUserStream { 7 | constructor (io) { 8 | super(io, CHANNEL.USER_HISTORY, debug) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | export const promisify = (action) => 2 | new Promise((resolve, reject) => 3 | action((err, res) => { 4 | if (err) { reject(err) } 5 | 6 | resolve(res) 7 | }) 8 | ) 9 | 10 | export const getTimestamp = () => { 11 | return Date.now() / 1000 12 | } 13 | 14 | export function isEthereumAddress (address) { 15 | return /^(0x)?[0-9a-fA-F]{40}$/i.test(address) 16 | } 17 | -------------------------------------------------------------------------------- /src/api/api-utils.js: -------------------------------------------------------------------------------- 1 | import { getConfig } from '../config/config' 2 | import urljoin from 'url-join' 3 | 4 | function getEndpoint (service) { 5 | return urljoin(getConfig().api.baseURL, service) 6 | } 7 | 8 | function requestProperties (method = 'GET') { 9 | let headers = { 10 | 'Access-Control-Allow-Origin': '*', 11 | 'Content-Type': 'application/json' 12 | } 13 | 14 | return { 15 | method: method, 16 | headers: headers 17 | } 18 | } 19 | 20 | module.exports = { 21 | getEndpoint, 22 | requestProperties 23 | } 24 | -------------------------------------------------------------------------------- /src/ws/streams/constants.js: -------------------------------------------------------------------------------- 1 | export const SUBSCRIBE = 'subscribe' 2 | 3 | export const UNSUBSCRIBE = 'unsubscribe' 4 | 5 | export const SEND_CHANNEL = 'data' 6 | 7 | export const RESPONSE_CHANNEL = 'message' 8 | 9 | export const MSG_TYPE = { 10 | SNAPSHOT: 'snapshot', 11 | UPDATE: 'update' 12 | } 13 | 14 | export const CHANNEL = { 15 | ORDER_BOOK: 'order_book', 16 | CANDLESTICKS: 'candlesticks', 17 | TRADE_HISTORY: 'trade_history', 18 | USER_HISTORY: 'user_history', 19 | TICKER_STATS: 'ticker_stats', 20 | USER_DATA: 'user_data' 21 | } 22 | 23 | export const USER_ID_PARAM = 'userId' 24 | -------------------------------------------------------------------------------- /test/the-ocean-trade.test.js: -------------------------------------------------------------------------------- 1 | /* global expect, describe, it, beforeEach */ 2 | 3 | import { Trade } from '../src/the-ocean-trade' 4 | 5 | describe('Trade ', () => { 6 | let trade 7 | 8 | beforeEach(() => { 9 | trade = new Trade() 10 | }) 11 | 12 | it('implements trade methods', () => { 13 | expect(trade.newMarketOrder).toBeDefined() 14 | expect(trade.newLimitOrder).toBeDefined() 15 | expect(trade.cancelOrder).toBeDefined() 16 | expect(trade.userHistory).toBeDefined() 17 | expect(trade.tokenAvailableBalance).toBeDefined() 18 | expect(trade.tokenCommittedAmount).toBeDefined() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const normalizeOpts = ({method = 'get', body, qs, ...opts} = {}) => ({ 4 | ...opts, 5 | params: qs, 6 | data: body, 7 | method: method.toLowerCase() 8 | }) 9 | const normalizeResponse = response => response.data 10 | const normalizeFail = error => { 11 | const { code, response: { data, status } } = error 12 | const statusCode = code || status 13 | error.message = statusCode + ' - ' + (JSON && JSON.stringify ? JSON.stringify(data) : data) 14 | error.statusCode = statusCode 15 | throw error 16 | } 17 | 18 | export default opts => axios(normalizeOpts(opts)) 19 | .then(normalizeResponse) 20 | .catch(normalizeFail) 21 | -------------------------------------------------------------------------------- /src/api/api-utils.test.js: -------------------------------------------------------------------------------- 1 | import { getConfig, setConfig } from '../config/config' 2 | import { getEndpoint } from './api-utils' 3 | 4 | describe('getEndpoint', () => { 5 | it('should resolve simple endpoints', () => { 6 | setConfig({ 7 | api: { 8 | baseURL: 'http://1.1.1.1:3333/api/v1', 9 | ORDER: '/order' 10 | } 11 | }) 12 | const url = getEndpoint(getConfig().api.ORDER) 13 | 14 | expect(url).toBe('http://1.1.1.1:3333/api/v1/order') 15 | }) 16 | 17 | it('should resolve tricky endpoints', () => { 18 | setConfig({ 19 | api: { 20 | baseURL: 'http://1.1.1.1:3333/api/v2/', 21 | ORDER: '/order' 22 | } 23 | }) 24 | const url = getEndpoint(getConfig().api.ORDER) 25 | 26 | expect(url).toBe('http://1.1.1.1:3333/api/v2/order') 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/utils/constans.js: -------------------------------------------------------------------------------- 1 | export const zeroExConfigByNetworkId = { 2 | 1000: { // Localchain 3 | exchangeContractAddress: '0x499d511d6bc3a1a267439645cf50d5dddc688d54', 4 | tokenRegistryContractAddress: '0x16d9b9a68ef707c5d5601fae2d29a78a9dde1fe3', 5 | tokenTransferProxyContractAddress: '0x81f0dfc0a761bba60d63ac4ce8c776d6a1e69979', 6 | zrxContractAddress: '0x4db7ba0b930f0d886987fb2abc7cd79725afafcd' 7 | }, 8 | 42: { // Kovan 9 | exchangeContractAddress: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', 10 | tokenRegistryContractAddress: '0xf18e504561f4347bea557f3d4558f559dddbae7f', 11 | tokenTransferProxyContractAddress: '0x087eed4bc1ee3de49befbd66c662b434b15d49d4', 12 | zrxContractAddress: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570' 13 | }, 14 | 3: { // Robsten 15 | }, 16 | 1: { // Main 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ws/streams/base-user-stream.js: -------------------------------------------------------------------------------- 1 | import BaseStream from './base-stream' 2 | import map from 'lodash/map' 3 | import { 4 | USER_ID_PARAM 5 | } from './constants' 6 | 7 | export default class BaseUserStream extends BaseStream { 8 | subscribe (payload, callback) { 9 | super.subscribe(payload) 10 | return this._addSubscription( 11 | this.channel, 12 | this._getUnsubscriptionParams(payload), 13 | callback 14 | ) 15 | } 16 | 17 | getSubscriptions () { 18 | return map(this.subscriptions, (subscription, key) => ({ 19 | [this.channel]: key, 20 | unsubscribe: subscription.unsubscribe, 21 | resubscribe: subscription.resubscribe 22 | })) 23 | } 24 | 25 | _getUnsubscriptionParams (payload) { 26 | return { 27 | [USER_ID_PARAM]: payload[USER_ID_PARAM] 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Ocean X 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ledger/ledger.js: -------------------------------------------------------------------------------- 1 | // This is the ledger provider to use with web3: 2 | // https://github.com/Neufund/ledger-wallet-provider 3 | // 1. Plugg-in the Ledger Wallet Nano S 4 | // 2. Input the 4 digit pin 5 | // 3. Execute the function `init()` below 6 | // 4. If you see your Ledger accounts in the console, it works. 7 | // 8 | // To test the function init() right here, write init() before the export default init 9 | // and it will execute the code. If you see your Ledger accounts in the console, it works. 10 | 11 | import Web3 from 'web3' 12 | import ProviderEngine from 'web3-provider-engine' 13 | import RpcSubprovider from 'web3-provider-engine/subproviders/rpc' 14 | import LedgerWalletSubproviderFactory from 'ledger-wallet-provider' 15 | 16 | async function init () { 17 | const engine = new ProviderEngine() 18 | const web3 = new Web3(engine) 19 | const ledgerWalletSubProvider = await LedgerWalletSubproviderFactory() 20 | 21 | engine.addProvider(ledgerWalletSubProvider) 22 | engine.addProvider(new RpcSubprovider({rpcUrl: '/api'})) // you need RPC endpoint 23 | engine.start() 24 | // It will show undefined until the steps at the top are completed 25 | web3.eth.getAccounts(console.log) 26 | } 27 | 28 | export default init 29 | -------------------------------------------------------------------------------- /src/utils/serializers.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Converts the order's parameters to the right types 4 | * @param order 5 | * @returns {{exchangeContractAddress, expirationUnixTimestampSec, feeRecipient, maker, taker: *|string|{$ref: string}|taker|{$ref}, makerTokenAddress, takerTokenAddress, orderHash: *|string|String, takerFee, makerFee, salt: string, makerTokenAmount: string, takerTokenAmount: string, ecSignature: *|{$ref: string}|properties.ecSignature|{$ref}|ECSignature}} 6 | */ 7 | function serializeOrder (order) { 8 | return { 9 | exchangeContractAddress: order.exchangeContractAddress, 10 | maker: order.maker, 11 | taker: order.taker, 12 | makerTokenAddress: order.makerTokenAddress, 13 | takerTokenAddress: order.takerTokenAddress, 14 | feeRecipient: order.feeRecipient, 15 | makerTokenAmount: order.makerTokenAmount.toString(), 16 | takerTokenAmount: order.takerTokenAmount.toString(), 17 | takerFee: order.takerFee.toString(), 18 | makerFee: order.makerFee.toString(), 19 | expirationUnixTimestampSec: order.expirationUnixTimestampSec.toString(), 20 | salt: order.salt.toString(), 21 | orderHash: order.orderHash, 22 | ecSignature: order.ecSignature 23 | } 24 | }; 25 | 26 | module.exports = { 27 | serializeOrder 28 | } 29 | -------------------------------------------------------------------------------- /src/ws/streams/generic-pairs-stream.js: -------------------------------------------------------------------------------- 1 | import BaseStream from './base-stream' 2 | 3 | const debug = require('debug')('the-ocean:stream:generic-pair-stream') 4 | 5 | export default class GenericPairsStream extends BaseStream { 6 | constructor (io, channel) { 7 | super(io, channel, debug) 8 | } 9 | 10 | getChannelId (payload) { 11 | return this.channel + '_' + payload.baseTokenAddress + '_' + payload.quoteTokenAddress 12 | } 13 | 14 | subscribe (payload, callback) { 15 | super.subscribe(payload) 16 | return this._addSubscription(this.getChannelId(payload), 17 | this._getUnsubscriptionParams(payload), 18 | callback) 19 | } 20 | 21 | getSubscriptions () { 22 | let subs = [] 23 | Object.keys(this.subscriptions).forEach(key => { 24 | const subscription = this.subscriptions[key] 25 | const channelId = key.split('_') 26 | subs.push({ 27 | baseTokenAddress: channelId[channelId.length - 2], 28 | quoteTokenAddress: channelId[channelId.length - 1], 29 | unsubscribe: subscription.unsubscribe, 30 | resubscribe: subscription.resubscribe 31 | }) 32 | }) 33 | return subs 34 | } 35 | 36 | _getUnsubscriptionParams (payload) { 37 | return { 38 | baseTokenAddress: payload.baseTokenAddress, 39 | quoteTokenAddress: payload.quoteTokenAddress 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/ws/streams/candlestick-stream.js: -------------------------------------------------------------------------------- 1 | import BaseStream from './base-stream' 2 | 3 | const debug = require('debug')('the0cean:stream:generic-pair-stream') 4 | 5 | export default class CandlestickStream extends BaseStream { 6 | constructor (io, channel) { 7 | super(io, channel, debug) 8 | } 9 | 10 | getChannelId (payload) { 11 | return this.channel + '_' + payload.baseTokenAddress + '_' + payload.quoteTokenAddress + '_' + payload.interval 12 | } 13 | 14 | subscribe (payload, callback) { 15 | super.subscribe(payload) 16 | return this._addSubscription(this.getChannelId(payload), 17 | this._getUnsubscriptionParams(payload), 18 | callback) 19 | } 20 | 21 | getSubscriptions () { 22 | let subs = [] 23 | Object.keys(this.subscriptions).forEach(key => { 24 | const subscription = this.subscriptions[key] 25 | const channelId = key.split('_') 26 | subs.push({ 27 | baseTokenAddress: channelId[channelId.length - 3], 28 | quoteTokenAddress: channelId[channelId.length - 2], 29 | interval: channelId[channelId.length - 1], 30 | unsubscribe: subscription.unsubscribe, 31 | resubscribe: subscription.resubscribe 32 | }) 33 | }) 34 | return subs 35 | } 36 | 37 | _getUnsubscriptionParams (payload) { 38 | return { 39 | baseTokenAddress: payload.baseTokenAddress, 40 | quoteTokenAddress: payload.quoteTokenAddress, 41 | interval: payload.interval 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /test/the-ocean-market-data.test.js: -------------------------------------------------------------------------------- 1 | /* global expect, describe, it, beforeEach */ 2 | 3 | import { setConfig } from '../src/config/config' 4 | import { MarketData } from '../src/the-ocean-market-data' 5 | 6 | describe('MarketData ', () => { 7 | let market 8 | 9 | beforeEach(() => { 10 | setConfig({ 11 | websockets: 'localhost:3001', 12 | api: { 13 | baseURL: 'http://localhost:3000/api/v0' 14 | }, 15 | relay: { 16 | exchange: '0x499d511d6bc3a1a267439645cf50d5dddc688d54', 17 | funnel: '0x00ba938cc0df182c25108d7bf2ee3d37bce07513', 18 | feeRecipient: '0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe' 19 | } 20 | }) 21 | market = new MarketData() 22 | }) 23 | 24 | it('implements market data methods', () => { 25 | expect(market.tokenPairs).toBeDefined() 26 | expect(market.ticker).toBeDefined() 27 | expect(market.tickers).toBeDefined() 28 | expect(market.orderBook).toBeDefined() 29 | expect(market.tradeHistory).toBeDefined() 30 | expect(market.candlesticks).toBeDefined() 31 | expect(market.candlesticksIntervals).toBeDefined() 32 | expect(market.orderInfo).toBeDefined() 33 | expect(market.feeComponents).toBeDefined() 34 | }) 35 | 36 | // it('resolves tickers and waits for docker-containers with api-node, redis db, and localchain', async () => { 37 | // const result = await market.tickers() 38 | // expect(result).toBeInstanceOf(Array) 39 | // expect(result.length).toEqual(7) 40 | // }) 41 | }) 42 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | import has from 'lodash/has' 2 | import merge from 'lodash/merge' 3 | 4 | const defaultConfig = { 5 | web3Provider: null, 6 | websockets: process.env.WS_URL || 'localhost:3001', 7 | api: { 8 | baseURL: process.env.API_URL || 'http://localhost:3000/api/v0', 9 | ORDER: '/order', 10 | TOKEN_PAIRS: '/token_pairs', 11 | ORDER_BOOK: '/order_book', 12 | CANCEL_ORDER: '/cancel_order', 13 | RESERVE_MARKET_ORDER: '/market_order/reserve', 14 | PLACE_MARKET_ORDER: '/market_order/place', 15 | RESERVE_LIMIT_ORDER: '/limit_order/reserve', 16 | PLACE_LIMIT_ORDER: '/limit_order/place', 17 | USER_HISTORY: '/user_history', 18 | TICKER: '/ticker', 19 | TICKERS: '/tickers', 20 | CANDLESTICKS: '/candlesticks', 21 | CANDLESTICKS_INTERVALS: '/candlesticks/intervals', 22 | TRADE_HISTORY: '/trade_history', 23 | ORDER_INFO: '/order', 24 | AUTH_TOKENS: '/auth/token', 25 | AUTH_REFRESH: '/auth/refresh', 26 | AVAILABLE_BALANCE: '/available_balance', 27 | COMMITTED_AMOUNT: '/committed_amount', 28 | FEE_COMPONENTS: '/fee_components', 29 | USER_DATA: '/user_data' 30 | }, 31 | relay: { 32 | funnel: '0x00ba938cc0df182c25108d7bf2ee3d37bce07513', 33 | feeRecipient: '0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe' 34 | } 35 | } 36 | 37 | let config = defaultConfig 38 | 39 | function updateConfigExchange (zeroEx) { 40 | let c = getConfig() 41 | if (!has(c, 'relay.exchange')) { 42 | c.relay.exchange = zeroEx.exchange.getContractAddress() 43 | } 44 | setConfig(c) 45 | } 46 | 47 | const setConfig = c => { 48 | config = merge({}, defaultConfig, c) 49 | } 50 | 51 | const getConfig = () => config 52 | 53 | module.exports = { 54 | setConfig, 55 | getConfig, 56 | updateConfigExchange 57 | } 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ============= 3 | 4 | 1.3.0 5 | ----- 6 | 7 | * Handle ws reconnect 8 | * Handle ws resubscribe 9 | 10 | 1.2.4 11 | ----- 12 | 13 | * add property `wsConnectionError` to Error with the value true when a Websocket connection could not be established 14 | * remove warning when you do not initialize the client with a web3 provider 15 | * change warning message when initialized without authentication 16 | 17 | 1.2.1 18 | ----- 19 | 20 | * unsignedTargetOrder for newLimitOrder can contain `error` field if target order could not be created 21 | * add `cancelAllOrders` method 22 | 23 | 1.1.1 24 | ----- 25 | 26 | * change etherAddress, userAddress to walletAddress 27 | * move getTokenCommittedAmount and getTokenAvailableBalance to trade 28 | * getTokenCommittedAmount and getTokenAvailableBalance return {BigNumber} object 29 | 30 | 1.0.1 31 | ----- 32 | 33 | * fix minor bug 34 | 35 | 1.0.0 36 | ----- 37 | 38 | * rename package to `the-ocean` 39 | 40 | 0.5.0 41 | ----- 42 | 43 | * change default config with new addresses 44 | 45 | 0.4.10 46 | ------ 47 | 48 | * walletAddress required in /reserve endpoint 49 | * Add /committed_amounts endpoint 50 | * Add /fee_components endpoint 51 | * Add /user_data endpoint 52 | * Add user_data stream ws channel 53 | 54 | 0.4.0 55 | ------ 56 | 57 | * Better authentication 58 | * Fees components 59 | * `candlesticksIntervals` method which allows to get available candlesticks intervals 60 | * better error handling for websockets connection 61 | 62 | 0.3.1 63 | ------ 64 | 65 | * No need to provide `api.baseURL` and `websockets` params during initialization 66 | * Change websockets messages to contain more information (refer to https://docs.theoceanx.com) 67 | 68 | 0.2.1 69 | ------ 70 | 71 | * Change ethereumjs-util version 72 | 73 | 0.2.0 74 | ------ 75 | 76 | * Add getAvailableBalance endpoint 77 | * Remove unauthenticated trading 78 | * Websockets interface adjustments 79 | * Instantiation adjustments 80 | * Various bug fixes and general refactoring 81 | -------------------------------------------------------------------------------- /src/the-ocean.js: -------------------------------------------------------------------------------- 1 | import { ZeroEx } from '0x.js' 2 | import Web3 from 'web3' 3 | 4 | import MarketData from './the-ocean-market-data' 5 | import Trade from './the-ocean-trade' 6 | import Wallet from './the-ocean-wallet' 7 | import { getConfig, setConfig, updateConfigExchange } from './config/config' 8 | import { setApiKey, setDashboardUserTokens } from './auth/auth' 9 | import { zeroExConfigByNetworkId } from './utils/constans' 10 | import { promisify } from './utils/utils' 11 | import OceanXStreams from './ws/the-ocean-websockets' 12 | 13 | /** 14 | * Creates TheOcean client 15 | * @param {Web3Provider} [web3Provider=null] The web3 provider 16 | * @param {Object} [config={}] The config object 17 | * @returns Promise 18 | */ 19 | async function createTheOcean (config = {}) { 20 | setConfig(config) 21 | const apiKeyAuth = config.api && config.api.key && config.api.secret 22 | const dashboardAuth = config.dashboardAuth && config.dashboardAuth.username && config.dashboardAuth.accessToken && config.dashboardAuth.idToken && config.dashboardAuth.refreshToken 23 | const hasAuth = apiKeyAuth || dashboardAuth 24 | if (apiKeyAuth) { 25 | await setApiKey(config.api.key, config.api.secret) 26 | } else if (dashboardAuth) { 27 | await setDashboardUserTokens(config.dashboardAuth.username, config.dashboardAuth.accessToken, config.dashboardAuth.idToken, config.dashboardAuth.refreshToken) 28 | } else { 29 | console.warn('Ocean client initialized without authentication! Trade methods are unavailable.') 30 | } 31 | if (!config.web3Provider) { 32 | return { 33 | marketData: new MarketData(), 34 | ws: new OceanXStreams(getConfig().websockets) 35 | } 36 | } 37 | 38 | const provider = config.web3Provider 39 | const web3 = new Web3(provider) 40 | const getWeb3Accounts = () => promisify(web3.eth.getAccounts) 41 | if (!web3.eth.defaultAccount) { 42 | web3.eth.defaultAccount = (await getWeb3Accounts())[0] 43 | } 44 | 45 | const networkId = await promisify(web3.version.getNetwork) 46 | 47 | const zeroExConfig = { 48 | networkId: parseInt(networkId), 49 | ...zeroExConfigByNetworkId[networkId] 50 | } 51 | const zeroEx = new ZeroEx(provider, zeroExConfig) 52 | 53 | updateConfigExchange(zeroEx) 54 | 55 | const Ocean = { 56 | marketData: new MarketData(), 57 | wallet: new Wallet(web3, zeroEx), 58 | getWeb3Accounts, 59 | setApiKeyAndSecret: setApiKey, 60 | ws: new OceanXStreams(getConfig().websockets) 61 | } 62 | 63 | if (hasAuth) { 64 | Ocean.trade = new Trade(web3, zeroEx) 65 | Ocean.setDashboardUserTokens = setDashboardUserTokens 66 | } 67 | 68 | return Ocean 69 | } 70 | 71 | // following line is left intentionally to support webpack's UMD output format 72 | module.exports = createTheOcean 73 | 74 | export default createTheOcean 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "the-ocean", 3 | "version": "1.3.0", 4 | "description": "Official client to connect with The Ocean.", 5 | "main": "dist/the-ocean.commonjs2.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "start": "npm run build", 9 | "build": "webpack --progress --color ", 10 | "build-watch": "webpack --progress --color --watch", 11 | "lint": "standard 'src/**/*.js' 'src/*.js' 'test/**/*.js' 'test/*.js'", 12 | "lint-fix": "standard --fix 'src/**/*.js' 'src/*.js' 'test/**/*.js' 'test/*.js'", 13 | "test": "jest", 14 | "test-watch": "jest --watch", 15 | "test-integration": "jest --config integration/jest.config.js", 16 | "test-integration-watch": "jest --config integration/jest.config.js --watch" 17 | }, 18 | "files": [ 19 | "dist", 20 | "docs", 21 | "src", 22 | "web.js", 23 | "node.js" 24 | ], 25 | "repository": "git+https://github.com/TheOceanTrade/theoceanx-javascript.git", 26 | "keywords": [ 27 | "The Ocean", 28 | "api", 29 | "client", 30 | "js" 31 | ], 32 | "author": "The Ocean team", 33 | "license": "ISC", 34 | "bugs": { 35 | "url": "https://github.com/TheOceanTrade/theoceanx-javascript/issues" 36 | }, 37 | "homepage": "https://github.com/TheOceanTrade/theoceanx-javascript#readme", 38 | "dependencies": { 39 | "0x.js": "^0.30.1", 40 | "axios": "^0.18.0", 41 | "babel-runtime": "^6.26.0", 42 | "bignumber.js": "4.1.0", 43 | "bluebird": "^3.5.1", 44 | "debug": "^3.1.0", 45 | "ethereumjs-util": "5.1.5", 46 | "socket.io-client": "^2.0.4", 47 | "url-join": "^2.0.2", 48 | "url-search-params": "^0.10.0", 49 | "web3": "^0.20.4" 50 | }, 51 | "devDependencies": { 52 | "babel-core": "6.26.0", 53 | "babel-loader": "^7.1.3", 54 | "babel-minify-webpack-plugin": "^0.3.0", 55 | "babel-plugin-transform-runtime": "^6.23.0", 56 | "babel-polyfill": "6.26.0", 57 | "babel-preset-env": "1.6.1", 58 | "babel-preset-es2015": "^6.24.1", 59 | "babel-preset-stage-2": "^6.24.1", 60 | "chai": "4.1.2", 61 | "chai-bignumber": "^2.0.2", 62 | "jest": "^21.2.1", 63 | "jest-cli": "^21.2.1", 64 | "json-loader": "0.5.7", 65 | "rimraf": "^2.6.2", 66 | "standard": "^10.0.3", 67 | "watch-http-server": "0.7.6", 68 | "webpack": "3.6.0", 69 | "webpack-dev-server": "^2.9.7", 70 | "webpack-node-externals": "^1.6.0" 71 | }, 72 | "jest": { 73 | "testEnvironment": "node", 74 | "verbose": true, 75 | "bail": false, 76 | "watchPathIgnorePatterns": [ 77 | "/node_modules/", 78 | "/.git/" 79 | ], 80 | "testMatch": [ 81 | "/(src|test)/**/*.test.js" 82 | ] 83 | }, 84 | "standard": { 85 | "globals": [ 86 | "expect", 87 | "describe", 88 | "it" 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/the-ocean.test.js: -------------------------------------------------------------------------------- 1 | /* global jest, expect, describe, it, beforeAll */ 2 | 3 | import { setConfig } from '../src/config/config' 4 | import createTheOcean from '../src/the-ocean' 5 | import Web3 from 'web3' 6 | 7 | describe('MarketData ', () => { 8 | beforeAll(() => { 9 | setConfig({ 10 | websockets: 'localhost:3001', 11 | api: { 12 | baseURL: 'http://localhost:3000/api/v0' 13 | } 14 | }) 15 | }) 16 | 17 | it('warns about initialization without authentication and hides trade methods', async () => { 18 | console.warn = jest.fn(warn => {}) 19 | let web3Provider = new Web3.providers.HttpProvider('https://kovan.infura.io/XGsH1k50zyZmp6J5CwUz') 20 | const api = await createTheOcean({ 21 | web3Provider: web3Provider, 22 | api: { 23 | baseURL: process.env.API_URL 24 | } 25 | }) 26 | expect(api).toBeDefined() 27 | expect(console.warn).toHaveBeenCalledWith('Ocean client initialized without authentication! Trade methods are unavailable.') 28 | expect(api.marketData).toBeDefined() 29 | expect(api.ws).toBeDefined() 30 | expect(api.wallet).toBeDefined() 31 | expect(api.getWeb3Accounts).toBeDefined() 32 | expect(api.setApiKeyAndSecret).toBeDefined() 33 | expect(api.trade).toBeUndefined() 34 | }) 35 | 36 | it('exposes trade api if API key and secret are provided', async () => { 37 | console.warn = jest.fn(warn => {}) 38 | let web3Provider = new Web3.providers.HttpProvider('https://kovan.infura.io/XGsH1k50zyZmp6J5CwUz') 39 | const api = await createTheOcean({ 40 | web3Provider: web3Provider, 41 | api: { 42 | baseURL: process.env.API_URL, 43 | key: 'apikey', 44 | secret: 'secret' 45 | } 46 | }) 47 | expect(api).toBeDefined() 48 | expect(console.warn).not.toHaveBeenCalled() 49 | expect(api.marketData).toBeDefined() 50 | expect(api.ws).toBeDefined() 51 | expect(api.wallet).toBeDefined() 52 | expect(api.getWeb3Accounts).toBeDefined() 53 | expect(api.setApiKeyAndSecret).toBeDefined() 54 | expect(api.trade).toBeDefined() 55 | }) 56 | 57 | it('exposes trade api if dashboard user tokens are provided', async () => { 58 | console.warn = jest.fn(warn => {}) 59 | let web3Provider = new Web3.providers.HttpProvider('https://kovan.infura.io/XGsH1k50zyZmp6J5CwUz') 60 | const api = await createTheOcean({ 61 | web3Provider: web3Provider, 62 | api: { 63 | baseURL: process.env.API_URL 64 | }, 65 | dashboardAuth: { 66 | username: 'email@theoceanx.com', 67 | accessToken: 'access_token', 68 | idToken: 'id_token', 69 | refreshToken: 'refresh_token' 70 | } 71 | }) 72 | expect(api).toBeDefined() 73 | expect(console.warn).not.toHaveBeenCalled() 74 | expect(api.marketData).toBeDefined() 75 | expect(api.ws).toBeDefined() 76 | expect(api.wallet).toBeDefined() 77 | expect(api.getWeb3Accounts).toBeDefined() 78 | expect(api.setApiKeyAndSecret).toBeDefined() 79 | expect(api.trade).toBeDefined() 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /src/the-ocean-market-data.js: -------------------------------------------------------------------------------- 1 | import api from './api' 2 | import './utils/jsdocsModels' 3 | 4 | export class MarketData { 5 | /** 6 | * Get the token pairs ZRX and WETH 7 | * @returns {Promise} Returns the array of object token pairs 8 | */ 9 | async tokenPairs () { 10 | return api.market.getPairs() 11 | } 12 | 13 | /** 14 | * To get recent activity on a given token pair 15 | * @param {Object} params 16 | * @param {String} params.baseTokenAddress The pair's base token address 17 | * @param {String} params.quoteTokenAddress The pair's quote token address 18 | * @returns {Promise} 19 | */ 20 | async ticker (params) { 21 | return api.market.getTicker(params) 22 | } 23 | 24 | /** 25 | * To get the activity data for all the token pairs 26 | * @returns {Promise} 27 | */ 28 | async tickers () { 29 | return api.market.getTickers() 30 | } 31 | 32 | /** 33 | * To get the order book with the existing orders 34 | * @param {Object} params 35 | * @param {String} params.baseTokenAddress The address of base token 36 | * @param {String} params.quoteTokenAddress The address of quote token 37 | * @param {Number} params.depth The maximum number of orders in the book 38 | * @returns {Promise} 39 | */ 40 | async orderBook (params) { 41 | return api.market.getOrderBook(params) 42 | } 43 | 44 | /** 45 | * Get a list of all past and present orders for a user 46 | * @param {Object} params 47 | * @param {String} params.baseTokenAddress The address of base token 48 | * @param {String} params.quoteTokenAddress The address of quote token 49 | * @returns {Promise} 50 | */ 51 | async tradeHistory (params) { 52 | return api.market.getTradeHistory(params) 53 | } 54 | 55 | /** 56 | * To get the candlesticks for the specific tokens, time and interval 57 | * @param {Object} params 58 | * @param {String} params.baseTokenAddress The address of base token 59 | * @param {String} params.quoteTokenAddress The address of quote token 60 | * @param {String} params.startTime The start time in unix epoch 61 | * @param {String} params.endTime The end time in unix epoch 62 | * @param {String} params.interval The interval in seconds 63 | * @returns {Promise} 64 | */ 65 | async candlesticks (params) { 66 | return api.market.getCandlesticks(params) 67 | } 68 | 69 | /** 70 | * To get a list of available candlesticks intervals 71 | * @returns {Promise} 72 | */ 73 | async candlesticksIntervals () { 74 | return api.market.getCandlesticksIntervals() 75 | } 76 | 77 | /** 78 | * Get order info 79 | * @param {Object} params 80 | * @param {String} params.orderHash The hash of order 81 | * @returns {Promise} 82 | */ 83 | async orderInfo (params) { 84 | return api.market.getOrderInfo(params) 85 | } 86 | 87 | /** 88 | * Get fee components 89 | * @returns {Promise} 90 | */ 91 | async feeComponents () { 92 | return api.market.getFeeComponents() 93 | } 94 | } 95 | 96 | export default MarketData 97 | -------------------------------------------------------------------------------- /src/config/config.test.js: -------------------------------------------------------------------------------- 1 | /* global describe expect it beforeEach */ 2 | 3 | import { getConfig, setConfig, updateConfigExchange } from './config' 4 | 5 | describe('Config ', () => { 6 | beforeEach(() => { 7 | setConfig({}) 8 | }) 9 | 10 | it('should have default values', () => { 11 | expect(getConfig()).toEqual( 12 | { 13 | web3Provider: null, 14 | websockets: 'localhost:3001', 15 | api: { 16 | baseURL: 'http://localhost:3000/api/v0', 17 | ORDER: '/order', 18 | TOKEN_PAIRS: '/token_pairs', 19 | ORDER_BOOK: '/order_book', 20 | CANCEL_ORDER: '/cancel_order', 21 | RESERVE_MARKET_ORDER: '/market_order/reserve', 22 | PLACE_MARKET_ORDER: '/market_order/place', 23 | RESERVE_LIMIT_ORDER: '/limit_order/reserve', 24 | PLACE_LIMIT_ORDER: '/limit_order/place', 25 | USER_HISTORY: '/user_history', 26 | TICKER: '/ticker', 27 | TICKERS: '/tickers', 28 | CANDLESTICKS: '/candlesticks', 29 | CANDLESTICKS_INTERVALS: '/candlesticks/intervals', 30 | FEE_COMPONENTS: '/fee_components', 31 | TRADE_HISTORY: '/trade_history', 32 | ORDER_INFO: '/order', 33 | AUTH_TOKENS: '/auth/token', 34 | AUTH_REFRESH: '/auth/refresh', 35 | AVAILABLE_BALANCE: '/available_balance', 36 | COMMITTED_AMOUNT: '/committed_amount', 37 | USER_DATA: '/user_data' 38 | }, 39 | relay: { 40 | funnel: '0x00ba938cc0df182c25108d7bf2ee3d37bce07513', 41 | feeRecipient: '0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe' 42 | } 43 | } 44 | ) 45 | }) 46 | 47 | it('should overwrite default values', () => { 48 | setConfig({ 49 | api: { 50 | baseURL: 'http://1.1.1.1:3333/api/v1' 51 | }, 52 | websockets: '1.1.1.1:3331' 53 | }) 54 | const config = getConfig() 55 | 56 | expect(config.api.baseURL).toBe('http://1.1.1.1:3333/api/v1') 57 | expect(config.websockets).toBe('1.1.1.1:3331') 58 | expect(config.api.ORDER).toBe('/order') 59 | expect(config.relay.exchange).toBeUndefined() 60 | }) 61 | 62 | it('should set exchange address if provided', async () => { 63 | setConfig({ 64 | relay: { 65 | exchange: '0x499d511d6bc3a1a267439645cf50d5dddc688d55' 66 | } 67 | }) 68 | const config = getConfig() 69 | 70 | expect(config.api.ORDER).toBe('/order') 71 | expect(config.relay.exchange).toBe('0x499d511d6bc3a1a267439645cf50d5dddc688d55') 72 | }) 73 | 74 | it('should update exchange address from zeroEx', async () => { 75 | setConfig({}) 76 | const zeroEx = { 77 | exchange: { 78 | getContractAddress: () => '0x499d511d6bc3a1a267439645cf50d5dddc688d66' 79 | } 80 | } 81 | updateConfigExchange(zeroEx) 82 | 83 | const config = getConfig() 84 | expect(config.relay.exchange).toBe('0x499d511d6bc3a1a267439645cf50d5dddc688d66') 85 | expect(config.relay.funnel).toBe('0x00ba938cc0df182c25108d7bf2ee3d37bce07513') 86 | expect(config.relay.feeRecipient).toBe('0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe') 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /src/ws/streams/base-stream.js: -------------------------------------------------------------------------------- 1 | import { 2 | SEND_CHANNEL, 3 | SUBSCRIBE, 4 | UNSUBSCRIBE 5 | } from './constants' 6 | 7 | /** 8 | * Base class for Stream subscriptions 9 | */ 10 | export default class BaseStream { 11 | /** 12 | * Constructor 13 | * @param io socket instance 14 | * @param channel type of messages that will be handle 15 | * @param debug debug instance to categorize correctly by the identifier that defines the inherit class 16 | */ 17 | constructor (io, channel, debug) { 18 | this.subscriptions = {} 19 | this.io = io 20 | this.channel = channel 21 | this._debug = debug 22 | } 23 | 24 | /** 25 | * Emit a SUBSCRIBE message to SEND_CHANNEL with the payload 26 | * @param payload 27 | */ 28 | subscribe (payload) { 29 | const message = {type: SUBSCRIBE, channel: this.channel, payload} 30 | this._debug('subscribing %s with params %s', SEND_CHANNEL, message) 31 | this.io.emit(SEND_CHANNEL, message) 32 | } 33 | 34 | /** 35 | * Store a subscription with key channelId 36 | * All subscriptions are composed of callback and unsubscribe function 37 | * @param channelId identifier of subscription 38 | * @param unsubscriptionParams params needed to call backend for unsubsctiption 39 | * @param callback function will ve invoked when a message is received 40 | * @returns {*} the subscription 41 | * @private 42 | */ 43 | _addSubscription (channelId, unsubscriptionParams, callback) { 44 | const unsubscribeFn = (channelId, channel, payload) => { 45 | return () => { 46 | this._debug('unsubscribe %s with params %O', channelId, payload) 47 | this.io.emit(SEND_CHANNEL, { 48 | type: UNSUBSCRIBE, 49 | channel: channel, 50 | payload: payload 51 | }) 52 | delete this.subscriptions[channelId] 53 | } 54 | } 55 | 56 | const resubscribeFn = (channelId, channel, payload) => { 57 | return () => { 58 | payload.snapshot = false 59 | this._debug('resubscribe %s with params %O', channelId, payload) 60 | this.io.emit(SEND_CHANNEL, { 61 | type: SUBSCRIBE, 62 | channel: channel, 63 | payload: payload 64 | }) 65 | } 66 | } 67 | 68 | this.subscriptions[channelId] = { 69 | callback: callback, 70 | unsubscribe: unsubscribeFn.call(this, channelId, this.channel, unsubscriptionParams), 71 | resubscribe: resubscribeFn.call(this, channelId, this.channel, unsubscriptionParams) 72 | } 73 | 74 | return this.subscriptions[channelId] 75 | } 76 | 77 | /** 78 | * Unsubscribe all 79 | * this function invoke all unsubscribe functions and clean the registry of subscriptions 80 | */ 81 | unsubscribeAll () { 82 | Object.keys(this.subscriptions).forEach(key => { 83 | this.subscriptions[key].unsubscribe() 84 | delete this.subscriptions[key] 85 | }) 86 | } 87 | 88 | resubscribeAll () { 89 | Object.keys(this.subscriptions).forEach(key => { 90 | this.subscriptions[key].resubscribe() 91 | }) 92 | } 93 | 94 | /** 95 | * Handler of message.All message received at this point must be of the same type. 96 | * Not is a global handler 97 | * @param msg Message received from backend 98 | */ 99 | handleMessage (msg) { 100 | this._debug('received', msg) 101 | const channelId = msg.channelId 102 | if (this.subscriptions[channelId]) { 103 | this.subscriptions[channelId].callback(msg) 104 | } else { 105 | this._debug('not event handler for ', channelId) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/auth/auth.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | import { getTimestamp } from '../utils/utils' 3 | 4 | import { getConfig } from '../config/config' 5 | import urljoin from 'url-join' 6 | import crypto from 'crypto' 7 | 8 | // credentials for API key user 9 | let apiKey = null 10 | let secret = null 11 | 12 | // credentials for dashboard user 13 | let username = null // currently this is email 14 | let accessToken = null 15 | let idToken = null 16 | let refreshToken = null 17 | 18 | const setDashboardUserTokens = async (_username, _accessToken, _idToken, _refreshToken) => { 19 | username = _username 20 | accessToken = _accessToken 21 | idToken = _idToken 22 | refreshToken = _refreshToken 23 | } 24 | 25 | const setApiKey = async (_apiKey, _secret) => { 26 | apiKey = _apiKey 27 | secret = _secret 28 | } 29 | 30 | const refreshTokens = async () => { 31 | if (!username || !refreshToken) { 32 | return 33 | } 34 | const response = await request({ 35 | method: 'POST', 36 | url: urljoin(getConfig().api.baseURL, getConfig().api.AUTH_REFRESH), 37 | body: { 38 | email: username, 39 | refreshToken: refreshToken 40 | } 41 | }) 42 | 43 | if (response.accessToken) { 44 | accessToken = response.accessToken 45 | idToken = response.idToken 46 | refreshToken = response.refreshToken 47 | } 48 | } 49 | 50 | const getApiKeySignature = (timestamp, method, body) => { 51 | // create the prehash string by concatenating required parts 52 | const prehash = apiKey + timestamp + method + body 53 | 54 | // create a sha256 hmac with the secret 55 | const hmac = crypto.createHmac('sha256', secret) 56 | return hmac.update(prehash).digest('base64') 57 | } 58 | 59 | const setAuthHeaders = (requestProperties) => { 60 | if (accessToken !== null) { 61 | requestProperties.headers['Authorization'] = accessToken 62 | } else if (apiKey !== null) { 63 | const timestamp = getTimestamp() 64 | 65 | // undefined/null body is an empty object on server 66 | const body = requestProperties.body ? requestProperties.body : {} 67 | 68 | const signature = getApiKeySignature(timestamp, requestProperties.method, JSON.stringify(body)) 69 | 70 | requestProperties.headers['TOX-ACCESS-KEY'] = apiKey 71 | requestProperties.headers['TOX-ACCESS-SIGN'] = signature 72 | requestProperties.headers['TOX-ACCESS-TIMESTAMP'] = timestamp 73 | } 74 | } 75 | 76 | const getWsAuthQuery = () => { 77 | if (accessToken !== null) { 78 | return { token: accessToken } 79 | } else if (apiKey !== null) { 80 | const timestamp = getTimestamp() 81 | 82 | const signature = getApiKeySignature(timestamp, 'ws', '') 83 | 84 | return { apiKey: apiKey, timestamp: timestamp, signature: signature } 85 | } else { 86 | return {} 87 | } 88 | } 89 | 90 | const isDashboardAuth = () => { 91 | return (username && accessToken && idToken && refreshToken) 92 | } 93 | 94 | async function authRequestWrapper (params, retries = 5) { 95 | setAuthHeaders(params) 96 | 97 | if (retries < 0) { 98 | throw new Error('Too many authentication retries!') 99 | } 100 | 101 | try { 102 | return await request(params) 103 | } catch (error) { 104 | // check to see if this is expired access token 105 | // if it refresh the token and try the request again 106 | if (error.statusCode === 401 && isDashboardAuth() && error.message === 'Authentication failed') { 107 | await refreshTokens() 108 | 109 | return authRequestWrapper(params, --retries) 110 | } else { 111 | throw error 112 | } 113 | } 114 | } 115 | 116 | module.exports = { 117 | setDashboardUserTokens, 118 | setApiKey, 119 | setAuthHeaders, 120 | getWsAuthQuery, 121 | refreshTokens, 122 | isDashboardAuth, 123 | authRequestWrapper 124 | } 125 | -------------------------------------------------------------------------------- /src/api/market-api.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | import { Assert } from '../utils/asserts' 3 | import { getEndpoint, requestProperties } from './api-utils' 4 | import { getConfig } from '../config/config' 5 | 6 | /** 7 | * 8 | * @returns {Promise<*>} 9 | */ 10 | async function getPairs () { 11 | return request({ 12 | url: getEndpoint(getConfig().api.TOKEN_PAIRS), 13 | ...requestProperties() 14 | }) 15 | } 16 | 17 | /** 18 | * 19 | * @param {Object} params 20 | * @param params.baseTokenAddress 21 | * @param params.quoteTokenAddress 22 | * @returns {Promise<*>} 23 | */ 24 | async function getTicker ({baseTokenAddress, quoteTokenAddress}) { 25 | return request({ 26 | ...requestProperties(), 27 | url: getEndpoint(getConfig().api.TICKER), 28 | qs: { 29 | baseTokenAddress, 30 | quoteTokenAddress 31 | } 32 | }) 33 | } 34 | 35 | /** 36 | * 37 | * @returns {Promise<*>} 38 | */ 39 | async function getTickers () { 40 | return request({ 41 | ...requestProperties(), 42 | url: getEndpoint(getConfig().api.TICKERS) 43 | }) 44 | } 45 | 46 | /** 47 | * 48 | * @param {Object} params 49 | * @param params.baseTokenAddress 50 | * @param params.quoteTokenAddress 51 | * @param params.depth 52 | * @returns {Promise<*>} 53 | */ 54 | async function getOrderBook ({baseTokenAddress, quoteTokenAddress, depth}) { 55 | if (!Assert.isString(baseTokenAddress) || !Assert.isString(quoteTokenAddress)) { 56 | throw new Error(`Expected values: 2 token names of type string, actual: ${baseTokenAddress} , ${quoteTokenAddress}`) 57 | } 58 | 59 | return request({ 60 | ...requestProperties(), 61 | url: getEndpoint(getConfig().api.ORDER_BOOK), 62 | qs: { 63 | baseTokenAddress, 64 | quoteTokenAddress, 65 | depth 66 | } 67 | }) 68 | } 69 | 70 | /** 71 | * 72 | * @param {Object} params 73 | * @param params.baseTokenAddress 74 | * @param params.quoteTokenAddress 75 | * @returns {Promise<*>} 76 | */ 77 | async function getTradeHistory ({baseTokenAddress, quoteTokenAddress}) { 78 | return request({ 79 | ...requestProperties(), 80 | url: getEndpoint(getConfig().api.TRADE_HISTORY), 81 | qs: { 82 | baseTokenAddress, 83 | quoteTokenAddress 84 | } 85 | }) 86 | } 87 | 88 | /** 89 | * 90 | * @param {Object} params 91 | * @param params.baseTokenAddress 92 | * @param params.quoteTokenAddress 93 | * @param params.startTime 94 | * @param params.endTime 95 | * @param params.interval 96 | * @returns {Promise<*>} 97 | */ 98 | async function getCandlesticks ({baseTokenAddress, quoteTokenAddress, startTime, endTime, interval}) { 99 | return request({ 100 | ...requestProperties(), 101 | url: getEndpoint(getConfig().api.CANDLESTICKS), 102 | qs: { 103 | baseTokenAddress, 104 | quoteTokenAddress, 105 | startTime, 106 | endTime, 107 | interval 108 | } 109 | }) 110 | } 111 | 112 | /** 113 | * 114 | * @returns {Promise<*>} 115 | */ 116 | async function getCandlesticksIntervals () { 117 | return request({ 118 | ...requestProperties(), 119 | url: getEndpoint(getConfig().api.CANDLESTICKS_INTERVALS) 120 | }) 121 | } 122 | 123 | /** 124 | * 125 | * @param {Object} params 126 | * @param params.orderHash 127 | * @returns {Promise<*>} 128 | */ 129 | async function getOrderInfo ({orderHash}) { 130 | return request({ 131 | ...requestProperties(), 132 | url: `${getEndpoint(getConfig().api.ORDER_INFO)}/${orderHash}` 133 | }) 134 | } 135 | 136 | /** 137 | * 138 | * @returns {Promise<*>} 139 | */ 140 | async function getFeeComponents () { 141 | return request({ 142 | ...requestProperties(), 143 | url: getEndpoint(getConfig().api.FEE_COMPONENTS) 144 | }) 145 | } 146 | 147 | module.exports = { 148 | getPairs, 149 | getTicker, 150 | getTickers, 151 | getOrderBook, 152 | getTradeHistory, 153 | getCandlesticks, 154 | getCandlesticksIntervals, 155 | getOrderInfo, 156 | getFeeComponents 157 | } 158 | -------------------------------------------------------------------------------- /src/ws/the-ocean-websockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gravity on 13/11/17. 3 | */ 4 | import io from 'socket.io-client' 5 | import { CHANNEL, RESPONSE_CHANNEL } from './streams/constants' 6 | import GenericPairsStream from './streams/generic-pairs-stream' 7 | import UserHistoryStream from './streams/user-history-stream' 8 | import UserDataStream from './streams/user-data-stream' 9 | import { getWsAuthQuery } from '../auth/auth' 10 | import CandlestickStream from './streams/candlestick-stream' 11 | 12 | const debug = require('debug')('the-ocean:stream') 13 | 14 | let CONTROLLERS = {} 15 | 16 | export default class OceanXStreams { 17 | constructor (url) { 18 | this.url = url 19 | this.handledErrorEvents = [ 20 | 'error', 21 | 'connect_error' 22 | ] 23 | } 24 | 25 | _initControllers () { 26 | CONTROLLERS[CHANNEL.ORDER_BOOK] = new GenericPairsStream(this.io, CHANNEL.ORDER_BOOK) 27 | CONTROLLERS[CHANNEL.CANDLESTICKS] = new CandlestickStream(this.io, CHANNEL.CANDLESTICKS) 28 | CONTROLLERS[CHANNEL.TRADE_HISTORY] = new GenericPairsStream(this.io, CHANNEL.TRADE_HISTORY) 29 | CONTROLLERS[CHANNEL.USER_HISTORY] = new UserHistoryStream(this.io) 30 | CONTROLLERS[CHANNEL.USER_DATA] = new UserDataStream(this.io) 31 | CONTROLLERS[CHANNEL.TICKER_STATS] = new GenericPairsStream(this.io, CHANNEL.TICKER_STATS) 32 | } 33 | 34 | /** 35 | * Handler for received messages 36 | * @param msg 37 | * @private 38 | */ 39 | _messageHandler (msg) { 40 | debug('message handler', msg) 41 | const channel = msg.channel 42 | if (CONTROLLERS[channel]) { 43 | CONTROLLERS[channel].handleMessage(msg) 44 | } else { 45 | debug('Not exist stream handler for channel %c', channel) 46 | } 47 | } 48 | 49 | _transformError (error, errorType) { 50 | if (errorType === 'connect_error' && error.message === 'xhr poll error') { 51 | const newError = new Error('The Ocean client could not connect to the websocket server.') 52 | newError.wsConnectionError = true 53 | return newError 54 | } 55 | 56 | return error 57 | } 58 | 59 | /** 60 | * Subscribe to channel 61 | * @param channel 62 | * @param payload 63 | * @param callback 64 | */ 65 | subscribe (channel, payload, callback) { 66 | CONTROLLERS[channel].subscribe(payload, callback) 67 | } 68 | 69 | /** 70 | * Unsubscribe all subscriptions of the channel 71 | * @param channel 72 | */ 73 | unsubscribe (channel) { 74 | if (CONTROLLERS[channel]) { 75 | CONTROLLERS[channel].unsubscribeAll() 76 | } 77 | } 78 | 79 | resubscribeAll () { 80 | let subscriptions = this.getSubscriptions() 81 | Object.keys(subscriptions).forEach(key => { 82 | for (let subscription of subscriptions[key]) { 83 | subscription.resubscribe() 84 | } 85 | }) 86 | } 87 | 88 | /** 89 | * Get all subscriptions 90 | * TODO: this class should reconstruct to a structure similar to the one that the user sent in the subscription 91 | * @returns {Array} 92 | */ 93 | getSubscriptions () { 94 | let subscriptions = {} 95 | Object.keys(CONTROLLERS).forEach((key, index) => { 96 | const controllerSubs = CONTROLLERS[key].getSubscriptions() 97 | if (controllerSubs && controllerSubs.length > 0) { 98 | subscriptions[key] = controllerSubs 99 | } 100 | }) 101 | return subscriptions 102 | } 103 | 104 | /** 105 | * Disconnect the socket 106 | */ 107 | disconnect () { 108 | this.io.disconnect() 109 | this.connected = false 110 | } 111 | 112 | isConnected () { 113 | return this.connected 114 | } 115 | 116 | connect () { 117 | return new Promise((resolve, reject) => { 118 | const authQuery = getWsAuthQuery() 119 | this.io = io(this.url, { query: authQuery, forceNew: true }) 120 | this.io.on('connect', () => { 121 | debug('connect event') 122 | this.io.on(RESPONSE_CHANNEL, this._messageHandler) 123 | this.connected = true 124 | if (Object.keys(CONTROLLERS).length === 0) { 125 | this._initControllers() 126 | } 127 | resolve() 128 | }) 129 | this.io.on('reconnect', (attempt) => { 130 | debug('reconnect event') 131 | this.resubscribeAll() 132 | }) 133 | this.io.on('reconnecting', (attempt) => { 134 | debug('reconnecting') 135 | }) 136 | this.io.on('reconnect_error', (error) => { 137 | debug('reconnect_error event: %s', error) 138 | }) 139 | this.io.on('reconnect_failed', () => { 140 | debug('reconnect_failed event') 141 | }) 142 | this.io.on('disconnect', (attempt) => { 143 | debug('disconnect event') 144 | }) 145 | this.io.on('error', (error) => { 146 | debug('error: %s', error) 147 | }) 148 | this.handledErrorEvents.forEach(errorType => this.io.on(errorType, (error) => { 149 | this.connected = false 150 | reject(this._transformError(error, errorType)) 151 | })) 152 | }) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/api/trade-api.js: -------------------------------------------------------------------------------- 1 | import { getEndpoint, requestProperties } from './api-utils' 2 | import { getConfig } from '../config/config' 3 | import { authRequestWrapper } from '../auth/auth' 4 | 5 | /** 6 | * To reserve market order 7 | * @param {Object} params 8 | * @param params.baseTokenAddress 9 | * @param params.quoteTokenAddress 10 | * @param params.side 11 | * @param params.orderAmount 12 | * @param params.feeOption 13 | * @param params.walletAddress 14 | * @returns {Promise<*>} 15 | */ 16 | async function reserveMarketOrder ({baseTokenAddress, quoteTokenAddress, side, orderAmount, feeOption, walletAddress}) { 17 | return authRequestWrapper({ 18 | ...requestProperties('POST'), 19 | url: getEndpoint(getConfig().api.RESERVE_MARKET_ORDER), 20 | body: { 21 | baseTokenAddress, 22 | quoteTokenAddress, 23 | side, 24 | orderAmount, 25 | feeOption, 26 | walletAddress 27 | } 28 | }) 29 | } 30 | 31 | /** 32 | * To reserve limit order 33 | * @param {Object} params 34 | * @param params.baseTokenAddress 35 | * @param params.quoteTokenAddress 36 | * @param params.side 37 | * @param params.orderAmount 38 | * @param params.price 39 | * @param params.feeOption 40 | * @param params.walletAddress 41 | * @returns {Promise<*>} 42 | */ 43 | async function reserveLimitOrder ({baseTokenAddress, quoteTokenAddress, side, orderAmount, price, feeOption, walletAddress}) { 44 | return authRequestWrapper({ 45 | ...requestProperties('POST'), 46 | url: getEndpoint(getConfig().api.RESERVE_LIMIT_ORDER), 47 | body: { 48 | baseTokenAddress, 49 | quoteTokenAddress, 50 | side, 51 | orderAmount, 52 | price, 53 | feeOption, 54 | walletAddress 55 | } 56 | }) 57 | } 58 | 59 | /** 60 | * To place market order 61 | * @param {Object} params 62 | * @param params.order The order to place 63 | * @returns {Promise<*>} 64 | */ 65 | async function placeMarketOrder ({order}) { 66 | return authRequestWrapper({ 67 | ...requestProperties('POST'), 68 | url: getEndpoint(getConfig().api.PLACE_MARKET_ORDER), 69 | body: order 70 | }) 71 | } 72 | 73 | /** 74 | * To place limit order 75 | * @param {Object} params 76 | * @param params.order The order to place 77 | * @returns {Promise<*>} 78 | */ 79 | async function placeLimitOrder ({order}) { 80 | return authRequestWrapper({ 81 | ...requestProperties('POST'), 82 | url: getEndpoint(getConfig().api.PLACE_LIMIT_ORDER), 83 | body: order 84 | }) 85 | } 86 | 87 | /** 88 | * To cancel order 89 | * @param {Object} params 90 | * @param {String} params.orderHash The order hash 91 | * @returns {Promise<*>} 92 | */ 93 | async function cancelOrder ({orderHash}) { 94 | return authRequestWrapper({ 95 | ...requestProperties('DELETE'), 96 | url: `${getEndpoint(getConfig().api.ORDER)}/${orderHash}` 97 | }) 98 | } 99 | 100 | /** 101 | * 102 | * @param {Object} params 103 | * @param params.baseTokenAddress 104 | * @param params.quoteTokenAddress 105 | * @returns {Promise<*>} 106 | */ 107 | async function cancelAllOrders (params) { 108 | let qs = {} 109 | if (params && params.baseTokenAddress && params.quoteTokenAddress) { 110 | qs = { 111 | baseTokenAddress: params.baseTokenAddress, 112 | quoteTokenAddress: params.quoteTokenAddress 113 | } 114 | } 115 | return authRequestWrapper({ 116 | ...requestProperties('DELETE'), 117 | url: `${getEndpoint(getConfig().api.ORDER_INFO)}`, 118 | qs: qs 119 | }) 120 | } 121 | 122 | /** 123 | * To get user history 124 | * @param {Object=} params 125 | * @param {string} params.userId=null 126 | * @returns {Promise<*>} 127 | */ 128 | async function getUserHistory ({userId} = {userId: null}) { 129 | return authRequestWrapper({ 130 | url: getEndpoint(getConfig().api.USER_HISTORY), 131 | ...requestProperties(), 132 | qs: { 133 | userId 134 | } 135 | }) 136 | } 137 | 138 | /** 139 | * To get user data 140 | * @returns {Promise<*>} 141 | */ 142 | async function userData () { 143 | return authRequestWrapper({ 144 | url: getEndpoint(getConfig().api.USER_DATA), 145 | ...requestProperties() 146 | }) 147 | } 148 | 149 | /** 150 | * 151 | * @param {Object} params 152 | * @param params.tokenAddress 153 | * @param params.walletAddress 154 | * @returns {Promise<*>} 155 | */ 156 | async function getTokenAvailableBalance ({tokenAddress, walletAddress}) { 157 | return authRequestWrapper({ 158 | ...requestProperties(), 159 | url: `${getEndpoint(getConfig().api.AVAILABLE_BALANCE)}`, 160 | qs: { 161 | tokenAddress, 162 | walletAddress 163 | } 164 | }) 165 | } 166 | 167 | /** 168 | * 169 | * @param {Object} params 170 | * @param params.tokenAddress 171 | * @param params.walletAddress 172 | * @returns {Promise<*>} 173 | */ 174 | async function getTokenCommittedAmount ({tokenAddress, walletAddress}) { 175 | return authRequestWrapper({ 176 | ...requestProperties(), 177 | url: `${getEndpoint(getConfig().api.COMMITTED_AMOUNT)}`, 178 | qs: { 179 | tokenAddress, 180 | walletAddress 181 | } 182 | }) 183 | } 184 | 185 | module.exports = { 186 | getTokenAvailableBalance, 187 | getTokenCommittedAmount, 188 | reserveMarketOrder, 189 | reserveLimitOrder, 190 | placeMarketOrder, 191 | placeLimitOrder, 192 | cancelOrder, 193 | cancelAllOrders, 194 | getUserHistory, 195 | userData 196 | } 197 | -------------------------------------------------------------------------------- /src/the-ocean-wallet.js: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js' 2 | 3 | BigNumber.config({EXPONENTIAL_AT: [-7, 50]}) 4 | 5 | export default class Wallet { 6 | constructor (web3, zeroEx) { 7 | this.web3 = web3 8 | this.zeroEx = zeroEx 9 | } 10 | 11 | /** 12 | * To get how many tokens in an account 13 | * @param params 14 | * @param {String} params.walletAddress The owner address 15 | * @param {String} params.tokenAddress The string address of the token 16 | * @return Promise The bignumber with your balance 17 | */ 18 | async getTokenBalance ({walletAddress, tokenAddress}) { 19 | const address = walletAddress || this.web3.eth.defaultAccount 20 | return this.zeroEx.token.getBalanceAsync(tokenAddress, address) 21 | } 22 | 23 | /** 24 | * To get how many tokens have you allowed to the relayer 25 | * @param params 26 | * @param {String} params.walletAddress The owner address that allowed the relayer 27 | * @param {String} params.tokenAddress The string address of the token 28 | * @return Promise The bignumber with your allowance 29 | */ 30 | async getTokenAllowance ({walletAddress, tokenAddress}) { 31 | const address = walletAddress || this.web3.eth.defaultAccount 32 | return this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, address) 33 | } 34 | 35 | /** 36 | * To give an allowance to the relayer for later use 37 | * @param params 38 | * @param {String} params.walletAddress The address of your ETH account 39 | * @param {String} params.tokenAddress The string address of the token 40 | * @param {BigNumber} params.amountInWei How many tokens you want to allow. 41 | * @callback {submittedCallback} [params.onSubmit] The callback to be called after transaction send to the network 42 | * @return Promise Operation status 43 | */ 44 | async setTokenAllowance ({walletAddress, tokenAddress, amountInWei, onSubmit}) { 45 | const bigAmount = new BigNumber(this.web3.fromWei(amountInWei)) 46 | const address = walletAddress || this.web3.eth.defaultAccount 47 | const txHash = await this.zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, bigAmount) 48 | 49 | if (onSubmit) onSubmit() 50 | 51 | try { 52 | await this.zeroEx.awaitTransactionMinedAsync(txHash) 53 | return true 54 | } catch (e) { 55 | console.error('setTokenAllowance Error', e) 56 | return false 57 | } 58 | } 59 | 60 | /** 61 | * To allow a gigantestic amount of tokens for the relayer, considered unlimited 62 | * @param params 63 | * @param {String} params.walletAddress The address of your ETH account 64 | * @param {String} params.tokenAddress The string address of the token 65 | * @callback {submittedCallback} [params.onSubmit] The callback to be called after transaction send to the network 66 | * @return Promise Operation status 67 | */ 68 | async setTokenAllowanceUnlimited ({walletAddress, tokenAddress, onSubmit}) { 69 | const ownerAddress = walletAddress || this.web3.eth.defaultAccount 70 | const txHash = await this.zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, ownerAddress) 71 | 72 | if (onSubmit) onSubmit() 73 | 74 | try { 75 | await this.zeroEx.awaitTransactionMinedAsync(txHash) 76 | return true 77 | } catch (e) { 78 | console.error('setTokenAllowanceUnlimited Error', e) 79 | return false 80 | } 81 | } 82 | 83 | /** 84 | * To convert your ETH to WETH for using them inside the relayer as an ERC20 token 85 | * @param params 86 | * @param {BigNumber} params.amountInWei How many ETH you want to convert to WETH 87 | * @param {String} params.address The address of your ETH account 88 | * @callback {submittedCallback} [params.onSubmit] The callback to be called after transaction send to the network 89 | * @return Promise Operation status 90 | */ 91 | async wrapEth ({amountInWei, address, onSubmit}) { 92 | const walletAddress = await this.zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync('WETH') 93 | const bigAmount = new BigNumber(amountInWei) 94 | const depositor = address || this.web3.eth.defaultAccount 95 | const txHash = await this.zeroEx.etherToken.depositAsync(walletAddress, bigAmount, depositor) 96 | 97 | if (onSubmit) onSubmit(txHash) 98 | 99 | try { 100 | await this.zeroEx.awaitTransactionMinedAsync(txHash) 101 | return true 102 | } catch (e) { 103 | console.error('wrapEth Error', e) 104 | return false 105 | } 106 | } 107 | 108 | /** 109 | * To convert back your WETH to ETH 110 | * @param params 111 | * @param {BigNumber} params.amountInWei How many WETH you want to convert to ETH 112 | * @param {String} params.address The address of your ETH account 113 | * @callback {submittedCallback} [params.onSubmit] The callback to be called after transaction send to the network 114 | * @return Promise Operation status 115 | */ 116 | async unwrapEth ({amountInWei, address, onSubmit}) { 117 | const etherTokenAddress = await this.zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync('WETH') 118 | const bigAmount = new BigNumber(amountInWei) 119 | const withdrawer = address || this.web3.eth.defaultAccount 120 | const txHash = await this.zeroEx.etherToken.withdrawAsync(etherTokenAddress, bigAmount, withdrawer) 121 | 122 | if (onSubmit) onSubmit(txHash) 123 | 124 | try { 125 | await this.zeroEx.awaitTransactionMinedAsync(txHash) 126 | return true 127 | } catch (e) { 128 | console.error('unwrapEth Error', e) 129 | return false 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/utils/jsdocsModels.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Token 3 | * @property {String} address The token address 4 | * @property {String} symbol The token symbol 5 | * @property {String} decimals The amount of token decimal places 6 | * @property {String} minAmount The minimum amount needed to submit an order 7 | * @property {String} maxAmount The maxiumum amount allowed to submit an order 8 | * @property {String} precision The level of precision for token amounts 9 | */ 10 | 11 | /** 12 | * @typedef {Object} TokenPair 13 | * @property {Token} baseToken The base token 14 | * @property {Token} quoteToken The quote token 15 | */ 16 | 17 | /** 18 | * @typedef {Object} Ticker 19 | * @property {String} bid The current highest bid price 20 | * @property {String} ask The current lowest ask price 21 | * @property {String} last The price of the last trade 22 | * @property {String} volume The amount of base tokens traded in the last 24 hours 23 | * @property {String} timestamp The end of the 24-hour period over which volume was measured 24 | */ 25 | 26 | /** 27 | * @typedef {Object} TickerEntry 28 | * @property {String} baseTokenAddress The base token address 29 | * @property {String} quoteTokenAddress The quote token address 30 | * @property {Ticker} last The ticker for 31 | */ 32 | 33 | /** 34 | * @typedef {Object} OrderBookItem 35 | * @property {String} orderHash The hash of the SignedOrder sent to the 0cean to be filled at a later time 36 | * @property {String} price The price of the order 37 | * @property {String} availableAmount The amount of tokens available to be filled 38 | * @property {String} creationTimestamp The timestamp when the order was placed 39 | * @property {String} expirationTimestampInSec The number of seconds until the order will expire 40 | */ 41 | 42 | /** 43 | * @typedef {Object} OrderBook 44 | * @property {OrderBookItem[]} bids array of fillable buy OrderBookItems 45 | * @property {OrderBookItem[]} asks array of fillable sell OrderBookItems 46 | */ 47 | 48 | /** 49 | * @typedef {Object} TradeHistoryItem 50 | * @property {String} id The unique trade id 51 | * @property {String} transactionHash The hash of the ethereum transaction sent to the network 52 | * @property {String} amount The amount of base tokens exchanged 53 | * @property {String} price The price of tokens exchanged 54 | * @property {String} status The state of the trade 55 | * @property {String} lastUpdated The timestamp of the last status update 56 | */ 57 | 58 | /** 59 | * @typedef {Object} Candlestick 60 | * @property {String} high The highest price 61 | * @property {String} low The lowest price 62 | * @property {String} open The price at the beginning of the interval 63 | * @property {String} close The price at the end of the interval 64 | * @property {String} baseVolume The volume of base tokens 65 | * @property {String} quoteVolume The volume of quote tokens 66 | * @property {String} startTime The start time 67 | */ 68 | 69 | /** 70 | * @typedef {Object} OceanOrder 71 | * @property {String} baseTokenAddress The address of base token 72 | * @property {String} quoteTokenAddress The address of quote token 73 | * @property {String} side The side, 'buy or 'sell' 74 | * @property {String} amount The order amount in wei (amounts of the base token) 75 | * @property {String} price The order price in eth 76 | * @property {String} created The time order was created (microseconds) 77 | * @property {String} expires The time order will expire (microseconds) 78 | * @property {SignedOrder} zeroExOrder The signed 0x order 79 | */ 80 | 81 | /** 82 | * @typedef {Object} UserHistoryItem 83 | * @property {String} orderHash The hash of the order 84 | * @property {String[]} transactionHashes An array of hashes for transactions submitted to the blockchain 85 | * @property {String} baseTokenAddress The address of base token 86 | * @property {String} quoteTokenAddress The address of quote token 87 | * @property {String} side The side of the book the order was placed 88 | * @property {String} open_amount The amount available to be filled 89 | * @property {String} filled_amount The amount filled and waiting to be submitted to the blockchain 90 | * @property {String} settled_amount The amount exchanged successfully 91 | * @property {String} dead_amount The amount that will never be exchanged 92 | * @property {String} price The price denominated in quote tokens 93 | * @property {UserHistoryTimelineItem[]} timeline An array of UserHistoryTimelineItems 94 | */ 95 | 96 | /** 97 | * @typedef {('placed'|'filled'|'submitted'|'settled'|'submitted'|'failed_settlement'|'failed_fill'|'canceled'|'pruned'|'expired')} UserHistoryEvent 98 | */ 99 | 100 | /** 101 | * @typedef {Object} UserHistoryTimelineItem 102 | * @property {UserHistoryEvent} action An an action that was taken on the order 103 | * @property {String} amount The amount of the order affected by the action 104 | * @property {String} timestamp The time the action took place 105 | */ 106 | 107 | /** 108 | * @typedef {Object} PlaceOrderResponse 109 | * @property {String} transactionHash The hash of transaction 110 | */ 111 | 112 | /** 113 | * @typedef {Object} NewMarketOrderResponse 114 | * @property {String} orderSubmitted The status of submitting 115 | */ 116 | 117 | /** 118 | * @typedef {Object} NewLimitOrderResponse 119 | * @property {String} orderSubmitted The status of submitting 120 | */ 121 | 122 | /** 123 | * @typedef {Object} PlaceLimitOrderNotImmediatelyPlaceableResponse 124 | * @property {String} orderPlaced The status of placing 125 | */ 126 | 127 | /** 128 | * @typedef {Object} PlaceLimitOrderPartiallyImmediatelyPlaceableResponse 129 | * @property {String} orderPlaced The status of placing 130 | * @property {String} orderSubmitted The status of submitting 131 | */ 132 | 133 | /** 134 | * @typedef {Object} PlaceLimitOrderCompletelyImmediatelyPlaceableResponse 135 | * @property {String} orderSubmitted The status of submitting 136 | */ 137 | -------------------------------------------------------------------------------- /src/the-ocean-trade.js: -------------------------------------------------------------------------------- 1 | import api from './api' 2 | import serializers from './utils/serializers' 3 | import { isEthereumAddress } from './utils/utils' 4 | import { ZeroEx } from '0x.js' 5 | import { BigNumber } from 'bignumber.js' 6 | import './utils/jsdocsModels' 7 | 8 | /** A class to handle all trade functions */ 9 | export class Trade { 10 | /** 11 | * Setup web3 and zeroEx 12 | * @param {Web3Provider} web3 - The web3 provider instance 13 | * @param {ZeroEx} zeroEx - The zeroEx instance 14 | */ 15 | constructor (web3, zeroEx) { 16 | this.web3 = web3 17 | this.zeroEx = zeroEx 18 | } 19 | 20 | /** 21 | * Signs order 22 | * @param {Order|SignedOrder} order The order 23 | * @param {String} signerAddress The ethereum address you wish to sign it with 24 | * @returns Promise 25 | */ 26 | async _signOrder (order, signerAddress) { 27 | const orderHash = ZeroEx.getOrderHashHex(order) 28 | const signature = await this.zeroEx.signOrderHashAsync(orderHash, signerAddress) 29 | return Object.assign({}, order, {orderHash: orderHash, ecSignature: signature}) 30 | } 31 | 32 | /** 33 | * Places existing market orders for the specified token to buy or sell. First 34 | * it gets the existing orders then it matches the cheapest ones until the amount 35 | * to buy or sell is reached. For instance if you want to buy 10 ZRX at market 36 | * price, then this function will get all the orders that are bidding for ZRX 37 | * and places until your amount is reached. 38 | * 39 | * 1. Request market_order/reserve with token pair and amount 40 | * 2. Returns unsigned order with unique ID 41 | * 3. Sign the order with zeroEx 42 | * 4. Places the order with market_order/place with the signed order and intent ID 43 | * @param {Object} params 44 | * @param {String} params.baseTokenAddress The address of base token 45 | * @param {String} params.quoteTokenAddress The address of quote token 46 | * @param {String} params.side=('buy'|'sell') The side of the order 47 | * @param {String} params.orderAmount The amount of tokens to sell or buy 48 | * @param {String} params.feeOption=('feeInZrx'|'feeInNative') Chosen fee method 49 | * @param {String} [account=web3.defaultAccount] The address of the account 50 | * @callback {reservedCallback} onReserved The callback called when was reserved 51 | * @returns {Promise} 52 | */ 53 | async newMarketOrder (params, account = this.web3.eth.defaultAccount, onReserved) { 54 | if (!isEthereumAddress(account)) { 55 | throw Error(`Bad account provided (${account}) to newMarketOrder`) 56 | } 57 | const reserve = await api.trade.reserveMarketOrder({ walletAddress: account, ...params }) 58 | if (onReserved) { 59 | onReserved(reserve) 60 | } 61 | 62 | const matchingOrder = Object.assign({}, reserve.unsignedMatchingOrder, {maker: account}) 63 | const signedMatchingOrder = await this._signOrder(matchingOrder, account) 64 | const serializedMatchingOrder = serializers.serializeOrder(signedMatchingOrder) 65 | const order = { 66 | signedMatchingOrder: serializedMatchingOrder, 67 | matchingOrderID: reserve.matchingOrderID 68 | } 69 | return api.trade.placeMarketOrder({order}) 70 | } 71 | 72 | /** 73 | * To place a side market order for any available orders at a better price, then 74 | * posts an order to the order book for the remaining amount 75 | * 76 | * 1. Request limit_order/reserve with token pair, amount and limitPrice 77 | * 2. Returns 2 unsigned orders. One is the matching order (if any) 78 | * and the other is the order that will go to the order book 79 | * 3. Sign both orders with zeroEx 80 | * 4. Place the orders with limit_order/place with the signed orders 81 | * 82 | * @param {Object} params 83 | * @param {String} params.baseTokenAddress The address of base token 84 | * @param {String} params.quoteTokenAddress The address of quote token 85 | * @param {String} params.side=('buy'|'sell') The side of the order 86 | * @param {String} params.orderAmount The amount of tokens to sell or buy 87 | * @param {String} params.feeOption=('feeInZrx'|'feeInNative') Chosen fee method 88 | * @param {String} [account=web3.defaultAccount] The address of the placing account 89 | * @callback {reservedCallback} onReserved The callback called when was reserved 90 | * @returns {Promise} 91 | */ 92 | async newLimitOrder (params, account = this.web3.eth.defaultAccount, onReserved) { 93 | if (!isEthereumAddress(account)) { 94 | throw Error(`Bad account provided (${account}) to newLimitOrder`) 95 | } 96 | const reserve = await api.trade.reserveLimitOrder({ walletAddress: account, ...params }) 97 | if (onReserved) { 98 | onReserved(reserve) 99 | } 100 | 101 | const order = {} 102 | if (reserve.unsignedTargetOrder && reserve.unsignedTargetOrder.error === undefined) { 103 | const targetOrder = Object.assign({}, reserve.unsignedTargetOrder, {maker: account}) 104 | const signedTargetOrder = await this._signOrder(targetOrder, account) 105 | order.signedTargetOrder = serializers.serializeOrder(signedTargetOrder) 106 | } 107 | if (reserve.unsignedMatchingOrder) { 108 | const matchingOrder = Object.assign({}, reserve.unsignedMatchingOrder, {maker: account}) 109 | const signedMatchingOrder = await this._signOrder(matchingOrder, account) 110 | const serializedMatchingOrder = serializers.serializeOrder(signedMatchingOrder) 111 | order.signedMatchingOrder = serializedMatchingOrder 112 | order.matchingOrderID = reserve.matchingOrderID 113 | } 114 | 115 | return api.trade.placeLimitOrder({order}) 116 | } 117 | 118 | /** 119 | * Cancels an order 120 | * @param {Object} params 121 | * @param params.orderHash The order hash 122 | * @returns {Promise} the cancelled order 123 | */ 124 | async cancelOrder (params) { 125 | return api.trade.cancelOrder(params) 126 | } 127 | 128 | /** 129 | * Cancels all open order. Provide token pair to limit delete to it. 130 | * @param {Object} params 131 | * @param {String} params.baseTokenAddress The address of base token 132 | * @param {String} params.quoteTokenAddress The address of quote token 133 | * @returns {Promise} the cancelled orders 134 | */ 135 | async cancelAllOrders (params) { 136 | return api.trade.cancelAllOrders(params) 137 | } 138 | 139 | /** 140 | * Gets user history 141 | * @param {Object} params 142 | * @param params.userId The id of the user 143 | * @returns {Promise} 144 | */ 145 | async userHistory (params) { 146 | return api.trade.getUserHistory(params) 147 | } 148 | 149 | /** 150 | * Gets user data 151 | * @returns {Promise} 152 | */ 153 | async userData () { 154 | return api.trade.userData() 155 | } 156 | 157 | /** 158 | * Get token available balance for user 159 | * @param {Object} params 160 | * @param {String} params.tokenAddress The hash of token 161 | * @param {String} params.walletAddress The hash of user 162 | * @returns {BigNumber} 163 | */ 164 | async tokenAvailableBalance (params) { 165 | const response = await api.trade.getTokenAvailableBalance(params) 166 | if (response.availableBalance) { 167 | return new BigNumber(response.availableBalance) 168 | } else { return response } 169 | } 170 | 171 | /** 172 | * Get committed amounts for user 173 | * @param {Object} params 174 | * @param {String} params.tokenAddress The hash of token 175 | * @param {String} params.walletAddress The hash of wallet address 176 | * @returns {BigNumber} 177 | */ 178 | async tokenCommittedAmount (params) { 179 | const response = await api.trade.getTokenCommittedAmount(params) 180 | if (response.amount) { 181 | return new BigNumber(response.amount) 182 | } else { 183 | return response 184 | } 185 | } 186 | } 187 | 188 | export default Trade 189 | --------------------------------------------------------------------------------