import { Transaction , TxStatus , Wallet } from '@zilliqa-js/account' ; import { GET_TX_ATTEMPTS , Provider , RPCMethod , sign } from '@zilliqa-js/core' ; import { fromBech32Address , isValidChecksumAddress , normaliseAddress , toChecksumAddress , } from '@zilliqa-js/crypto' ; import { BN , validation } from '@zilliqa-js/util' ; import { Blockchain } from '@zilliqa-js/blockchain' ; import { Contracts } from './factory' ; import { ABI , CallParams , ContractStatus , DeployError , DeployParams , DeploySuccess , Init , State , Value , } from './types' ; const NIL_ADDRESS = '0x0000000000000000000000000000000000000000' ; export class Contract { factory : Contracts ; provider : Provider ; signer : Wallet ; blockchain : Blockchain ; init : Init ; abi ? : ABI ; state ? : State ; address ? : string ; code ? : string ; status : ContractStatus ; error ? : any ; constructor ( factory , code ? , abi ? , address ? , init ? , state ? , checkAddr = false ) { this . factory = factory ; this . provider = factory . provider ; this . signer = factory . signer ; this . blockchain = new Blockchain ( factory . provider , factory . signer ) ; if ( address ) { this . abi = abi ; if ( checkAddr ) { this . address = normaliseAddress ( address ) ; } else { if ( validation . isBech32 ( address ) ) { this . address = fromBech32Address ( address ) ; } else if ( isValidChecksumAddress ( address ) ) { this . address = address ; } else { this . address = toChecksumAddress ( address ) ; } } this . init = init ; this . state = state ; this . status = ContractStatus . Deployed ; } else { this . abi = abi ; this . code = code ; this . init = init ; this . status = ContractStatus . Initialised ; } } returns { boolean } isInitialised ( ) { return this . status === ContractStatus . Initialised ; } returns { boolean } isDeployed ( ) { return this . status === ContractStatus . Deployed ; } returns { boolean } isRejected ( ) { return this . status === ContractStatus . Rejected ; } @ sign async prepareTx ( tx , attempts = GET_TX_ATTEMPTS , interval = 1000 , isDeploy ) { const response = await this . provider . send < DeploySuccess , DeployError > ( RPCMethod . CreateTransaction , { ... tx . txParams , priority : tx . toDS } ) ; if ( response . error ) { this . address = undefined ; this . error = response . error ; return tx . setStatus ( TxStatus . Rejected ) ; } if ( isDeploy ) { this . address = response . result . ContractAddress ? toChecksumAddress ( response . result . ContractAddress ) : undefined ; } return tx . confirm ( response . result . TranID , attempts , interval ) ; } @ sign async prepare ( tx ) { const response = await this . provider . send < DeploySuccess , DeployError > ( RPCMethod . CreateTransaction , { ... tx . txParams , priority : tx . toDS } ) ; if ( response . error || ! response . result ) { this . address = undefined ; this . error = response . error ; tx . setStatus ( TxStatus . Rejected ) ; } else { tx . id = response . result . TranID ; tx . setStatus ( TxStatus . Pending ) ; return response . result . ContractAddress ; } } param params param toDs async deployWithoutConfirm ( params , toDs = false ) { if ( ! this . code || ! this . init ) { throw new Error ( 'Cannot deploy without code or initialisation parameters.' ) ; } const tx = new Transaction ( { ... params , toAddr : NIL_ADDRESS , amount : new BN ( 0 ) , code : this . code , data : JSON . stringify ( this . init ) . replace ( /\\"/g , '"' ) , } , this . provider , TxStatus . Initialised , toDs ) ; try { this . address = await this . prepare ( tx ) ; this . status = this . address === undefined ? ContractStatus . Rejected : ContractStatus . Initialised ; return [ tx , this ] ; } catch ( err ) { throw err ; } } param { DeployParams } params returns { Promise < Contract > } async deploy ( params , attempts = 33 , interval = 1000 , toDs = false ) { if ( ! this . code || ! this . init ) { throw new Error ( 'Cannot deploy without code or initialisation parameters.' ) ; } try { const tx = await this . prepareTx ( new Transaction ( { ... params , toAddr : NIL_ADDRESS , amount : new BN ( 0 ) , code : this . code , data : JSON . stringify ( this . init ) . replace ( /\\"/g , '"' ) , } , this . provider , TxStatus . Initialised , toDs ) , attempts , interval , true ) ; if ( tx . isRejected ( ) ) { this . status = ContractStatus . Rejected ; this . address = undefined ; return [ tx , this ] ; } this . status = ContractStatus . Deployed ; this . address = this . address && isValidChecksumAddress ( this . address ) ? this . address : Contracts . getAddressForContract ( tx ) ; return [ tx , this ] ; } catch ( err ) { throw err ; } } async callWithoutConfirm ( transition , args , params , toDs = false ) { const data = { _tag : transition , params : args , } ; if ( this . error ) { return Promise . reject ( this . error ) ; } if ( ! this . address ) { return Promise . reject ( 'Contract has not been deployed!' ) ; } const tx = new Transaction ( { ... params , toAddr : this . address , data : JSON . stringify ( data ) , } , this . provider , TxStatus . Initialised , toDs ) ; try { await this . prepare ( tx ) ; return tx ; } catch ( err ) { throw err ; } } param { string } transition param { any } params returns { Promise < Transaction > } async call ( transition , args , params , attempts = 33 , interval = 1000 , toDs = false ) { const data = { _tag : transition , params : args , } ; if ( this . error ) { return Promise . reject ( this . error ) ; } if ( ! this . address ) { return Promise . reject ( 'Contract has not been deployed!' ) ; } try { return await this . prepareTx ( new Transaction ( { ... params , toAddr : this . address , data : JSON . stringify ( data ) , } , this . provider , TxStatus . Initialised , toDs ) , attempts , interval , false ) ; } catch ( err ) { throw err ; } } async getState ( ) { if ( this . status !== ContractStatus . Deployed ) { return Promise . resolve ( [ ] ) ; } if ( ! this . address ) { throw new Error ( 'Cannot get state of uninitialised contract' ) ; } const response = await this . blockchain . getSmartContractState ( this . address ) ; return response . result ; } async getSubState ( variableName , indices ? ) { if ( this . status !== ContractStatus . Deployed ) { return Promise . resolve ( [ ] ) ; } if ( ! this . address ) { throw new Error ( 'Cannot get state of uninitialised contract' ) ; } if ( ! variableName ) { throw new Error ( 'Variable name required' ) ; } const response = await this . blockchain . getSmartContractSubState ( this . address , variableName , indices ) ; return response . result ; } async getInit ( ) { if ( this . status !== ContractStatus . Deployed ) { return Promise . resolve ( [ ] ) ; } if ( ! this . address ) { throw new Error ( 'Cannot get state of uninitialised contract' ) ; } const response = await this . blockchain . getSmartContractInit ( this . address ) ; return response . result ; } }