import BigNumber from 'bignumber.js' ; import EthereumTx from 'ethereumjs-tx' ; import { hashPersonalMessage , ecsign , toRpcSig , toBuffer , privateToAddress } from 'ethereumjs-util' ; import { join } from 'path' ; import { ApiHandler } from './ApiHandler' ; import { Web3Handler } from './Web3Handler' ; import { AuthError } from '../errors/errors' ; import { Candle } from '../models/Candle' ; import { Fee } from '../models/Fee' ; import { LockedBalance } from '../models/LockedBalance' ; import { Market } from '../models/Market' ; import { Order , OrderType , Side , Status } from '../models/Order' ; import { Orderbook , OrderbookLevel } from '../models/Orderbook' ; import { OrderList } from '../models/OrderList' ; import { Ticker } from '../models/Ticker' ; import { TradeList } from '../models/TradeList' ; export interface HydroClientOptions { apiUrl ? : string ; web3Url ? : string ; } export class HydroClient { private account : Account ; private apiHandler : ApiHandler ; private web3Handler : Web3Handler ; private tokenAddresses : Map < string , string > ; private constructor ( account , options ? ) { this . account = account ; this . apiHandler = new ApiHandler ( account , options ) ; this . web3Handler = new Web3Handler ( account , options ) ; this . tokenAddresses = new Map ( ) ; } /**
     * If you only want to make public API calls, no authentication is needed
     */ public static withoutAuth ( options ? ) { const errorFn = ( _ ) => { throw new AuthError ( 'Cannot authenticate without a private key!' ) ; } ; const account = { address : '' , sign : errorFn , signTransaction : errorFn , } ; return new HydroClient ( account , options ) ; } param privateKey public static withPrivateKey ( privateKey , options ? ) { const pkBuffer = toBuffer ( privateKey ) as Buffer ; let address = '0x' + ( privateToAddress ( pkBuffer ) as Buffer ) . toString ( 'hex' ) ; let sign = async ( message ) => { const shaMessage = hashPersonalMessage ( toBuffer ( message ) ) ; const ecdsaSignature = ecsign ( shaMessage , pkBuffer ) ; return toRpcSig ( ecdsaSignature . v , ecdsaSignature . r , ecdsaSignature . s ) ; } ; let signTransaction = async ( txParams ) => { const tx = new EthereumTx ( txParams ) ; tx . sign ( pkBuffer ) ; return '0x' + tx . serialize ( ) . toString ( 'hex' ) ; } ; return new HydroClient ( { address , sign , signTransaction } , options ) ; } param address param sign param signTransaction public static withCustomAuth ( address , sign , signTransaction , options ? ) { return new HydroClient ( { address , sign , signTransaction } , options ) ; } /**
     * Public API Calls
     *
     * These calls do not require any authentication to complete, and will generally give you
     * public state about the Hydro API
     *
     * See https://docs.ddex.io/#public-rest-api
     */ /**
     * Returns all active markets
     *
     * See https://docs.ddex.io/#list-markets
     */ public async listMarkets ( ) { const data = await this . apiHandler . get ( join ( 'markets' ) ) ; return data . markets . map ( ( market ) => new Market ( market ) ) ; } param marketId public async getMarket ( marketId ) { const data = await this . apiHandler . get ( join ( 'markets' , marketId ) ) ; return new Market ( data . market ) ; } /**
     * Returns tickers for all active markets
     *
     * See https://docs.ddex.io/#list-tickers
     */ public async listTickers ( ) { const data = await this . apiHandler . get ( join ( 'markets' , 'tickers' ) ) ; return data . tickers . map ( ( ticker ) => new Ticker ( ticker ) ) ; } param marketId public async getTicker ( marketId ) { const data = await this . apiHandler . get ( join ( 'markets' , marketId , 'ticker' ) ) ; return new Ticker ( data . ticker ) ; } param marketId param level public async getOrderbook ( marketId , level ? ) { const data = await this . apiHandler . get ( join ( 'markets' , marketId , 'orderbook' ) , { level : level , } ) ; return new Orderbook ( data . orderBook , level ) ; } param marketId param page param perPage public async listTrades ( marketId , page ? , perPage ? ) { const data = await this . apiHandler . get ( join ( 'markets' , marketId , 'trades' ) , { page , perPage , } ) ; return new TradeList ( data ) ; } param marketId param from param to param granularity public async listCandles ( marketId , from , to , granularity ) { const data = await this . apiHandler . get ( join ( 'markets' , marketId , 'candles' ) , { from , to , granularity , } ) ; return data . candles . map ( ( candle ) => new Candle ( candle ) ) ; } param marketId param price param amount public async calculateFees ( marketId , price , amount ) { const data = await this . apiHandler . get ( join ( 'fees' ) , { marketId , price , amount , } ) ; return new Fee ( data ) ; } /**
     * Private API Calls
     *
     * These calls require authentication, meaning you must have a valid trading address
     * and the ability to sign requests with that address' private key.
     *
     * See https://docs.ddex.io/#private-rest-api
     */ param marketId param side param orderType param price param amount param expires public async buildOrder ( marketId , side , orderType , price , amount , expires = 0 ) { const data = await this . apiHandler . post ( join ( 'orders' , 'build' ) , { marketId , side , orderType , price , amount , expires , } ) ; return new Order ( data . order ) ; } param orderId param signature public async placeOrder ( orderId , signature ) { const data = await this . apiHandler . post ( join ( 'orders' ) , { orderId , signature , method : SignatureMethod . ETH_SIGN , } ) ; return new Order ( data . order ) ; } param marketId param side param orderType param price param amount param expires public async createOrder ( marketId , side , orderType , price , amount , expires = 0 ) { const order = await this . buildOrder ( marketId , side , orderType , price , amount , expires ) ; const signature = await this . account . sign ( order . id ) ; return await this . placeOrder ( order . id , signature ) ; } param orderId public async cancelOrder ( orderId ) { await this . apiHandler . delete ( join ( 'orders' , orderId ) ) ; } param marketId param status param page param perPage public async listOrders ( marketId ? , status ? , page ? , perPage ? ) { const data = await this . apiHandler . get ( join ( 'orders' ) , { marketId , status , page , perPage } , true ) ; return new OrderList ( data ) ; } param orderId public async getOrder ( orderId ) { const data = await this . apiHandler . get ( join ( 'orders' , orderId ) , { } , true ) ; return new Order ( data . order ) ; } param marketId param page param perPage public async listAccountTrades ( marketId , page ? , perPage ? ) { const data = await this . apiHandler . get ( join ( 'markets' , marketId , 'trades' , 'mine' ) , { page , perPage } , true ) ; return new TradeList ( data ) ; } /**
     * Return locked balances for each active token
     *
     * See https://docs.ddex.io/#list-locked-balances
     */ public async listLockedBalances ( ) { const data = await this . apiHandler . get ( join ( 'account' , 'lockedBalances' ) , { } , true ) ; return data . lockedBalances . map ( ( lockedBalance ) => new LockedBalance ( lockedBalance ) ) ; } param symbol public async getLockedBalance ( symbol ) { const data = await this . apiHandler . get ( join ( 'account' , 'lockedBalance' ) , { symbol : symbol } , true ) ; return new LockedBalance ( data . lockedBalance ) ; } /**
     * Helper Methods (requires auth)
     *
     * These helper methods don't generally don't call the Hydro API, instead querying the blockchain
     * directly. They are useful in helping to wrap/unwrap ETH on your account, and allowing you to
     * approve tokens to be traded on the DDEX-1.0 relayer.
     *
     * To use these methods, you must provide a mainnet endpoint url, like infura, which will be used
     * to interact with the blockchain. It is taken in as one of the HydroClient options, as web3_url.
     *
     * See
     * * https://docs.ddex.io/#wrapping-ether
     * * https://docs.ddex.io/#enabling-token-trading
     */ param symbol return public async getBalance ( symbol ? ) { let address ; if ( symbol ) { address = await this . getTokenAddress ( symbol ) ; } return this . web3Handler . getBalance ( address ) ; } param amount param wait return public async wrapEth ( amount , wait ? ) { const wethAddress = await this . getTokenAddress ( 'WETH' ) ; return this . web3Handler . wrapEth ( wethAddress , amount , wait ) ; } param amount param wait return public async unwrapEth ( amount , wait ? ) { const wethAddress = await this . getTokenAddress ( 'WETH' ) ; return this . web3Handler . unwrapEth ( wethAddress , amount , wait ) ; } param symbol public async isTokenEnabled ( symbol ) { const tokenAddress = await this . getTokenAddress ( symbol ) ; const allowance = await this . web3Handler . getAllowance ( tokenAddress ) ; return new BigNumber ( allowance ) . gte ( new BigNumber ( 10 ) . pow ( 10 ) ) ; } param symbol param wait return public async enableToken ( symbol , wait ? ) { const tokenAddress = await this . getTokenAddress ( symbol ) ; return this . web3Handler . enableToken ( tokenAddress , wait ) ; } param symbol param wait return public async disableToken ( symbol , wait ? ) { const tokenAddress = await this . getTokenAddress ( symbol ) ; return this . web3Handler . disableToken ( tokenAddress , wait ) ; } private async getTokenAddress ( token ) { if ( ! this . tokenAddresses . get ( token ) ) { const data = await this . apiHandler . get ( join ( 'tokens' , token ) ) ; this . tokenAddresses . set ( token , data . token . address ) ; } const address = this . tokenAddresses . get ( token ) ; if ( ! address ) { throw new Error ( 'Unable to get token address' ) ; } return address ; } } enum SignatureMethod { ETH_SIGN , EIP_712 } export interface Transaction { nonce ? : number ; chainId ? : number ; from ? : string ; to ? : string ; data ? : string ; value ? : string | number ; gas ? : string | number ; gasPrice ? : string | number ; } export interface Account { address : string ; sign ( message ) : Promise < string > ; signTransaction ( tx ) : Promise < string > ; }