import { Injectable } from "@angular/core" ; import { Observable } from "rxjs/Observable" ; import { of } from "rxjs/observable/of" ; import { catchError , map , tap } from "rxjs/operators" ; import { HttpClient , HttpHeaders } from "@angular/common/http" ; import { util } from "@zilliqa-js/account" ; import { RPCMethod , HTTPProvider } from "@zilliqa-js/core" ; import { sign } from "@zilliqa-js/crypto" ; import { BN , Long } from "@zilliqa-js/util" ; import "setimmediate" ; import * as $ from "jquery" ; import { Wallet } from "./wallet/wallet" ; import { Constants } from "./constants" ; import { environment } from "../environments/environment" ; import { Zilliqa } from "zilliqa-js" ; import { secp256k1 , randomBytes , pbkdf2Sync , sha3 , sha256 } from "bcrypto" ; import * as scryptAsync from "scrypt-async" ; import * as aesjs from "aes-js" ; import * as Signature from "elliptic/lib/elliptic/ec/signature" ; import * as uuid from "uuid" ; declare const Buffer ; @ Injectable ( ) export class ZilliqaService { provider : HTTPProvider ; zilliqa : any ; node : any ; walletData : { version : null ; encryptedWalletFile : null ; } ; nodeData : { } ; userWallet : Wallet ; networkLoading : boolean ; envName : string ; popupTriggered : boolean ; recentTxns : Array < any > ; constructor ( private http ) { this . networkLoading = false ; this . popupTriggered = false ; this . envName = environment . envName ; this . userWallet = new Wallet ( ) ; this . recentTxns = [ ] ; this . provider = new HTTPProvider ( environment . API_URL || Constants . API_URL ) ; this . walletData = { version : null , encryptedWalletFile : null } ; this . nodeData = { networkId : null , latestDsBlock : null } ; this . initLib ( ) ; } /**
     * connect to a node using the zilliqa js lib and store its reference
     */ initLib ( ) { const nodeUrl = environment . API_URL || Constants . API_URL ; this . zilliqa = new Zilliqa ( { nodeUrl : nodeUrl } ) ; this . node = this . zilliqa . getNode ( ) ; } returns { Promise } getInitData ( ) { var deferred = new $ . Deferred ( ) ; var that = this ; that . node . getNetworkId ( function ( err , data1 ) { if ( err || ! data1 . result ) { deferred . reject ( err ) ; } else { that . node . getLatestTxBlock ( function ( err , data2 ) { if ( err || ! data2 . result ) { deferred . reject ( err ) ; } else { deferred . resolve ( { networkId : data1 . result , latestTxBlock : data2 . result . header . BlockNum } ) ; } } ) ; } } ) ; return deferred . promise ( ) ; } returns { Wallet } getWallet ( ) { return this . userWallet ; } param { string } contents uploadWalletFile ( contents ) { this . walletData . encryptedWalletFile = contents ; } param { string } passphrase returns { string } generateWalletJson ( passphrase ) { var deferred = new $ . Deferred ( ) ; var that = this ; let privateKey = new Buffer ( this . userWallet . privateKey , "hex" ) ; let address = this . userWallet . address ; let salt = randomBytes ( 32 ) ; let iv = randomBytes ( 16 ) ; scryptAsync ( passphrase , salt , { N : 262144 , r : 1 , p : 8 , dkLen : 32 } , function ( derivedKey ) { derivedKey = new Buffer ( derivedKey ) ; let aesctr = new aesjs . ModeOfOperation . ctr ( derivedKey . slice ( 0 , 16 ) , new aesjs . Counter ( iv ) ) ; let ciphertext = aesctr . encrypt ( privateKey ) ; var result = { address : address , crypto : { cipher : "aes-128-ctr" , cipherparams : { iv : iv . toString ( "hex" ) } , ciphertext : new Buffer ( ciphertext ) . toString ( "hex" ) , kdf : "scrypt" , kdfparams : { dklen : 32 , n : 262144 , r : 1 , p : 8 , salt : salt . toString ( "hex" ) } , mac : sha3 . digest ( Buffer . concat ( [ derivedKey . slice ( 16 , 32 ) , new Buffer ( ciphertext ) ] ) ) . toString ( "hex" ) } , id : uuid . v4 ( { random : randomBytes ( 16 ) } ) , version : 3 } ; deferred . resolve ( { result : JSON . stringify ( result ) } ) ; } ) ; return deferred . promise ( ) ; } returns { boolean } checkEncryptedWallet ( ) { if ( this . walletData . encryptedWalletFile == null ) return false ; try { let parsed = JSON . parse ( this . walletData . encryptedWalletFile ) ; } catch ( e ) { return false ; } return true ; } param { string } passphrase returns { Promise } decryptWalletFile ( passphrase ) { let that = this ; var deferred = new $ . Deferred ( ) ; if ( passphrase && passphrase . length >= 8 ) { let walletJson = JSON . parse ( this . walletData . encryptedWalletFile ) ; let ciphertext = new Buffer ( walletJson [ "crypto" ] [ "ciphertext" ] , "hex" ) ; let iv = new Buffer ( walletJson [ "crypto" ] [ "cipherparams" ] [ "iv" ] , "hex" ) ; let salt = new Buffer ( walletJson [ "crypto" ] [ "kdfparams" ] [ "salt" ] , "hex" ) ; let kdfparams = walletJson [ "crypto" ] [ "kdfparams" ] ; scryptAsync ( passphrase , salt , { N : kdfparams [ "n" ] , r : kdfparams [ "r" ] , p : kdfparams [ "p" ] , dkLen : kdfparams [ "dklen" ] } , function ( derivedKey ) { derivedKey = new Buffer ( derivedKey , "hex" ) ; let mac = sha3 . digest ( Buffer . concat ( [ derivedKey . slice ( 16 , 32 ) , ciphertext ] ) ) . toString ( "hex" ) ; if ( mac . toLowerCase ( ) !== walletJson [ "crypto" ] [ "mac" ] . toLowerCase ( ) ) { deferred . resolve ( { result : false } ) ; } let aesctr = new aesjs . ModeOfOperation . ctr ( derivedKey . slice ( 0 , 16 ) , new aesjs . Counter ( iv ) ) ; let decryptedSeed = aesctr . decrypt ( ciphertext ) ; deferred . resolve ( { result : new Buffer ( decryptedSeed ) . toString ( "hex" ) } ) ; } ) ; } else { deferred . resolve ( { result : false } ) ; } return deferred . promise ( ) ; } updateAccount ( ) { var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getBalance ( { address : this . userWallet . address } , function ( err , data ) { if ( err || data . error ) { that . userWallet . balance = 0 ; that . userWallet . nonce = 0 ; deferred . resolve ( ) ; console . log ( `Couldn't fetch account details, balance: ` + that . userWallet . balance + `, nonce: ` + that . userWallet . nonce ) ; } else { that . userWallet . balance = data . result . balance ; that . userWallet . nonce = data . result . nonce ; console . log ( `Fetched account details successfully, balance: ` + that . userWallet . balance + `, nonce: ` + that . userWallet . nonce ) ; deferred . resolve ( ) ; } } ) ; return deferred . promise ( ) ; } param { string } address returns { Promise } refreshBalance ( ) { var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getBalance ( { address : this . userWallet . address } , function ( err , data ) { if ( err || data . error ) { deferred . reject ( { error : err } ) ; } else { const newUserWallet = new Wallet ( { address : that . userWallet . address , balance : data . result . balance , nonce : data . result . nonce , privateKey : that . userWallet . privateKey } ) ; that . userWallet = newUserWallet ; deferred . resolve ( { result : true } ) ; } } ) ; return deferred . promise ( ) ; } param { string } privateKey returns { string } getAddressFromPrivateKey ( privateKey ) { if ( typeof privateKey == "string" ) privateKey = new Buffer ( privateKey , "hex" ) ; let pubKey = secp256k1 . publicKeyCreate ( privateKey , true ) ; let pubKeyHash = sha256 . digest ( pubKey ) ; let address = pubKeyHash . toString ( "hex" , 12 ) ; return address ; } returns { string } getPublicKeyfromPrivateKey ( ) { let privateKey = this . userWallet . privateKey ; if ( typeof privateKey == "string" ) privateKey = new Buffer ( privateKey , "hex" ) ; let pubKey = secp256k1 . publicKeyCreate ( privateKey , true ) ; return pubKey . toString ( "hex" ) ; } returns { string } createWallet ( ) { let key = secp256k1 . generatePrivateKey ( ) ; this . userWallet = new Wallet ( { address : this . getAddressFromPrivateKey ( key ) , privateKey : key . toString ( "hex" ) , balance : 0 , nonce : 0 } ) ; return key . toString ( "hex" ) ; } param { string } privateKey returns { Promise } importWallet ( privateKey ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; if ( ! ! privateKey . match ( /[0-9a-fA-F]{64}/ ) == false ) { deferred . reject ( { error : "Invalid private key." } ) ; return deferred . promise ( ) ; } if ( typeof privateKey == "string" ) privateKey = new Buffer ( privateKey , "hex" ) ; try { if ( secp256k1 . privateKeyVerify ( privateKey ) ) { let addr = this . getAddressFromPrivateKey ( privateKey ) ; let that = this ; this . node . getBalance ( { address : addr } , function ( err , data ) { if ( err || ( data . error && data . error . code !== - 5 ) ) { deferred . reject ( { error : err } ) ; } else if ( data . error && data . error . code === - 5 ) { that . userWallet = new Wallet ( { address : addr , balance : 0 , nonce : 0 , privateKey : privateKey . toString ( "hex" ) } ) ; deferred . resolve ( { result : true } ) ; } else { console . log ( data ) ; that . userWallet = new Wallet ( { address : addr , balance : data . result . balance , nonce : data . result . nonce , privateKey : privateKey . toString ( "hex" ) } ) ; deferred . resolve ( { result : true } ) ; } that . endLoading ( ) ; } ) ; } else { deferred . reject ( { error : "Invalid private key." } ) ; this . endLoading ( ) ; } } catch ( e ) { deferred . reject ( { error : e } ) ; this . endLoading ( ) ; } return deferred . promise ( ) ; } /**
     * destroys and resets the userWallet data
     */ resetWallet ( ) { this . userWallet = new Wallet ( ) ; for ( var i = 0 ; i < this . recentTxns . length ; i ++ ) { clearInterval ( this . recentTxns [ i ] . tid ) ; } this . recentTxns = [ ] ; this . networkLoading = false ; } param { number } val param { number } paddedSize returns { Array } intToByteArray ( val , paddedSize ) { var arr = [ ] ; let hexVal = val . toString ( 16 ) ; let hexRep = [ ] ; var i ; for ( i = 0 ; i < hexVal . length ; i ++ ) { hexRep [ i ] = hexVal [ i ] . toString ( ) ; } for ( i = 0 ; i < paddedSize - hexVal . length ; i ++ ) { arr . push ( "0" ) ; } for ( i = 0 ; i < hexVal . length ; i ++ ) { arr . push ( hexRep [ i ] ) ; } return arr ; } param payment param { string } payment . to param { number } payment . amount param { number } payment . gasPrice param { number } payment . gasLimit returns { Promise } sendPayment ( payment ) { this . startLoading ( ) ; const deferred = new $ . Deferred ( ) ; const txCoreFields = { version : 0 , nonce : this . userWallet . nonce + 1 , pubKey : this . getPublicKeyfromPrivateKey ( ) , toAddr : payment . address , amount : new BN ( payment . amount ) , gasPrice : new BN ( payment . gasPrice ) , gasLimit : Long . fromNumber ( payment . gasLimit ) } ; const msg = util . encodeTransactionProto ( txCoreFields ) ; const signature = sign ( msg , this . userWallet . privateKey , this . getPublicKeyfromPrivateKey ( ) ) ; const tx = { ... txCoreFields , amount : txCoreFields . amount . toString ( ) , gasPrice : txCoreFields . gasPrice . toString ( ) , gasLimit : txCoreFields . gasLimit . toString ( ) , data : "" , code : "" , signature } ; let that = this ; this . provider . send ( RPCMethod . CreateTransaction , tx ) . then ( res => { console . log ( "Result: " , res ) ; that . addLocalTxn ( { txnId : res . result . TranID , amount : tx . amount , toAddr : tx . toAddr } ) ; deferred . resolve ( { txId : res . result . TranID } ) ; that . endLoading ( ) ; } ) . catch ( err => { deferred . reject ( err ) ; } ) ; return deferred . promise ( ) ; } addLocalTxn ( args ) { let that = this ; let tid = setInterval ( ( ) => { that . node . getTransaction ( { txHash : args . txnId } , function ( err , data ) { console . log ( "Querying tx: " , data ) ; if ( err || data . error || ! data . result ) { return ; } else { console . log ( "Success: " , data ) ; let id = data . result [ "ID" ] ; let amount = data . result [ "amount" ] ; let toAddr = data . result [ "toAddr" ] ; let i = that . recentTxns . findIndex ( txn => txn . id === id ) ; if ( i === - 1 ) return ; that . recentTxns [ i ] [ "confirmed" ] = true ; that . recentTxns [ i ] [ "id" ] = id ; that . recentTxns [ i ] [ "amount" ] = amount ; that . recentTxns [ i ] [ "toAddr" ] = toAddr ; clearInterval ( that . recentTxns [ i ] . tid ) ; that . refreshBalance ( ) ; } } ) ; } , 5000 ) ; this . recentTxns . push ( { id : args . txnId , tid : tid , confirmed : false , amount : args . amount , toAddr : args . toAddr } ) ; } returns { Promise } createContract ( codeStr , initParams , amount , gas ) { var deferred = new $ . Deferred ( ) ; let that = this ; var toPubKey = sha256 . digest ( new Buffer ( "" , "hex" ) ) ; let toAddr = toPubKey . toString ( "hex" , 12 ) ; this . updateAccount ( ) . then ( ( ) => { console . log ( "now nonce is " + that . userWallet . nonce ) ; var txn = this . zilliqa . util . createTransactionJson ( this . userWallet . privateKey , { version : 0 , nonce : + that . userWallet . nonce + 1 , to : "0000000000000000000000000000000000000000" , amount : amount , gasPrice : 1 , gasLimit : gas , code : codeStr , data : JSON . stringify ( initParams ) . replace ( /\\"/g , '"' ) } ) ; console . log ( "sending txn:" ) ; console . log ( txn ) ; this . node . createTransaction ( txn , function ( err , data ) { if ( err || data . error ) { deferred . reject ( err ) ; } else { let nonceStr = that . zilliqa . util . intToByteArray ( that . userWallet . nonce , 64 ) . join ( "" ) ; let newstr = that . userWallet . address + nonceStr ; var contractPubKey = sha256 . digest ( new Buffer ( newstr , "hex" ) ) ; var contractAddr = contractPubKey . toString ( "hex" , 12 ) ; console . log ( "Contract should be deployed at address " + contractAddr + " soon." ) ; deferred . resolve ( { result : data . result , addr : contractAddr } ) ; } } ) ; } ) ; return deferred . promise ( ) ; } getContractHistory ( ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getSmartContracts ( { address : this . userWallet . address } , function ( err , data ) { if ( err || data . error ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data . result } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } checkPendingTxns ( txnid , idx ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getTransaction ( { txHash : txnid } , function ( err , data ) { if ( err || data . error || data . result . error ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data . result , index : idx } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } callTxnMethod ( addr , method , amount , gas , params ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; var data = JSON . stringify ( { _from : this . userWallet . address , _tag : method , _amount : amount , params : params } ) ; this . updateAccount ( ) . then ( ( ) => { var txn = that . zilliqa . util . createTransactionJson ( that . userWallet . privateKey , { version : 0 , nonce : + that . userWallet . nonce + 1 , to : addr , amount : amount , gasPrice : 1 , gasLimit : gas , data : data } ) ; console . log ( "Sending txn..." ) ; console . log ( txn ) ; this . node . createTransaction ( txn , function ( err , data ) { if ( err || data . error ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data . result } ) ; } that . endLoading ( ) ; } ) ; } ) ; return deferred . promise ( ) ; } getContractState ( addr ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getSmartContractState ( { address : addr } , function ( err , data ) { if ( err || ( data . result && data . result . Error ) ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data . result } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } getContractCode ( addr ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getSmartContractCode ( { address : addr } , function ( err , data ) { if ( err || ( data . result && data . result . Error ) ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data . result } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } getContractInit ( addr ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . getSmartContractInit ( { address : addr } , function ( err , data ) { if ( err || ( data . result && data . result . Error ) ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data . result } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } checkContractCode ( code ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . checkCode ( { code : code } , function ( err , data ) { if ( err || ( data . result && data . result . Error ) ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } checkContractCodeTest ( code , init , blockchain , state , msg ) { this . startLoading ( ) ; var deferred = new $ . Deferred ( ) ; let that = this ; this . node . checkCodeTest ( { code : code , init : init , blockchain : blockchain , state : state , message : msg } , function ( err , data ) { if ( err || ( data . result && data . result . Error ) ) { deferred . reject ( err ) ; } else { deferred . resolve ( { result : data } ) ; } that . endLoading ( ) ; } ) ; return deferred . promise ( ) ; } startLoading ( ) { setTimeout ( ( ) => { this . networkLoading = true ; } , 0 ) ; } endLoading ( ) { setTimeout ( ( ) => { this . networkLoading = false ; } , 0 ) ; } triggerPopup ( ) { if ( ! this . popupTriggered ) { this . popupTriggered = true ; $ ( "#modalButton" ) . click ( ) ; } } }