module import { inflate , deflate } from 'pako' ; import { AbiProvider , ActionSerializerType , AuthorityProvider , BinaryAbi , CachedAbi , ContextFreeGroupCallback , Query , QueryConfig , SignatureProvider , TransactConfig , Transaction , TransactResult , } from './eosjs-api-interfaces' ; import { JsonRpc } from './eosjs-jsonrpc' ; import { Abi , BlockTaposInfo , GetInfoResult , PushTransactionArgs , GetBlockHeaderStateResult , GetBlockInfoResult , GetBlockResult , ReadOnlyTransactResult , } from './eosjs-rpc-interfaces' ; import * as ser from './eosjs-serialize' ; export class Api { /** Issues RPC calls */ public rpc : JsonRpc ; /** Get subset of `availableKeys` needed to meet authorities in a `transaction` */ public authorityProvider : AuthorityProvider ; /** Supplies ABIs in raw form (binary) */ public abiProvider : AbiProvider ; /** Signs transactions */ public signatureProvider : SignatureProvider ; /** Identifies chain */ public chainId : string ; public textEncoder : TextEncoder ; public textDecoder : TextDecoder ; /** Converts abi files between binary and structured form (`abi.abi.json`) */ public abiTypes : Map < string , ser . Type > ; /** Converts transactions between binary and structured form (`transaction.abi.json`) */ public transactionTypes : Map < string , ser . Type > ; /** Holds information needed to serialize contract actions */ public contracts = new Map < string , ser . Contract > ( ) ; /** Fetched abis */ public cachedAbis = new Map < string , CachedAbi > ( ) ; param args constructor ( args ) { this . rpc = args . rpc ; this . authorityProvider = args . authorityProvider || args . rpc ; this . abiProvider = args . abiProvider || args . rpc ; this . signatureProvider = args . signatureProvider ; this . chainId = args . chainId ; this . textEncoder = args . textEncoder ; this . textDecoder = args . textDecoder ; this . abiTypes = ser . getTypesFromAbi ( ser . createAbiTypes ( ) ) ; this . transactionTypes = ser . getTypesFromAbi ( ser . createTransactionTypes ( ) ) ; } /** Decodes an abi as Uint8Array into json. */ public rawAbiToJson ( rawAbi ) { const buffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder , array : rawAbi , } ) ; if ( ! ser . supportedAbiVersion ( buffer . getString ( ) ) ) { throw new Error ( 'Unsupported abi version' ) ; } buffer . restartRead ( ) ; return this . abiTypes . get ( 'abi_def' ) . deserialize ( buffer ) ; } /** Encodes a json abi as Uint8Array. */ public jsonToRawAbi ( jsonAbi ) { const buffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder , } ) ; this . abiTypes . get ( 'abi_def' ) . serialize ( buffer , jsonAbi ) ; if ( ! ser . supportedAbiVersion ( buffer . getString ( ) ) ) { throw new Error ( 'Unsupported abi version' ) ; } return buffer . asUint8Array ( ) ; } /** Get abi in both binary and structured forms. Fetch when needed. */ public async getCachedAbi ( accountName , reload = false ) { if ( ! reload && this . cachedAbis . get ( accountName ) ) { return this . cachedAbis . get ( accountName ) ; } let cachedAbi ; try { const rawAbi = ( await this . abiProvider . getRawAbi ( accountName ) ) . abi ; const abi = this . rawAbiToJson ( rawAbi ) ; cachedAbi = { rawAbi , abi } ; } catch ( e ) { e . message = `fetching abi for ${ accountName }: ${ e . message }` ; throw e ; } if ( ! cachedAbi ) { throw new Error ( `Missing abi for ${ accountName }` ) ; } this . cachedAbis . set ( accountName , cachedAbi ) ; return cachedAbi ; } /** Get abi in structured form. Fetch when needed. */ public async getAbi ( accountName , reload = false ) { return ( await this . getCachedAbi ( accountName , reload ) ) . abi ; } /** Get abis needed by a transaction */ public async getTransactionAbis ( transaction , reload = false ) { const actions = ( transaction . context_free_actions || [ ] ) . concat ( transaction . actions ) ; const accounts = actions . map ( ( action ) : string => action . account ) ; const uniqueAccounts = new Set ( accounts ) ; const actionPromises = [ ... uniqueAccounts ] . map ( async ( account ) : Promise < BinaryAbi > => ( { accountName : account , abi : ( await this . getCachedAbi ( account , reload ) ) . rawAbi , } ) ) ; return Promise . all ( actionPromises ) ; } /** Get data needed to serialize actions in a contract */ public async getContract ( accountName , reload = false ) { if ( ! reload && this . contracts . get ( accountName ) ) { return this . contracts . get ( accountName ) ; } const abi = await this . getAbi ( accountName , reload ) ; const types = ser . getTypesFromAbi ( ser . createInitialTypes ( ) , abi ) ; const actions = new Map < string , ser . Type > ( ) ; for ( const { name , type } of abi . actions ) { actions . set ( name , ser . getType ( types , type ) ) ; } const result = { types , actions } ; this . contracts . set ( accountName , result ) ; return result ; } /** Convert `value` to binary form. `type` must be a built-in abi type or in `transaction.abi.json`. */ public serialize ( buffer , type , value ) { this . transactionTypes . get ( type ) . serialize ( buffer , value ) ; } /** Convert data in `buffer` to structured form. `type` must be a built-in abi type or in `transaction.abi.json`. */ public deserialize ( buffer , type ) { return this . transactionTypes . get ( type ) . deserialize ( buffer ) ; } /** Convert a transaction to binary */ public serializeTransaction ( transaction ) { const buffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder } ) ; this . serialize ( buffer , 'transaction' , { max_net_usage_words : 0 , max_cpu_usage_ms : 0 , delay_sec : 0 , context_free_actions : [ ] , actions : [ ] , transaction_extensions : [ ] , ... transaction , } ) ; return buffer . asUint8Array ( ) ; } /** Serialize context-free data */ public serializeContextFreeData ( contextFreeData ) { if ( ! contextFreeData || ! contextFreeData . length ) { return null ; } const buffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder } ) ; buffer . pushVaruint32 ( contextFreeData . length ) ; for ( const data of contextFreeData ) { buffer . pushBytes ( data ) ; } return buffer . asUint8Array ( ) ; } /** Convert a transaction from binary. Leaves actions in hex. */ public deserializeTransaction ( transaction ) { const buffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder } ) ; buffer . pushArray ( transaction ) ; return this . deserialize ( buffer , 'transaction' ) ; } private transactionExtensions = [ { id : 1 , type : 'resource_payer' , keys : [ 'payer' , 'max_net_bytes' , 'max_cpu_us' , 'max_memory_bytes' ] } , ] ; public serializeTransactionExtensions ( transaction ) { let transaction_extensions = [ ] ; if ( transaction . resource_payer ) { const extensionBuffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder } ) ; const types = ser . getTypesFromAbi ( ser . createTransactionExtensionTypes ( ) ) ; types . get ( 'resource_payer' ) . serialize ( extensionBuffer , transaction . resource_payer ) ; transaction_extensions = [ ... transaction_extensions , [ 1 , ser . arrayToHex ( extensionBuffer . asUint8Array ( ) ) ] ] ; } return transaction_extensions ; } ; public deserializeTransactionExtensions ( data ) { const transaction = { } as any ; data . forEach ( ( extensionData ) => { const transactionExtension = this . transactionExtensions . find ( extension => extension . id === extensionData [ 0 ] ) ; if ( transactionExtension === undefined ) { throw new Error ( `Transaction Extension could not be determined: ${ extensionData }` ) ; } const types = ser . getTypesFromAbi ( ser . createTransactionExtensionTypes ( ) ) ; const extensionBuffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder } ) ; extensionBuffer . pushArray ( ser . hexToUint8Array ( extensionData [ 1 ] ) ) ; const deserializedObj = types . get ( transactionExtension . type ) . deserialize ( extensionBuffer ) ; if ( extensionData [ 0 ] === 1 ) { deserializedObj . max_net_bytes = Number ( deserializedObj . max_net_bytes ) ; deserializedObj . max_cpu_us = Number ( deserializedObj . max_cpu_us ) ; deserializedObj . max_memory_bytes = Number ( deserializedObj . max_memory_bytes ) ; transaction . resource_payer = deserializedObj ; } } ) ; return transaction ; } ; public deleteTransactionExtensionObjects ( transaction ) { delete transaction . resource_payer ; return transaction ; } /** Convert actions to hex */ public async serializeActions ( actions ) { return await Promise . all ( actions . map ( async ( action ) => { const { account , name , authorization , data } = action ; const contract = await this . getContract ( account ) ; if ( typeof data !== 'object' ) { return action ; } return ser . serializeAction ( contract , account , name , authorization , data , this . textEncoder , this . textDecoder ) ; } ) ) ; } /** Convert actions from hex */ public async deserializeActions ( actions ) { return await Promise . all ( actions . map ( async ( { account , name , authorization , data } ) => { const contract = await this . getContract ( account ) ; return ser . deserializeAction ( contract , account , name , authorization , data , this . textEncoder , this . textDecoder ) ; } ) ) ; } /** Convert a transaction from binary. Also deserializes actions. */ public async deserializeTransactionWithActions ( transaction ) { if ( typeof transaction === 'string' ) { transaction = ser . hexToUint8Array ( transaction ) ; } const deserializedTransaction = this . deserializeTransaction ( transaction ) ; const deserializedCFActions = await this . deserializeActions ( deserializedTransaction . context_free_actions ) ; const deserializedActions = await this . deserializeActions ( deserializedTransaction . actions ) ; return { ... deserializedTransaction , context_free_actions : deserializedCFActions , actions : deserializedActions } ; } /** Deflate a serialized object */ public deflateSerializedArray ( serializedArray ) { return deflate ( serializedArray , { level : 9 } ) ; } /** Inflate a compressed serialized object */ public inflateSerializedArray ( compressedSerializedArray ) { return inflate ( compressedSerializedArray ) ; } returns public async transact ( transaction , { broadcast = true , sign = true , readOnlyTrx , returnFailureTraces , requiredKeys , compression , blocksBehind , useLastIrreversible , expireSeconds } = { } ) { let info ; if ( typeof blocksBehind === 'number' && useLastIrreversible ) { throw new Error ( 'Use either blocksBehind or useLastIrreversible' ) ; } if ( ! this . chainId ) { info = await this . rpc . get_info ( ) ; this . chainId = info . chain_id ; } if ( ( typeof blocksBehind === 'number' || useLastIrreversible ) && expireSeconds ) { transaction = await this . generateTapos ( info , transaction , blocksBehind , useLastIrreversible , expireSeconds ) ; } if ( ! this . hasRequiredTaposFields ( transaction ) ) { throw new Error ( 'Required configuration or TAPOS fields are not present' ) ; } const abis = await this . getTransactionAbis ( transaction ) ; transaction = { ... transaction , transaction_extensions : await this . serializeTransactionExtensions ( transaction ) , context_free_actions : await this . serializeActions ( transaction . context_free_actions || [ ] ) , actions : await this . serializeActions ( transaction . actions ) } ; transaction = this . deleteTransactionExtensionObjects ( transaction ) ; const serializedTransaction = this . serializeTransaction ( transaction ) ; const serializedContextFreeData = this . serializeContextFreeData ( transaction . context_free_data ) ; let pushTransactionArgs = { serializedTransaction , serializedContextFreeData , signatures : [ ] } ; if ( sign ) { if ( ! requiredKeys ) { const availableKeys = await this . signatureProvider . getAvailableKeys ( ) ; requiredKeys = await this . authorityProvider . getRequiredKeys ( { transaction , availableKeys } ) ; } pushTransactionArgs = await this . signatureProvider . sign ( { chainId : this . chainId , requiredKeys , serializedTransaction , serializedContextFreeData , abis , } ) ; } if ( broadcast ) { if ( compression ) { return this . pushCompressedSignedTransaction ( pushTransactionArgs , readOnlyTrx , returnFailureTraces ) as Promise < TransactResult | ReadOnlyTransactResult > ; } return this . pushSignedTransaction ( pushTransactionArgs , readOnlyTrx , returnFailureTraces ) as Promise < TransactResult | ReadOnlyTransactResult > ; } return pushTransactionArgs as PushTransactionArgs ; } public async query ( account , short , query , { sign , requiredKeys , authorization = [ ] } ) { const info = await this . rpc . get_info ( ) ; const refBlock = await this . tryRefBlockFromGetInfo ( info ) ; const queryBuffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder } ) ; ser . serializeQuery ( queryBuffer , query ) ; const transaction = { ... ser . transactionHeader ( refBlock , 60 * 30 ) , context_free_actions : [ ] as ser . Action [ ] , actions : [ { account , name : 'queryit' , authorization , data : ser . arrayToHex ( queryBuffer . asUint8Array ( ) ) , } ] , } ; const serializedTransaction = this . serializeTransaction ( transaction ) ; let signatures = [ ] ; if ( sign ) { const abis = await this . getTransactionAbis ( transaction ) ; if ( ! requiredKeys ) { const availableKeys = await this . signatureProvider . getAvailableKeys ( ) ; requiredKeys = await this . authorityProvider . getRequiredKeys ( { transaction , availableKeys } ) ; } const signResponse = await this . signatureProvider . sign ( { chainId : this . chainId , requiredKeys , serializedTransaction , serializedContextFreeData : null , abis , } ) ; signatures = signResponse . signatures ; } const response = await this . rpc . send_transaction ( { signatures , compression : 0 , serializedTransaction } ) as any ; const returnBuffer = new ser . SerialBuffer ( { textEncoder : this . textEncoder , textDecoder : this . textDecoder , array : ser . hexToUint8Array ( response . processed . action_traces [ 0 ] [ 1 ] . return_value ) } ) ; if ( short ) { return ser . deserializeAnyvarShort ( returnBuffer ) ; } else { return ser . deserializeAnyvar ( returnBuffer ) ; } } /** Broadcast a signed transaction */ public async pushSignedTransaction ( { signatures , serializedTransaction , serializedContextFreeData } , readOnlyTrx = false , returnFailureTraces = false ) { if ( readOnlyTrx ) { return this . rpc . push_ro_transaction ( { signatures , serializedTransaction , serializedContextFreeData , } , returnFailureTraces ) ; } return this . rpc . push_transaction ( { signatures , serializedTransaction , serializedContextFreeData } ) ; } public async pushCompressedSignedTransaction ( { signatures , serializedTransaction , serializedContextFreeData } , readOnlyTrx = false , returnFailureTraces = false ) { const compressedSerializedTransaction = this . deflateSerializedArray ( serializedTransaction ) ; const compressedSerializedContextFreeData = this . deflateSerializedArray ( serializedContextFreeData || new Uint8Array ( 0 ) ) ; if ( readOnlyTrx ) { return this . rpc . push_ro_transaction ( { signatures , compression : 1 , serializedTransaction : compressedSerializedTransaction , serializedContextFreeData : compressedSerializedContextFreeData } , returnFailureTraces ) ; } return this . rpc . push_transaction ( { signatures , compression : 1 , serializedTransaction : compressedSerializedTransaction , serializedContextFreeData : compressedSerializedContextFreeData } ) ; } private async generateTapos ( info , transaction , blocksBehind , useLastIrreversible , expireSeconds ) { if ( ! info ) { info = await this . rpc . get_info ( ) ; } if ( useLastIrreversible ) { const block = await this . tryRefBlockFromGetInfo ( info ) ; return { ... ser . transactionHeader ( block , expireSeconds ) , ... transaction } ; } const taposBlockNumber = info . head_block_num - blocksBehind ; const refBlock = taposBlockNumber <= info . last_irreversible_block_num ? await this . tryGetBlockInfo ( taposBlockNumber ) : await this . tryGetBlockHeaderState ( taposBlockNumber ) ; return { ... ser . transactionHeader ( refBlock , expireSeconds ) , ... transaction } ; } private hasRequiredTaposFields ( { expiration , ref_block_num , ref_block_prefix } ) { return ! ! ( expiration && typeof ( ref_block_num ) === 'number' && typeof ( ref_block_prefix ) === 'number' ) ; } private async tryGetBlockHeaderState ( taposBlockNumber ) { try { return await this . rpc . get_block_header_state ( taposBlockNumber ) ; } catch ( error ) { return await this . tryGetBlockInfo ( taposBlockNumber ) ; } } private async tryGetBlockInfo ( blockNumber ) { try { return await this . rpc . get_block_info ( blockNumber ) ; } catch ( error ) { return await this . rpc . get_block ( blockNumber ) ; } } private async tryRefBlockFromGetInfo ( info ) { if ( info . hasOwnProperty ( 'last_irreversible_block_id' ) && info . hasOwnProperty ( 'last_irreversible_block_num' ) && info . hasOwnProperty ( 'last_irreversible_block_time' ) ) { return { block_num : info . last_irreversible_block_num , id : info . last_irreversible_block_id , timestamp : info . last_irreversible_block_time , } ; } else { const block = await this . tryGetBlockInfo ( info . last_irreversible_block_num ) ; return { block_num : block . block_num , id : block . id , timestamp : block . timestamp , } ; } } public with ( accountName ) { return new ActionBuilder ( this , accountName ) ; } public buildTransaction ( cb ? ) { const tx = new TransactionBuilder ( this ) ; if ( cb ) { return cb ( tx ) ; } return tx as TransactionBuilder ; } } export class TransactionBuilder { private api : Api ; private actions : ActionBuilder [ ] = [ ] ; private contextFreeGroups : ContextFreeGroupCallback [ ] = [ ] ; constructor ( api ) { this . api = api ; } public with ( accountName ) { const actionBuilder = new ActionBuilder ( this . api , accountName ) ; this . actions . push ( actionBuilder ) ; return actionBuilder ; } public associateContextFree ( contextFreeGroup ) { this . contextFreeGroups . push ( contextFreeGroup ) ; return this ; } public async send ( config ? ) { const contextFreeDataSet = [ ] ; const contextFreeActions = [ ] ; const actions = this . actions . map ( ( actionBuilder ) => actionBuilder . serializedData as ser . SerializedAction ) ; await Promise . all ( this . contextFreeGroups . map ( async ( contextFreeCallback ) => { const { action , contextFreeAction , contextFreeData } = contextFreeCallback ( { cfd : contextFreeDataSet . length , cfa : contextFreeActions . length } ) ; if ( action ) { actions . push ( action ) ; } if ( contextFreeAction ) { contextFreeActions . push ( contextFreeAction ) ; } if ( contextFreeData ) { contextFreeDataSet . push ( contextFreeData ) ; } } ) ) ; this . contextFreeGroups = [ ] ; this . actions = [ ] ; return await this . api . transact ( { context_free_data : contextFreeDataSet , context_free_actions : contextFreeActions , actions } , config ) ; } } export class ActionBuilder { private api : Api ; private readonly accountName : string ; public serializedData : ser . SerializedAction ; constructor ( api , accountName ) { this . api = api ; this . accountName = accountName ; } public as ( actorName = [ ] ) { let authorization = [ ] ; if ( actorName && typeof actorName === 'string' ) { authorization = [ { actor : actorName , permission : 'active' } ] ; } else { authorization = actorName as ser . Authorization [ ] ; } return new ActionSerializer ( this , this . api , this . accountName , authorization ) as ActionSerializerType ; } } class ActionSerializer implements ActionSerializerType { constructor ( parent , api , accountName , authorization ) { const jsonAbi = api . cachedAbis . get ( accountName ) ; if ( ! jsonAbi ) { throw new Error ( 'ABI must be cached before using ActionBuilder, run api.getAbi()' ) ; } const types = ser . getTypesFromAbi ( ser . createInitialTypes ( ) , jsonAbi . abi ) ; const actions = new Map < string , ser . Type > ( ) ; for ( const { name , type } of jsonAbi . abi . actions ) { actions . set ( name , ser . getType ( types , type ) ) ; } actions . forEach ( ( type , name ) => { Object . assign ( this , { [ name ] : ( ... args ) => { const data = { } ; args . forEach ( ( arg , index ) => { const field = type . fields [ index ] ; data [ field . name ] = arg ; } ) ; const serializedData = ser . serializeAction ( { types , actions } , accountName , name , authorization , data , api . textEncoder , api . textDecoder ) ; parent . serializedData = serializedData ; return serializedData ; } } ) ; } ) ; } }