diff --git a/lib/wallets/crypto_currency/coins/solana.dart b/lib/wallets/crypto_currency/coins/solana.dart index 632e0f977..0307d6bde 100644 --- a/lib/wallets/crypto_currency/coins/solana.dart +++ b/lib/wallets/crypto_currency/coins/solana.dart @@ -1,9 +1,9 @@ import 'package:solana/solana.dart'; import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_currency.dart'; -import 'package:stackwallet/utilities/default_nodes.dart'; class Solana extends Bip39Currency { Solana(super.network) { @@ -20,7 +20,8 @@ class Solana extends Bip39Currency { switch (network) { case CryptoCurrencyNetwork.main: return NodeModel( - host: "https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one + host: + "https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one port: 443, name: DefaultNodes.defaultName, id: DefaultNodes.buildId(Coin.solana), @@ -40,9 +41,18 @@ class Solana extends Bip39Currency { @override bool validateAddress(String address) { - return isPointOnEd25519Curve(Ed25519HDPublicKey.fromBase58(address).toByteArray()); + return isPointOnEd25519Curve( + Ed25519HDPublicKey.fromBase58(address).toByteArray()); } @override String get genesisHash => throw UnimplementedError(); -} \ No newline at end of file + + @override + bool operator ==(Object other) { + return other is Solana && other.network == network; + } + + @override + int get hashCode => Object.hash(Solana, network); +} diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart index e7d93ed0f..d2f3582c9 100644 --- a/lib/wallets/wallet/impl/solana_wallet.dart +++ b/lib/wallets/wallet/impl/solana_wallet.dart @@ -28,24 +28,30 @@ import 'package:tuple/tuple.dart'; class SolanaWallet extends Bip39Wallet { SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network)); + static const String _addressDerivationPath = "m/44'/501'/0'/0'"; + NodeModel? _solNode; RpcClient? _rpcClient; // The Solana RpcClient. Future _getKeyPair() async { - return Ed25519HDKeyPair.fromMnemonic(await getMnemonic(), - account: 0, change: 0); + return Ed25519HDKeyPair.fromMnemonic( + await getMnemonic(), + account: 0, + change: 0, + ); } - Future
_getCurrentAddress() async { + Future
_generateAddress() async { final addressStruct = Address( - walletId: walletId, - value: (await _getKeyPair()).address, - publicKey: List.empty(), - derivationIndex: 0, - derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'", - type: cryptoCurrency.coin.primaryAddressType, - subType: AddressSubType.unknown); + walletId: walletId, + value: (await _getKeyPair()).address, + publicKey: List.empty(), + derivationIndex: 0, + derivationPath: DerivationPath()..value = _addressDerivationPath, + type: cryptoCurrency.coin.primaryAddressType, + subType: AddressSubType.receiving, + ); return addressStruct; } @@ -65,7 +71,10 @@ class SolanaWallet extends Bip39Wallet { recipientAccount: pubKey, lamports: transferAmount.raw.toInt(), ) - ]).compile(recentBlockhash: latestBlockhash!.value.blockhash, feePayer: (await _getKeyPair()).publicKey); + ]).compile( + recentBlockhash: latestBlockhash!.value.blockhash, + feePayer: pubKey, + ); return await _rpcClient?.getFeeForMessage( base64Encode(compiledMessage.toByteArray().toList()), @@ -79,18 +88,13 @@ class SolanaWallet extends Bip39Wallet { @override Future checkSaveInitialReceivingAddress() async { try { - final address = (await _getKeyPair()).address; + Address? address = await getCurrentReceivingAddress(); - await mainDB.updateOrPutAddresses([ - Address( - walletId: walletId, - value: address, - publicKey: List.empty(), - derivationIndex: 0, - derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'", - type: cryptoCurrency.coin.primaryAddressType, - subType: AddressSubType.unknown) - ]); + if (address == null) { + address = await _generateAddress(); + + await mainDB.updateOrPutAddresses([address]); + } } catch (e, s) { Logging.instance.log( "$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s", @@ -120,9 +124,10 @@ class SolanaWallet extends Bip39Wallet { "Failed to get fees, please check your node connection."); } + final address = await getCurrentReceivingAddress(); + // Rent exemption of Solana - final accInfo = - await _rpcClient?.getAccountInfo((await _getKeyPair()).address); + final accInfo = await _rpcClient?.getAccountInfo(address!.value); final int minimumRent = await _rpcClient?.getMinimumBalanceForRentExemption( accInfo!.value!.data.toString().length) ?? @@ -132,7 +137,9 @@ class SolanaWallet extends Bip39Wallet { txData.amount!.raw.toInt() - feeAmount)) { throw Exception( - "Insufficient remaining balance for rent exemption, minimum rent: ${minimumRent / pow(10, cryptoCurrency.fractionDigits)}"); + "Insufficient remaining balance for rent exemption, minimum rent: " + "${minimumRent / pow(10, cryptoCurrency.fractionDigits)}", + ); } return txData.copyWith( @@ -255,7 +262,7 @@ class SolanaWallet extends Bip39Wallet { @override Future recover({required bool isRescan}) async { await refreshMutex.protect(() async { - final addressStruct = await _getCurrentAddress(); + final addressStruct = await _generateAddress(); await mainDB.updateOrPutAddresses([addressStruct]); @@ -277,13 +284,13 @@ class SolanaWallet extends Bip39Wallet { @override Future updateBalance() async { try { + final address = await getCurrentReceivingAddress(); _checkClient(); - final balance = await _rpcClient?.getBalance(info.cachedReceivingAddress); + final balance = await _rpcClient?.getBalance(address!.value); // Rent exemption of Solana - final accInfo = - await _rpcClient?.getAccountInfo((await _getKeyPair()).address); + final accInfo = await _rpcClient?.getAccountInfo(address!.value); // TODO [prio=low]: handle null account info. final int minimumRent = await _rpcClient?.getMinimumBalanceForRentExemption( @@ -366,6 +373,8 @@ class SolanaWallet extends Bip39Wallet { final txsList = List>.empty(growable: true); + final myAddress = (await getCurrentReceivingAddress())!; + // TODO [prio=low]: Revisit null assertion below. for (final tx in transactionsList!) { @@ -380,13 +389,16 @@ class SolanaWallet extends Bip39Wallet { fractionDigits: cryptoCurrency.fractionDigits, ); - if ((senderAddress == (await _getKeyPair()).address) && (receiverAddress == "11111111111111111111111111111111")) { - // The account that is only 1's are System Program accounts which means there is no receiver except the sender, see: https://explorer.solana.com/address/11111111111111111111111111111111 + if ((senderAddress == myAddress.value) && + (receiverAddress == "11111111111111111111111111111111")) { + // The account that is only 1's are System Program accounts which + // means there is no receiver except the sender, + // see: https://explorer.solana.com/address/11111111111111111111111111111111 txType = isar.TransactionType.sentToSelf; receiverAddress = senderAddress; - } else if (senderAddress == (await _getKeyPair()).address) { + } else if (senderAddress == myAddress.value) { txType = isar.TransactionType.outgoing; - } else if (receiverAddress == (await _getKeyPair()).address) { + } else if (receiverAddress == myAddress.value) { txType = isar.TransactionType.incoming; } @@ -411,15 +423,16 @@ class SolanaWallet extends Bip39Wallet { ); final txAddress = Address( - walletId: walletId, - value: receiverAddress, - publicKey: List.empty(), - derivationIndex: 0, - derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'", - type: AddressType.solana, - subType: txType == isar.TransactionType.outgoing - ? AddressSubType.unknown - : AddressSubType.receiving); + walletId: walletId, + value: receiverAddress, + publicKey: List.empty(), + derivationIndex: 0, + derivationPath: DerivationPath()..value = _addressDerivationPath, + type: AddressType.solana, + subType: txType == isar.TransactionType.outgoing + ? AddressSubType.unknown + : AddressSubType.receiving, + ); txsList.add(Tuple2(transaction, txAddress)); }