diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index ff18ad043..66272c6af 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -55,7 +55,7 @@ jobs: /opt/android/cake_wallet/cw_monero/android/.cxx /opt/android/cake_wallet/cw_monero/ios/External /opt/android/cake_wallet/cw_shared_external/ios/External - key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh') }} + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} name: Generate Externals @@ -128,6 +128,7 @@ jobs: echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart + echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - name: Rename app run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties diff --git a/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart b/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart index d4397dead..fac7e93c4 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart @@ -1,4 +1,4 @@ class BitcoinTransactionNoInputsException implements Exception { @override - String toString() => 'Not enough inputs available'; + String toString() => 'Not enough inputs available. Please select more under Coin Control'; } diff --git a/cw_core/lib/monero_balance.dart b/cw_core/lib/monero_balance.dart index 7d569ef2f..bf30110a3 100644 --- a/cw_core/lib/monero_balance.dart +++ b/cw_core/lib/monero_balance.dart @@ -2,24 +2,31 @@ import 'package:cw_core/balance.dart'; import 'package:cw_core/monero_amount_format.dart'; class MoneroBalance extends Balance { - MoneroBalance({required this.fullBalance, required this.unlockedBalance}) + MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0}) : formattedFullBalance = moneroAmountToString(amount: fullBalance), - formattedUnlockedBalance = - moneroAmountToString(amount: unlockedBalance), + formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance), + frozenFormatted = moneroAmountToString(amount: frozenBalance), super(unlockedBalance, fullBalance); MoneroBalance.fromString( {required this.formattedFullBalance, - required this.formattedUnlockedBalance}) + required this.formattedUnlockedBalance, + this.frozenFormatted = '0.0'}) : fullBalance = moneroParseAmount(amount: formattedFullBalance), unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance), + frozenBalance = moneroParseAmount(amount: frozenFormatted), super(moneroParseAmount(amount: formattedUnlockedBalance), moneroParseAmount(amount: formattedFullBalance)); final int fullBalance; final int unlockedBalance; + final int frozenBalance; final String formattedFullBalance; final String formattedUnlockedBalance; + final String frozenFormatted; + + @override + String get formattedFrozenBalance => frozenFormatted == '0.0' ? '' : frozenFormatted; @override String get formattedAvailableBalance => formattedUnlockedBalance; diff --git a/cw_core/lib/unspent_coins_info.dart b/cw_core/lib/unspent_coins_info.dart index 75c13f2cd..6fd682c06 100644 --- a/cw_core/lib/unspent_coins_info.dart +++ b/cw_core/lib/unspent_coins_info.dart @@ -12,7 +12,9 @@ class UnspentCoinsInfo extends HiveObject { required this.noteRaw, required this.address, required this.vout, - required this.value}); + required this.value, + this.keyImage = null + }); static const typeId = 9; static const boxName = 'Unspent'; @@ -42,6 +44,9 @@ class UnspentCoinsInfo extends HiveObject { @HiveField(7, defaultValue: 0) int vout; + @HiveField(8, defaultValue: null) + String? keyImage; + String get note => noteRaw ?? ''; set note(String value) => noteRaw = value; diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 18c456cae..3f6b57edc 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -42,7 +42,9 @@ abstract class WalletBase< set syncStatus(SyncStatus status); - String get seed; + String? get seed; + + String? get privateKey => null; Object get keys; diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index fb3be4d44..d8c236657 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -44,13 +44,15 @@ abstract class EthereumWalletBase with Store { EthereumWalletBase({ required WalletInfo walletInfo, - required String mnemonic, + String? mnemonic, + String? privateKey, required String password, required EncryptionFileUtils encryptionFileUtils, ERC20Balance? initialBalance, }) : syncStatus = NotConnectedSyncStatus(), _password = password, _mnemonic = mnemonic, + _hexPrivateKey = privateKey, _isTransactionUpdating = false, _encryptionFileUtils = encryptionFileUtils, _client = EthereumClient(), @@ -72,14 +74,15 @@ abstract class EthereumWalletBase _sharedPrefs.complete(SharedPreferences.getInstance()); } - final String _mnemonic; + final String? _mnemonic; + final String? _hexPrivateKey; final String _password; final EncryptionFileUtils _encryptionFileUtils; late final Box erc20TokensBox; - late final EthPrivateKey _privateKey; + late final EthPrivateKey _ethPrivateKey; late EthereumClient _client; @@ -107,8 +110,12 @@ abstract class EthereumWalletBase erc20TokensBox = await Hive.openBox(Erc20Token.boxName); await walletAddresses.init(); await transactionHistory.init(); - _privateKey = await getPrivateKey(_mnemonic, _password); - walletAddresses.address = _privateKey.address.toString(); + _ethPrivateKey = await getPrivateKey( + mnemonic: _mnemonic, + privateKey: _hexPrivateKey, + password: _password, + ); + walletAddresses.address = _ethPrivateKey.address.toString(); await save(); } @@ -116,8 +123,7 @@ abstract class EthereumWalletBase int calculateEstimatedFee(TransactionPriority priority, int? amount) { try { if (priority is EthereumTransactionPriority) { - final priorityFee = - EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt(); + final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); } @@ -150,7 +156,7 @@ abstract class EthereumWalletBase throw Exception("Ethereum Node connection failed"); } - _client.setListeners(_privateKey.address, _onNewTransaction); + _client.setListeners(_ethPrivateKey.address, _onNewTransaction); _setTransactionUpdateTimer(); @@ -210,7 +216,7 @@ abstract class EthereumWalletBase } final pendingEthereumTransaction = await _client.signTransaction( - privateKey: _privateKey, + privateKey: _ethPrivateKey, toAddress: _credentials.outputs.first.isParsedAddress ? _credentials.outputs.first.extractedAddress! : _credentials.outputs.first.address, @@ -248,7 +254,7 @@ abstract class EthereumWalletBase @override Future> fetchTransactions() async { - final address = _privateKey.address.hex; + final address = _ethPrivateKey.address.hex; final transactions = await _client.fetchTransactions(address); final List>> erc20TokensTransactions = []; @@ -308,7 +314,10 @@ abstract class EthereumWalletBase } @override - String get seed => _mnemonic; + String? get seed => _mnemonic; + + @override + String get privateKey => HEX.encode(_ethPrivateKey.privateKey); @action @override @@ -335,6 +344,7 @@ abstract class EthereumWalletBase String toJSON() => json.encode({ 'mnemonic': _mnemonic, + 'private_key': privateKey, 'balance': balance[currency]!.toJSON(), }); @@ -347,13 +357,15 @@ abstract class EthereumWalletBase final path = await pathForWallet(name: name, type: walletInfo.type); final jsonSource = await encryptionFileUtils.read(path: path, password: password); final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String; + final mnemonic = data['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero); return EthereumWallet( walletInfo: walletInfo, password: password, mnemonic: mnemonic, + privateKey: privateKey, initialBalance: balance, encryptionFileUtils: encryptionFileUtils, ); @@ -367,7 +379,7 @@ abstract class EthereumWalletBase } Future _fetchEthBalance() async { - final balance = await _client.getBalance(_privateKey.address); + final balance = await _client.getBalance(_ethPrivateKey.address); return ERC20Balance(balance.getInWei); } @@ -376,7 +388,7 @@ abstract class EthereumWalletBase try { if (token.enabled) { balance[token] = await _client.fetchERC20Balances( - _privateKey.address, + _ethPrivateKey.address, token.contractAddress, ); } else { @@ -386,8 +398,15 @@ abstract class EthereumWalletBase } } - Future getPrivateKey(String mnemonic, String password) async { - final seed = bip39.mnemonicToSeed(mnemonic); + Future getPrivateKey( + {String? mnemonic, String? privateKey, required String password}) async { + assert(mnemonic != null || privateKey != null); + + if (privateKey != null) { + return EthPrivateKey.fromHex(privateKey); + } + + final seed = bip39.mnemonicToSeed(mnemonic!); final root = bip32.BIP32.fromSeed(seed); @@ -423,7 +442,7 @@ abstract class EthereumWalletBase if (_token.enabled) { balance[_token] = await _client.fetchERC20Balances( - _privateKey.address, + _ethPrivateKey.address, _token.contractAddress, ); } else { diff --git a/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart b/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart index c1fc43fa3..3d1efa54a 100644 --- a/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart +++ b/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart @@ -8,16 +8,22 @@ class EthereumNewWalletCredentials extends WalletCredentials { class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials { EthereumRestoreWalletFromSeedCredentials( - {required String name, required String password, required this.mnemonic, WalletInfo? walletInfo}) + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) : super(name: name, password: password, walletInfo: walletInfo); final String mnemonic; } -class EthereumRestoreWalletFromWIFCredentials extends WalletCredentials { - EthereumRestoreWalletFromWIFCredentials( - {required String name, required String password, required this.wif, WalletInfo? walletInfo}) +class EthereumRestoreWalletFromPrivateKey extends WalletCredentials { + EthereumRestoreWalletFromPrivateKey( + {required String name, + required String password, + required this.privateKey, + WalletInfo? walletInfo}) : super(name: name, password: password, walletInfo: walletInfo); - final String wif; + final String privateKey; } diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index 9ff44276b..66066b996 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -14,7 +14,7 @@ import 'package:bip39/bip39.dart' as bip39; import 'package:collection/collection.dart'; class EthereumWalletService extends WalletService { + EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromPrivateKey> { EthereumWalletService(this.walletInfoSource, this.isDirect); final Box walletInfoSource; @@ -70,8 +70,18 @@ class EthereumWalletService extends WalletService restoreFromKeys(credentials) { - throw UnimplementedError(); + Future restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async { + final wallet = EthereumWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; } @override diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index a83447405..d81907813 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "thread" #include "CwWalletListener.h" #if __APPLE__ @@ -189,6 +190,62 @@ extern "C" } }; + struct CoinsInfoRow + { + uint64_t blockHeight; + char *hash; + uint64_t internalOutputIndex; + uint64_t globalOutputIndex; + bool spent; + bool frozen; + uint64_t spentHeight; + uint64_t amount; + bool rct; + bool keyImageKnown; + uint64_t pkIndex; + uint32_t subaddrIndex; + uint32_t subaddrAccount; + char *address; + char *addressLabel; + char *keyImage; + uint64_t unlockTime; + bool unlocked; + char *pubKey; + bool coinbase; + char *description; + + CoinsInfoRow(Monero::CoinsInfo *coinsInfo) + { + blockHeight = coinsInfo->blockHeight(); + std::string *hash_str = new std::string(coinsInfo->hash()); + hash = strdup(hash_str->c_str()); + internalOutputIndex = coinsInfo->internalOutputIndex(); + globalOutputIndex = coinsInfo->globalOutputIndex(); + spent = coinsInfo->spent(); + frozen = coinsInfo->frozen(); + spentHeight = coinsInfo->spentHeight(); + amount = coinsInfo->amount(); + rct = coinsInfo->rct(); + keyImageKnown = coinsInfo->keyImageKnown(); + pkIndex = coinsInfo->pkIndex(); + subaddrIndex = coinsInfo->subaddrIndex(); + subaddrAccount = coinsInfo->subaddrAccount(); + address = strdup(coinsInfo->address().c_str()) ; + addressLabel = strdup(coinsInfo->addressLabel().c_str()); + keyImage = strdup(coinsInfo->keyImage().c_str()); + unlockTime = coinsInfo->unlockTime(); + unlocked = coinsInfo->unlocked(); + pubKey = strdup(coinsInfo->pubKey().c_str()); + coinbase = coinsInfo->coinbase(); + description = strdup(coinsInfo->description().c_str()); + } + + void setUnlocked(bool unlocked); + + }; + + Monero::Coins *m_coins; + Monero::Wallet *m_wallet; Monero::TransactionHistory *m_transaction_history; MoneroWalletListener *m_listener; @@ -196,6 +253,7 @@ extern "C" Monero::SubaddressAccount *m_account; uint64_t m_last_known_wallet_height; uint64_t m_cached_syncing_blockchain_height = 0; + std::list m_coins_info; std::mutex store_lock; bool is_storing = false; @@ -232,6 +290,17 @@ extern "C" { m_subaddress = nullptr; } + + m_coins_info = std::list(); + + if (wallet != nullptr) + { + m_coins = wallet->coins(); + } + else + { + m_coins = nullptr; + } } Monero::Wallet *get_current_wallet() @@ -520,9 +589,18 @@ extern "C" FUNCTION_VISABILITY_ATTRIBUTE bool transaction_create(char *address, char *payment_id, char *amount, - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + uint8_t priority_raw, uint32_t subaddr_account, + char **preferred_inputs, uint32_t preferred_inputs_size, + Utf8Box &error, PendingTransactionRaw &pendingTransaction) { nice(19); + + std::set _preferred_inputs; + + for (int i = 0; i < preferred_inputs_size; i++) { + _preferred_inputs.insert(std::string(*preferred_inputs)); + preferred_inputs++; + } auto priority = static_cast(priority_raw); std::string _payment_id; @@ -536,11 +614,11 @@ extern "C" if (amount != nullptr) { uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); - transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); } else { - transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs); } int status = transaction->status(); @@ -561,7 +639,9 @@ extern "C" FUNCTION_VISABILITY_ATTRIBUTE bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size, - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + uint8_t priority_raw, uint32_t subaddr_account, + char **preferred_inputs, uint32_t preferred_inputs_size, + Utf8Box &error, PendingTransactionRaw &pendingTransaction) { nice(19); @@ -575,6 +655,13 @@ extern "C" amounts++; } + std::set _preferred_inputs; + + for (int i = 0; i < preferred_inputs_size; i++) { + _preferred_inputs.insert(std::string(*preferred_inputs)); + preferred_inputs++; + } + auto priority = static_cast(priority_raw); std::string _payment_id; Monero::PendingTransaction *transaction; @@ -861,6 +948,91 @@ extern "C" return m_wallet->trustedDaemon(); } + CoinsInfoRow* coin(int index) + { + if (index >= 0 && index < m_coins_info.size()) { + std::list::iterator it = m_coins_info.begin(); + std::advance(it, index); + Monero::CoinsInfo* element = *it; + std::cout << "Element at index " << index << ": " << element << std::endl; + return new CoinsInfoRow(element); + } else { + std::cout << "Invalid index." << std::endl; + return nullptr; // Return a default value (nullptr) for invalid index + } + } + + void refresh_coins(uint32_t accountIndex) + { + m_coins_info.clear(); + + m_coins->refresh(); + for (const auto i : m_coins->getAll()) { + if (i->subaddrAccount() == accountIndex && !(i->spent())) { + m_coins_info.push_back(i); + } + } + } + + uint64_t coins_count() + { + return m_coins_info.size(); + } + + CoinsInfoRow** coins_from_account(uint32_t accountIndex) + { + std::vector matchingCoins; + + for (int i = 0; i < coins_count(); i++) { + CoinsInfoRow* coinInfo = coin(i); + if (coinInfo->subaddrAccount == accountIndex) { + matchingCoins.push_back(coinInfo); + } + } + + CoinsInfoRow** result = new CoinsInfoRow*[matchingCoins.size()]; + std::copy(matchingCoins.begin(), matchingCoins.end(), result); + return result; + } + + CoinsInfoRow** coins_from_txid(const char* txid, size_t* count) + { + std::vector matchingCoins; + + for (int i = 0; i < coins_count(); i++) { + CoinsInfoRow* coinInfo = coin(i); + if (std::string(coinInfo->hash) == txid) { + matchingCoins.push_back(coinInfo); + } + } + + *count = matchingCoins.size(); + CoinsInfoRow** result = new CoinsInfoRow*[*count]; + std::copy(matchingCoins.begin(), matchingCoins.end(), result); + return result; + } + + CoinsInfoRow** coins_from_key_image(const char** keyimages, size_t keyimageCount, size_t* count) + { + std::vector matchingCoins; + + for (int i = 0; i < coins_count(); i++) { + CoinsInfoRow* coinsInfoRow = coin(i); + for (size_t j = 0; j < keyimageCount; j++) { + if (coinsInfoRow->keyImageKnown && std::string(coinsInfoRow->keyImage) == keyimages[j]) { + matchingCoins.push_back(coinsInfoRow); + break; + } + } + } + + *count = matchingCoins.size(); + CoinsInfoRow** result = new CoinsInfoRow*[*count]; + std::copy(matchingCoins.begin(), matchingCoins.end(), result); + return result; + } + + #ifdef __cplusplus } #endif diff --git a/cw_monero/lib/api/coins_info.dart b/cw_monero/lib/api/coins_info.dart new file mode 100644 index 000000000..9a5303f9d --- /dev/null +++ b/cw_monero/lib/api/coins_info.dart @@ -0,0 +1,23 @@ +import 'dart:ffi'; +import 'package:cw_monero/api/signatures.dart'; +import 'package:cw_monero/api/structs/coins_info_row.dart'; +import 'package:cw_monero/api/types.dart'; +import 'package:cw_monero/api/monero_api.dart'; + +final refreshCoinsNative = moneroApi + .lookup>('refresh_coins') + .asFunction(); + +final coinsCountNative = moneroApi + .lookup>('coins_count') + .asFunction(); + +final coinNative = moneroApi + .lookup>('coin') + .asFunction(); + +void refreshCoins(int accountIndex) => refreshCoinsNative(accountIndex); + +int countOfCoins() => coinsCountNative(); + +CoinsInfoRow getCoin(int index) => coinNative(index).ref; diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 82bc7801e..e208414c8 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; +import 'package:cw_monero/api/structs/coins_info_row.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/structs/ut8_box.dart'; import 'package:ffi/ffi.dart'; @@ -9,8 +10,8 @@ typedef create_wallet = Int8 Function( typedef restore_wallet_from_seed = Int8 Function( Pointer, Pointer, Pointer, Int32, Int64, Pointer); -typedef restore_wallet_from_keys = Int8 Function(Pointer, Pointer, - Pointer, Pointer, Pointer, Pointer, Int32, Int64, Pointer); +typedef restore_wallet_from_keys = Int8 Function(Pointer, Pointer, Pointer, + Pointer, Pointer, Pointer, Int32, Int64, Pointer); typedef is_wallet_exist = Int8 Function(Pointer); @@ -63,8 +64,7 @@ typedef subaddrress_refresh = Void Function(Int32); typedef subaddress_get_all = Pointer Function(); -typedef subaddress_add_new = Void Function( - Int32 accountIndex, Pointer label); +typedef subaddress_add_new = Void Function(Int32 accountIndex, Pointer label); typedef subaddress_set_label = Void Function( Int32 accountIndex, Int32 addressIndex, Pointer label); @@ -77,8 +77,7 @@ typedef account_get_all = Pointer Function(); typedef account_add_new = Void Function(Pointer label); -typedef account_set_label = Void Function( - Int32 accountIndex, Pointer label); +typedef account_set_label = Void Function(Int32 accountIndex, Pointer label); typedef transactions_refresh = Void Function(); @@ -94,6 +93,8 @@ typedef transaction_create = Int8 Function( Pointer amount, Int8 priorityRaw, Int32 subaddrAccount, + Pointer> preferredInputs, + Int32 preferredInputsSize, Pointer error, Pointer pendingTransaction); @@ -104,6 +105,8 @@ typedef transaction_create_mult_dest = Int8 Function( Int32 size, Int8 priorityRaw, Int32 subaddrAccount, + Pointer> preferredInputs, + Int32 preferredInputsSize, Pointer error, Pointer pendingTransaction); @@ -123,10 +126,16 @@ typedef on_startup = Void Function(); typedef rescan_blockchain = Void Function(); -typedef get_subaddress_label = Pointer Function( - Int32 accountIndex, - Int32 addressIndex); +typedef get_subaddress_label = Pointer Function(Int32 accountIndex, Int32 addressIndex); typedef set_trusted_daemon = Void Function(Int8 trusted); -typedef trusted_daemon = Int8 Function(); \ No newline at end of file +typedef trusted_daemon = Int8 Function(); + +typedef refresh_coins = Void Function(Int32 accountIndex); + +typedef coins_count = Int64 Function(); + +// typedef coins_from_txid = Pointer Function(Pointer txid); + +typedef coin = Pointer Function(Int32 index); diff --git a/cw_monero/lib/api/structs/coins_info_row.dart b/cw_monero/lib/api/structs/coins_info_row.dart new file mode 100644 index 000000000..ff6f6ce73 --- /dev/null +++ b/cw_monero/lib/api/structs/coins_info_row.dart @@ -0,0 +1,73 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class CoinsInfoRow extends Struct { + @Int64() + external int blockHeight; + + external Pointer hash; + + @Uint64() + external int internalOutputIndex; + + @Uint64() + external int globalOutputIndex; + + @Int8() + external int spent; + + @Int8() + external int frozen; + + @Uint64() + external int spentHeight; + + @Uint64() + external int amount; + + @Int8() + external int rct; + + @Int8() + external int keyImageKnown; + + @Uint64() + external int pkIndex; + + @Uint32() + external int subaddrIndex; + + @Uint32() + external int subaddrAccount; + + external Pointer address; + + external Pointer addressLabel; + + external Pointer keyImage; + + @Uint64() + external int unlockTime; + + @Int8() + external int unlocked; + + external Pointer pubKey; + + @Int8() + external int coinbase; + + external Pointer description; + + String getHash() => hash.toDartString(); + + String getAddress() => address.toDartString(); + + String getAddressLabel() => addressLabel.toDartString(); + + String getKeyImage() => keyImage.toDartString(); + + String getPubKey() => pubKey.toDartString(); + + String getDescription() => description.toDartString(); +} diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 0fc507500..6f848cb3d 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -35,9 +35,8 @@ final transactionCommitNative = moneroApi .lookup>('transaction_commit') .asFunction(); -final getTxKeyNative = moneroApi - .lookup>('get_tx_key') - .asFunction(); +final getTxKeyNative = + moneroApi.lookup>('get_tx_key').asFunction(); String getTxKey(String txId) { final txIdPointer = txId.toNativeUtf8(); @@ -71,10 +70,21 @@ PendingTransactionDescription createTransactionSync( required String paymentId, required int priorityRaw, String? amount, - int accountIndex = 0}) { + int accountIndex = 0, + List preferredInputs = const []}) { final addressPointer = address.toNativeUtf8(); final paymentIdPointer = paymentId.toNativeUtf8(); final amountPointer = amount != null ? amount.toNativeUtf8() : nullptr; + + final int preferredInputsSize = preferredInputs.length; + final List> preferredInputsPointers = + preferredInputs.map((output) => output.toNativeUtf8()).toList(); + final Pointer> preferredInputsPointerPointer = calloc(preferredInputsSize); + + for (int i = 0; i < preferredInputsSize; i++) { + preferredInputsPointerPointer[i] = preferredInputsPointers[i]; + } + final errorMessagePointer = calloc(); final pendingTransactionRawPointer = calloc(); final created = transactionCreateNative( @@ -83,10 +93,16 @@ PendingTransactionDescription createTransactionSync( amountPointer, priorityRaw, accountIndex, + preferredInputsPointerPointer, + preferredInputsSize, errorMessagePointer, pendingTransactionRawPointer) != 0; + calloc.free(preferredInputsPointerPointer); + + preferredInputsPointers.forEach((element) => calloc.free(element)); + calloc.free(addressPointer); calloc.free(paymentIdPointer); @@ -111,15 +127,16 @@ PendingTransactionDescription createTransactionSync( PendingTransactionDescription createTransactionMultDestSync( {required List outputs, - required String paymentId, - required int priorityRaw, - int accountIndex = 0}) { + required String paymentId, + required int priorityRaw, + int accountIndex = 0, + List preferredInputs = const []}) { final int size = outputs.length; - final List> addressesPointers = outputs.map((output) => - output.address.toNativeUtf8()).toList(); + final List> addressesPointers = + outputs.map((output) => output.address.toNativeUtf8()).toList(); final Pointer> addressesPointerPointer = calloc(size); - final List> amountsPointers = outputs.map((output) => - output.amount.toNativeUtf8()).toList(); + final List> amountsPointers = + outputs.map((output) => output.amount.toNativeUtf8()).toList(); final Pointer> amountsPointerPointer = calloc(size); for (int i = 0; i < size; i++) { @@ -127,25 +144,38 @@ PendingTransactionDescription createTransactionMultDestSync( amountsPointerPointer[i] = amountsPointers[i]; } + final int preferredInputsSize = preferredInputs.length; + final List> preferredInputsPointers = + preferredInputs.map((output) => output.toNativeUtf8()).toList(); + final Pointer> preferredInputsPointerPointer = calloc(preferredInputsSize); + + for (int i = 0; i < preferredInputsSize; i++) { + preferredInputsPointerPointer[i] = preferredInputsPointers[i]; + } + final paymentIdPointer = paymentId.toNativeUtf8(); final errorMessagePointer = calloc(); final pendingTransactionRawPointer = calloc(); final created = transactionCreateMultDestNative( - addressesPointerPointer, - paymentIdPointer, - amountsPointerPointer, - size, - priorityRaw, - accountIndex, - errorMessagePointer, - pendingTransactionRawPointer) != + addressesPointerPointer, + paymentIdPointer, + amountsPointerPointer, + size, + priorityRaw, + accountIndex, + preferredInputsPointerPointer, + preferredInputsSize, + errorMessagePointer, + pendingTransactionRawPointer) != 0; calloc.free(addressesPointerPointer); calloc.free(amountsPointerPointer); + calloc.free(preferredInputsPointerPointer); addressesPointers.forEach((element) => calloc.free(element)); amountsPointers.forEach((element) => calloc.free(element)); + preferredInputsPointers.forEach((element) => calloc.free(element)); calloc.free(paymentIdPointer); @@ -164,13 +194,12 @@ PendingTransactionDescription createTransactionMultDestSync( pointerAddress: pendingTransactionRawPointer.address); } -void commitTransactionFromPointerAddress({required int address}) => commitTransaction( - transactionPointer: Pointer.fromAddress(address)); +void commitTransactionFromPointerAddress({required int address}) => + commitTransaction(transactionPointer: Pointer.fromAddress(address)); void commitTransaction({required Pointer transactionPointer}) { final errorMessagePointer = calloc(); - final isCommited = - transactionCommitNative(transactionPointer, errorMessagePointer) != 0; + final isCommited = transactionCommitNative(transactionPointer, errorMessagePointer) != 0; if (!isCommited) { final message = errorMessagePointer.ref.getValue(); @@ -185,13 +214,15 @@ PendingTransactionDescription _createTransactionSync(Map args) { final amount = args['amount'] as String?; final priorityRaw = args['priorityRaw'] as int; final accountIndex = args['accountIndex'] as int; + final preferredInputs = args['preferredInputs'] as List; return createTransactionSync( address: address, paymentId: paymentId, amount: amount, priorityRaw: priorityRaw, - accountIndex: accountIndex); + accountIndex: accountIndex, + preferredInputs: preferredInputs); } PendingTransactionDescription _createTransactionMultDestSync(Map args) { @@ -199,12 +230,14 @@ PendingTransactionDescription _createTransactionMultDestSync(Map args) { final paymentId = args['paymentId'] as String; final priorityRaw = args['priorityRaw'] as int; final accountIndex = args['accountIndex'] as int; + final preferredInputs = args['preferredInputs'] as List; return createTransactionMultDestSync( outputs: outputs, paymentId: paymentId, priorityRaw: priorityRaw, - accountIndex: accountIndex); + accountIndex: accountIndex, + preferredInputs: preferredInputs); } Future createTransaction( @@ -212,23 +245,27 @@ Future createTransaction( required int priorityRaw, String? amount, String paymentId = '', - int accountIndex = 0}) => + int accountIndex = 0, + List preferredInputs = const []}) => compute(_createTransactionSync, { 'address': address, 'paymentId': paymentId, 'amount': amount, 'priorityRaw': priorityRaw, - 'accountIndex': accountIndex + 'accountIndex': accountIndex, + 'preferredInputs': preferredInputs }); Future createTransactionMultDest( - {required List outputs, - required int priorityRaw, - String paymentId = '', - int accountIndex = 0}) => + {required List outputs, + required int priorityRaw, + String paymentId = '', + int accountIndex = 0, + List preferredInputs = const []}) => compute(_createTransactionMultDestSync, { 'outputs': outputs, 'paymentId': paymentId, 'priorityRaw': priorityRaw, - 'accountIndex': accountIndex + 'accountIndex': accountIndex, + 'preferredInputs': preferredInputs }); diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 051f317c9..2c92f2d80 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -1,4 +1,5 @@ import 'dart:ffi'; +import 'package:cw_monero/api/structs/coins_info_row.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/structs/ut8_box.dart'; import 'package:ffi/ffi.dart'; @@ -92,6 +93,8 @@ typedef TransactionCreate = int Function( Pointer amount, int priorityRaw, int subaddrAccount, + Pointer> preferredInputs, + int preferredInputsSize, Pointer error, Pointer pendingTransaction); @@ -102,6 +105,8 @@ typedef TransactionCreateMultDest = int Function( int size, int priorityRaw, int subaddrAccount, + Pointer> preferredInputs, + int preferredInputsSize, Pointer error, Pointer pendingTransaction); @@ -127,4 +132,10 @@ typedef GetSubaddressLabel = Pointer Function( typedef SetTrustedDaemon = void Function(int); -typedef TrustedDaemon = int Function(); \ No newline at end of file +typedef TrustedDaemon = int Function(); + +typedef RefreshCoins = void Function(int); + +typedef CoinsCount = int Function(); + +typedef GetCoin = Pointer Function(int); diff --git a/cw_monero/lib/monero_transaction_creation_exception.dart b/cw_monero/lib/exceptions/monero_transaction_creation_exception.dart similarity index 100% rename from cw_monero/lib/monero_transaction_creation_exception.dart rename to cw_monero/lib/exceptions/monero_transaction_creation_exception.dart diff --git a/cw_monero/lib/exceptions/monero_transaction_no_inputs_exception.dart b/cw_monero/lib/exceptions/monero_transaction_no_inputs_exception.dart new file mode 100644 index 000000000..5d808be8f --- /dev/null +++ b/cw_monero/lib/exceptions/monero_transaction_no_inputs_exception.dart @@ -0,0 +1,4 @@ +class MoneroTransactionNoInputsException implements Exception { + @override + String toString() => 'Not enough inputs available. Please select more under Coin Control'; +} diff --git a/cw_monero/lib/monero_subaddress_list.dart b/cw_monero/lib/monero_subaddress_list.dart index 8d8eeb469..0dcddc679 100644 --- a/cw_monero/lib/monero_subaddress_list.dart +++ b/cw_monero/lib/monero_subaddress_list.dart @@ -1,6 +1,6 @@ -import 'package:cw_monero/api/structs/subaddress_row.dart'; import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; +import 'package:cw_monero/api/coins_info.dart'; import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list; import 'package:cw_core/subaddress.dart'; @@ -22,6 +22,8 @@ abstract class MoneroSubaddressListBase with Store { bool _isUpdating; void update({required int accountIndex}) { + // refreshCoins(accountIndex); + if (_isUpdating) { return; } diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart new file mode 100644 index 000000000..c2ff9f9db --- /dev/null +++ b/cw_monero/lib/monero_unspent.dart @@ -0,0 +1,28 @@ +import 'package:cw_monero/api/structs/coins_info_row.dart'; + +class MoneroUnspent { + MoneroUnspent(this.address, this.hash, this.keyImage, this.value, this.isFrozen, this.isUnlocked) + : isSending = true, + note = ''; + + MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) + : address = coinsInfoRow.getAddress(), + hash = coinsInfoRow.getHash(), + keyImage = coinsInfoRow.getKeyImage(), + value = coinsInfoRow.amount, + isFrozen = coinsInfoRow.frozen == 1, + isUnlocked = coinsInfoRow.unlocked == 1, + isSending = true, + note = ''; + + final String address; + final String hash; + final String keyImage; + final int value; + + final bool isUnlocked; + + bool isFrozen; + bool isSending; + String note; +} diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index bc455531c..12381af44 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -1,33 +1,35 @@ import 'dart:async'; import 'dart:io'; -import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_monero/monero_transaction_creation_exception.dart'; -import 'package:cw_monero/monero_transaction_info.dart'; -import 'package:cw_monero/monero_wallet_addresses.dart'; -import 'package:cw_core/monero_wallet_utils.dart'; -import 'package:cw_monero/api/structs/pending_transaction.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cw_monero/api/transaction_history.dart' - as monero_transaction_history; -import 'package:cw_monero/api/wallet.dart'; -import 'package:cw_monero/api/wallet.dart' as monero_wallet; -import 'package:cw_monero/api/transaction_history.dart' as transaction_history; -import 'package:cw_monero/api/monero_output.dart'; -import 'package:cw_monero/monero_transaction_creation_credentials.dart'; -import 'package:cw_monero/pending_monero_transaction.dart'; -import 'package:cw_core/monero_wallet_keys.dart'; -import 'package:cw_core/monero_balance.dart'; -import 'package:cw_monero/monero_transaction_history.dart'; import 'package:cw_core/account.dart'; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/node.dart'; -import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/monero_amount_format.dart'; +import 'package:cw_core/monero_balance.dart'; +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_core/monero_wallet_keys.dart'; +import 'package:cw_core/monero_wallet_utils.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_monero/api/coins_info.dart'; +import 'package:cw_monero/api/monero_output.dart'; +import 'package:cw_monero/api/structs/pending_transaction.dart'; +import 'package:cw_monero/api/transaction_history.dart' as transaction_history; +import 'package:cw_monero/api/wallet.dart' as monero_wallet; +import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; +import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart'; +import 'package:cw_monero/pending_monero_transaction.dart'; +import 'package:cw_monero/monero_transaction_creation_credentials.dart'; +import 'package:cw_monero/monero_transaction_history.dart'; +import 'package:cw_monero/monero_transaction_info.dart'; +import 'package:cw_monero/monero_unspent.dart'; +import 'package:cw_monero/monero_wallet_addresses.dart'; +import 'package:mobx/mobx.dart'; +import 'package:hive/hive.dart'; part 'monero_wallet.g.dart'; @@ -39,38 +41,40 @@ abstract class MoneroWalletBase extends WalletBase with Store { MoneroWalletBase({ required WalletInfo walletInfo, + required Box unspentCoinsInfo, required String password}) : balance = ObservableMap.of({ - CryptoCurrency.xmr: MoneroBalance( + CryptoCurrency.xmr: MoneroBalance( fullBalance: monero_wallet.getFullBalance(accountIndex: 0), unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)) - }), + }), _isTransactionUpdating = false, _hasSyncAfterStartup = false, _password = password, walletAddresses = MoneroWalletAddresses(walletInfo), syncStatus = NotConnectedSyncStatus(), + unspentCoins = [], + this.unspentCoinsInfo = unspentCoinsInfo, super(walletInfo) { transactionHistory = MoneroTransactionHistory(); - _onAccountChangeReaction = reaction((_) => walletAddresses.account, - (Account? account) { + _onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) { if (account == null) { return; } - balance = ObservableMap.of( - { - currency: MoneroBalance( + balance = ObservableMap.of({ + currency: MoneroBalance( fullBalance: monero_wallet.getFullBalance(accountIndex: account.id), - unlockedBalance: - monero_wallet.getUnlockedBalance(accountIndex: account.id)) - }); + unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: account.id)) + }); walletAddresses.updateSubaddressList(accountIndex: account.id); }); } static const int _autoSaveInterval = 30; + Box unspentCoinsInfo; + @override MoneroWalletAddresses walletAddresses; @@ -95,11 +99,12 @@ abstract class MoneroWalletBase extends WalletBase unspentCoins; String _password; Future init() async { @@ -177,10 +182,12 @@ abstract class MoneroWalletBase extends WalletBase createTransaction(Object credentials) async { final _credentials = credentials as MoneroTransactionCreationCredentials; + final inputs = []; final outputs = _credentials.outputs; final hasMultiDestination = outputs.length > 1; final unlockedBalance = monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); + var allInputsAmount = 0; PendingTransactionDescription pendingTransactionDescription; @@ -188,6 +195,21 @@ abstract class MoneroWalletBase extends WalletBase item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { @@ -215,7 +237,8 @@ abstract class MoneroWalletBase extends WalletBase updateUnspent() async { + // refreshCoins(walletAddresses.account!.id); + + unspentCoins.clear(); + + final coinCount = countOfCoins(); + for (var i = 0; i < coinCount; i++) { + final coin = getCoin(i); + if (coin.spent == 0) { + unspentCoins.add(MoneroUnspent.fromCoinsInfoRow(coin)); + } + } + + if (unspentCoinsInfo.isEmpty) { + unspentCoins.forEach((coin) => _addCoinInfo(coin)); + return; + } + + if (unspentCoins.isNotEmpty) { + unspentCoins.forEach((coin) { + final coinInfoList = unspentCoinsInfo.values.where((element) => + element.walletId.contains(id) && element.hash.contains(coin.hash)); + + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; + + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + } else { + _addCoinInfo(coin); + } + }); + } + + await _refreshUnspentCoinsInfo(); + _askForUpdateBalance(); + } + + Future _addCoinInfo(MoneroUnspent coin) async { + final newInfo = UnspentCoinsInfo( + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + noteRaw: coin.note, + address: coin.address, + value: coin.value, + vout: 0, + keyImage: coin.keyImage + ); + + await unspentCoinsInfo.add(newInfo); + } + + Future _refreshUnspentCoinsInfo() async { + try { + final List keys = []; + final currentWalletUnspentCoins = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id)); + + if (currentWalletUnspentCoins.isNotEmpty) { + currentWalletUnspentCoins.forEach((element) { + final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash)); + + if (existUnspentCoins.isEmpty) { + keys.add(element.key); + } + }); + } + + if (keys.isNotEmpty) { + await unspentCoinsInfo.deleteAll(keys); + } + } catch (e) { + print(e.toString()); + } + } + String getTransactionAddress(int accountIndex, int addressIndex) => monero_wallet.getAddress( accountIndex: accountIndex, @@ -369,7 +471,7 @@ abstract class MoneroWalletBase extends WalletBase> fetchTransactions() async { - monero_transaction_history.refreshTransactions(); + transaction_history.refreshTransactions(); return _getAllTransactions(null).fold>( {}, (Map acc, MoneroTransactionInfo tx) { @@ -400,7 +502,7 @@ abstract class MoneroWalletBase extends WalletBase _getAllTransactions(dynamic _) => - monero_transaction_history + transaction_history .getAllTransations() .map((row) => MoneroTransactionInfo.fromRow(row)) .toList(); @@ -415,7 +517,7 @@ abstract class MoneroWalletBase extends WalletBase monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); + int _getFrozenBalance() { + var frozenBalance = 0; + + for (var coin in unspentCoinsInfo.values) { + if (coin.isFrozen) + frozenBalance += coin.value; + } + + return frozenBalance; + } + void _onNewBlock(int height, int blocksLeft, double ptc) async { try { if (walletInfo.isRecovery) { diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 6f5645f66..181bc7c66 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -1,15 +1,16 @@ import 'dart:io'; -import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/monero_wallet_utils.dart'; -import 'package:hive/hive.dart'; -import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; -import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; -import 'package:cw_monero/monero_wallet.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; +import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; +import 'package:cw_monero/monero_wallet.dart'; +import 'package:hive/hive.dart'; class MoneroNewWalletCredentials extends WalletCredentials { MoneroNewWalletCredentials({required String name, required this.language, String? password}) @@ -52,9 +53,10 @@ class MoneroWalletService extends WalletService< MoneroNewWalletCredentials, MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials> { - MoneroWalletService(this.walletInfoSource); + MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); final Box walletInfoSource; + final Box unspentCoinsInfoSource; static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync(); @@ -71,8 +73,9 @@ class MoneroWalletService extends WalletService< password: credentials.password!, language: credentials.language); final wallet = MoneroWallet( + walletInfo: credentials.walletInfo!, - password: credentials.password!); + password: credentials.password!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; @@ -109,7 +112,7 @@ class MoneroWalletService extends WalletService< final walletInfo = walletInfoSource.values.firstWhere( (info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( - walletInfo: walletInfo, + walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); final isValid = wallet.walletAddresses.validate(); @@ -160,7 +163,8 @@ class MoneroWalletService extends WalletService< String currentName, String password, String newName) async { final currentWalletInfo = walletInfoSource.values.firstWhere( (info) => info.id == WalletBase.idFor(currentName, getType())); - final currentWallet = MoneroWallet(walletInfo: currentWalletInfo, password: password); + final currentWallet = + MoneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); await currentWallet.renameWalletFiles(newName); @@ -185,8 +189,9 @@ class MoneroWalletService extends WalletService< viewKey: credentials.viewKey, spendKey: credentials.spendKey); final wallet = MoneroWallet( + walletInfo: credentials.walletInfo!, - password: credentials.password!); + password: credentials.password!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; @@ -208,8 +213,9 @@ class MoneroWalletService extends WalletService< seed: credentials.mnemonic, restoreHeight: credentials.height!); final wallet = MoneroWallet( + walletInfo: credentials.walletInfo!, - password: credentials.password!); + password: credentials.password!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); return wallet; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f13c68629..6f441c587 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -174,12 +174,12 @@ DEPENDENCIES: - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - platform_device_id (from `.symlinks/plugins/platform_device_id/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - UnstoppableDomainsResolution (~> 4.0.0) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -236,7 +236,7 @@ EXTERNAL SOURCES: package_info: :path: ".symlinks/plugins/package_info/ios" path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" platform_device_id: @@ -246,7 +246,7 @@ EXTERNAL SOURCES: share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/ios" + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" uni_links: :path: ".symlinks/plugins/uni_links/ios" url_launcher_ios: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index e1c1e137c..f03e47460 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -129,7 +129,8 @@ class CWBitcoin extends Bitcoin { bitcoinUnspent.address.address, bitcoinUnspent.hash, bitcoinUnspent.value, - bitcoinUnspent.vout)) + bitcoinUnspent.vout, + null)) .toList(); } @@ -161,4 +162,4 @@ class CWBitcoin extends Bitcoin { @override TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow; -} \ No newline at end of file +} diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 68be59f4e..cf4cbd124 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -1,8 +1,10 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:flutter/material.dart'; class OnRamperBuyProvider { OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) @@ -27,7 +29,11 @@ class OnRamperBuyProvider { } } - Uri requestUrl() { + String getColorStr(Color color) { + return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); + } + + Uri requestUrl(BuildContext context) { String primaryColor, secondaryColor, primaryTextColor, @@ -35,31 +41,16 @@ class OnRamperBuyProvider { containerColor, cardColor; - switch (_settingsStore.currentTheme.type) { - case ThemeType.bright: - primaryColor = '815dfbff'; - secondaryColor = 'ffffff'; - primaryTextColor = '141519'; - secondaryTextColor = '6b6f80'; - containerColor = 'ffffff'; - cardColor = 'f2f0faff'; - break; - case ThemeType.light: - primaryColor = '2194ffff'; - secondaryColor = 'ffffff'; - primaryTextColor = '141519'; - secondaryTextColor = '6b6f80'; - containerColor = 'ffffff'; - cardColor = 'e5f7ff'; - break; - case ThemeType.dark: - primaryColor = '456effff'; - secondaryColor = '1b2747ff'; - primaryTextColor = 'ffffff'; - secondaryTextColor = 'ffffff'; - containerColor = '19233C'; - cardColor = '232f4fff'; - break; + primaryColor = getColorStr(Theme.of(context).primaryColor); + secondaryColor = getColorStr(Theme.of(context).colorScheme.background); + primaryTextColor = getColorStr(Theme.of(context).extension()!.titleColor); + secondaryTextColor = + getColorStr(Theme.of(context).extension()!.secondaryTextColor); + containerColor = getColorStr(Theme.of(context).colorScheme.background); + cardColor = getColorStr(Theme.of(context).cardColor); + + if (_settingsStore.currentTheme.title == S.current.high_contrast_theme) { + cardColor = getColorStr(Colors.white); } final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); diff --git a/lib/di.dart b/lib/di.dart index 6795a979d..a696138b9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -180,8 +180,6 @@ import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/core/secure_storage.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/wallet_type.dart'; @@ -222,10 +220,10 @@ late Box