diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index 834e130a3..cf73b13db 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -82,7 +82,7 @@ abstract class EVMChainClient { Future signTransaction({ required EthPrivateKey privateKey, required String toAddress, - required String amount, + required BigInt amount, required int gas, required EVMChainTransactionPriority priority, required CryptoCurrency currency, @@ -103,7 +103,7 @@ abstract class EVMChainClient { from: privateKey.address, to: EthereumAddress.fromHex(toAddress), maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), - amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), + amount: isEVMCompatibleChain ? EtherAmount.inWei(amount) : EtherAmount.zero(), data: data != null ? hexToBytes(data) : null, ); @@ -124,7 +124,7 @@ abstract class EVMChainClient { _sendTransaction = () async { await erc20.transfer( EthereumAddress.fromHex(toAddress), - BigInt.parse(amount), + amount, credentials: privateKey, transaction: transaction, ); @@ -133,7 +133,7 @@ abstract class EVMChainClient { return PendingEVMChainTransaction( signedTransaction: signedTransaction, - amount: amount, + amount: amount.toString(), fee: BigInt.from(gas) * (await price).getInWei, sendTransaction: _sendTransaction, exponent: exponent, diff --git a/cw_evm/lib/evm_chain_exceptions.dart b/cw_evm/lib/evm_chain_exceptions.dart index 1c09ecf6d..8aa371b19 100644 --- a/cw_evm/lib/evm_chain_exceptions.dart +++ b/cw_evm/lib/evm_chain_exceptions.dart @@ -9,3 +9,14 @@ class EVMChainTransactionCreationException implements Exception { @override String toString() => exceptionMessage; } + + +class EVMChainTransactionFeesException implements Exception { + final String exceptionMessage; + + EVMChainTransactionFeesException() + : exceptionMessage = 'Current balance is less than the estimated fees for this transaction.'; + + @override + String toString() => exceptionMessage; +} diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index bebff4f54..c90a3e809 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -234,7 +234,7 @@ abstract class EVMChainWalletBase final CryptoCurrency transactionCurrency = balance.keys.firstWhere((element) => element.title == _credentials.currency.title); - final _erc20Balance = balance[transactionCurrency]!; + final erc20Balance = balance[transactionCurrency]!; BigInt totalAmount = BigInt.zero; int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; num amountToEVMChainMultiplier = pow(10, exponent); @@ -249,7 +249,7 @@ abstract class EVMChainWalletBase outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); - if (_erc20Balance.balance < totalAmount) { + if (erc20Balance.balance < totalAmount) { throw EVMChainTransactionCreationException(transactionCurrency); } } else { @@ -258,18 +258,27 @@ abstract class EVMChainWalletBase // then no need to subtract the fees from the amount if send all final BigInt allAmount; if (transactionCurrency is Erc20Token) { - allAmount = _erc20Balance.balance; + allAmount = erc20Balance.balance; } else { - allAmount = _erc20Balance.balance - - BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); - } - final totalOriginalAmount = - EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0); - totalAmount = output.sendAll - ? allAmount - : BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); + final estimatedFee = BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); - if (_erc20Balance.balance < totalAmount) { + if (estimatedFee > erc20Balance.balance) { + throw EVMChainTransactionFeesException(); + } + + allAmount = erc20Balance.balance - estimatedFee; + } + + if (output.sendAll) { + totalAmount = allAmount; + } else { + final totalOriginalAmount = + EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0); + + totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); + } + + if (erc20Balance.balance < totalAmount) { throw EVMChainTransactionCreationException(transactionCurrency); } } @@ -279,7 +288,7 @@ abstract class EVMChainWalletBase toAddress: _credentials.outputs.first.isParsedAddress ? _credentials.outputs.first.extractedAddress! : _credentials.outputs.first.address, - amount: totalAmount.toString(), + amount: totalAmount, gas: _estimatedGas!, priority: _credentials.priority!, currency: transactionCurrency, diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index ea4a9161a..781fff5f7 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -96,16 +96,30 @@ class SolanaWalletClient { return SolanaBalance(totalBalance); } - Future getGasForMessage(String message) async { + Future getFeeForMessage(String message, Commitment commitment) async { try { - final gasPrice = await _client!.rpcClient.getFeeForMessage(message) ?? 0; - final fee = gasPrice / lamportsPerSol; + final feeForMessage = + await _client!.rpcClient.getFeeForMessage(message, commitment: commitment); + final fee = (feeForMessage ?? 0.0) / lamportsPerSol; return fee; } catch (_) { - return 0; + return 0.0; } } + Future getEstimatedFee(Ed25519HDKeyPair ownerKeypair) async { + const commitment = Commitment.confirmed; + + final message = + _getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol); + + final recentBlockhash = await _getRecentBlockhash(commitment); + + final estimatedFee = + _getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment); + return estimatedFee; + } + /// Load the Address's transactions into the account Future> fetchTransactions( Ed25519HDPublicKey publicKey, { @@ -257,24 +271,15 @@ class SolanaWalletClient { Future signSolanaTransaction({ required String tokenTitle, required int tokenDecimals, - String? tokenMint, required double inputAmount, required String destinationAddress, required Ed25519HDKeyPair ownerKeypair, + required bool isSendAll, + String? tokenMint, List references = const [], }) async { const commitment = Commitment.confirmed; - final latestBlockhash = - await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value; - - final recentBlockhash = RecentBlockhash( - blockhash: latestBlockhash.blockhash, - feeCalculator: const FeeCalculator( - lamportsPerSignature: 500, - ), - ); - if (tokenTitle == CryptoCurrency.sol.title) { final pendingNativeTokenTransaction = await _signNativeTokenTransaction( tokenTitle: tokenTitle, @@ -282,8 +287,8 @@ class SolanaWalletClient { inputAmount: inputAmount, destinationAddress: destinationAddress, ownerKeypair: ownerKeypair, - recentBlockhash: recentBlockhash, commitment: commitment, + isSendAll: isSendAll, ); return pendingNativeTokenTransaction; } else { @@ -294,25 +299,29 @@ class SolanaWalletClient { inputAmount: inputAmount, destinationAddress: destinationAddress, ownerKeypair: ownerKeypair, - recentBlockhash: recentBlockhash, commitment: commitment, ); return pendingSPLTokenTransaction; } } - Future _signNativeTokenTransaction({ - required String tokenTitle, - required int tokenDecimals, - required double inputAmount, - required String destinationAddress, - required Ed25519HDKeyPair ownerKeypair, - required RecentBlockhash recentBlockhash, - required Commitment commitment, - }) async { - // Convert SOL to lamport - int lamports = (inputAmount * lamportsPerSol).toInt(); + Future _getRecentBlockhash(Commitment commitment) async { + final latestBlockhash = + await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value; + final recentBlockhash = RecentBlockhash( + blockhash: latestBlockhash.blockhash, + feeCalculator: const FeeCalculator(lamportsPerSignature: 500), + ); + + return recentBlockhash; + } + + Message _getMessageForNativeTransaction( + Ed25519HDKeyPair ownerKeypair, + String destinationAddress, + int lamports, + ) { final instructions = [ SystemInstruction.transfer( fundingAccount: ownerKeypair.publicKey, @@ -322,21 +331,75 @@ class SolanaWalletClient { ]; final message = Message(instructions: instructions); + return message; + } + + Future _getFeeFromCompiledMessage( + Message message, + Ed25519HDPublicKey feePayer, + RecentBlockhash recentBlockhash, + Commitment commitment, + ) async { + final compile = message.compile( + recentBlockhash: recentBlockhash.blockhash, + feePayer: feePayer, + ); + + final base64Message = base64Encode(compile.toByteArray().toList()); + + final fee = await getFeeForMessage(base64Message, commitment); + + return fee; + } + + Future _signNativeTokenTransaction({ + required String tokenTitle, + required int tokenDecimals, + required double inputAmount, + required String destinationAddress, + required Ed25519HDKeyPair ownerKeypair, + required Commitment commitment, + required bool isSendAll, + }) async { + // Convert SOL to lamport + int lamports = (inputAmount * lamportsPerSol).toInt(); + + Message message = _getMessageForNativeTransaction(ownerKeypair, destinationAddress, lamports); + final signers = [ownerKeypair]; - final signedTx = await _signTransactionInternal( - message: message, - signers: signers, - commitment: commitment, - recentBlockhash: recentBlockhash, - ); + RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment); final fee = await _getFeeFromCompiledMessage( message, - recentBlockhash, signers.first.publicKey, + recentBlockhash, + commitment, ); + SignedTx signedTx; + if (isSendAll) { + final feeInLamports = (fee * lamportsPerSol).toInt(); + final updatedLamports = lamports - feeInLamports; + + final updatedMessage = + _getMessageForNativeTransaction(ownerKeypair, destinationAddress, updatedLamports); + + signedTx = await _signTransactionInternal( + message: updatedMessage, + signers: signers, + commitment: commitment, + recentBlockhash: recentBlockhash, + ); + } else { + signedTx = await _signTransactionInternal( + message: message, + signers: signers, + commitment: commitment, + recentBlockhash: recentBlockhash, + ); + } + sendTx() async => await sendTransaction( signedTransaction: signedTx, commitment: commitment, @@ -360,7 +423,6 @@ class SolanaWalletClient { required double inputAmount, required String destinationAddress, required Ed25519HDKeyPair ownerKeypair, - required RecentBlockhash recentBlockhash, required Commitment commitment, }) async { final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress); @@ -408,8 +470,18 @@ class SolanaWalletClient { ); final message = Message(instructions: [instruction]); + final signers = [ownerKeypair]; + RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment); + + final fee = await _getFeeFromCompiledMessage( + message, + signers.first.publicKey, + recentBlockhash, + commitment, + ); + final signedTx = await _signTransactionInternal( message: message, signers: signers, @@ -417,12 +489,6 @@ class SolanaWalletClient { recentBlockhash: recentBlockhash, ); - final fee = await _getFeeFromCompiledMessage( - message, - recentBlockhash, - signers.first.publicKey, - ); - sendTx() async => await sendTransaction( signedTransaction: signedTx, commitment: commitment, @@ -438,19 +504,6 @@ class SolanaWalletClient { return pendingTransaction; } - Future _getFeeFromCompiledMessage( - Message message, RecentBlockhash recentBlockhash, Ed25519HDPublicKey feePayer) async { - final compile = message.compile( - recentBlockhash: recentBlockhash.blockhash, - feePayer: feePayer, - ); - - final base64Message = base64Encode(compile.toByteArray().toList()); - - final fee = await getGasForMessage(base64Message); - return fee; - } - Future _signTransactionInternal({ required Message message, required List signers, @@ -466,13 +519,18 @@ class SolanaWalletClient { required SignedTx signedTransaction, required Commitment commitment, }) async { - final signature = await _client!.rpcClient.sendTransaction( - signedTransaction.encode(), - preflightCommitment: commitment, - ); + try { + final signature = await _client!.rpcClient.sendTransaction( + signedTransaction.encode(), + preflightCommitment: commitment, + ); - _client!.waitForSignatureStatus(signature, status: commitment); + _client!.waitForSignatureStatus(signature, status: commitment); - return signature; + return signature; + } catch (e) { + print('Error while sending transaction: ${e.toString()}'); + throw Exception(e); + } } } diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index de4d70674..f69a597ae 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -75,6 +75,9 @@ abstract class SolanaWalletBase late SolanaWalletClient _client; + @observable + double? estimatedFee; + Timer? _transactionsUpdateTimer; late final Box splTokensBox; @@ -171,6 +174,14 @@ abstract class SolanaWalletBase } } + Future _getEstimatedFees() async { + try { + estimatedFee = await _client.getEstimatedFee(_walletKeyPair!); + } catch (e) { + estimatedFee = 0.0; + } + } + @override Future createTransaction(Object credentials) async { final solCredentials = credentials as SolanaTransactionCredentials; @@ -188,6 +199,8 @@ abstract class SolanaWalletBase double totalAmount = 0.0; + bool isSendAll = false; + if (hasMultiDestination) { if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { throw SolanaTransactionWrongBalanceException(transactionCurrency); @@ -204,9 +217,15 @@ abstract class SolanaWalletBase } else { final output = outputs.first; - final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); + isSendAll = output.sendAll; - totalAmount = output.sendAll ? walletBalanceForCurrency : totalOriginalAmount; + if (isSendAll) { + totalAmount = walletBalanceForCurrency; + } else { + final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); + + totalAmount = totalOriginalAmount; + } if (walletBalanceForCurrency < totalAmount) { throw SolanaTransactionWrongBalanceException(transactionCurrency); @@ -228,6 +247,7 @@ abstract class SolanaWalletBase destinationAddress: solCredentials.outputs.first.isParsedAddress ? solCredentials.outputs.first.extractedAddress! : solCredentials.outputs.first.address, + isSendAll: isSendAll, ); return pendingSolanaTransaction; @@ -269,7 +289,10 @@ abstract class SolanaWalletBase Future _updateSPLTokenTransactions() async { List splTokenTransactions = []; - for (var token in balance.keys) { + // Make a copy of keys to avoid concurrent modification + var tokenKeys = List.from(balance.keys); + + for (var token in tokenKeys) { if (token is SPLToken) { final tokenTxs = await _client.getSPLTokenTransfers( token.mintAddress, @@ -326,6 +349,7 @@ abstract class SolanaWalletBase _updateBalance(), _updateNativeSOLTransactions(), _updateSPLTokenTransactions(), + _getEstimatedFees(), ]); syncStatus = SyncedSyncStatus(); @@ -433,18 +457,22 @@ abstract class SolanaWalletBase final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress); // Fetch token's metadata account - final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey); + try { + final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey); - if (token == null) { + if (token == null) { + return null; + } + + return SPLToken.fromMetadata( + name: token.name, + mint: token.mint, + symbol: token.symbol, + mintAddress: mintAddress, + ); + } catch (e) { return null; } - - return SPLToken.fromMetadata( - name: token.name, - mint: token.mint, - symbol: token.symbol, - mintAddress: mintAddress, - ); } @override @@ -475,9 +503,9 @@ abstract class SolanaWalletBase } _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) { - _updateSPLTokenTransactions(); - _updateNativeSOLTransactions(); _updateBalance(); + _updateNativeSOLTransactions(); + _updateSPLTokenTransactions(); }); } diff --git a/cw_solana/lib/solana_wallet_service.dart b/cw_solana/lib/solana_wallet_service.dart index b3ff22e7e..83370ff73 100644 --- a/cw_solana/lib/solana_wallet_service.dart +++ b/cw_solana/lib/solana_wallet_service.dart @@ -32,6 +32,7 @@ class SolanaWalletService extends WalletService openWallet(String name, String password) async { final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); - final wallet = await SolanaWalletBase.open( - name: name, - password: password, - walletInfo: walletInfo, - ); - await wallet.init(); - await wallet.save(); + try { + final wallet = await SolanaWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); - return wallet; + await wallet.init(); + await wallet.save(); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + + final wallet = await SolanaWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); + + await wallet.init(); + await wallet.save(); + return wallet; + } } @override @@ -110,6 +126,7 @@ class SolanaWalletService extends WalletService addSPLToken(WalletBase wallet, CryptoCurrency token) async => - await (wallet as SolanaWallet).addSPLToken(token as SPLToken); + Future addSPLToken( + WalletBase wallet, + CryptoCurrency token, + String contractAddress, + ) async { + final splToken = SPLToken( + name: token.name, + symbol: token.title, + mintAddress: contractAddress, + decimal: token.decimals, + mint: token.name.toUpperCase(), + enabled: token.enabled, + ); + + await (wallet as SolanaWallet).addSPLToken(splToken); + } @override Future deleteSPLToken(WalletBase wallet, CryptoCurrency token) async => @@ -115,4 +129,9 @@ class CWSolana extends Solana { return null; } + + @override + double? getEstimateFees(WalletBase wallet) { + return (wallet as SolanaWallet).estimatedFee; + } } diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index 720a8cc14..1a1db8658 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -195,12 +195,14 @@ class _EditTokenPageBodyState extends State { onPressed: () async { if (_formKey.currentState!.validate() && (!_showDisclaimer || _disclaimerChecked)) { - await widget.homeSettingsViewModel.addToken(Erc20Token( - name: _tokenNameController.text, - symbol: _tokenSymbolController.text, + await widget.homeSettingsViewModel.addToken( + token: CryptoCurrency( + name: _tokenNameController.text, + title: _tokenSymbolController.text.toUpperCase(), + decimals: int.parse(_tokenDecimalController.text), + ), contractAddress: _contractAddressController.text, - decimal: int.parse(_tokenDecimalController.text), - )); + ); if (context.mounted) { Navigator.pop(context); } diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 3f5714be9..d36997814 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -323,8 +323,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin GestureDetector( - onTap: () => _setTransactionPriority(context), + onTap: sendViewModel.hasFeesPriority + ? () => _setTransactionPriority(context) + : () {}, child: Container( padding: EdgeInsets.only(top: 24), child: Row( diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 6d31a5af8..4b9811c37 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -44,17 +44,37 @@ abstract class HomeSettingsViewModelBase with Store { @action void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value; - Future addToken(CryptoCurrency token) async { + Future addToken({ + required String contractAddress, + required CryptoCurrency token, + }) async { if (_balanceViewModel.wallet.type == WalletType.ethereum) { - await ethereum!.addErc20Token(_balanceViewModel.wallet, token); + final erc20token = Erc20Token( + name: token.name, + symbol: token.title, + decimal: token.decimals, + contractAddress: contractAddress, + ); + + await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token); } if (_balanceViewModel.wallet.type == WalletType.polygon) { - await polygon!.addErc20Token(_balanceViewModel.wallet, token); + final polygonToken = Erc20Token( + name: token.name, + symbol: token.title, + decimal: token.decimals, + contractAddress: contractAddress, + ); + await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken); } if (_balanceViewModel.wallet.type == WalletType.solana) { - await solana!.addSPLToken(_balanceViewModel.wallet, token); + await solana!.addSPLToken( + _balanceViewModel.wallet, + token, + contractAddress, + ); } _updateTokensList(); @@ -117,7 +137,8 @@ abstract class HomeSettingsViewModelBase with Store { } if (_balanceViewModel.wallet.type == WalletType.solana) { - solana!.addSPLToken(_balanceViewModel.wallet, token); + final address = solana!.getTokenAddress(token); + solana!.addSPLToken(_balanceViewModel.wallet, token, address); } _refreshTokensList(); diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index c881284b3..62db9cbcd 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; @@ -116,6 +117,10 @@ abstract class OutputBase with Store { @computed double get estimatedFee { try { + if (_wallet.type == WalletType.solana) { + return solana!.getEstimateFees(_wallet) ?? 0.0; + } + final fee = _wallet.calculateEstimatedFee( _settingsStore.priority[_wallet.type]!, formattedCryptoAmount); diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 28f964a9c..a631cde02 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -106,8 +106,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get isBatchSending => outputs.length > 1; - bool get shouldDisplaySendALL => walletType != WalletType.solana; - @computed String get pendingTransactionFiatAmount { if (pendingTransaction == null) { @@ -208,6 +206,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; + @computed + bool get hasFeesPriority => + wallet.type != WalletType.nano && + wallet.type != WalletType.banano && + wallet.type != WalletType.solana; @observable CryptoCurrency selectedCryptoCurrency; diff --git a/tool/configure.dart b/tool/configure.dart index e24ab2062..30123c45e 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -948,7 +948,11 @@ abstract class Solana { required CryptoCurrency currency, }); List getSPLTokenCurrencies(WalletBase wallet); - Future addSPLToken(WalletBase wallet, CryptoCurrency token); + Future addSPLToken( + WalletBase wallet, + CryptoCurrency token, + String contractAddress, + ); Future deleteSPLToken(WalletBase wallet, CryptoCurrency token); Future getSPLToken(WalletBase wallet, String contractAddress); @@ -956,6 +960,7 @@ abstract class Solana { double getTransactionAmountRaw(TransactionInfo transactionInfo); String getTokenAddress(CryptoCurrency asset); List? getValidationLength(CryptoCurrency type); + double? getEstimateFees(WalletBase wallet); } """;