import { DriverNoVendor } from './driver-no-vendor' ; import { Net , Wallet } from './interfaces' ; import { Transaction , Certificate , blake2b256 } from 'thor-devkit' ; import { randomBytes } from 'crypto' ; /** class fully implements DriverInterface */ export class Driver extends DriverNoVendor { param net param wallet public static async connect ( net , wallet ? ) { const genesis = await net . http ( 'GET' , 'blocks/0' ) ; const best = await net . http ( 'GET' , 'blocks/best' , { validateResponseHeader : headers => { const xgid = headers [ 'x-genesis-id' ] ; if ( xgid && xgid !== genesis . id ) { throw new Error ( `responded 'x-genesis-id' not matched` ) ; } } } ) ; return new Driver ( net , genesis , { id : best . id , number : best . number , timestamp : best . timestamp , parentID : best . parentID , txsFeatures : best . txsFeatures , gasLimit : best . gasLimit } , wallet ) ; } /** handler to receive txs committed */ public onTxCommit ? : ( txObj : TxObject ) => void ; /** params for tx construction */ public txParams = { expiration : 18 , gasPriceCoef : 0 } ; constructor ( net , genesis , initialHead ? , private readonly wallet ? ) { super ( net , genesis , initialHead ) ; } public async signTx ( msg , options ) { options . onAccepted && options . onAccepted ( ) ; const key = this . findKey ( options . signer ) ; const clauses = msg . map ( c => ( { to : c . to ? c . to . toLowerCase ( ) : null , value : c . value . toString ( ) . toLowerCase ( ) , data : ( c . data || '0x' ) . toLowerCase ( ) , } ) ) ; const gas = options . gas || ( await this . estimateGas ( clauses , key . address ) ) ; const txBody = { chainTag : Number . parseInt ( this . genesis . id . slice ( - 2 ) , 16 ) , blockRef : this . head . id . slice ( 0 , 18 ) , expiration : this . txParams . expiration , clauses , gasPriceCoef : this . txParams . gasPriceCoef , gas , dependsOn : options . dependsOn || null , nonce : '0x' + randomBytes ( 8 ) . toString ( 'hex' ) } ; let tx ; if ( options . delegator ) { const delegatedTx = new Transaction ( { ... txBody , reserved : { features : 1 } } ) ; const originSig = await key . sign ( delegatedTx . signingHash ( ) ) ; const unsigned = { raw : '0x' + delegatedTx . encode ( ) . toString ( 'hex' ) , origin : key . address } ; try { const result = await this . net . http ( 'POST' , options . delegator . url , { body : unsigned } ) ; delegatedTx . signature = Buffer . concat ( [ originSig , Buffer . from ( result . signature . slice ( 2 ) , 'hex' ) ] ) ; tx = delegatedTx ; } catch ( err ) { console . warn ( 'tx delegation error: ' , err ) ; } } if ( ! tx ) { tx = new Transaction ( txBody ) ; tx . signature = await key . sign ( tx . signingHash ( ) ) ; } const raw = '0x' + tx . encode ( ) . toString ( 'hex' ) ; if ( this . onTxCommit ) { this . onTxCommit ( { id : tx . id ! , raw , resend : async ( ) => { await this . sendTx ( raw ) ; } } ) ; } await this . sendTx ( raw ) ; return { txid : tx . id ! , signer : key . address } ; } public async signCert ( msg , options ) { options . onAccepted && options . onAccepted ( ) ; const key = this . findKey ( options . signer ) ; const annex = { domain : 'localhost' , timestamp : this . head . timestamp , signer : key . address } ; const unsigned = Certificate . encode ( { ... msg , ... annex } ) ; const signature = await key . sign ( blake2b256 ( unsigned ) ) ; return { annex , signature : '0x' + signature . toString ( 'hex' ) } ; } private findKey ( addr ? ) { if ( this . wallet ) { const keys = this . wallet . list ; const key = addr ? keys . find ( k => k . address === addr ) : keys [ 0 ] ; if ( key ) { return key ; } } throw new Error ( 'empty wallet' ) ; } private sendTx ( raw ) { return this . httpPost ( 'transactions' , { raw } ) ; } private async estimateGas ( clauses , caller ) { const outputs = await this . explain ( { clauses , caller , } , this . head . id ) ; const execGas = outputs . reduce ( ( sum , out ) => sum + out . gasUsed , 0 ) ; const intrinsicGas = Transaction . intrinsicGas ( clauses ) ; return intrinsicGas + ( execGas ? ( execGas + 15000 ) : 0 ) ; } } export interface TxObject { id : string ; raw : string ; resend ( ) : Promise < void > ; }