import { EcdsaParty2 as Party2 , EcdsaParty2Share as Party2Share , EcdsaSignature as Signature } from '@kzen-networks/thresh-sig' ; import 'babel-polyfill' ; const bncClient = require ( '@binance-chain/javascript-sdk' ) ; import WebSocket from 'ws' ; import crypto from 'crypto' ; import fs from 'fs' ; import path from 'path' ; import low from 'lowdb' ; import FileSync from 'lowdb/adapters/FileSync' ; import { rejects } from "assert" ; const P1_ENDPOINT = 'http://localhost:8000' ; const HD_COIN_INDEX = 0 ; const CLIENT_DB_PATH = path . join ( __dirname , '../../client_db' ) ; const BINANCE_CHAIN_HOST_MAINNET = 'dex.binance.org' ; const BINANCE_CHAIN_HOST_TESTNET = 'testnet-dex.binance.org' ; const BINANCE_CHAIN_HTTP_URL_MAINNET = `https://${ BINANCE_CHAIN_HOST_MAINNET }` ; const BINANCE_CHAIN_HTTP_URL_TESTNET = `https://${ BINANCE_CHAIN_HOST_TESTNET }` ; const BINANCE_CHAIN_WS_URL_MAINNET = `wss://${ BINANCE_CHAIN_HOST_MAINNET }/api/ws` ; const BINANCE_CHAIN_WS_URL_TESTNET = `wss://${ BINANCE_CHAIN_HOST_TESTNET }/api/ws` ; const api = { getTransactions : '/api/v1/transactions' } ; interface GetTransactionsOptions { address ? : string ; blockHeight ? : number ; startTime ? : number ; endTime ? : number ; limit ? : number ; offset ? : number ; side ? : string ; txAsset ? : string ; txType ? : string ; } export class BncThreshSigClient { private mainnet : boolean ; private p2 : Party2 ; private p2MasterKeyShare : Party2Share ; private bncClient : any ; private db : any ; ws : WebSocket ; constructor ( mainnet = false , useAsyncBroadcast = false ) { this . mainnet = mainnet ; const httpUrl = mainnet ? BINANCE_CHAIN_HTTP_URL_MAINNET : BINANCE_CHAIN_HTTP_URL_TESTNET ; this . bncClient = new bncClient ( httpUrl , useAsyncBroadcast ) ; this . p2 = new Party2 ( P1_ENDPOINT ) ; this . bncClient . setSigningDelegate ( this . sign . bind ( this ) ) ; } public async init ( ) { return Promise . all ( [ this . bncClient . initChain ( ) , ( async ( ) => { this . initDb ( ) ; await this . initMasterKey ( ) ; } ) ( ) ] ) ; } public async initWebSocket ( ) { const wsUrl = this . mainnet ? BINANCE_CHAIN_WS_URL_MAINNET : BINANCE_CHAIN_WS_URL_TESTNET ; this . ws = new WebSocket ( wsUrl ) ; return new Promise ( ( resolve ) => { this . ws . onopen = resolve ; } ) ; } public async terminate ( ) { this . ws . terminate ( ) ; } param addressIndex public getAddress ( addressIndex = 0 ) { const publicKey = this . getPublicKey ( addressIndex ) ; const publicKeyHex = publicKey . encode ( 'hex' , true ) ; const address = bncClient . crypto . getAddressFromPublicKey ( publicKeyHex ) ; const dbAddress = this . db . get ( 'addresses' ) . find ( { address } ) . value ( ) ; if ( ! dbAddress ) { this . db . get ( 'addresses' ) . push ( { address , index : addressIndex } ) . write ( ) ; } return address ; } param { String } fromAddress param { String } toAddress param { Number } amount param { String } asset param { String } memo param { Number } sequence return { Promise } public async transfer ( fromAddress , toAddress , amount , asset , memo = '' , sequence = null ) { fromAddress = fromAddress || this . getAddress ( 0 ) ; return this . bncClient . transfer ( fromAddress , toAddress , amount , asset , memo , sequence ) ; } param { String } address . If param { String } symbol param { Number } side param { Number } price param { Number } quantity param { Number } sequence param { Number } timeinforce return { Promise } public async placeOrder ( address , symbol , side , price , quantity , sequence = null , timeinforce = 1 ) { address = address || this . getAddress ( 0 ) ; return this . bncClient . placeOrder ( address , symbol , side , price , quantity , null , timeinforce ) ; } param { String } address . If param { String } symbol param { String } refid param { Number } sequence return { Promise } public async cancelOrder ( address , symbol , refid , sequence = null ) { address = address || this . getAddress ( 0 ) ; return this . bncClient . cancelOrder ( address , symbol , refid , sequence ) ; } param { String } address return { Promise } public async getAccount ( address = this . getAddress ( 0 ) ) { return this . bncClient . getAccount ( address ) ; } param { String } address return { Promise } public async getBalance ( address ? ) { if ( ! address ) { address = this . getAddress ( 0 ) ; } return this . bncClient . getBalance ( address ) ; } public async getTransactions ( options = { } ) { const address = options . address || this . getAddress ( 0 ) ; return this . bncClient . _httpClient . request ( "get" , `${ api . getTransactions }?address=${ address }` + ( options . blockHeight ? `&blockHeight=${ options . blockHeight }` : '' ) + ( options . startTime ? `&startTime=${ options . startTime }` : '' ) + ( options . endTime ? `&endTime=${ options . endTime }` : '' ) + ( options . limit ? `&limit=${ options . limit }` : '' ) + ( options . offset ? `&offset=${ options . offset }` : '' ) + ( options . side ? `&side=${ options . side }` : '' ) + ( options . txAsset ? `&txAsset=${ options . txAsset }` : '' ) + ( options . txType ? `&txType=${ options . txType }` : '' ) ) ; } return { Promise } private async initChain ( ) { return this . bncClient . initChain ( ) ; } private initDb ( ) { ensureDirSync ( CLIENT_DB_PATH ) ; const adapter = new FileSync ( `${ CLIENT_DB_PATH }/db.json` ) ; this . db = low ( adapter ) ; this . db . defaults ( { mkShare : null , addresses : [ ] } ) . write ( ) ; } return { Promise } private async initMasterKey ( ) { this . p2MasterKeyShare = await this . restoreOrGenerateMasterKey ( ) ; } param { Transaction } tx param { Object } signMsg return { Transaction } private async sign ( tx , signMsg ) { const fromAddress = ( signMsg . inputs && signMsg . inputs [ 0 ] . address ) || signMsg . sender ; const addressObj = this . db . get ( 'addresses' ) . find ( { address : fromAddress } ) . value ( ) ; const addressIndex = addressObj . index ; const signBytes = tx . getSignBytes ( signMsg ) ; const p2ChildShare = this . p2 . getChildShare ( this . p2MasterKeyShare , HD_COIN_INDEX , addressIndex ) ; const msgHash = crypto . createHash ( 'sha256' ) . update ( signBytes ) . digest ( ) ; const signature = await this . p2 . sign ( msgHash , p2ChildShare , HD_COIN_INDEX , addressIndex ) ; const publicKey = this . getPublicKey ( addressIndex ) ; tx . addSignature ( publicKey , signature . toBuffer ( ) ) ; return tx ; } return { Elliptic . PublicKey } private getPublicKey ( addressIndex ) { const p2ChildShare = this . p2 . getChildShare ( this . p2MasterKeyShare , HD_COIN_INDEX , addressIndex ) ; return p2ChildShare . getPublicKey ( ) ; } private async restoreOrGenerateMasterKey ( ) { const p2MasterKeyShare = this . db . get ( 'mkShare' ) . value ( ) ; if ( p2MasterKeyShare ) { return p2MasterKeyShare ; } return this . generateMasterKeyShare ( ) ; } private async generateMasterKeyShare ( ) { const p2MasterKeyShare = await this . p2 . generateMasterKey ( ) ; this . db . set ( 'mkShare' , p2MasterKeyShare ) . write ( ) ; return p2MasterKeyShare ; } } function ensureDirSync ( dirpath ) { try { fs . mkdirSync ( dirpath , { recursive : true } ) ; } catch ( err ) { if ( err . code !== 'EEXIST' ) throw err ; } }