import { Interface } from '@ethersproject/abi' ; import Conf from 'conf' ; import * as fs from 'fs' ; import _ from 'lodash' ; import * as path from 'path' ; import { keccak } from 'ethereumjs-util' ; import { Networks , defaultNetworks } from './networks' ; import { ABI , ABIInput , ABIItem , Contract } from '../types' ; import { Abis , knownAbis } from './knownAbis' ; import { add0x } from './utils' ; export class ConfigService { config : Conf < any > ; constructor ( config ) { this . config = config ; } getPrivateKey = ( pk , networkId ) => { const addresses = this . config . get ( 'addresses' , { } ) ; const knownAddress = addresses [ pk ] ; if ( ! knownAddress ) { return add0x ( pk ) ; } const knownAddressForNetwork = knownAddress [ networkId ] || knownAddress [ '*' ] ; if ( ! knownAddressForNetwork ) { throw new Error ( `Selected address doesn't exist on network ${ networkId }` ) ; } if ( knownAddressForNetwork . privateKey ) { return add0x ( knownAddressForNetwork . privateKey ) ; } else { throw new Error ( "Selected address doesn't have a known private key" ) ; } } ; updateAbis = ( abis ) : void => { this . config . set ( 'abis' , abis ) ; } ; getAddresses = ( ) => { return this . config . get ( 'addresses' , { } ) ; } ; updateAddresses = ( addresses ) => { this . config . set ( 'addresses' , addresses ) ; } ; getContracts = ( ) => { return this . config . get ( 'contracts' , { } ) ; } ; updateContracts = ( contracts ) => { this . config . set ( 'contracts' , contracts ) ; } ; addContract = ( name , abi , address ) => { const contracts = this . getContracts ( ) ; this . updateContracts ( { ... contracts , [ name ] : { abi , address } , } ) ; } ; removeContract = ( name ) : boolean => { const contracts = this . getContracts ( ) ; if ( ! contracts [ name ] ) { return false ; } delete contracts [ name ] ; this . updateContracts ( contracts ) ; return true ; } ; getContract = ( name ) : Maybe < { abi : string ; address : string ; } > => { const contracts = this . getContracts ( ) ; return contracts [ name ] || null ; } ; getAddress = ( name , networkId ) => { const addresses = this . getAddresses ( ) ; if ( addresses [ name ] ) { if ( networkId && addresses [ name ] [ networkId ] ) { return add0x ( addresses [ name ] [ networkId ] . address ) ; } else if ( addresses [ name ] [ '*' ] ) { return add0x ( addresses [ name ] [ '*' ] . address ) ; } else { throw new Error ( `No known address named ${ name }` ) ; } } else { return add0x ( name ) ; } } ; getNetworks = ( ) : Networks => { const addedNetworks = this . config . get ( 'networks' , { } ) ; return { ... defaultNetworks , ... addedNetworks } ; } ; getAbis = ( ) => { const addedAbis = this . config . get ( 'abis' , { } ) ; return { ... knownAbis , ... addedAbis } ; } ; getAbiByName = ( name ) : object | null => { const knownAbis = this . getAbis ( ) ; if ( knownAbis [ name ] ) { return knownAbis [ name ] ; } return null ; } ; getStringAbiByName = ( abiName ) : string | null => { const abiObj = this . getAbiByName ( abiName ) ; if ( abiObj ) { return JSON . stringify ( abiObj ) ; } return abiObj ; } ; getAbiList = ( ) : string [ ] => { const knownAbis = this . getAbis ( ) ; return Object . keys ( knownAbis ) ; } ; updateNetworks = ( networks ) : void => { this . config . set ( 'networks' , networks ) ; } ; loadContract = ( contract , networkId ) : Contract => { let abiArg ; let addressArg ; let name = null ; if ( contract . indexOf ( '@' ) !== - 1 ) { ; [ abiArg , addressArg ] = ConfigService . parseAbiAtAddress ( contract ) ; } else { const knownContract = this . getContract ( contract ) ; name = _ . camelCase ( contract ) ; if ( ! knownContract ) { throw new Error ( `Unknown contract ${ contract }, add one with contract:add or use abi@address syntax` ) ; } abiArg = knownContract . abi ; addressArg = knownContract . address ; } const { abi , name : defaultName } = this . loadABI ( abiArg ) ; const address = this . getAddress ( addressArg , networkId ) ; return { abi , address , name : name || defaultName , } ; } ; static parseAbiAtAddress = ( abiAtAddress ) : [ string , string ] => { const [ abi , address ] = abiAtAddress . split ( '@' ) ; if ( ! abi || ! address ) { throw new Error ( `Invalid argument '${ abiAtAddress }', expected <abi>@<contractAddress>` ) ; } return [ abi , address ] ; } ; loadABI = ( abiPath ) : { abi : any ; name : string ; } => { let abiStr = this . getStringAbiByName ( abiPath ) ; let name = null ; if ( abiStr ) { name = _ . camelCase ( abiPath ) ; } else { if ( fs . existsSync ( abiPath ) ) { const [ filename ] = path . basename ( abiPath ) . split ( '.' ) ; name = _ . camelCase ( filename ) ; abiStr = fs . readFileSync ( abiPath ) . toString ( ) ; } else { name = 'contract' ; const iface = new Interface ( [ abiPath ] ) ; const fragment = JSON . parse ( JSON . stringify ( iface . fragments [ 0 ] ) ) ; const inputs = fragment . inputs . map ( ( x ) => { if ( x . components === null ) { delete x . components ; } return x ; } ) ; const outputs = fragment . outputs ? fragment . outputs . map ( ( x ) => { if ( x . components === null ) { delete x . components ; } return x ; } ) : [ ] ; const fixedFragment = { ... fragment , inputs , outputs , } ; abiStr = JSON . stringify ( [ fixedFragment ] ) ; } } let abi = null ; try { abi = JSON . parse ( abiStr ) ; if ( abi . abi ) { abi = abi . abi ; } } catch ( e ) { console . error ( 'Error parsing abi' , e ) ; process . exit ( 1 ) ; } return { abi , name } ; } ; getMethodsAndEvents = ( abiPath ) => { const { abi } = this . loadABI ( abiPath ) ; const methods = ConfigService . extractMethodsAndEventsFromABI ( abi ) . map ( ( { name , inputs , kind } ) => { const params = inputs . map ( ( x ) => x . type ) . join ( ',' ) ; const signature = `${ name }(${ params })` ; const signatureHash = keccak ( signature ) . toString ( 'hex' ) . slice ( 0 , 8 ) ; return { signature , signatureHash , kind } ; } ) ; return methods ; } ; getMethods = ( abiPath ) => { return this . getMethodsAndEvents ( abiPath ) . filter ( x => x . kind === 'function' ) ; } ; getEvents = ( abiPath ) => { return this . getMethodsAndEvents ( abiPath ) . filter ( x => x . kind === 'event' ) ; } ; static extractMethodsAndEventsFromABI = ( abi ) : Array < { name : string | undefined ; inputs : any | undefined ; kind : 'function' | 'event' ; } > => { return abi . filter ( ( x ) => x . type === 'function' || ( x . type === 'event' && 'name' in x ) ) . map ( ( { name , inputs , type } ) => ( { name , inputs , kind : type === 'function' ? 'function' : 'event' , } ) ) ; } ; } export const configService = new ConfigService ( new Conf < any > ( { projectName : 'eth-cli' } ) ) ;