From 6704d8288967886d3cf32f52b6517e8614ddace6 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 31 Mar 2023 17:17:15 -0600 Subject: [PATCH] eth + token send fixes --- .../coins/ethereum/ethereum_wallet.dart | 156 ++++++++++++++---- .../ethereum/ethereum_token_service.dart | 100 +++++++++-- 2 files changed, 212 insertions(+), 44 deletions(-) diff --git a/lib/services/coins/ethereum/ethereum_wallet.dart b/lib/services/coins/ethereum/ethereum_wallet.dart index 0afd30bf8..5c93e6a13 100644 --- a/lib/services/coins/ethereum/ethereum_wallet.dart +++ b/lib/services/coins/ethereum/ethereum_wallet.dart @@ -32,6 +32,7 @@ import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/eth_commons.dart'; +import 'package:stackwallet/utilities/extensions/extensions.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -183,7 +184,8 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { Future> get transactions => db .getTransactions(walletId) .filter() - .otherDataEqualTo(null) + .otherDataEqualTo( + null) // eth txns with other data where other data is the token contract address .sortByTimestampDesc() .findAll(); @@ -440,22 +442,70 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { final feeEstimate = await estimateFeeFor(satoshiAmount, fee); - bool isSendAll = false; - final availableBalance = balance.spendable; - if (satoshiAmount == availableBalance) { - isSendAll = true; - } + // bool isSendAll = false; + // final availableBalance = balance.spendable; + // if (satoshiAmount == availableBalance) { + // isSendAll = true; + // } + // + // if (isSendAll) { + // //Subtract fee amount from send amount + // satoshiAmount -= feeEstimate; + // } - if (isSendAll) { - //Subtract fee amount from send amount - satoshiAmount -= feeEstimate; - } + final decimalAmount = Format.satoshisToAmount(satoshiAmount, coin: coin); + final bigIntAmount = amountToBigInt( + decimalAmount.toDouble(), + Constants.decimalPlacesForCoin(coin), + ); + + final client = getEthClient(); + + final myAddress = await currentReceivingAddress; + final myWeb3Address = web3.EthereumAddress.fromHex(myAddress); + + final est = await client.estimateGas( + sender: myWeb3Address, + to: web3.EthereumAddress.fromHex(address), + gasPrice: web3.EtherAmount.fromUnitAndValue( + web3.EtherUnit.wei, + fee, + ), + amountOfGas: BigInt.from(_gasLimit), + value: web3.EtherAmount.inWei(bigIntAmount), + ); + + final nonce = args?["nonce"] as int? ?? + await client.getTransactionCount(myWeb3Address, + atBlock: const web3.BlockNum.pending()); + + final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); + print("=============================================================="); + print("ETH client.estimateGas: $est"); + print("ETH estimateFeeFor : $feeEstimate"); + print("ETH nonce custom response: $nResponse"); + print("ETH actual nonce : $nonce"); + print("=============================================================="); + + final tx = web3.Transaction( + to: web3.EthereumAddress.fromHex(address), + gasPrice: web3.EtherAmount.fromUnitAndValue( + web3.EtherUnit.wei, + fee, + ), + maxGas: _gasLimit, + value: web3.EtherAmount.inWei(bigIntAmount), + nonce: nonce, + ); Map txData = { "fee": feeEstimate, "feeInWei": fee, "address": address, "recipientAmt": satoshiAmount, + "ethTx": tx, + "chainId": (await client.getChainId()).toInt(), + "nonce": tx.nonce, }; return txData; @@ -464,22 +514,12 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { @override Future confirmSend({required Map txData}) async { web3.Web3Client client = getEthClient(); - final chainId = await client.getChainId(); - final amount = txData['recipientAmt'] as int; - final decimalAmount = Format.satoshisToAmount(amount, coin: coin); - final bigIntAmount = amountToBigInt( - decimalAmount.toDouble(), - Constants.decimalPlacesForCoin(coin), - ); - final tx = web3.Transaction( - to: web3.EthereumAddress.fromHex(txData['address'] as String), - gasPrice: web3.EtherAmount.fromUnitAndValue( - web3.EtherUnit.wei, txData['feeInWei']), - maxGas: _gasLimit, - value: web3.EtherAmount.inWei(bigIntAmount)); - final txid = await client.sendTransaction(_credentials, tx, - chainId: chainId.toInt()); + final txid = await client.sendTransaction( + _credentials, + txData["ethTx"] as web3.Transaction, + chainId: txData["chainId"] as int, + ); return txid; } @@ -558,7 +598,6 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { .findAll(); Future refreshIfThereIsNewData() async { - web3.Web3Client client = getEthClient(); if (longMutex) return false; if (_hasCalledExit) return false; final currentChainHeight = await chainHeight; @@ -574,14 +613,16 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { } for (String txid in txnsToCheck) { - final txn = await client.getTransactionByHash(txid); - final int txBlockNumber = txn.blockNumber.blockNum; + final response = await EthereumAPI.getEthTransactionByHash(txid); + final txBlockNumber = response.value?.blockNumber; - final int txConfirmations = currentChainHeight - txBlockNumber; - bool isUnconfirmed = txConfirmations < MINIMUM_CONFIRMATIONS; - if (!isUnconfirmed) { - needsRefresh = true; - break; + if (txBlockNumber != null) { + final int txConfirmations = currentChainHeight - txBlockNumber; + bool isUnconfirmed = txConfirmations < MINIMUM_CONFIRMATIONS; + if (!isUnconfirmed) { + needsRefresh = true; + break; + } } } if (!needsRefresh) { @@ -857,7 +898,54 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { @override Future updateSentCachedTxData(Map txData) async { - //Only used for Electrumx coins + final txid = txData["txid"] as String; + final addressString = txData["address"] as String; + final response = await EthereumAPI.getEthTransactionByHash(txid); + + final transaction = Transaction( + walletId: walletId, + txid: txid, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: TransactionType.outgoing, + subType: TransactionSubType.none, + amount: txData["recipientAmt"] as int, + amountString: Amount( + rawValue: BigInt.from(txData["recipientAmt"] as int), + fractionDigits: coin.decimals, + ).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: (txData["nonce"] as int?) ?? + response.value?.nonce.toBigIntFromHex.toInt(), + inputs: [], + outputs: [], + ); + + Address? address = await db.getAddress( + walletId, + addressString, + ); + + address ??= Address( + walletId: walletId, + value: addressString, + publicKey: [], + derivationIndex: -1, + derivationPath: null, + type: AddressType.ethereum, + subType: AddressSubType.nonWallet, + ); + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override diff --git a/lib/services/ethereum/ethereum_token_service.dart b/lib/services/ethereum/ethereum_token_service.dart index 1b609d69c..09fe51e21 100644 --- a/lib/services/ethereum/ethereum_token_service.dart +++ b/lib/services/ethereum/ethereum_token_service.dart @@ -95,8 +95,11 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { final client = await getEthClient(); + final myAddress = await currentReceivingAddress; + final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress); + final est = await client.estimateGas( - sender: web3dart.EthereumAddress.fromHex(await currentReceivingAddress), + sender: myWeb3Address, to: web3dart.EthereumAddress.fromHex(address), data: _sendFunction.encodeCall( [web3dart.EthereumAddress.fromHex(address), bigIntAmount]), @@ -108,9 +111,16 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { value: web3dart.EtherAmount.inWei(BigInt.one), ); + final nonce = args?["nonce"] as int? ?? + await client.getTransactionCount(myWeb3Address, + atBlock: const web3dart.BlockNum.pending()); + + final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); print("=============================================================="); - print("client.estimateGas: $est"); - print("estimateFeeFor : $feeEstimate"); + print("TOKEN client.estimateGas: $est"); + print("TOKEN estimateFeeFor : $feeEstimate"); + print("TOKEN nonce custom response: $nResponse"); + print("TOKEN actual nonce : $nonce"); print("=============================================================="); final tx = web3dart.Transaction.callContract( @@ -122,7 +132,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { web3dart.EtherUnit.wei, fee, ), - nonce: args?["nonce"] as int?, + nonce: nonce, ); Map txData = { @@ -131,18 +141,88 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { "address": address, "recipientAmt": satoshiAmount, "ethTx": tx, + "chainId": (await client.getChainId()).toInt(), + "nonce": tx.nonce, }; return txData; } Future confirmSend({required Map txData}) async { - final txid = await _client.sendTransaction( - _credentials, - txData["ethTx"] as web3dart.Transaction, + try { + final txid = await _client.sendTransaction( + _credentials, + txData["ethTx"] as web3dart.Transaction, + chainId: txData["chainId"] as int, + ); + + try { + txData["txid"] = txid; + await updateSentCachedTxData(txData); + } catch (e, s) { + // do not rethrow as that would get handled as a send failure further up + // also this is not critical code and transaction should show up on \ + // refresh regardless + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + + notifyListeners(); + return txid; + } catch (e) { + // rethrow to pass error in alert + rethrow; + } + } + + Future updateSentCachedTxData(Map txData) async { + final txid = txData["txid"] as String; + final addressString = txData["address"] as String; + final response = await EthereumAPI.getEthTransactionByHash(txid); + + final transaction = Transaction( + walletId: ethWallet.walletId, + txid: txid, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: TransactionType.outgoing, + subType: TransactionSubType.ethToken, + amount: txData["recipientAmt"] as int, + amountString: Amount( + rawValue: BigInt.from(txData["recipientAmt"] as int), + fractionDigits: tokenContract.decimals, + ).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: tokenContract.address, + slateId: null, + nonce: (txData["nonce"] as int?) ?? + response.value?.nonce.toBigIntFromHex.toInt(), + inputs: [], + outputs: [], ); - return txid; + Address? address = await ethWallet.db.getAddress( + ethWallet.walletId, + addressString, + ); + + address ??= Address( + walletId: ethWallet.walletId, + value: addressString, + publicKey: [], + derivationIndex: -1, + derivationPath: null, + type: AddressType.ethereum, + subType: AddressSubType.nonWallet, + ); + + await ethWallet.db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + ethWallet.walletId, + ); } Future get currentReceivingAddress async { @@ -499,14 +579,14 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { return isValidEthereumAddress(address); } - Future getCurrentNode() async { + NodeModel getCurrentNode() { return NodeService(secureStorageInterface: _secureStore) .getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); } Future getEthClient() async { - final node = await getCurrentNode(); + final node = getCurrentNode(); return web3dart.Web3Client(node.host, Client()); } }