import { PinoLogger } from 'nestjs-pino' ; import axios from 'axios' ; import { CeloProvider } from '@celo-tools/celo-ethers-wrapper' ; import { CeloError } from './CeloError' ; import { BurnCeloErc20 , CeloBurnErc721 , CeloDeployErc721 , CeloMintErc721 , CeloMintMultipleErc721 , CeloSmartContractMethodInvocation , CeloTransferErc721 , CEUR_ADDRESS_MAINNET , CEUR_ADDRESS_TESTNET , Currency , CUSD_ADDRESS_MAINNET , CUSD_ADDRESS_TESTNET , DeployCeloErc20 , generateAddressFromXPub , generatePrivateKeyFromMnemonic , generateWallet , MintCeloErc20 , prepareCeloBurnErc20SignedTransaction , prepareCeloBurnErc721SignedTransaction , prepareCeloDeployErc20SignedTransaction , prepareCeloDeployErc721SignedTransaction , prepareCeloMintErc20SignedTransaction , prepareCeloMintErc721SignedTransaction , prepareCeloMintMultipleErc721SignedTransaction , prepareCeloOrCUsdSignedTransaction , prepareCeloSmartContractWriteMethodInvocation , prepareCeloTransferErc20SignedTransaction , prepareCeloTransferErc721SignedTransaction , sendCeloSmartContractReadMethodInvocationTransaction , SmartContractReadMethodInvocation , TransactionHash , TransferCeloOrCeloErc20Token , } from '@tatumio/tatum' ; import erc721_abi from '@tatumio/tatum/dist/src/contracts/erc721/erc721_abi' ; import token_abi from '@tatumio/tatum/dist/src/contracts/erc20/token_abi' ; import { fromWei } from 'web3-utils' ; import { Block , Transaction , TransactionReceipt } from 'web3-eth' ; import Web3 from 'web3' ; export abstract class CeloService { protected constructor ( protected readonly logger ) { } protected abstract storeKMSTransaction ( txData , currency , signatureId , index ? ) ; protected abstract completeKMSTransaction ( txId , signatureId ) ; protected abstract isTestnet ( ) ; protected abstract getNodesUrl ( testnet ) ; public async broadcast ( txData , signatureId ? , withdrawalId ? ) { this . logger . info ( `Broadcast tx for CELO with data '${ txData }'` ) ; let txId ; try { const url = ( await this . getNodesUrl ( await this . isTestnet ( ) ) ) [ 0 ] ; const { result , error } = ( await axios . post ( url , { jsonrpc : '2.0' , id : 0 , method : 'eth_sendRawTransaction' , params : [ txData ] } , { headers : { 'Content-Type' : 'application/json' } } ) ) . data ; if ( error ) { throw new CeloError ( `Unable to broadcast transaction due to ${ error . message }.` , 'celo.broadcast.failed' ) ; } txId = result ; } catch ( e ) { if ( e . constructor . name === CeloError . name ) { throw e ; } this . logger . error ( e ) ; throw new CeloError ( `Unable to broadcast transaction due to ${ e . message }.` , 'celo.broadcast.failed' ) ; } if ( signatureId ) { try { await this . completeKMSTransaction ( txId , signatureId ) ; } catch ( e ) { this . logger . error ( e ) ; return { txId , failed : true } ; } } return { txId } ; } public async getCurrentBlock ( testnet ? ) { const t = testnet === undefined ? await this . isTestnet ( ) : testnet ; return ( await this . getClient ( t ) ) . eth . getBlockNumber ( ) ; } public async getBalance ( address , testnet ? ) { const t = testnet === undefined ? await this . isTestnet ( ) : testnet ; const provider = new CeloProvider ( ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; const cUsd = new ( ( await this . getClient ( t ) ) ) . eth . Contract ( token_abi , t ? CUSD_ADDRESS_TESTNET : CUSD_ADDRESS_MAINNET ) ; const cEur = new ( ( await this . getClient ( t ) ) ) . eth . Contract ( token_abi , t ? CEUR_ADDRESS_TESTNET : CEUR_ADDRESS_MAINNET ) ; return { celo : fromWei ( ( await provider . getBalance ( address ) ) . toString ( ) , 'ether' ) , cUsd : fromWei ( await cUsd . methods . balanceOf ( address ) . call ( ) , 'ether' ) , cEur : fromWei ( await cEur . methods . balanceOf ( address ) . call ( ) , 'ether' ) , } ; } public async getBlock ( hash , testnet ? ) { const t = testnet === undefined ? await this . isTestnet ( ) : testnet ; return ( await this . getClient ( t ) ) . eth . getBlock ( hash , true ) ; } public async getTransaction ( txId , testnet ? ) { const t = testnet === undefined ? await this . isTestnet ( ) : testnet ; try { const web3 = await this . getClient ( t ) ; const { r , s , v , hash , ... transaction } = ( await web3 . eth . getTransaction ( txId ) ) as any ; let receipt = undefined ; try { receipt = await web3 . eth . getTransactionReceipt ( hash ) ; } catch ( _ ) { transaction . transactionHash = hash ; } return { ... transaction , ... receipt } ; } catch ( e ) { this . logger . error ( e ) ; throw new CeloError ( 'Transaction not found. Possible not exists or is still pending.' , 'tx.not.found' ) ; } } public async transfer ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloOrCUsdSignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async transferErc20 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloTransferErc20SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async invokeSmartContractMethod ( body ) { const testnet = await this . isTestnet ( ) ; if ( body . methodABI . stateMutability === 'view' ) { return sendCeloSmartContractReadMethodInvocationTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; } const txData = await prepareCeloSmartContractWriteMethodInvocation ( testnet , body as CeloSmartContractMethodInvocation , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( ( body as CeloSmartContractMethodInvocation ) . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ ( body as CeloSmartContractMethodInvocation ) . signatureId ] , ( body as CeloSmartContractMethodInvocation ) . index ) } ; } else { return this . broadcast ( txData ) ; } } public async mintErc20 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloMintErc20SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async burnErc20 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloBurnErc20SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async deployErc20 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloDeployErc20SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async getErc20Balance ( address , contractAddress ) { const c = new ( ( await this . getClient ( await this . isTestnet ( ) ) ) ) . eth . Contract ( token_abi , contractAddress ) ; return { balance : await c . methods . balanceOf ( address ) . call ( ) } ; } public async getTransactionCount ( address , c ? ) { const client = c || await this . getClient ( await this . isTestnet ( ) ) ; return client . eth . getTransactionCount ( address , 'pending' ) ; } public async getBalanceErc721 ( address , contractAddress ) { const c = new ( await this . getClient ( await this . isTestnet ( ) ) ) . eth . Contract ( erc721_abi , contractAddress ) ; try { return { data : await c . methods . balanceOf ( address ) . call ( ) } ; } catch ( e ) { this . logger . error ( e ) ; throw new CeloError ( `Unable to obtain information for token. ${ e }` , 'celo.erc721.failed' ) ; } } public async getTokenErc721 ( address , index , contractAddress ) { const c = new ( await this . getClient ( await this . isTestnet ( ) ) ) . eth . Contract ( erc721_abi , contractAddress ) ; try { return { data : await c . methods . tokenOfOwnerByIndex ( address , index ) . call ( ) } ; } catch ( e ) { this . logger . error ( e ) ; throw new CeloError ( `Unable to obtain information for token. ${ e }` , 'celo.erc721.failed' ) ; } } public async getMetadataErc721 ( token , contractAddress ) { const c = new ( await this . getClient ( await this . isTestnet ( ) ) ) . eth . Contract ( erc721_abi , contractAddress ) ; try { return { data : await c . methods . tokenURI ( token ) . call ( ) } ; } catch ( e ) { this . logger . error ( e ) ; throw new CeloError ( `Unable to obtain information for token. ${ e }` , 'celo.erc721.failed' ) ; } } public async getOwnerErc721 ( token , contractAddress ) { const c = new ( await this . getClient ( await this . isTestnet ( ) ) ) . eth . Contract ( erc721_abi , contractAddress ) ; try { return { data : await c . methods . ownerOf ( token ) . call ( ) } ; } catch ( e ) { this . logger . error ( e ) ; throw new CeloError ( `Unable to obtain information for token. ${ e }` , 'celo.erc721.failed' ) ; } } public async getTokensOfOwner ( address , contractAddress ) { const c = new ( await this . getClient ( await this . isTestnet ( ) ) ) . eth . Contract ( erc721_abi , contractAddress ) ; try { return { data : await c . methods . tokensOfOwner ( address ) . call ( ) } ; } catch ( e ) { this . logger . error ( e ) ; throw new CeloError ( `Unable to obtain information for token. ${ e }` , 'celo.erc721.failed' ) ; } } public async transferErc721 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloTransferErc721SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async mintErc721 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloMintErc721SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async mintMultipleErc721 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloMintMultipleErc721SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async burnErc721 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloBurnErc721SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async deployErc721 ( body ) { const testnet = await this . isTestnet ( ) ; const txData = await prepareCeloDeployErc721SignedTransaction ( testnet , body , ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; if ( body . signatureId ) { return { signatureId : await this . storeKMSTransaction ( txData , Currency . CELO , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( txData ) ; } } public async web3Method ( body ) { try { return ( await axios . post ( ( await this . getNodesUrl ( await this . isTestnet ( ) ) ) [ 0 ] , body , { headers : { 'Content-Type' : 'application/json' } } ) ) . data ; } catch ( e ) { this . logger . error ( e ) ; throw e ; } } public async generateAddress ( xpub , i ) { return { address : await generateAddressFromXPub ( Currency . CELO , await this . isTestnet ( ) , xpub , i ) } ; } public async generateWallet ( mnemonic ? ) { return generateWallet ( Currency . CELO , await this . isTestnet ( ) , mnemonic ) ; } public async generateAddressPrivateKey ( index , mnemonic ) { return { key : await generatePrivateKeyFromMnemonic ( Currency . CELO , await this . isTestnet ( ) , mnemonic , index ) } ; } protected async getClient ( testnet ) { return new Web3 ( ( await this . getNodesUrl ( testnet ) ) [ 0 ] ) ; } }