diff --git a/packages/mobile/src/web3/contracts.ts b/packages/mobile/src/web3/contracts.ts index 5a7f02cf3ce..e5cc1d9c455 100644 --- a/packages/mobile/src/web3/contracts.ts +++ b/packages/mobile/src/web3/contracts.ts @@ -1,4 +1,4 @@ -import { addLocalAccount as web3utilsAddLocalAccount, StaticNodeUtils } from '@celo/walletkit' +import { addLocalAccount as web3utilsAddLocalAccount } from '@celo/walletkit' import { DocumentDirectoryPath } from 'react-native-fs' import * as net from 'react-native-tcp' import { isGethFreeMode } from 'src/geth/consts' @@ -50,15 +50,15 @@ const getIpcProvider = (testnet: Testnets) => { } const getWebSocketProvider = (url: string) => { - Logger.debug(tag, 'creating WebsocketProvider...') - const wsProvider = new Web3.providers.WebsocketProvider(url) - Logger.debug(tag, 'created WebsocketProvider') + Logger.debug(tag, 'creating HttpProvider...') + const provider = new Web3.providers.HttpProvider(url) + Logger.debug(tag, 'created HttpProvider') // In the future, we might decide to over-ride the error handler via the following code. - // wsProvider.on('error', () => { + // provider.on('error', () => { // Logger.showError('Error occurred') // }) - return wsProvider + return provider } let web3: Web3 @@ -67,20 +67,13 @@ export async function getWeb3() { if (web3 === undefined) { Logger.info(`Initializing web3, geth free mode: ${isGethFreeMode()}`) if (isGethFreeMode()) { - Logger.debug('contracts@getWeb3', `Default testnet is ${DEFAULT_TESTNET}`) - const statipNodeIps: string = await StaticNodeUtils.getStaticNodesAsync(DEFAULT_TESTNET) - Logger.debug('contracts@getWeb3', `Static node IPs are ${statipNodeIps}`) - const enodeWithIp: string = JSON.parse(statipNodeIps)[0] - // Extract IP from "enode://@:" - const staticNodeIp: string = enodeWithIp.split('@')[1].split(':')[0] - Logger.debug('contracts@getWeb3', `Static node IP is ${staticNodeIp}`) - if (staticNodeIp === undefined) { - throw new Error('Static node IP is undefined') - } - if (staticNodeIp === null) { - throw new Error('Static node IP is null') - } - const provider = getWebSocketProvider(`ws://${staticNodeIp}:8546`) + // Warning: This hostname is not yet enabled for all the networks. + // It is only enabled for "integration" and "alfajores" networks as of now. + // It is being enabled here for all the networks + // https://github.com/celo-org/celo-monorepo/pull/1034/ + const url = `https://${DEFAULT_TESTNET}-infura.celo-testnet.org/` + Logger.debug('contracts@getWeb3', `Connecting to url ${url}`) + const provider = getWebSocketProvider(url) web3 = new Web3(provider) } else { const provider = getIpcProvider(DEFAULT_TESTNET) diff --git a/packages/walletkit/src/contract-utils.ts b/packages/walletkit/src/contract-utils.ts index 25178e333ae..bf55e66a21c 100644 --- a/packages/walletkit/src/contract-utils.ts +++ b/packages/walletkit/src/contract-utils.ts @@ -11,8 +11,10 @@ import { GasPriceMinimum as GasPriceMinimumType } from '../types/GasPriceMinimum import { GoldToken } from '../types/GoldToken' import { StableToken } from '../types/StableToken' import { getGasPriceMinimumContract } from './contracts' -import { Logger } from './logger' +import { getGoldTokenAddress } from './erc20-utils' +import { Logger, LogLevel } from './logger' +Logger.setLogLevel(LogLevel.DEBUG) const gasInflateFactor = 1.3 export function selectContractByAddress(contracts: Contract[], address: string) { @@ -223,7 +225,7 @@ async function getGasPrice( } const gasPriceMinimum: GasPriceMinimumType = await getGasPriceMinimumContract(web3) const gasPrice: string = await gasPriceMinimum.methods.getGasPriceMinimum(gasCurrency).call() - console.info(`Gas price is ${gasPrice}`) + Logger.debug('contract-utils@getGasPrice',`Gas price is ${gasPrice}`) return String(parseInt(gasPrice, 10) * 10) } @@ -290,10 +292,14 @@ export async function sendTransactionAsync( // Ideally, we should fill these fields in CeloProvider but as of now, // we don't have access to web3 inside it, so, in the short-term // fill the fields here. - const gasCurrency = gasCurrencyContract._address + let gasCurrency = gasCurrencyContract._address const gasFeeRecipient = await web3.eth.getCoinbase() - const gasPrice = await getGasPrice(web3, gasCurrency) Logger.debug('contract-utils@sendTransactionAsync', `Gas fee recipient is ${gasFeeRecipient}`) + const gasPrice = await getGasPrice(web3, gasCurrency) + if (gasCurrency === undefined) { + gasCurrency = await getGoldTokenAddress(web3) + } + Logger.debug('contract-utils@sendTransactionAsync', `Gas currency: ${gasCurrency}`) let recievedTxHash: string | null = null let alreadyInformedResolversAboutConfirmation = false @@ -315,55 +321,97 @@ export async function sendTransactionAsync( } } - const nonce: number = await web3.eth.getTransactionCount(account) + // Use pending transaction count, so that, multiple transactions can be sent without + // waiting for the earlier ones to be confirmed. + const nonce: number = await web3.eth.getTransactionCount(account, 'pending') Logger.debug('contract-utils@sendTransactionAsync', `sendTransactionAsync@nonce is ${nonce}`) Logger.debug( 'contract-utils@sendTransactionAsync', `sendTransactionAsync@sending from ${account}` ) - tx.send({ + const celoTx = { from: account, nonce, // @ts-ignore - gasCurrency: gasCurrencyContract._address, + gasCurrency, gas: estimatedGas, // Hack to prevent web3 from adding the suggested gold gas price, allowing geth to add // the suggested price in the selected gasCurrency. gasPrice, gasFeeRecipient, - }) - .on('receipt', (r: TransactionReceipt) => { - logger(ReceiptReceived(r)) - if (resolvers.receipt) { - resolvers.receipt(r) - } - }) - .on('transactionHash', (txHash: string) => { - recievedTxHash = txHash - logger(TransactionHashReceived(txHash)) - - if (resolvers.transactionHash) { - resolvers.transactionHash(txHash) - } - }) - .on('confirmation', (confirmationNumber: number) => { - if (confirmationNumber > 1) { - console.debug(`Confirmation number is ${confirmationNumber} > 1, ignored...`) - // "confirmation" event is called for 24 blocks. - // if check to avoid polluting the logs and trying to remove the standby notification more than once - return - } - informAboutConfirmation() - }) - .on('error', (error: Error) => { - Logger.info( - 'contract-utils@sendTransactionAsync', - `Txn failed: txn ${util.inspect(error)} ` - ) - logger(Failed(error)) - rejectAll(error) - }) + } + try { + await tx.send(celoTx) + // TODO: disable this only in infura mode. + // .on('receipt', (r: TransactionReceipt) => { + // logger(ReceiptReceived(r)) + // if (resolvers.receipt) { + // resolvers.receipt(r) + // } + // }) + // .on('transactionHash', (txHash: string) => { + // recievedTxHash = txHash + // logger(TransactionHashReceived(txHash)) + + // if (resolvers.transactionHash) { + // resolvers.transactionHash(txHash) + // } + // }) + // .on('confirmation', (confirmationNumber: number) => { + // if (confirmationNumber > 1) { + // console.debug(`Confirmation number is ${confirmationNumber} > 1, ignored...`) + // // "confirmation" event is called for 24 blocks. + // // if check to avoid polluting the logs and trying to remove the standby notification more than once + // return + // } + // informAboutConfirmation() + // }) + // .on('error', (error: Error) => { + // Logger.info( + // 'contract-utils@sendTransactionAsync', + // `Txn failed: txn ${util.inspect(error)} ` + // ) + // logger(Failed(error)) + // rejectAll(error) + // }) + } catch (e) { + Logger.debug('contract-utils@sendTransactionAsync',`Ignoring error: ${util.inspect(e)}`) + Logger.debug('contract-utils@sendTransactionAsync',`error message: ${e.message}`) + // Ideally, I want to only ignore error whose messsage contains + // "Failed to subscribe to new newBlockHeaders" but seems like another wrapped + // error (listed below) gets thrown and there is no way to catch that. + // "Failed to subscribe to new newBlockHeaders" is thrown when the wallet kit is connected + // to a remote node via https. + // { [Error: [object Object]] + // line: 352771, + // column: 24, + // sourceURL: 'http://localhost:8081/index.delta?platform=android&dev=true&minify=false' } + // contract-utils@sendTransactionAsync: error: { [Error: Failed to subscribe to new newBlockHeaders to confi + // rm the transaction receipts. + // { + // "line": 352771, + // "column": 24, + // "sourceURL": "http://localhost:8081/index.delta?platform=android&dev=true&minify=false" + // }] + // line: 157373, + // column: 24, + // sourceURL: 'http://localhost:8081/index.delta?platform=android&dev=true&minify=false' } + if (e.message.indexOf('Failed to subscribe to new newBlockHeaders') >= 0) { + // Ignore this error + Logger.warn('contract-utils@sendTransactionAsync', `Expected error ignored: ${JSON.stringify(e)}`) + } else { + // TODO(ashishb): testing only + Logger.debug('contract-utils@sendTransactionAsync',`Unexpected error ignored: ${util.inspect(e)}`) + } + const signedTxn = await web3.eth.signTransaction(celoTx) + recievedTxHash = web3.utils.sha3(signedTxn.raw) + Logger.info('contract-utils@sendTransactionAsync', `Locally calculated recievedTxHash is ${recievedTxHash}`) + logger(TransactionHashReceived(recievedTxHash)) + if (resolvers.transactionHash) { + resolvers.transactionHash(recievedTxHash) + } + } // This code is required for infura-like setup. // When mobile client directly connects to the remote full node then