mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-28 18:19:13 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into mweb-bg-sync-2
This commit is contained in:
commit
91ffe6c18f
95 changed files with 1259 additions and 426 deletions
assets
images
solana_node_list.ymlcw_bitcoin
lib
electrum_transaction_info.dartelectrum_wallet.dartelectrum_wallet_addresses.dartlitecoin_wallet.dart
pubspec.lockcw_core/lib
cw_haven/lib
cw_monero
lib
pubspec.lockpubspec.yamlcw_wownero
lib
pubspec.lockpubspec.yamllib
core
exchange/provider
monero
src/screens
dashboard
exchange
new_wallet
advanced_privacy_settings_page.dartwallet_group_description_page.dartwallet_group_display_page.dart
widgets
receive
restore
root
wallet_list
view_model
contact_list
dashboard
exchange
transaction_details_view_model.dartwallet_address_list
wallet_address_edit_or_create_view_model.dartwallet_address_hidden_list_header.dartwallet_address_list_item.dartwallet_address_list_view_model.dart
wallet_creation_vm.dartwallet_list
res/values
strings_ar.arbstrings_bg.arbstrings_cs.arbstrings_de.arbstrings_en.arbstrings_es.arbstrings_fr.arbstrings_ha.arbstrings_hi.arbstrings_hr.arbstrings_hy.arbstrings_id.arbstrings_it.arbstrings_ja.arbstrings_ko.arbstrings_my.arbstrings_nl.arbstrings_pl.arbstrings_pt.arbstrings_ru.arbstrings_th.arbstrings_tl.arbstrings_tr.arbstrings_uk.arbstrings_ur.arbstrings_vi.arbstrings_yo.arbstrings_zh.arb
scripts
tool
Binary file not shown.
Before ![]() (image error) Size: 4.9 KiB |
BIN
assets/images/wallet_group_bright.png
Normal file
BIN
assets/images/wallet_group_bright.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 10 KiB |
BIN
assets/images/wallet_group_dark.png
Normal file
BIN
assets/images/wallet_group_dark.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 6.9 KiB |
BIN
assets/images/wallet_group_light.png
Normal file
BIN
assets/images/wallet_group_light.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 7 KiB |
|
@ -4,4 +4,7 @@
|
|||
useSSL: true
|
||||
-
|
||||
uri: api.mainnet-beta.solana.com:443
|
||||
useSSL: true
|
||||
-
|
||||
uri: solana-rpc.publicnode.com:443
|
||||
useSSL: true
|
|
@ -35,7 +35,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
List<String>? outputAddresses,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required bool isReplaced,
|
||||
bool isReplaced = false,
|
||||
required DateTime date,
|
||||
required int confirmations,
|
||||
String? to,
|
||||
|
|
|
@ -1328,6 +1328,11 @@ abstract class ElectrumWalletBase
|
|||
});
|
||||
}
|
||||
|
||||
// Set the balance of all non-silent payment addresses to 0 before updating
|
||||
walletAddresses.allAddresses.forEach((addr) {
|
||||
if(addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
|
||||
});
|
||||
|
||||
await Future.wait(walletAddresses.allAddresses.map((address) async {
|
||||
updatedUnspentCoins.addAll(await fetchUnspent(address));
|
||||
}));
|
||||
|
@ -1790,7 +1795,8 @@ abstract class ElectrumWalletBase
|
|||
final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
|
||||
final hiddenAddresses = addressesByType.where((addr) => addr.isHidden == true);
|
||||
final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false);
|
||||
|
||||
walletAddresses.hiddenAddresses.addAll(hiddenAddresses.map((e) => e.address));
|
||||
await walletAddresses.saveAddressesInBox();
|
||||
await Future.wait(addressesByType.map((addressRecord) async {
|
||||
final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip());
|
||||
|
||||
|
@ -1946,6 +1952,18 @@ abstract class ElectrumWalletBase
|
|||
var totalConfirmed = 0;
|
||||
var totalUnconfirmed = 0;
|
||||
|
||||
unspentCoinsInfo.values.forEach((info) {
|
||||
unspentCoins.forEach((element) {
|
||||
if (element.hash == info.hash &&
|
||||
element.vout == info.vout &&
|
||||
info.isFrozen &&
|
||||
element.bitcoinAddressRecord.address == info.address &&
|
||||
element.value == info.value) {
|
||||
totalFrozen += element.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (hasSilentPaymentsScanning) {
|
||||
// Add values from unspent coins that are not fetched by the address list
|
||||
// i.e. scanned silent payments
|
||||
|
|
|
@ -163,6 +163,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@override
|
||||
set address(String addr) {
|
||||
if (addr == "Silent Payments" && SilentPaymentsAddresType.p2sp != addressPageType) {
|
||||
return;
|
||||
}
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||
|
||||
|
@ -174,12 +177,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||
try {
|
||||
final addressRecord = _addresses.firstWhere(
|
||||
(addressRecord) => addressRecord.address == addr,
|
||||
);
|
||||
|
||||
previousAddressRecord = addressRecord;
|
||||
receiveAddresses.remove(addressRecord);
|
||||
receiveAddresses.insert(0, addressRecord);
|
||||
} catch (e) {
|
||||
print("ElectrumWalletAddressBase: set address ($addr): $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -332,7 +332,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
@action
|
||||
@override
|
||||
Future<void> stopSync() async {
|
||||
print("STOPPING SYNC");
|
||||
_syncTimer?.cancel();
|
||||
_utxoStream?.cancel();
|
||||
_feeRatesTimer?.cancel();
|
||||
|
@ -822,13 +821,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
if (outputs.length == 1 && outputs[0].toOutput.amount == BigInt.zero) {
|
||||
// TODO: for some reason we can't type cast BitcoinScriptOutput to BitcoinBaseOutput (even though one implements the other)
|
||||
// this breaks using the ALL button on litecoin mweb tx's:
|
||||
outputs = [
|
||||
BitcoinScriptOutput(
|
||||
script: outputs[0].toOutput.scriptPubKey, value: utxos.sumOfUtxosValue())
|
||||
];
|
||||
}
|
||||
|
||||
// https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation
|
||||
final preOutputSum =
|
||||
outputs.fold<BigInt>(BigInt.zero, (acc, output) => acc + output.toOutput.amount);
|
||||
|
|
|
@ -37,10 +37,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.3"
|
||||
version: "1.5.5"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -79,7 +79,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: cake-update-v7
|
||||
resolved-ref: bc49e3b1cba601828f8ddc3d016188d8c2499088
|
||||
resolved-ref: f577e83fe78766b2655ea0602baa9299b953a31b
|
||||
url: "https://github.com/cake-tech/bitcoin_base"
|
||||
source: git
|
||||
version: "4.7.0"
|
||||
|
@ -311,10 +311,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
ffigen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -584,10 +584,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.6"
|
||||
mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -744,10 +744,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
reactive_ble_mobile:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -886,7 +886,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: "sp_v4.0.0"
|
||||
resolved-ref: "9b04f4b0af80dd7dae9274b496a53c23dcc80ea5"
|
||||
resolved-ref: ca1add293bd1e06920aa049b655832da50d0dab2
|
||||
url: "https://github.com/cake-tech/sp_scanner"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
|
|
|
@ -174,11 +174,11 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static const zen = CryptoCurrency(title: 'ZEN', fullName: 'Horizen', raw: 44, name: 'zen', iconPath: 'assets/images/zen_icon.png', decimals: 8);
|
||||
static const xvg = CryptoCurrency(title: 'XVG', fullName: 'Verge', raw: 45, name: 'xvg', iconPath: 'assets/images/xvg_icon.png', decimals: 8);
|
||||
|
||||
static const usdcpoly = CryptoCurrency(title: 'USDC', tag: 'POLY', fullName: 'USD Coin', raw: 46, name: 'usdcpoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const usdcpoly = CryptoCurrency(title: 'USDC', tag: 'POL', fullName: 'USD Coin', raw: 46, name: 'usdcpoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const dcr = CryptoCurrency(title: 'DCR', fullName: 'Decred', raw: 47, name: 'dcr', iconPath: 'assets/images/dcr_icon.png', decimals: 8);
|
||||
static const kmd = CryptoCurrency(title: 'KMD', fullName: 'Komodo', raw: 48, name: 'kmd', iconPath: 'assets/images/kmd_icon.png', decimals: 8);
|
||||
static const mana = CryptoCurrency(title: 'MANA', tag: 'ETH', fullName: 'Decentraland', raw: 49, name: 'mana', iconPath: 'assets/images/mana_icon.png', decimals: 18);
|
||||
static const maticpoly = CryptoCurrency(title: 'POL', tag: 'POLY', fullName: 'Polygon', raw: 50, name: 'maticpoly', iconPath: 'assets/images/matic_icon.png', decimals: 18);
|
||||
static const maticpoly = CryptoCurrency(title: 'POL', tag: 'POL', fullName: 'Polygon', raw: 50, name: 'maticpoly', iconPath: 'assets/images/matic_icon.png', decimals: 18);
|
||||
static const matic = CryptoCurrency(title: 'MATIC', tag: 'ETH', fullName: 'Polygon', raw: 51, name: 'matic', iconPath: 'assets/images/matic_icon.png', decimals: 18);
|
||||
static const mkr = CryptoCurrency(title: 'MKR', tag: 'ETH', fullName: 'Maker', raw: 52, name: 'mkr', iconPath: 'assets/images/mkr_icon.png', decimals: 18);
|
||||
static const near = CryptoCurrency(title: 'NEAR', fullName: 'NEAR Protocol', raw: 53, name: 'near', iconPath: 'assets/images/near_icon.png', decimals: 24);
|
||||
|
@ -215,8 +215,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png', decimals: 18);
|
||||
static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png', decimals: 18);
|
||||
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
|
||||
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POL', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POL', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
||||
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
|
||||
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
class Subaddress {
|
||||
Subaddress({required this.id, required this.address, required this.label});
|
||||
Subaddress({
|
||||
required this.id,
|
||||
required this.address,
|
||||
required this.label,
|
||||
this.balance = null,
|
||||
this.txCount = null,
|
||||
});
|
||||
|
||||
Subaddress.fromMap(Map<String, Object?> map)
|
||||
: this.id = map['id'] == null ? 0 : int.parse(map['id'] as String),
|
||||
this.address = (map['address'] ?? '') as String,
|
||||
this.label = (map['label'] ?? '') as String;
|
||||
this.label = (map['label'] ?? '') as String,
|
||||
this.balance = (map['balance'] ?? '') as String?,
|
||||
this.txCount = (map['txCount'] ?? '') as int?;
|
||||
|
||||
final int id;
|
||||
final String address;
|
||||
final String label;
|
||||
final String? balance;
|
||||
final int? txCount;
|
||||
}
|
||||
|
|
|
@ -1,26 +1,58 @@
|
|||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
abstract class WalletAddresses {
|
||||
WalletAddresses(this.walletInfo)
|
||||
: addressesMap = {},
|
||||
allAddressesMap = {},
|
||||
addressInfos = {};
|
||||
addressInfos = {},
|
||||
usedAddresses = {},
|
||||
hiddenAddresses = walletInfo.hiddenAddresses?.toSet() ?? {},
|
||||
manualAddresses = walletInfo.manualAddresses?.toSet() ?? {};
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
|
||||
String get address;
|
||||
|
||||
String get latestAddress {
|
||||
if (walletInfo.type == WalletType.monero || walletInfo.type == WalletType.wownero) {
|
||||
if (addressesMap.keys.length == 0) return address;
|
||||
return addressesMap[addressesMap.keys.last] ?? address;
|
||||
}
|
||||
return _localAddress ?? address;
|
||||
}
|
||||
|
||||
String? get primaryAddress => null;
|
||||
|
||||
set address(String address);
|
||||
String? _localAddress;
|
||||
|
||||
set address(String address) => _localAddress = address;
|
||||
|
||||
String get addressForExchange => address;
|
||||
|
||||
Map<String, String> addressesMap;
|
||||
Map<String, String> allAddressesMap;
|
||||
|
||||
Map<String, String> get usableAddressesMap {
|
||||
final tmp = addressesMap.map((key, value) => MapEntry(key, value)); // copy address map
|
||||
tmp.removeWhere((key, value) => hiddenAddresses.contains(key) || manualAddresses.contains(key));
|
||||
return tmp;
|
||||
}
|
||||
|
||||
Map<String, String> get usableAllAddressesMap {
|
||||
final tmp = allAddressesMap.map((key, value) => MapEntry(key, value)); // copy address map
|
||||
tmp.removeWhere((key, value) => hiddenAddresses.contains(key) || manualAddresses.contains(key));
|
||||
return tmp;
|
||||
}
|
||||
|
||||
Map<int, List<AddressInfo>> addressInfos;
|
||||
|
||||
Set<String> usedAddresses = {};
|
||||
Set<String> usedAddresses;
|
||||
|
||||
Set<String> hiddenAddresses;
|
||||
|
||||
Set<String> manualAddresses;
|
||||
|
||||
Future<void> init();
|
||||
|
||||
|
@ -32,6 +64,8 @@ abstract class WalletAddresses {
|
|||
walletInfo.addresses = addressesMap;
|
||||
walletInfo.addressInfos = addressInfos;
|
||||
walletInfo.usedAddresses = usedAddresses.toList();
|
||||
walletInfo.hiddenAddresses = hiddenAddresses.toList();
|
||||
walletInfo.manualAddresses = manualAddresses.toList();
|
||||
|
||||
if (walletInfo.isInBox) {
|
||||
await walletInfo.save();
|
||||
|
|
|
@ -189,6 +189,15 @@ class WalletInfo extends HiveObject {
|
|||
|
||||
@HiveField(22)
|
||||
String? parentAddress;
|
||||
|
||||
@HiveField(23)
|
||||
List<String>? hiddenAddresses;
|
||||
|
||||
@HiveField(24)
|
||||
List<String>? manualAddresses;
|
||||
|
||||
|
||||
|
||||
|
||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cw_core/wallet_addresses_with_account.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/account.dart';
|
||||
import 'package:cw_haven/api/wallet.dart';
|
||||
import 'package:cw_haven/haven_account_list.dart';
|
||||
import 'package:cw_haven/haven_subaddress_list.dart';
|
||||
import 'package:cw_core/subaddress.dart';
|
||||
|
@ -36,7 +37,7 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount<Accou
|
|||
@override
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
account = accountList.accounts.isEmpty ? Account(id: 0, label: "Primary address") : accountList.accounts.first;
|
||||
updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
@ -81,8 +82,9 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount<Accou
|
|||
|
||||
void updateSubaddressList({required int accountIndex}) {
|
||||
subaddressList.update(accountIndex: accountIndex);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress!.address;
|
||||
address = subaddressList.subaddresses.isNotEmpty
|
||||
? subaddressList.subaddresses.first.address
|
||||
: getAddress();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/api/transaction_history.dart';
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
|
||||
|
@ -14,6 +15,10 @@ class SubaddressInfoMetadata {
|
|||
|
||||
SubaddressInfoMetadata? subaddress = null;
|
||||
|
||||
String getRawLabel({required int accountIndex, required int addressIndex}) {
|
||||
return monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
}
|
||||
|
||||
void refreshSubaddresses({required int accountIndex}) {
|
||||
try {
|
||||
isUpdating = true;
|
||||
|
@ -29,31 +34,94 @@ class Subaddress {
|
|||
Subaddress({
|
||||
required this.addressIndex,
|
||||
required this.accountIndex,
|
||||
required this.received,
|
||||
required this.txCount,
|
||||
});
|
||||
String get address => monero.Wallet_address(
|
||||
wptr!,
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
late String address = getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
final int addressIndex;
|
||||
final int accountIndex;
|
||||
String get label => monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
final int received;
|
||||
final int txCount;
|
||||
String get label {
|
||||
final localLabel = monero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
if (localLabel.startsWith("#$addressIndex")) return localLabel; // don't duplicate the ID if it was user-providen
|
||||
return "#$addressIndex ${localLabel}".trim();
|
||||
}
|
||||
}
|
||||
|
||||
class TinyTransactionDetails {
|
||||
TinyTransactionDetails({
|
||||
required this.address,
|
||||
required this.amount,
|
||||
});
|
||||
final List<String> address;
|
||||
final int amount;
|
||||
}
|
||||
|
||||
int lastWptr = 0;
|
||||
int lastTxCount = 0;
|
||||
List<TinyTransactionDetails> ttDetails = [];
|
||||
|
||||
List<Subaddress> getAllSubaddresses() {
|
||||
txhistory = monero.Wallet_history(wptr!);
|
||||
final txCount = monero.TransactionHistory_count(txhistory!);
|
||||
if (lastTxCount != txCount && lastWptr != wptr!.address) {
|
||||
final List<TinyTransactionDetails> newttDetails = [];
|
||||
lastTxCount = txCount;
|
||||
lastWptr = wptr!.address;
|
||||
for (var i = 0; i < txCount; i++) {
|
||||
final tx = monero.TransactionHistory_transaction(txhistory!, index: i);
|
||||
if (monero.TransactionInfo_direction(tx) == monero.TransactionInfo_Direction.Out) continue;
|
||||
final subaddrs = monero.TransactionInfo_subaddrIndex(tx).split(",");
|
||||
final account = monero.TransactionInfo_subaddrAccount(tx);
|
||||
newttDetails.add(TinyTransactionDetails(
|
||||
address: List.generate(subaddrs.length, (index) => getAddress(accountIndex: account, addressIndex: int.tryParse(subaddrs[index])??0)),
|
||||
amount: monero.TransactionInfo_amount(tx),
|
||||
));
|
||||
}
|
||||
ttDetails.clear();
|
||||
ttDetails.addAll(newttDetails);
|
||||
}
|
||||
final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
|
||||
final list = List.generate(size, (index) {
|
||||
final ttDetailsLocal = ttDetails.where((element) {
|
||||
final address = getAddress(
|
||||
accountIndex: subaddress!.accountIndex,
|
||||
addressIndex: index,
|
||||
);
|
||||
if (element.address.contains(address)) return true;
|
||||
return false;
|
||||
}).toList();
|
||||
int received = 0;
|
||||
for (var i = 0; i < ttDetailsLocal.length; i++) {
|
||||
received += ttDetailsLocal[i].amount;
|
||||
}
|
||||
return Subaddress(
|
||||
accountIndex: subaddress!.accountIndex,
|
||||
addressIndex: index,
|
||||
received: received,
|
||||
txCount: ttDetailsLocal.length,
|
||||
);
|
||||
}).reversed.toList();
|
||||
if (list.length == 0) {
|
||||
list.add(Subaddress(addressIndex: subaddress!.accountIndex, accountIndex: 0));
|
||||
list.add(
|
||||
Subaddress(
|
||||
addressIndex: subaddress!.accountIndex,
|
||||
accountIndex: 0,
|
||||
received: 0,
|
||||
txCount: 0,
|
||||
));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
int numSubaddresses(int subaccountIndex) {
|
||||
return monero.Wallet_numSubaddresses(wptr!, accountIndex: subaccountIndex);
|
||||
}
|
||||
|
||||
void addSubaddressSync({required int accountIndex, required String label}) {
|
||||
monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label);
|
||||
refreshSubaddresses(accountIndex: accountIndex);
|
||||
|
|
|
@ -5,32 +5,42 @@ import 'package:cw_monero/api/account_list.dart';
|
|||
import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
|
||||
import 'package:cw_monero/api/monero_output.dart';
|
||||
import 'package:cw_monero/api/structs/pending_transaction.dart';
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen;
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
||||
|
||||
String getTxKey(String txId) {
|
||||
return monero.Wallet_getTxKey(wptr!, txid: txId);
|
||||
}
|
||||
|
||||
final txHistoryMutex = Mutex();
|
||||
monero.TransactionHistory? txhistory;
|
||||
|
||||
void refreshTransactions() {
|
||||
bool isRefreshingTx = false;
|
||||
Future<void> refreshTransactions() async {
|
||||
if (isRefreshingTx == true) return;
|
||||
isRefreshingTx = true;
|
||||
txhistory ??= monero.Wallet_history(wptr!);
|
||||
monero.TransactionHistory_refresh(txhistory!);
|
||||
final ptr = txhistory!.address;
|
||||
await txHistoryMutex.acquire();
|
||||
await Isolate.run(() {
|
||||
monero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
|
||||
});
|
||||
txHistoryMutex.release();
|
||||
isRefreshingTx = false;
|
||||
}
|
||||
|
||||
int countOfTransactions() => monero.TransactionHistory_count(txhistory!);
|
||||
|
||||
List<Transaction> getAllTransactions() {
|
||||
Future<List<Transaction>> getAllTransactions() async {
|
||||
List<Transaction> dummyTxs = [];
|
||||
|
||||
|
||||
await txHistoryMutex.acquire();
|
||||
txhistory ??= monero.Wallet_history(wptr!);
|
||||
monero.TransactionHistory_refresh(txhistory!);
|
||||
int size = countOfTransactions();
|
||||
final list = List.generate(size, (index) => Transaction(txInfo: monero.TransactionHistory_transaction(txhistory!, index: index)));
|
||||
|
||||
txHistoryMutex.release();
|
||||
final accts = monero.Wallet_numSubaddressAccounts(wptr!);
|
||||
for (var i = 0; i < accts; i++) {
|
||||
final fullBalance = monero.Wallet_balance(wptr!, accountIndex: i);
|
||||
|
@ -45,6 +55,8 @@ List<Transaction> getAllTransactions() {
|
|||
confirmations: 0,
|
||||
blockheight: 0,
|
||||
accountIndex: i,
|
||||
addressIndex: 0,
|
||||
addressIndexList: [0],
|
||||
paymentId: "",
|
||||
amount: fullBalance - availBalance,
|
||||
isSpend: false,
|
||||
|
@ -251,19 +263,28 @@ Future<PendingTransactionDescription> createTransactionMultDest(
|
|||
|
||||
class Transaction {
|
||||
final String displayLabel;
|
||||
String subaddressLabel = monero.Wallet_getSubaddressLabel(wptr!, accountIndex: 0, addressIndex: 0);
|
||||
late final String address = monero.Wallet_address(
|
||||
late final String subaddressLabel = monero.Wallet_getSubaddressLabel(
|
||||
wptr!,
|
||||
accountIndex: 0,
|
||||
addressIndex: 0,
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
late final String address = getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
late final List<String> addressList = List.generate(addressIndexList.length, (index) =>
|
||||
getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndexList[index],
|
||||
));
|
||||
final String description;
|
||||
final int fee;
|
||||
final int confirmations;
|
||||
late final bool isPending = confirmations < 10;
|
||||
final int blockheight;
|
||||
final int addressIndex = 0;
|
||||
final int addressIndex;
|
||||
final int accountIndex;
|
||||
final List<int> addressIndexList;
|
||||
final String paymentId;
|
||||
final int amount;
|
||||
final bool isSpend;
|
||||
|
@ -309,6 +330,8 @@ class Transaction {
|
|||
amount = monero.TransactionInfo_amount(txInfo),
|
||||
paymentId = monero.TransactionInfo_paymentId(txInfo),
|
||||
accountIndex = monero.TransactionInfo_subaddrAccount(txInfo),
|
||||
addressIndex = int.tryParse(monero.TransactionInfo_subaddrIndex(txInfo).split(", ")[0]) ?? 0,
|
||||
addressIndexList = monero.TransactionInfo_subaddrIndex(txInfo).split(", ").map((e) => int.tryParse(e) ?? 0).toList(),
|
||||
blockheight = monero.TransactionInfo_blockHeight(txInfo),
|
||||
confirmations = monero.TransactionInfo_confirmations(txInfo),
|
||||
fee = monero.TransactionInfo_fee(txInfo),
|
||||
|
@ -331,6 +354,8 @@ class Transaction {
|
|||
required this.confirmations,
|
||||
required this.blockheight,
|
||||
required this.accountIndex,
|
||||
required this.addressIndexList,
|
||||
required this.addressIndex,
|
||||
required this.paymentId,
|
||||
required this.amount,
|
||||
required this.isSpend,
|
||||
|
|
|
@ -66,9 +66,20 @@ String getSeedLegacy(String? language) {
|
|||
return legacy;
|
||||
}
|
||||
|
||||
String getAddress({int accountIndex = 0, int addressIndex = 0}) =>
|
||||
monero.Wallet_address(wptr!,
|
||||
Map<int, Map<int, Map<int, String>>> addressCache = {};
|
||||
|
||||
String getAddress({int accountIndex = 0, int addressIndex = 0}) {
|
||||
// print("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}");
|
||||
while (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
|
||||
print("adding subaddress");
|
||||
monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
|
||||
}
|
||||
addressCache[wptr!.address] ??= {};
|
||||
addressCache[wptr!.address]![accountIndex] ??= {};
|
||||
addressCache[wptr!.address]![accountIndex]![addressIndex] ??= monero.Wallet_address(wptr!,
|
||||
accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
return addressCache[wptr!.address]![accountIndex]![addressIndex]!;
|
||||
}
|
||||
|
||||
int getFullBalance({int accountIndex = 0}) =>
|
||||
monero.Wallet_balance(wptr!, accountIndex: accountIndex);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cw_core/subaddress.dart';
|
||||
import 'package:cw_monero/api/coins_info.dart';
|
||||
import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list;
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
|
@ -54,18 +55,12 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
final address = s.address;
|
||||
final label = s.label;
|
||||
final id = s.addressIndex;
|
||||
final hasDefaultAddressName =
|
||||
label.toLowerCase() == 'Primary account'.toLowerCase() ||
|
||||
label.toLowerCase() == 'Untitled account'.toLowerCase();
|
||||
final isPrimaryAddress = id == 0 && hasDefaultAddressName;
|
||||
return Subaddress(
|
||||
id: id,
|
||||
address: address,
|
||||
label: isPrimaryAddress
|
||||
? 'Primary address'
|
||||
: hasDefaultAddressName
|
||||
? ''
|
||||
: label);
|
||||
balance: (s.received/1e12).toStringAsFixed(6),
|
||||
txCount: s.txCount,
|
||||
label: label);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
|
@ -103,6 +98,9 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
required List<String> usedAddresses,
|
||||
}) async {
|
||||
_usedAddresses.addAll(usedAddresses);
|
||||
final _all = _usedAddresses.toSet().toList();
|
||||
_usedAddresses.clear();
|
||||
_usedAddresses.addAll(_all);
|
||||
if (_isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
@ -124,7 +122,8 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
Future<List<Subaddress>> _getAllUnusedAddresses(
|
||||
{required int accountIndex, required String label}) async {
|
||||
final allAddresses = subaddress_list.getAllSubaddresses();
|
||||
if (allAddresses.isEmpty || _usedAddresses.contains(allAddresses.last)) {
|
||||
// first because addresses come in reversed order.
|
||||
if (allAddresses.isEmpty || _usedAddresses.contains(allAddresses.first.address)) {
|
||||
final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label);
|
||||
if (!isAddressUnused) {
|
||||
return await _getAllUnusedAddresses(accountIndex: accountIndex, label: label);
|
||||
|
@ -139,12 +138,13 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
return Subaddress(
|
||||
id: id,
|
||||
address: address,
|
||||
balance: (s.received/1e12).toStringAsFixed(6),
|
||||
txCount: s.txCount,
|
||||
label: id == 0 &&
|
||||
label.toLowerCase() == 'Primary account'.toLowerCase()
|
||||
? 'Primary address'
|
||||
: label);
|
||||
})
|
||||
.toList();
|
||||
}).toList().reversed.toList();
|
||||
}
|
||||
|
||||
Future<bool> _newSubaddress({required int accountIndex, required String label}) async {
|
||||
|
|
|
@ -59,7 +59,7 @@ abstract class MoneroWalletBase
|
|||
}),
|
||||
_isTransactionUpdating = false,
|
||||
_hasSyncAfterStartup = false,
|
||||
isEnabledAutoGenerateSubaddress = false,
|
||||
isEnabledAutoGenerateSubaddress = true,
|
||||
_password = password,
|
||||
syncStatus = NotConnectedSyncStatus(),
|
||||
unspentCoins = [],
|
||||
|
@ -83,6 +83,9 @@ abstract class MoneroWalletBase
|
|||
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||
_updateSubAddress(enabled, account: walletAddresses.account);
|
||||
});
|
||||
_onTxHistoryChangeReaction = reaction((_) => transactionHistory, (__) {
|
||||
_updateSubAddress(isEnabledAutoGenerateSubaddress, account: walletAddresses.account);
|
||||
});
|
||||
}
|
||||
|
||||
static const int _autoSaveInterval = 30;
|
||||
|
@ -124,6 +127,7 @@ abstract class MoneroWalletBase
|
|||
|
||||
monero_wallet.SyncListener? _listener;
|
||||
ReactionDisposer? _onAccountChangeReaction;
|
||||
ReactionDisposer? _onTxHistoryChangeReaction;
|
||||
bool _isTransactionUpdating;
|
||||
bool _hasSyncAfterStartup;
|
||||
Timer? _autoSaveTimer;
|
||||
|
@ -149,8 +153,10 @@ abstract class MoneroWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
_autoSaveTimer =
|
||||
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||
_autoSaveTimer = Timer.periodic(
|
||||
Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||
// update transaction details after restore
|
||||
walletAddresses.subaddressList.update(accountIndex: walletAddresses.account?.id??0);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -160,6 +166,7 @@ abstract class MoneroWalletBase
|
|||
void close() async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onTxHistoryChangeReaction?.reaction.dispose();
|
||||
_autoSaveTimer?.cancel();
|
||||
}
|
||||
|
||||
|
@ -558,8 +565,9 @@ abstract class MoneroWalletBase
|
|||
@override
|
||||
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||
transaction_history.refreshTransactions();
|
||||
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
|
||||
.fold<Map<String, MoneroTransactionInfo>>(<String, MoneroTransactionInfo>{},
|
||||
return (await _getAllTransactionsOfAccount(walletAddresses.account?.id))
|
||||
.fold<Map<String, MoneroTransactionInfo>>(
|
||||
<String, MoneroTransactionInfo>{},
|
||||
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||
acc[tx.id] = tx;
|
||||
return acc;
|
||||
|
@ -573,8 +581,8 @@ abstract class MoneroWalletBase
|
|||
}
|
||||
|
||||
_isTransactionUpdating = true;
|
||||
transactionHistory.clear();
|
||||
final transactions = await fetchTransactions();
|
||||
transactionHistory.clear();
|
||||
transactionHistory.addMany(transactions);
|
||||
await transactionHistory.save();
|
||||
_isTransactionUpdating = false;
|
||||
|
@ -587,28 +595,31 @@ abstract class MoneroWalletBase
|
|||
String getSubaddressLabel(int accountIndex, int addressIndex) =>
|
||||
monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
|
||||
|
||||
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => transaction_history
|
||||
.getAllTransactions()
|
||||
.map(
|
||||
(row) => MoneroTransactionInfo(
|
||||
row.hash,
|
||||
row.blockheight,
|
||||
row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming,
|
||||
row.timeStamp,
|
||||
row.isPending,
|
||||
row.amount,
|
||||
row.accountIndex,
|
||||
0,
|
||||
row.fee,
|
||||
row.confirmations,
|
||||
)..additionalInfo = <String, dynamic>{
|
||||
'key': row.key,
|
||||
'accountIndex': row.accountIndex,
|
||||
'addressIndex': row.addressIndex
|
||||
},
|
||||
)
|
||||
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
||||
.toList();
|
||||
Future<List<MoneroTransactionInfo>> _getAllTransactionsOfAccount(int? accountIndex) async =>
|
||||
(await transaction_history
|
||||
.getAllTransactions())
|
||||
.map(
|
||||
(row) => MoneroTransactionInfo(
|
||||
row.hash,
|
||||
row.blockheight,
|
||||
row.isSpend
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
row.timeStamp,
|
||||
row.isPending,
|
||||
row.amount,
|
||||
row.accountIndex,
|
||||
0,
|
||||
row.fee,
|
||||
row.confirmations,
|
||||
)..additionalInfo = <String, dynamic>{
|
||||
'key': row.key,
|
||||
'accountIndex': row.accountIndex,
|
||||
'addressIndex': row.addressIndex
|
||||
},
|
||||
)
|
||||
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
||||
.toList();
|
||||
|
||||
void _setListeners() {
|
||||
_listener?.stop();
|
||||
|
|
|
@ -3,6 +3,8 @@ import 'package:cw_core/address_info.dart';
|
|||
import 'package:cw_core/subaddress.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list;
|
||||
import 'package:cw_monero/api/transaction_history.dart';
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:cw_monero/monero_account_list.dart';
|
||||
import 'package:cw_monero/monero_subaddress_list.dart';
|
||||
|
@ -27,6 +29,30 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get latestAddress {
|
||||
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
|
||||
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
while (hiddenAddresses.contains(address)) {
|
||||
addressIndex++;
|
||||
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
subaddressList.update(accountIndex: account?.id??0);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@override
|
||||
String get addressForExchange {
|
||||
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
|
||||
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
while (hiddenAddresses.contains(address) || manualAddresses.contains(address) || subaddress_list.getRawLabel(accountIndex: account?.id??0, addressIndex: addressIndex).isNotEmpty) {
|
||||
addressIndex++;
|
||||
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
subaddressList.update(accountIndex: account?.id??0);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@observable
|
||||
Account? account;
|
||||
|
||||
|
@ -37,10 +63,12 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
MoneroAccountList accountList;
|
||||
|
||||
Set<String> usedAddresses = Set();
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
account = accountList.accounts.isEmpty ? Account(id: 0, label: "Primary address") : accountList.accounts.first;
|
||||
updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
@ -89,8 +117,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
void updateSubaddressList({required int accountIndex}) {
|
||||
subaddressList.update(accountIndex: accountIndex);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress!.address;
|
||||
address = subaddressList.subaddresses.isNotEmpty
|
||||
? subaddressList.subaddresses.first.address
|
||||
: getAddress();
|
||||
}
|
||||
|
||||
Future<void> updateUsedSubaddress() async {
|
||||
|
@ -109,7 +138,10 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
accountIndex: accountIndex,
|
||||
defaultLabel: defaultLabel,
|
||||
usedAddresses: usedAddresses.toList());
|
||||
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last;
|
||||
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel, balance: '0', txCount: 0) : subaddressList.subaddresses.last;
|
||||
if (num.tryParse(subaddress!.balance??'0') != 0) {
|
||||
getAddress(accountIndex: accountIndex, addressIndex: (subaddress?.id??0)+1);
|
||||
}
|
||||
address = subaddress!.address;
|
||||
}
|
||||
|
||||
|
|
|
@ -463,8 +463,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "impls/monero.dart"
|
||||
ref: "3cb38bee9385faf46b03fd73aab85f3ac4115bf7"
|
||||
resolved-ref: "3cb38bee9385faf46b03fd73aab85f3ac4115bf7"
|
||||
ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
|
|
|
@ -25,7 +25,7 @@ dependencies:
|
|||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7 # monero_c hash
|
||||
ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cw_wownero/api/account_list.dart';
|
||||
import 'package:cw_wownero/api/transaction_history.dart';
|
||||
import 'package:cw_wownero/api/wallet.dart';
|
||||
import 'package:monero/wownero.dart' as wownero;
|
||||
|
||||
|
@ -28,27 +29,75 @@ class Subaddress {
|
|||
Subaddress({
|
||||
required this.addressIndex,
|
||||
required this.accountIndex,
|
||||
required this.txCount,
|
||||
required this.received,
|
||||
});
|
||||
String get address => wownero.Wallet_address(
|
||||
wptr!,
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
late String address = getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
final int addressIndex;
|
||||
final int accountIndex;
|
||||
String get label => wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
final int txCount;
|
||||
final int received;
|
||||
}
|
||||
|
||||
class TinyTransactionDetails {
|
||||
TinyTransactionDetails({
|
||||
required this.address,
|
||||
required this.amount,
|
||||
});
|
||||
final List<String> address;
|
||||
final int amount;
|
||||
}
|
||||
|
||||
int lastWptr = 0;
|
||||
int lastTxCount = 0;
|
||||
List<TinyTransactionDetails> ttDetails = [];
|
||||
|
||||
List<Subaddress> getAllSubaddresses() {
|
||||
txhistory = wownero.Wallet_history(wptr!);
|
||||
final txCount = wownero.TransactionHistory_count(txhistory!);
|
||||
if (lastTxCount != txCount && lastWptr != wptr!.address) {
|
||||
final List<TinyTransactionDetails> newttDetails = [];
|
||||
lastTxCount = txCount;
|
||||
lastWptr = wptr!.address;
|
||||
for (var i = 0; i < txCount; i++) {
|
||||
final tx = wownero.TransactionHistory_transaction(txhistory!, index: i);
|
||||
final subaddrs = wownero.TransactionInfo_subaddrIndex(tx).split(",");
|
||||
final account = wownero.TransactionInfo_subaddrAccount(tx);
|
||||
newttDetails.add(TinyTransactionDetails(
|
||||
address: List.generate(subaddrs.length, (index) => getAddress(accountIndex: account, addressIndex: int.tryParse(subaddrs[index])??0)),
|
||||
amount: wownero.TransactionInfo_amount(tx),
|
||||
));
|
||||
}
|
||||
ttDetails.clear();
|
||||
ttDetails.addAll(newttDetails);
|
||||
}
|
||||
final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
|
||||
final list = List.generate(size, (index) {
|
||||
final ttDetailsLocal = ttDetails.where((element) {
|
||||
final address = getAddress(
|
||||
accountIndex: subaddress!.accountIndex,
|
||||
addressIndex: index,
|
||||
);
|
||||
if (address == element.address) return true;
|
||||
return false;
|
||||
}).toList();
|
||||
int received = 0;
|
||||
for (var i = 0; i < ttDetailsLocal.length; i++) {
|
||||
received += ttDetailsLocal[i].amount;
|
||||
}
|
||||
return Subaddress(
|
||||
accountIndex: subaddress!.accountIndex,
|
||||
addressIndex: index,
|
||||
received: received,
|
||||
txCount: ttDetailsLocal.length,
|
||||
);
|
||||
}).reversed.toList();
|
||||
if (list.isEmpty) {
|
||||
list.add(Subaddress(addressIndex: 0, accountIndex: subaddress!.accountIndex));
|
||||
list.add(Subaddress(addressIndex: 0, accountIndex: subaddress!.accountIndex, txCount: 0, received: 0));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
@ -58,6 +107,10 @@ void addSubaddressSync({required int accountIndex, required String label}) {
|
|||
refreshSubaddresses(accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
int numSubaddresses(int subaccountIndex) {
|
||||
return wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaccountIndex);
|
||||
}
|
||||
|
||||
void setLabelForSubaddressSync(
|
||||
{required int accountIndex, required int addressIndex, required String label}) {
|
||||
wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:isolate';
|
|||
|
||||
import 'package:cw_wownero/api/account_list.dart';
|
||||
import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart';
|
||||
import 'package:cw_wownero/api/wallet.dart';
|
||||
import 'package:cw_wownero/api/wownero_output.dart';
|
||||
import 'package:cw_wownero/api/structs/pending_transaction.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
@ -16,9 +17,16 @@ String getTxKey(String txId) {
|
|||
|
||||
wownero.TransactionHistory? txhistory;
|
||||
|
||||
void refreshTransactions() {
|
||||
bool isRefreshingTx = false;
|
||||
Future<void> refreshTransactions() async {
|
||||
if (isRefreshingTx == true) return;
|
||||
isRefreshingTx = true;
|
||||
txhistory ??= wownero.Wallet_history(wptr!);
|
||||
wownero.TransactionHistory_refresh(txhistory!);
|
||||
final ptr = txhistory!.address;
|
||||
await Isolate.run(() {
|
||||
wownero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
|
||||
});
|
||||
isRefreshingTx = false;
|
||||
}
|
||||
|
||||
int countOfTransactions() => wownero.TransactionHistory_count(txhistory!);
|
||||
|
@ -45,6 +53,8 @@ List<Transaction> getAllTransactions() {
|
|||
confirmations: 0,
|
||||
blockheight: 0,
|
||||
accountIndex: i,
|
||||
addressIndex: 0,
|
||||
addressIndexList: [0],
|
||||
paymentId: "",
|
||||
amount: fullBalance - availBalance,
|
||||
isSpend: false,
|
||||
|
@ -243,23 +253,28 @@ Future<PendingTransactionDescription> createTransactionMultDest(
|
|||
|
||||
class Transaction {
|
||||
final String displayLabel;
|
||||
String subaddressLabel = wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: 0, addressIndex: 0);
|
||||
late final String address = wownero.Wallet_address(
|
||||
wptr!,
|
||||
accountIndex: 0,
|
||||
addressIndex: 0,
|
||||
late final String subaddressLabel = wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
late final String address = getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex,
|
||||
);
|
||||
late final List<String> addressList = List.generate(addressIndexList.length, (index) =>
|
||||
getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndexList[index],
|
||||
));
|
||||
final String description;
|
||||
final int fee;
|
||||
final int confirmations;
|
||||
late final bool isPending = confirmations < 3;
|
||||
final int blockheight;
|
||||
final int addressIndex = 0;
|
||||
final int addressIndex;
|
||||
final int accountIndex;
|
||||
final List<int> addressIndexList;
|
||||
final String paymentId;
|
||||
final int amount;
|
||||
final bool isSpend;
|
||||
late DateTime timeStamp;
|
||||
late final DateTime timeStamp;
|
||||
late final bool isConfirmed = !isPending;
|
||||
final String hash;
|
||||
final String key;
|
||||
|
@ -301,6 +316,8 @@ class Transaction {
|
|||
amount = wownero.TransactionInfo_amount(txInfo),
|
||||
paymentId = wownero.TransactionInfo_paymentId(txInfo),
|
||||
accountIndex = wownero.TransactionInfo_subaddrAccount(txInfo),
|
||||
addressIndex = int.tryParse(wownero.TransactionInfo_subaddrIndex(txInfo).split(", ")[0]) ?? 0,
|
||||
addressIndexList = wownero.TransactionInfo_subaddrIndex(txInfo).split(", ").map((e) => int.tryParse(e) ?? 0).toList(),
|
||||
blockheight = wownero.TransactionInfo_blockHeight(txInfo),
|
||||
confirmations = wownero.TransactionInfo_confirmations(txInfo),
|
||||
fee = wownero.TransactionInfo_fee(txInfo),
|
||||
|
@ -314,6 +331,8 @@ class Transaction {
|
|||
required this.confirmations,
|
||||
required this.blockheight,
|
||||
required this.accountIndex,
|
||||
required this.addressIndex,
|
||||
required this.addressIndexList,
|
||||
required this.paymentId,
|
||||
required this.amount,
|
||||
required this.isSpend,
|
||||
|
|
|
@ -67,10 +67,19 @@ String getSeedLegacy(String? language) {
|
|||
}
|
||||
return legacy;
|
||||
}
|
||||
Map<int, Map<int, Map<int, String>>> addressCache = {};
|
||||
|
||||
String getAddress({int accountIndex = 0, int addressIndex = 1}) =>
|
||||
wownero.Wallet_address(wptr!,
|
||||
String getAddress({int accountIndex = 0, int addressIndex = 1}) {
|
||||
while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
|
||||
print("adding subaddress");
|
||||
wownero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
|
||||
}
|
||||
addressCache[wptr!.address] ??= {};
|
||||
addressCache[wptr!.address]![accountIndex] ??= {};
|
||||
addressCache[wptr!.address]![accountIndex]![addressIndex] ??= wownero.Wallet_address(wptr!,
|
||||
accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
return addressCache[wptr!.address]![accountIndex]![addressIndex]!;
|
||||
}
|
||||
|
||||
int getFullBalance({int accountIndex = 0}) =>
|
||||
wownero.Wallet_balance(wptr!, accountIndex: accountIndex);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cw_core/subaddress.dart';
|
||||
import 'package:cw_wownero/api/coins_info.dart';
|
||||
import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list;
|
||||
import 'package:cw_wownero/api/wallet.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
|
@ -61,6 +62,8 @@ abstract class WowneroSubaddressListBase with Store {
|
|||
return Subaddress(
|
||||
id: id,
|
||||
address: address,
|
||||
balance: (s.received/1e12).toStringAsFixed(6),
|
||||
txCount: s.txCount,
|
||||
label: isPrimaryAddress
|
||||
? 'Primary address'
|
||||
: hasDefaultAddressName
|
||||
|
@ -103,6 +106,9 @@ abstract class WowneroSubaddressListBase with Store {
|
|||
required List<String> usedAddresses,
|
||||
}) async {
|
||||
_usedAddresses.addAll(usedAddresses);
|
||||
final _all = _usedAddresses.toSet().toList();
|
||||
_usedAddresses.clear();
|
||||
_usedAddresses.addAll(_all);
|
||||
if (_isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
@ -140,6 +146,8 @@ abstract class WowneroSubaddressListBase with Store {
|
|||
return Subaddress(
|
||||
id: id,
|
||||
address: address,
|
||||
balance: (s.received/1e12).toStringAsFixed(6),
|
||||
txCount: s.txCount,
|
||||
label: id == 0 &&
|
||||
label.toLowerCase() == 'Primary account'.toLowerCase()
|
||||
? 'Primary address'
|
||||
|
|
|
@ -59,7 +59,7 @@ abstract class WowneroWalletBase
|
|||
_isTransactionUpdating = false,
|
||||
_hasSyncAfterStartup = false,
|
||||
_password = password,
|
||||
isEnabledAutoGenerateSubaddress = false,
|
||||
isEnabledAutoGenerateSubaddress = true,
|
||||
syncStatus = NotConnectedSyncStatus(),
|
||||
unspentCoins = [],
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
|
@ -82,6 +82,10 @@ abstract class WowneroWalletBase
|
|||
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||
_updateSubAddress(enabled, account: walletAddresses.account);
|
||||
});
|
||||
|
||||
_onTxHistoryChangeReaction = reaction((_) => transactionHistory, (__) {
|
||||
_updateSubAddress(isEnabledAutoGenerateSubaddress, account: walletAddresses.account);
|
||||
});
|
||||
}
|
||||
|
||||
static const int _autoSaveInterval = 30;
|
||||
|
@ -123,6 +127,7 @@ abstract class WowneroWalletBase
|
|||
|
||||
wownero_wallet.SyncListener? _listener;
|
||||
ReactionDisposer? _onAccountChangeReaction;
|
||||
ReactionDisposer? _onTxHistoryChangeReaction;
|
||||
bool _isTransactionUpdating;
|
||||
bool _hasSyncAfterStartup;
|
||||
Timer? _autoSaveTimer;
|
||||
|
@ -158,6 +163,7 @@ abstract class WowneroWalletBase
|
|||
void close() async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onTxHistoryChangeReaction?.reaction.dispose();
|
||||
_autoSaveTimer?.cancel();
|
||||
}
|
||||
|
||||
|
@ -564,8 +570,8 @@ abstract class WowneroWalletBase
|
|||
}
|
||||
|
||||
_isTransactionUpdating = true;
|
||||
transactionHistory.clear();
|
||||
final transactions = await fetchTransactions();
|
||||
transactionHistory.clear();
|
||||
transactionHistory.addMany(transactions);
|
||||
await transactionHistory.save();
|
||||
_isTransactionUpdating = false;
|
||||
|
|
|
@ -3,6 +3,8 @@ import 'package:cw_core/address_info.dart';
|
|||
import 'package:cw_core/subaddress.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_wownero/api/transaction_history.dart';
|
||||
import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list;
|
||||
import 'package:cw_wownero/api/wallet.dart';
|
||||
import 'package:cw_wownero/wownero_account_list.dart';
|
||||
import 'package:cw_wownero/wownero_subaddress_list.dart';
|
||||
|
@ -27,6 +29,27 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get latestAddress {
|
||||
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
|
||||
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
while (hiddenAddresses.contains(address)) {
|
||||
addressIndex++;
|
||||
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@override
|
||||
String get addressForExchange {
|
||||
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;
|
||||
var address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
while (hiddenAddresses.contains(address) || manualAddresses.contains(address)) {
|
||||
addressIndex++;
|
||||
address = getAddress(accountIndex: account?.id??0, addressIndex: addressIndex);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
@observable
|
||||
Account? account;
|
||||
|
||||
|
@ -36,11 +59,14 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
WowneroSubaddressList subaddressList;
|
||||
|
||||
WowneroAccountList accountList;
|
||||
|
||||
@override
|
||||
Set<String> usedAddresses = Set();
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
account = accountList.accounts.isEmpty ? Account(id: 0, label: "Primary address") : accountList.accounts.first;
|
||||
updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
@ -89,8 +115,9 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
void updateSubaddressList({required int accountIndex}) {
|
||||
subaddressList.update(accountIndex: accountIndex);
|
||||
subaddress = subaddressList.subaddresses.first;
|
||||
address = subaddress!.address;
|
||||
address = subaddressList.subaddresses.isNotEmpty
|
||||
? subaddressList.subaddresses.first.address
|
||||
: getAddress();
|
||||
}
|
||||
|
||||
Future<void> updateUsedSubaddress() async {
|
||||
|
@ -109,7 +136,7 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
accountIndex: accountIndex,
|
||||
defaultLabel: defaultLabel,
|
||||
usedAddresses: usedAddresses.toList());
|
||||
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last;
|
||||
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel, balance: '0', txCount: 0) : subaddressList.subaddresses.last;
|
||||
address = subaddress!.address;
|
||||
}
|
||||
|
||||
|
|
|
@ -463,8 +463,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "impls/monero.dart"
|
||||
ref: "3cb38bee9385faf46b03fd73aab85f3ac4115bf7"
|
||||
resolved-ref: "3cb38bee9385faf46b03fd73aab85f3ac4115bf7"
|
||||
ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
resolved-ref: "6eb571ea498ed7b854934785f00fabfd0dadf75b"
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
|
|
|
@ -25,7 +25,7 @@ dependencies:
|
|||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7 # monero_c hash
|
||||
ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ import 'package:cake_wallet/solana/solana.dart';
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
|
||||
const BEFORE_REGEX = '(^|\\s)';
|
||||
const AFTER_REGEX = '(\$|\\s)';
|
||||
|
||||
class AddressValidator extends TextValidator {
|
||||
AddressValidator({required CryptoCurrency type})
|
||||
: super(
|
||||
|
@ -12,30 +15,34 @@ class AddressValidator extends TextValidator {
|
|||
useAdditionalValidation: type == CryptoCurrency.btc || type == CryptoCurrency.ltc
|
||||
? (String txt) => BitcoinAddressUtils.validateAddress(
|
||||
address: txt,
|
||||
network: type == CryptoCurrency.btc ? BitcoinNetwork.mainnet : LitecoinNetwork.mainnet,
|
||||
network: type == CryptoCurrency.btc
|
||||
? BitcoinNetwork.mainnet
|
||||
: LitecoinNetwork.mainnet,
|
||||
)
|
||||
: null,
|
||||
pattern: getPattern(type),
|
||||
length: getLength(type));
|
||||
|
||||
static String getPattern(CryptoCurrency type) {
|
||||
var pattern = "";
|
||||
if (type is Erc20Token) {
|
||||
return '0x[0-9a-zA-Z]';
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
}
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$';
|
||||
pattern = '4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}';
|
||||
case CryptoCurrency.ada:
|
||||
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
||||
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
||||
pattern = '[0-9a-zA-Z]{59}|[0-9a-zA-Z]{92}|[0-9a-zA-Z]{104}'
|
||||
'|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}';
|
||||
case CryptoCurrency.btc:
|
||||
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$';
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.ltc:
|
||||
return '^${P2wpkhAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
|
||||
pattern = '^${P2wpkhAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
|
||||
case CryptoCurrency.nano:
|
||||
return '[0-9a-zA-Z_]';
|
||||
pattern = '[0-9a-zA-Z_]+';
|
||||
case CryptoCurrency.banano:
|
||||
return '[0-9a-zA-Z_]';
|
||||
pattern = '[0-9a-zA-Z_]+';
|
||||
case CryptoCurrency.usdc:
|
||||
case CryptoCurrency.usdcpoly:
|
||||
case CryptoCurrency.usdtPoly:
|
||||
|
@ -71,11 +78,11 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.dydx:
|
||||
case CryptoCurrency.steth:
|
||||
case CryptoCurrency.shib:
|
||||
return '0x[0-9a-zA-Z]';
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.xrp:
|
||||
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
|
||||
pattern = '[0-9a-zA-Z]{34}|X[0-9a-zA-Z]{46}';
|
||||
case CryptoCurrency.xhv:
|
||||
return '^hvx|hvi|hvs[0-9a-zA-Z]';
|
||||
pattern = 'hvx|hvi|hvs[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.xag:
|
||||
case CryptoCurrency.xau:
|
||||
case CryptoCurrency.xaud:
|
||||
|
@ -97,38 +104,41 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.dash:
|
||||
case CryptoCurrency.eos:
|
||||
case CryptoCurrency.wow:
|
||||
return '[0-9a-zA-Z]';
|
||||
pattern = '[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.bch:
|
||||
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
|
||||
pattern =
|
||||
'(?!bitcoincash:)[0-9a-zA-Z]*|(?!bitcoincash:)q|p[0-9a-zA-Z]{41}|(?!bitcoincash:)q|p[0-9a-zA-Z]{42}|bitcoincash:q|p[0-9a-zA-Z]{41}|bitcoincash:q|p[0-9a-zA-Z]{42}';
|
||||
case CryptoCurrency.bnb:
|
||||
return '[0-9a-zA-Z]';
|
||||
pattern = '[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.hbar:
|
||||
return '[0-9a-zA-Z.]';
|
||||
pattern = '[0-9a-zA-Z.]+';
|
||||
case CryptoCurrency.zaddr:
|
||||
return '^zs[0-9a-zA-Z]{75}';
|
||||
pattern = 'zs[0-9a-zA-Z]{75}';
|
||||
case CryptoCurrency.zec:
|
||||
return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$';
|
||||
pattern = 't1[0-9a-zA-Z]{33}|t3[0-9a-zA-Z]{33}';
|
||||
case CryptoCurrency.dcr:
|
||||
return 'D[ksecS]([0-9a-zA-Z])+';
|
||||
pattern = 'D[ksecS]([0-9a-zA-Z])+';
|
||||
case CryptoCurrency.rvn:
|
||||
return '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
pattern = '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
case CryptoCurrency.near:
|
||||
return '[0-9a-f]{64}';
|
||||
pattern = '[0-9a-f]{64}';
|
||||
case CryptoCurrency.rune:
|
||||
return 'thor1[0-9a-z]{38}';
|
||||
pattern = 'thor1[0-9a-z]{38}';
|
||||
case CryptoCurrency.scrt:
|
||||
return 'secret1[0-9a-z]{38}';
|
||||
pattern = 'secret1[0-9a-z]{38}';
|
||||
case CryptoCurrency.stx:
|
||||
return 'S[MP][0-9a-zA-Z]+';
|
||||
pattern = 'S[MP][0-9a-zA-Z]+';
|
||||
case CryptoCurrency.kmd:
|
||||
return 'R[0-9a-zA-Z]{33}';
|
||||
pattern = 'R[0-9a-zA-Z]{33}';
|
||||
case CryptoCurrency.pivx:
|
||||
return 'D([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
case CryptoCurrency.btcln:
|
||||
return '^(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
||||
pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
||||
default:
|
||||
return '[0-9a-zA-Z]';
|
||||
pattern = '[0-9a-zA-Z]+';
|
||||
}
|
||||
|
||||
return '$BEFORE_REGEX($pattern)$AFTER_REGEX';
|
||||
}
|
||||
|
||||
static List<int>? getLength(CryptoCurrency type) {
|
||||
|
@ -269,57 +279,54 @@ class AddressValidator extends TextValidator {
|
|||
}
|
||||
|
||||
static String? getAddressFromStringPattern(CryptoCurrency type) {
|
||||
String? pattern = null;
|
||||
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
case CryptoCurrency.wow:
|
||||
return '([^0-9a-zA-Z]|^)4[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '(4[0-9a-zA-Z]{94})'
|
||||
'|(8[0-9a-zA-Z]{94})'
|
||||
'|([0-9a-zA-Z]{106})';
|
||||
case CryptoCurrency.btc:
|
||||
return '([^0-9a-zA-Z]|^)([1mn][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2pkhAddress type
|
||||
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
|
||||
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)' //P2trAddress type
|
||||
'|${SilentPaymentAddress.regex.pattern}\$';
|
||||
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.ltc:
|
||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
pattern = '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)((ltc|t)mweb1q[ac-hj-np-z02-9]{90,120})([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.eth:
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
case CryptoCurrency.maticpoly:
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.nano:
|
||||
return 'nano_[0-9a-zA-Z]{60}';
|
||||
pattern = 'nano_[0-9a-zA-Z]{60}';
|
||||
case CryptoCurrency.banano:
|
||||
return 'ban_[0-9a-zA-Z]{60}';
|
||||
pattern = 'ban_[0-9a-zA-Z]{60}';
|
||||
case CryptoCurrency.bch:
|
||||
return 'bitcoincash:q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
|
||||
'|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '(bitcoincash:)?q[0-9a-zA-Z]{41,42}';
|
||||
case CryptoCurrency.sol:
|
||||
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '[1-9A-HJ-NP-Za-km-z]+';
|
||||
case CryptoCurrency.trx:
|
||||
return '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
default:
|
||||
if (type.tag == CryptoCurrency.eth.title) {
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]{42}';
|
||||
}
|
||||
if (type.tag == CryptoCurrency.maticpoly.tag) {
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
pattern = '0x[0-9a-zA-Z]{42}';
|
||||
}
|
||||
if (type.tag == CryptoCurrency.sol.title) {
|
||||
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||
pattern = '[1-9A-HJ-NP-Za-km-z]{43,44}';
|
||||
}
|
||||
if (type.tag == CryptoCurrency.trx.title) {
|
||||
return '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pattern != null) {
|
||||
return "$BEFORE_REGEX($pattern)$AFTER_REGEX";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -278,8 +278,6 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
|
|||
return 'ERC20';
|
||||
case 'BSC':
|
||||
return 'BEP20';
|
||||
case 'POLY':
|
||||
return 'MATIC';
|
||||
default:
|
||||
return currency.tag!;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,13 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
|
|||
ObservableList<Subaddress> get subaddresses {
|
||||
final moneroWallet = _wallet as MoneroWallet;
|
||||
final subAddresses = moneroWallet.walletAddresses.subaddressList.subaddresses
|
||||
.map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label))
|
||||
.map((sub) => Subaddress(
|
||||
id: sub.id,
|
||||
address: sub.address,
|
||||
label: sub.label,
|
||||
received: sub.balance??"unknown",
|
||||
txCount: sub.txCount??0,
|
||||
))
|
||||
.toList();
|
||||
return ObservableList<Subaddress>.of(subAddresses);
|
||||
}
|
||||
|
@ -83,7 +89,12 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
|
|||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.walletAddresses.subaddressList
|
||||
.getAll()
|
||||
.map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
|
||||
.map((sub) => Subaddress(
|
||||
id: sub.id,
|
||||
label: sub.label,
|
||||
address: sub.address,
|
||||
txCount: sub.txCount??0,
|
||||
received: sub.balance??'unknown'))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
@ -91,7 +102,7 @@ class CWMoneroSubaddressList extends MoneroSubaddressList {
|
|||
Future<void> addSubaddress(Object wallet,
|
||||
{required int accountIndex, required String label}) async {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
await moneroWallet.walletAddresses.subaddressList
|
||||
return await moneroWallet.walletAddresses.subaddressList
|
||||
.addSubaddress(accountIndex: accountIndex, label: label);
|
||||
}
|
||||
|
||||
|
|
|
@ -155,13 +155,14 @@ class AddressPage extends BasePage {
|
|||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
||||
ThemeType.light))),
|
||||
ThemeType.light,
|
||||
))),
|
||||
SizedBox(height: 16),
|
||||
Observer(builder: (_) {
|
||||
if (addressListViewModel.hasAddressList) {
|
||||
return SelectButton(
|
||||
text: addressListViewModel.buttonTitle,
|
||||
onTap: () async => Navigator.of(context).pushNamed(Routes.receive),
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
||||
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
|
|
|
@ -20,7 +20,7 @@ class SignForm extends StatefulWidget {
|
|||
SignFormState createState() => SignFormState();
|
||||
}
|
||||
|
||||
class SignFormState extends State<SignForm> {
|
||||
class SignFormState extends State<SignForm> with AutomaticKeepAliveClientMixin {
|
||||
SignFormState()
|
||||
: formKey = GlobalKey<FormState>(),
|
||||
messageController = TextEditingController(),
|
||||
|
@ -42,8 +42,12 @@ class SignFormState extends State<SignForm> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
|
|
|
@ -15,7 +15,7 @@ class VerifyForm extends StatefulWidget {
|
|||
VerifyFormState createState() => VerifyFormState();
|
||||
}
|
||||
|
||||
class VerifyFormState extends State<VerifyForm> {
|
||||
class VerifyFormState extends State<VerifyForm> with AutomaticKeepAliveClientMixin {
|
||||
VerifyFormState()
|
||||
: formKey = GlobalKey<FormState>(),
|
||||
messageController = TextEditingController(),
|
||||
|
@ -36,9 +36,13 @@ class VerifyFormState extends State<VerifyForm> {
|
|||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: Form(
|
||||
|
|
|
@ -509,7 +509,7 @@ class ExchangePage extends BasePage {
|
|||
}
|
||||
});
|
||||
|
||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.address, (String address) {
|
||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.addressForExchange, (String address) {
|
||||
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
||||
depositKey.currentState!.changeAddress(address: address);
|
||||
}
|
||||
|
@ -565,7 +565,7 @@ class ExchangePage extends BasePage {
|
|||
key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
|
||||
|
||||
key.currentState!.changeAddress(
|
||||
address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.address : '');
|
||||
address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.addressForExchange : '');
|
||||
|
||||
key.currentState!.changeAmount(amount: '');
|
||||
}
|
||||
|
@ -576,9 +576,9 @@ class ExchangePage extends BasePage {
|
|||
|
||||
if (isCurrentTypeWallet) {
|
||||
key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
|
||||
key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.address;
|
||||
key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.addressForExchange;
|
||||
} else if (key.currentState!.addressController.text ==
|
||||
exchangeViewModel.wallet.walletAddresses.address) {
|
||||
exchangeViewModel.wallet.walletAddresses.addressForExchange) {
|
||||
key.currentState!.changeWalletName('');
|
||||
key.currentState!.addressController.text = '';
|
||||
}
|
||||
|
@ -629,7 +629,7 @@ class ExchangePage extends BasePage {
|
|||
initialCurrency: exchangeViewModel.depositCurrency,
|
||||
initialWalletName: depositWalletName ?? '',
|
||||
initialAddress: exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
? exchangeViewModel.wallet.walletAddresses.addressForExchange
|
||||
: exchangeViewModel.depositAddress,
|
||||
initialIsAmountEditable: true,
|
||||
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
|
||||
|
@ -694,7 +694,7 @@ class ExchangePage extends BasePage {
|
|||
initialCurrency: exchangeViewModel.receiveCurrency,
|
||||
initialWalletName: receiveWalletName ?? '',
|
||||
initialAddress: exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
? exchangeViewModel.wallet.walletAddresses.addressForExchange
|
||||
: exchangeViewModel.receiveAddress,
|
||||
initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable,
|
||||
isAmountEstimated: true,
|
||||
|
|
|
@ -129,7 +129,7 @@ class ExchangeTemplatePage extends BasePage {
|
|||
initialWalletName: depositWalletName ?? '',
|
||||
initialAddress: exchangeViewModel.depositCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
? exchangeViewModel.wallet.walletAddresses.addressForExchange
|
||||
: exchangeViewModel.depositAddress,
|
||||
initialIsAmountEditable: true,
|
||||
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
|
||||
|
@ -166,7 +166,7 @@ class ExchangeTemplatePage extends BasePage {
|
|||
initialWalletName: receiveWalletName ?? '',
|
||||
initialAddress: exchangeViewModel.receiveCurrency ==
|
||||
exchangeViewModel.wallet.currency
|
||||
? exchangeViewModel.wallet.walletAddresses.address
|
||||
? exchangeViewModel.wallet.walletAddresses.addressForExchange
|
||||
: exchangeViewModel.receiveAddress,
|
||||
initialIsAmountEditable: false,
|
||||
isAmountEstimated: true,
|
||||
|
|
|
@ -85,7 +85,9 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
|
|||
|
||||
class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBody> {
|
||||
final TextEditingController passphraseController = TextEditingController();
|
||||
final TextEditingController confirmPassphraseController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _passphraseFormKey = GlobalKey<FormState>();
|
||||
bool? testnetValue;
|
||||
|
||||
bool obscurePassphrase = true;
|
||||
|
@ -93,9 +95,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
@override
|
||||
void initState() {
|
||||
passphraseController.text = widget.seedTypeViewModel.passphrase ?? '';
|
||||
|
||||
passphraseController
|
||||
.addListener(() => widget.seedTypeViewModel.setPassphrase(passphraseController.text));
|
||||
confirmPassphraseController.text = widget.seedTypeViewModel.passphrase ?? '';
|
||||
|
||||
if (widget.isChildWallet) {
|
||||
if (widget.privacySettingsViewModel.type == WalletType.bitcoin) {
|
||||
|
@ -205,18 +205,47 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
if (widget.privacySettingsViewModel.hasPassphraseOption)
|
||||
Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: BaseTextFormField(
|
||||
hintText: S.current.passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
child: Form(
|
||||
key: _passphraseFormKey,
|
||||
child: Column(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BaseTextFormField(
|
||||
hintText: S.of(context).confirm_passphrase,
|
||||
controller: confirmPassphraseController,
|
||||
obscureText: obscurePassphrase,
|
||||
validator: (text) {
|
||||
if (text == passphraseController.text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return S.of(context).passphrases_doesnt_match;
|
||||
},
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -272,7 +301,8 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
}
|
||||
|
||||
widget.nodeViewModel.save();
|
||||
} else if (testnetValue == true) {
|
||||
}
|
||||
if (testnetValue == true) {
|
||||
// TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
|
||||
// Currently this is so you can create a working testnet wallet but you need to keep switching back the node if you use multiple wallets at once
|
||||
widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
|
||||
|
@ -280,6 +310,13 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
|
||||
widget.nodeViewModel.save();
|
||||
}
|
||||
if (passphraseController.text.isNotEmpty) {
|
||||
if (_passphraseFormKey.currentState != null && !_passphraseFormKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.seedTypeViewModel.setPassphrase(passphraseController.text);
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
|
@ -318,11 +355,4 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
passphraseController
|
||||
.removeListener(() => widget.seedTypeViewModel.setPassphrase(passphraseController.text));
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
|
|||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class WalletGroupDescriptionPage extends BasePage {
|
||||
WalletGroupDescriptionPage({required this.selectedWalletType});
|
||||
|
@ -16,16 +17,24 @@ class WalletGroupDescriptionPage extends BasePage {
|
|||
@override
|
||||
String get title => S.current.wallet_group;
|
||||
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
|
||||
final lightImage = 'assets/images/wallet_group_light.png';
|
||||
final darkImage = 'assets/images/wallet_group_dark.png';
|
||||
final brightImage = 'assets/images/wallet_group_bright.png';
|
||||
|
||||
final image = currentTheme.type == ThemeType.light ? lightImage : darkImage;
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/wallet_group.png',
|
||||
scale: 0.8,
|
||||
_getThemedWalletGroupImage(currentTheme.type),
|
||||
height: 200,
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Expanded(
|
||||
|
@ -87,4 +96,19 @@ class WalletGroupDescriptionPage extends BasePage {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getThemedWalletGroupImage(ThemeType theme) {
|
||||
final lightImage = 'assets/images/wallet_group_light.png';
|
||||
final darkImage = 'assets/images/wallet_group_dark.png';
|
||||
final brightImage = 'assets/images/wallet_group_bright.png';
|
||||
|
||||
switch (theme) {
|
||||
case ThemeType.bright:
|
||||
return brightImage;
|
||||
case ThemeType.light:
|
||||
return lightImage;
|
||||
default:
|
||||
return darkImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ class WalletGroupsDisplayBody extends StatelessWidget {
|
|||
final groupName =
|
||||
group.groupName ?? '${S.of(context).wallet_group} ${index + 1}';
|
||||
return GroupedWalletExpansionTile(
|
||||
shouldShowCurrentWalletPointer: false,
|
||||
leadingWidget:
|
||||
Icon(Icons.account_balance_wallet_outlined, size: 28),
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
|
|
|
@ -12,6 +12,7 @@ class GroupedWalletExpansionTile extends StatelessWidget {
|
|||
this.childWallets = const [],
|
||||
this.onTitleTapped,
|
||||
this.onChildItemTapped = _defaultVoidCallback,
|
||||
this.onExpansionChanged,
|
||||
this.leadingWidget,
|
||||
this.trailingWidget,
|
||||
this.childTrailingWidget,
|
||||
|
@ -22,13 +23,18 @@ class GroupedWalletExpansionTile extends StatelessWidget {
|
|||
this.borderRadius,
|
||||
this.margin,
|
||||
this.tileKey,
|
||||
this.isCurrentlySelectedWallet = false,
|
||||
this.shouldShowCurrentWalletPointer = false,
|
||||
}) : super(key: tileKey);
|
||||
|
||||
final Key? tileKey;
|
||||
final bool isSelected;
|
||||
final bool isCurrentlySelectedWallet;
|
||||
final bool shouldShowCurrentWalletPointer;
|
||||
|
||||
final VoidCallback? onTitleTapped;
|
||||
final void Function(WalletListItem item) onChildItemTapped;
|
||||
final void Function(bool)? onExpansionChanged;
|
||||
|
||||
final String title;
|
||||
final Widget? leadingWidget;
|
||||
|
@ -70,8 +76,13 @@ class GroupedWalletExpansionTile extends StatelessWidget {
|
|||
splashFactory: NoSplash.splashFactory,
|
||||
),
|
||||
child: ExpansionTile(
|
||||
onExpansionChanged: onExpansionChanged,
|
||||
initiallyExpanded: shouldShowCurrentWalletPointer
|
||||
? childWallets.any((element) => element.isCurrent)
|
||||
: false,
|
||||
key: tileKey,
|
||||
tilePadding: EdgeInsets.symmetric(vertical: 1, horizontal: 16),
|
||||
tilePadding:
|
||||
EdgeInsets.symmetric(vertical: 1, horizontal: !isCurrentlySelectedWallet ? 16 : 0),
|
||||
iconColor: effectiveArrowColor,
|
||||
collapsedIconColor: effectiveArrowColor,
|
||||
leading: leadingWidget,
|
||||
|
@ -90,19 +101,46 @@ class GroupedWalletExpansionTile extends StatelessWidget {
|
|||
),
|
||||
children: childWallets.map(
|
||||
(item) {
|
||||
final currentColor = item.isCurrent
|
||||
? Theme.of(context)
|
||||
.extension<WalletListTheme>()!
|
||||
.createNewWalletButtonBackgroundColor
|
||||
: Theme.of(context).colorScheme.background;
|
||||
final walletTypeToCrypto = walletTypeToCryptoCurrency(item.type);
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
key: ValueKey(item.name),
|
||||
trailing: childTrailingWidget?.call(item),
|
||||
onTap: () => onChildItemTapped(item),
|
||||
leading: Image.asset(
|
||||
walletTypeToCrypto.iconPath!,
|
||||
width: 32,
|
||||
height: 32,
|
||||
leading: SizedBox(
|
||||
width: 60,
|
||||
child: Row(
|
||||
children: [
|
||||
item.isCurrent && shouldShowCurrentWalletPointer
|
||||
? Container(
|
||||
height: 35,
|
||||
width: 6,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
color: currentColor,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: 6),
|
||||
SizedBox(width: 16),
|
||||
Image.asset(
|
||||
walletTypeToCrypto.iconPath!,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
|
|
@ -121,7 +121,8 @@ class ReceivePage extends BasePage {
|
|||
heroTag: _heroTag,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
isLight: currentTheme.type == ThemeType.light),
|
||||
isLight: currentTheme.type == ThemeType.light,
|
||||
),
|
||||
),
|
||||
AddressList(addressListViewModel: addressListViewModel),
|
||||
Padding(
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
|
@ -15,11 +16,14 @@ class AddressCell extends StatelessWidget {
|
|||
required this.textColor,
|
||||
this.onTap,
|
||||
this.onEdit,
|
||||
this.onHide,
|
||||
this.isHidden = false,
|
||||
this.onDelete,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false,
|
||||
this.hasBalance = false});
|
||||
this.hasBalance = false,
|
||||
this.hasReceived = false});
|
||||
|
||||
factory AddressCell.fromItem(
|
||||
WalletAddressListItem item, {
|
||||
|
@ -28,7 +32,10 @@ class AddressCell extends StatelessWidget {
|
|||
required Color textColor,
|
||||
Function(String)? onTap,
|
||||
bool hasBalance = false,
|
||||
bool hasReceived = false,
|
||||
Function()? onEdit,
|
||||
Function()? onHide,
|
||||
bool isHidden = false,
|
||||
Function()? onDelete,
|
||||
}) =>
|
||||
AddressCell(
|
||||
|
@ -40,11 +47,14 @@ class AddressCell extends StatelessWidget {
|
|||
textColor: textColor,
|
||||
onTap: onTap,
|
||||
onEdit: onEdit,
|
||||
onHide: onHide,
|
||||
isHidden: isHidden,
|
||||
onDelete: onDelete,
|
||||
txCount: item.txCount,
|
||||
balance: item.balance,
|
||||
isChange: item.isChange,
|
||||
hasBalance: hasBalance);
|
||||
hasBalance: hasBalance,
|
||||
hasReceived: hasReceived,);
|
||||
|
||||
final String address;
|
||||
final String name;
|
||||
|
@ -54,11 +64,14 @@ class AddressCell extends StatelessWidget {
|
|||
final Color textColor;
|
||||
final Function(String)? onTap;
|
||||
final Function()? onEdit;
|
||||
final Function()? onHide;
|
||||
final bool isHidden;
|
||||
final Function()? onDelete;
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
final bool hasBalance;
|
||||
final bool hasReceived;
|
||||
|
||||
static const int addressPreviewLength = 8;
|
||||
|
||||
|
@ -138,7 +151,7 @@ class AddressCell extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (hasBalance)
|
||||
if (hasBalance || hasReceived)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
|
@ -146,7 +159,7 @@ class AddressCell extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
'${S.of(context).balance}: $balance',
|
||||
'${hasReceived ? S.of(context).received : S.of(context).balance}: $balance',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
@ -178,14 +191,28 @@ class AddressCell extends StatelessWidget {
|
|||
enabled: !isCurrent,
|
||||
child: Slidable(
|
||||
key: Key(address),
|
||||
startActionPane: _actionPane(context),
|
||||
endActionPane: _actionPane(context),
|
||||
startActionPane: _actionPaneStart(context),
|
||||
endActionPane: _actionPaneEnd(context),
|
||||
child: cell,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ActionPane _actionPane(BuildContext context) => ActionPane(
|
||||
ActionPane _actionPaneEnd(BuildContext context) => ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: onDelete != null ? 0.4 : 0.3,
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (_) => onHide?.call(),
|
||||
backgroundColor: isHidden ? Colors.green : Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
icon: isHidden ? CupertinoIcons.arrow_left : CupertinoIcons.arrow_right,
|
||||
label: isHidden ? S.of(context).show : S.of(context).hide,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
ActionPane _actionPaneStart(BuildContext context) => ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: onDelete != null ? 0.4 : 0.3,
|
||||
children: [
|
||||
|
|
|
@ -10,16 +10,19 @@ import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
|
|||
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class AddressList extends StatelessWidget {
|
||||
class AddressList extends StatefulWidget {
|
||||
const AddressList({
|
||||
super.key,
|
||||
required this.addressListViewModel,
|
||||
|
@ -29,59 +32,123 @@ class AddressList extends StatelessWidget {
|
|||
final WalletAddressListViewModel addressListViewModel;
|
||||
final Function(String)? onSelect;
|
||||
|
||||
@override
|
||||
State<AddressList> createState() => _AddressListState();
|
||||
}
|
||||
|
||||
class _AddressListState extends State<AddressList> {
|
||||
|
||||
bool showHiddenAddresses = false;
|
||||
|
||||
void _toggleHiddenAddresses() {
|
||||
setState(() {
|
||||
showHiddenAddresses = !showHiddenAddresses;
|
||||
});
|
||||
updateItems();
|
||||
}
|
||||
|
||||
List<ListItem> getItems(List<ListItem> list, bool showHidden) {
|
||||
return list.where((element) {
|
||||
if (element is WalletAddressListItem) {
|
||||
if (showHidden && element.isHidden) return true;
|
||||
if (!showHidden && !element.isHidden) return true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<ListItem> items = [];
|
||||
|
||||
void updateItems() {
|
||||
setState(() {
|
||||
items = getItems(widget.addressListViewModel.items, showHiddenAddresses);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
items = getItems(widget.addressListViewModel.items, showHiddenAddresses);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool editable = onSelect == null;
|
||||
return Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
padding: EdgeInsets.all(0),
|
||||
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: addressListViewModel.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = addressListViewModel.items[index];
|
||||
Widget cell = Container();
|
||||
bool editable = widget.onSelect == null;
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.all(0),
|
||||
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
Widget cell = Container();
|
||||
|
||||
if (item is WalletAccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
showTrailingButton: true,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||
} else {
|
||||
await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<NanoAccountListPage>());
|
||||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
if (item is WalletAccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
showTrailingButton: true,
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (widget.addressListViewModel.type == WalletType.monero ||
|
||||
widget.addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||
updateItems();
|
||||
} else {
|
||||
await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<NanoAccountListPage>());
|
||||
updateItems();
|
||||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListHeader) {
|
||||
cell = HeaderTile(
|
||||
title: S.of(context).addresses,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
|
||||
showSearchButton: true,
|
||||
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||
trailingIcon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
if (item is WalletAddressHiddenListHeader) {
|
||||
cell = HeaderTile(
|
||||
title: S.of(context).hidden_addresses,
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
showTrailingButton: true,
|
||||
showSearchButton: false,
|
||||
trailingButtonTap: _toggleHiddenAddresses,
|
||||
trailingIcon: Icon(
|
||||
showHiddenAddresses ? Icons.toggle_on : Icons.toggle_off,
|
||||
size: 20,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
if (item is WalletAddressListHeader) {
|
||||
cell = HeaderTile(
|
||||
title: S.of(context).addresses,
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
|
||||
showSearchButton: true,
|
||||
onSearchCallback: updateItems,
|
||||
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
|
||||
updateItems(); // refresh the new address
|
||||
}),
|
||||
trailingIcon: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
if (item.isHidden && !showHiddenAddresses) {
|
||||
cell = Container();
|
||||
} else if (!item.isHidden && showHiddenAddresses) {
|
||||
cell = Container();
|
||||
} else {
|
||||
cell = Observer(builder: (_) {
|
||||
final isCurrent = item.address == addressListViewModel.address.address && editable;
|
||||
final isCurrent = item.address == widget.addressListViewModel.address.address && editable;
|
||||
final backgroundColor = isCurrent
|
||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
||||
|
@ -89,35 +156,51 @@ class AddressList extends StatelessWidget {
|
|||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
||||
|
||||
|
||||
return AddressCell.fromItem(
|
||||
item,
|
||||
isCurrent: isCurrent,
|
||||
hasBalance: addressListViewModel.isElectrumWallet,
|
||||
backgroundColor: backgroundColor,
|
||||
hasBalance: widget.addressListViewModel.isBalanceAvailable,
|
||||
hasReceived: widget.addressListViewModel.isReceivedAvailable,
|
||||
// hasReceived:
|
||||
backgroundColor: (kDebugMode && item.isHidden) ?
|
||||
Theme.of(context).colorScheme.error :
|
||||
(kDebugMode && item.isManual) ? Theme.of(context).colorScheme.error.withBlue(255) :
|
||||
backgroundColor,
|
||||
textColor: textColor,
|
||||
onTap: (_) {
|
||||
if (onSelect != null) {
|
||||
onSelect!(item.address);
|
||||
if (widget.onSelect != null) {
|
||||
widget.onSelect!(item.address);
|
||||
return;
|
||||
}
|
||||
addressListViewModel.setAddress(item);
|
||||
widget.addressListViewModel.setAddress(item);
|
||||
},
|
||||
onEdit: editable
|
||||
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item)
|
||||
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item).then((value) {
|
||||
updateItems(); // refresh the new address
|
||||
})
|
||||
: null,
|
||||
isHidden: item.isHidden,
|
||||
onHide: () => _hideAddress(item),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return index != 0
|
||||
? cell
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
||||
child: cell,
|
||||
);
|
||||
},
|
||||
),
|
||||
return index != 0
|
||||
? cell
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
||||
child: cell,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _hideAddress(WalletAddressListItem item) async {
|
||||
await widget.addressListViewModel.toggleHideAddress(item);
|
||||
updateItems();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ class HeaderTile extends StatefulWidget {
|
|||
this.showSearchButton = false,
|
||||
this.showTrailingButton = false,
|
||||
this.trailingButtonTap,
|
||||
this.onSearchCallback,
|
||||
this.trailingIcon,
|
||||
});
|
||||
|
||||
|
@ -18,6 +19,7 @@ class HeaderTile extends StatefulWidget {
|
|||
final bool showSearchButton;
|
||||
final bool showTrailingButton;
|
||||
final VoidCallback? trailingButtonTap;
|
||||
final VoidCallback? onSearchCallback;
|
||||
final Icon? trailingIcon;
|
||||
|
||||
@override
|
||||
|
@ -41,7 +43,10 @@ class _HeaderTileState extends State<HeaderTile> {
|
|||
_isSearchActive
|
||||
? Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) => widget.walletAddressListViewModel.updateSearchText(value),
|
||||
onChanged: (value) {
|
||||
widget.walletAddressListViewModel.updateSearchText(value);
|
||||
widget.onSearchCallback?.call();
|
||||
},
|
||||
cursorColor: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor,
|
||||
cursorWidth: 0.5,
|
||||
decoration: InputDecoration(
|
||||
|
|
|
@ -37,6 +37,10 @@ class QRWidget extends StatelessWidget {
|
|||
final int? qrVersion;
|
||||
final String? heroTag;
|
||||
|
||||
PaymentURI get addressUri {
|
||||
return addressListViewModel.uri;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final copyImage = Image.asset('assets/images/copy_address.png',
|
||||
|
@ -77,14 +81,14 @@ class QRWidget extends StatelessWidget {
|
|||
() async {
|
||||
await Navigator.pushNamed(context, Routes.fullscreenQR,
|
||||
arguments: QrViewData(
|
||||
data: addressListViewModel.uri.toString(),
|
||||
data: addressUri.toString(),
|
||||
heroTag: heroTag,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: Key(heroTag ?? addressListViewModel.uri.toString()),
|
||||
tag: Key(heroTag ?? addressUri.toString()),
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
|
@ -105,7 +109,7 @@ class QRWidget extends StatelessWidget {
|
|||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
child: QrImage(data: addressListViewModel.uri.toString())),
|
||||
child: QrImage(data: addressUri.toString())),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -148,7 +152,7 @@ class QRWidget extends StatelessWidget {
|
|||
builder: (context) => Observer(
|
||||
builder: (context) => GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: addressListViewModel.address.address));
|
||||
Clipboard.setData(ClipboardData(text: addressUri.address));
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: Row(
|
||||
|
@ -157,7 +161,7 @@ class QRWidget extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
addressListViewModel.address.address,
|
||||
addressUri.address,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
|
|
|
@ -78,6 +78,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
void Function()? repeatedPasswordListener;
|
||||
void Function()? passphraseListener;
|
||||
|
||||
bool obscurePassphrase = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_setSeedType(widget.seedSettingsViewModel.moneroSeedType);
|
||||
|
@ -283,7 +285,16 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
|||
BaseTextFormField(
|
||||
hintText: S.current.passphrase,
|
||||
controller: passphraseController,
|
||||
obscureText: true,
|
||||
obscureText: obscurePassphrase,
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
obscurePassphrase = !obscurePassphrase;
|
||||
}),
|
||||
child: Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: obscurePassphrase ? Colors.black54 : Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
]));
|
||||
|
|
|
@ -284,8 +284,11 @@ class WalletRestorePage extends BasePage {
|
|||
}
|
||||
|
||||
// bip39:
|
||||
const validSeedLengths = [12, 18, 24];
|
||||
if (!(validSeedLengths.contains(seedWords.length))) {
|
||||
final validBip39SeedLengths = [12, 18, 24];
|
||||
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven];
|
||||
// if it's a bip39 wallet and the length is not valid return false
|
||||
if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) &&
|
||||
!(validBip39SeedLengths.contains(seedWords.length))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -7,11 +7,13 @@ class EditWalletButtonWidget extends StatelessWidget {
|
|||
required this.width,
|
||||
required this.onTap,
|
||||
this.isGroup = false,
|
||||
this.isExpanded = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool isGroup;
|
||||
final double width;
|
||||
final bool isExpanded;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
|
@ -42,7 +44,7 @@ class EditWalletButtonWidget extends StatelessWidget {
|
|||
if (isGroup) ...{
|
||||
SizedBox(width: 6),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
|
||||
size: 24,
|
||||
color: Theme.of(context).extension<FilterTheme>()!.titlesColor,
|
||||
),
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
|
@ -156,7 +157,18 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
final group = widget.walletListViewModel.multiWalletGroups[index];
|
||||
final groupName = group.groupName ??
|
||||
'${S.current.wallet_group} ${index + 1}';
|
||||
|
||||
widget.walletListViewModel.updateTileState(
|
||||
index,
|
||||
widget.walletListViewModel.expansionTileStateTrack[index] ?? false,
|
||||
);
|
||||
|
||||
return GroupedWalletExpansionTile(
|
||||
onExpansionChanged: (value) {
|
||||
widget.walletListViewModel.updateTileState(index, value);
|
||||
setState(() {});
|
||||
},
|
||||
shouldShowCurrentWalletPointer: true,
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
margin: EdgeInsets.only(left: 20, right: 20, bottom: 12),
|
||||
title: groupName,
|
||||
|
@ -168,6 +180,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
trailingWidget: EditWalletButtonWidget(
|
||||
width: 74,
|
||||
isGroup: true,
|
||||
isExpanded:
|
||||
widget.walletListViewModel.expansionTileStateTrack[index]!,
|
||||
onTap: () {
|
||||
final wallet = widget.walletListViewModel
|
||||
.convertWalletInfoToWalletListItem(group.wallets.first);
|
||||
|
@ -193,13 +207,16 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
childTrailingWidget: (item) {
|
||||
return item.isCurrent
|
||||
? SizedBox.shrink()
|
||||
: EditWalletButtonWidget(
|
||||
width: 44,
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.walletEdit,
|
||||
arguments: WalletEditPageArguments(
|
||||
walletListViewModel: widget.walletListViewModel,
|
||||
editingWallet: item,
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: EditWalletButtonWidget(
|
||||
width: 44,
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.walletEdit,
|
||||
arguments: WalletEditPageArguments(
|
||||
walletListViewModel: widget.walletListViewModel,
|
||||
editingWallet: item,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -232,13 +249,40 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
updateFunction: widget.walletListViewModel.reorderAccordingToWalletList,
|
||||
itemBuilder: (context, index) {
|
||||
final wallet = widget.walletListViewModel.singleWalletsList[index];
|
||||
final currentColor = wallet.isCurrent
|
||||
? Theme.of(context)
|
||||
.extension<WalletListTheme>()!
|
||||
.createNewWalletButtonBackgroundColor
|
||||
: Theme.of(context).colorScheme.background;
|
||||
|
||||
return GroupedWalletExpansionTile(
|
||||
tileKey: ValueKey('single_wallets_expansion_tile_widget_$index'),
|
||||
leadingWidget: Image.asset(
|
||||
walletTypeToCryptoCurrency(wallet.type).iconPath!,
|
||||
width: 32,
|
||||
height: 32,
|
||||
isCurrentlySelectedWallet: wallet.isCurrent,
|
||||
leadingWidget: SizedBox(
|
||||
width: wallet.isCurrent ? 56 : 40,
|
||||
child: Row(
|
||||
children: [
|
||||
wallet.isCurrent
|
||||
? Container(
|
||||
height: 35,
|
||||
width: 6,
|
||||
margin: EdgeInsets.only(right: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
color: currentColor,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: 6),
|
||||
Image.asset(
|
||||
walletTypeToCryptoCurrency(wallet.type).iconPath!,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: wallet.name,
|
||||
isSelected: false,
|
||||
|
|
|
@ -27,32 +27,41 @@ abstract class ContactListViewModelBase with Store {
|
|||
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled {
|
||||
walletInfoSource.values.forEach((info) {
|
||||
if (isAutoGenerateEnabled && info.type == WalletType.monero && info.addressInfos != null) {
|
||||
info.addressInfos!.forEach((key, value) {
|
||||
final nextUnusedAddress = value.firstWhereOrNull(
|
||||
(addressInfo) => !(info.usedAddresses?.contains(addressInfo.address) ?? false));
|
||||
if (nextUnusedAddress != null) {
|
||||
final name = _createName(info.name, nextUnusedAddress.label);
|
||||
walletContacts.add(WalletContact(
|
||||
nextUnusedAddress.address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
}
|
||||
});
|
||||
final key = info.addressInfos!.keys.first;
|
||||
final value = info.addressInfos![key];
|
||||
final address = value?.first;
|
||||
if (address != null) {
|
||||
final name = _createName(info.name, address.label);
|
||||
walletContacts.add(WalletContact(
|
||||
address.address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
}
|
||||
} else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
|
||||
info.addresses!.forEach((address, label) {
|
||||
if (label.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final name = _createName(info.name, label);
|
||||
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)) {
|
||||
final address = info.address;
|
||||
final name = _createName(info.name, "");
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type,
|
||||
isTestnet:
|
||||
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
});
|
||||
} else {
|
||||
info.addresses!.forEach((address, label) {
|
||||
if (label.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final name = _createName(info.name, label);
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type,
|
||||
isTestnet:
|
||||
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
|
||||
));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
walletContacts.add(WalletContact(
|
||||
info.address,
|
||||
|
|
|
@ -151,34 +151,24 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
|
||||
bool isEthereum = _balanceViewModel.wallet.type == WalletType.ethereum;
|
||||
|
||||
print('An extra log for now');
|
||||
|
||||
bool isPotentialScamViaMoralis = await _isPotentialScamTokenViaMoralis(
|
||||
contractAddress,
|
||||
isEthereum ? 'eth' : 'polygon',
|
||||
);
|
||||
|
||||
print('Is Potential Scam from Moralis: $isPotentialScamViaMoralis');
|
||||
|
||||
bool isPotentialScamViaExplorers = await _isPotentialScamTokenViaExplorers(
|
||||
contractAddress,
|
||||
isEthereum: isEthereum,
|
||||
);
|
||||
|
||||
print('Is Potential Scam from Explorers: $isPotentialScamViaExplorers');
|
||||
|
||||
bool isUnverifiedContract = await _isContractUnverified(
|
||||
contractAddress,
|
||||
isEthereum: isEthereum,
|
||||
);
|
||||
|
||||
print('Is Unverified Contract: $isUnverifiedContract');
|
||||
|
||||
final showWarningForContractAddress =
|
||||
isPotentialScamViaMoralis || isUnverifiedContract || isPotentialScamViaExplorers;
|
||||
|
||||
print('Show Warning: $showWarningForContractAddress');
|
||||
|
||||
return showWarningForContractAddress;
|
||||
} finally {
|
||||
isValidatingContractAddress = false;
|
||||
|
@ -272,8 +262,8 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (decodedResponse['status'] != '1') {
|
||||
print('${response.body}\n');
|
||||
print('${decodedResponse['result']}\n');
|
||||
log('${response.body}\n');
|
||||
log('${decodedResponse['result']}\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
receiveAddress = '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
|
||||
provider = providersForCurrentPair().first;
|
||||
final initialProvider = provider;
|
||||
provider!.checkIsAvailable().then((bool isAvailable) {
|
||||
|
@ -155,6 +155,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
bool get hideAddressAfterExchange =>
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero;
|
||||
|
||||
bool _useTorOnly;
|
||||
final Box<Trade> trades;
|
||||
final ExchangeTemplateStore _exchangeTemplateStore;
|
||||
|
@ -540,6 +544,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
isFixedRate: isFixedRateMode,
|
||||
);
|
||||
|
||||
if (hideAddressAfterExchange) {
|
||||
wallet.walletAddresses.hiddenAddresses.add(depositAddress);
|
||||
await wallet.walletAddresses.saveAddressesInBox();
|
||||
}
|
||||
|
||||
var amount = isFixedRateMode ? receiveAmount : depositAmount;
|
||||
amount = amount.replaceAll(',', '.');
|
||||
|
||||
|
@ -603,8 +612,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
isReceiveAmountEntered = false;
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : '';
|
||||
receiveAddress = receiveCurrency == wallet.currency ? wallet.walletAddresses.address : '';
|
||||
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
|
||||
receiveAddress = receiveCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
|
||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||
isFixedRateMode = false;
|
||||
_onPairChange();
|
||||
|
|
|
@ -160,9 +160,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
case WalletType.ethereum:
|
||||
return 'https://etherscan.io/tx/${txId}';
|
||||
case WalletType.nano:
|
||||
return 'https://nanolooker.com/block/${txId}';
|
||||
return 'https://nanexplorer.com/nano/block/${txId}';
|
||||
case WalletType.banano:
|
||||
return 'https://bananolooker.com/block/${txId}';
|
||||
return 'https://nanexplorer.com/banano/block/${txId}';
|
||||
case WalletType.polygon:
|
||||
return 'https://polygonscan.com/tx/${txId}';
|
||||
case WalletType.solana:
|
||||
|
@ -190,9 +190,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
case WalletType.ethereum:
|
||||
return S.current.view_transaction_on + 'etherscan.io';
|
||||
case WalletType.nano:
|
||||
return S.current.view_transaction_on + 'nanolooker.com';
|
||||
return S.current.view_transaction_on + 'nanexplorer.com';
|
||||
case WalletType.banano:
|
||||
return S.current.view_transaction_on + 'bananolooker.com';
|
||||
return S.current.view_transaction_on + 'nanexplorer.com';
|
||||
case WalletType.polygon:
|
||||
return S.current.view_transaction_on + 'polygonscan.com';
|
||||
case WalletType.solana:
|
||||
|
|
|
@ -78,6 +78,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
wallet,
|
||||
accountIndex: monero!.getCurrentAccount(wallet).id,
|
||||
label: label);
|
||||
final addr = await monero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
|
||||
wallet.walletAddresses.manualAddresses.add(addr);
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
|
@ -88,6 +90,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
|
|||
wallet,
|
||||
accountIndex: wownero!.getCurrentAccount(wallet).id,
|
||||
label: label);
|
||||
final addr = await wownero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
|
||||
wallet.walletAddresses.manualAddresses.add(addr);
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class WalletAddressHiddenListHeader extends ListItem {}
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class WalletAddressListItem extends ListItem {
|
||||
const WalletAddressListItem({
|
||||
WalletAddressListItem({
|
||||
required this.address,
|
||||
required this.isPrimary,
|
||||
this.id,
|
||||
|
@ -11,6 +11,8 @@ class WalletAddressListItem extends ListItem {
|
|||
this.isChange = false,
|
||||
// Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc
|
||||
this.isOneTimeReceiveAddress = false,
|
||||
this.isHidden = false,
|
||||
this.isManual = false,
|
||||
}) : super();
|
||||
|
||||
final int? id;
|
||||
|
@ -20,6 +22,8 @@ class WalletAddressListItem extends ListItem {
|
|||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
bool isHidden;
|
||||
bool isManual;
|
||||
final bool? isOneTimeReceiveAddress;
|
||||
|
||||
@override
|
||||
|
|
|
@ -18,12 +18,16 @@ import 'package:cake_wallet/store/yat/yat_store.dart';
|
|||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/amount_converter.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
|
@ -271,56 +275,40 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
WalletType get type => wallet.type;
|
||||
|
||||
@computed
|
||||
WalletAddressListItem get address =>
|
||||
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
|
||||
WalletAddressListItem get address {
|
||||
return WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
|
||||
}
|
||||
|
||||
@computed
|
||||
PaymentURI get uri {
|
||||
if (wallet.type == WalletType.monero) {
|
||||
return MoneroURI(amount: amount, address: address.address);
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return MoneroURI(amount: amount, address: address.address);
|
||||
case WalletType.haven:
|
||||
return HavenURI(amount: amount, address: address.address);
|
||||
case WalletType.bitcoin:
|
||||
return BitcoinURI(amount: amount, address: address.address);
|
||||
case WalletType.litecoin:
|
||||
return LitecoinURI(amount: amount, address: address.address);
|
||||
case WalletType.ethereum:
|
||||
return EthereumURI(amount: amount, address: address.address);
|
||||
case WalletType.bitcoinCash:
|
||||
return BitcoinCashURI(amount: amount, address: address.address);
|
||||
case WalletType.banano:
|
||||
return NanoURI(amount: amount, address: address.address);
|
||||
case WalletType.nano:
|
||||
return NanoURI(amount: amount, address: address.address);
|
||||
case WalletType.polygon:
|
||||
return PolygonURI(amount: amount, address: address.address);
|
||||
case WalletType.solana:
|
||||
return SolanaURI(amount: amount, address: address.address);
|
||||
case WalletType.tron:
|
||||
return TronURI(amount: amount, address: address.address);
|
||||
case WalletType.wownero:
|
||||
return WowneroURI(amount: amount, address: address.address);
|
||||
case WalletType.none:
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.haven) {
|
||||
return HavenURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
return BitcoinURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.litecoin) {
|
||||
return LitecoinURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.ethereum) {
|
||||
return EthereumURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.bitcoinCash) {
|
||||
return BitcoinCashURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.nano) {
|
||||
return NanoURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.polygon) {
|
||||
return PolygonURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.solana) {
|
||||
return SolanaURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
return TronURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.wownero) {
|
||||
return WowneroURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
@computed
|
||||
|
@ -341,7 +329,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address);
|
||||
address: subaddress.address,
|
||||
balance: subaddress.received,
|
||||
txCount: subaddress.txCount,
|
||||
);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
}
|
||||
|
@ -468,6 +459,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressList.length; i++) {
|
||||
if (!(addressList[i] is WalletAddressListItem)) continue;
|
||||
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses.contains((addressList[i] as WalletAddressListItem).address);
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressList.length; i++) {
|
||||
if (!(addressList[i] is WalletAddressListItem)) continue;
|
||||
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses.contains((addressList[i] as WalletAddressListItem).address);
|
||||
}
|
||||
|
||||
if (searchText.isNotEmpty) {
|
||||
return ObservableList.of(addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
|
@ -479,7 +480,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
Future<void> toggleHideAddress(WalletAddressListItem item) async {
|
||||
if (item.isHidden) {
|
||||
wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
|
||||
} else {
|
||||
wallet.walletAddresses.hiddenAddresses.add(item.address);
|
||||
}
|
||||
await wallet.walletAddresses.saveAddressesInBox();
|
||||
if (wallet.type == WalletType.monero) {
|
||||
monero!.getSubaddressList(wallet).update(wallet, accountIndex: monero!.getCurrentAccount(wallet).id);
|
||||
} else if (wallet.type == WalletType.wownero) {
|
||||
wownero!.getSubaddressList(wallet).update(wallet, accountIndex: wownero!.getCurrentAccount(wallet).id);
|
||||
} else if (wallet.type == WalletType.haven) {
|
||||
haven!.getSubaddressList(wallet).update(wallet, accountIndex: haven!.getCurrentAccount(wallet).id);
|
||||
}
|
||||
}
|
||||
@observable
|
||||
bool hasAccounts;
|
||||
|
||||
|
@ -515,6 +530,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
@computed
|
||||
bool get isBalanceAvailable => isElectrumWallet;
|
||||
|
||||
@computed
|
||||
bool get isReceivedAvailable =>
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero;
|
||||
|
||||
@computed
|
||||
bool get isSilentPayments =>
|
||||
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||
|
@ -524,6 +547,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
|
||||
!isSilentPayments;
|
||||
|
||||
@computed
|
||||
bool get showAddManualAddresses =>
|
||||
!isAutoGenerateSubaddressEnabled ||
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
final YatStore yatStore;
|
||||
|
@ -542,6 +571,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
void _init() {
|
||||
_baseItems = [];
|
||||
|
||||
if (wallet.walletAddresses.hiddenAddresses.isNotEmpty) {
|
||||
_baseItems.add(WalletAddressHiddenListHeader());
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero ||
|
||||
wallet.type == WalletType.haven) {
|
||||
|
@ -551,6 +584,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
|
||||
_baseItems.add(WalletAddressListHeader());
|
||||
}
|
||||
if (wallet.isEnabledAutoGenerateSubaddress) {
|
||||
wallet.walletAddresses.address = wallet.walletAddresses.latestAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -5,8 +5,10 @@ import 'package:cake_wallet/di.dart';
|
|||
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||
import 'package:cake_wallet/entities/generate_name.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
|
@ -82,9 +84,22 @@ abstract class WalletCreationVMBase with Store {
|
|||
walletCreationService.checkIfExists(name);
|
||||
final dirPath = await pathForWalletDir(name: name, type: type);
|
||||
final path = await pathForWallet(name: name, type: type);
|
||||
final credentials = restoreWallet != null
|
||||
? getCredentialsFromRestoredWallet(options, restoreWallet)
|
||||
: getCredentials(options);
|
||||
|
||||
WalletCredentials credentials;
|
||||
if (restoreWallet != null) {
|
||||
if (restoreWallet.restoreMode == WalletRestoreMode.seed &&
|
||||
options == null &&
|
||||
(type == WalletType.nano ||
|
||||
type == WalletType.bitcoin ||
|
||||
type == WalletType.litecoin)) {
|
||||
final derivationInfo = await getDerivationInfo(restoreWallet);
|
||||
options ??= {};
|
||||
options["derivationInfo"] = derivationInfo.first;
|
||||
}
|
||||
credentials = getCredentialsFromRestoredWallet(options, restoreWallet);
|
||||
} else {
|
||||
credentials = getCredentials(options);
|
||||
}
|
||||
|
||||
final walletInfo = WalletInfo.external(
|
||||
id: WalletBase.idFor(name, type),
|
||||
|
@ -185,6 +200,30 @@ abstract class WalletCreationVMBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<DerivationInfo>> getDerivationInfo(RestoredWallet restoreWallet) async {
|
||||
var list = <DerivationInfo>[];
|
||||
final walletType = restoreWallet.type;
|
||||
var appStore = getIt.get<AppStore>();
|
||||
var node = appStore.settingsStore.getCurrentNode(walletType);
|
||||
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getDerivationsFromMnemonic(
|
||||
mnemonic: restoreWallet.mnemonicSeed!,
|
||||
node: node,
|
||||
);
|
||||
case WalletType.nano:
|
||||
return nanoUtil!.getDerivationsFromMnemonic(
|
||||
mnemonic: restoreWallet.mnemonicSeed!,
|
||||
node: node,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
|
||||
|
||||
Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();
|
||||
|
|
|
@ -22,7 +22,8 @@ abstract class WalletListViewModelBase with Store {
|
|||
this._walletManager,
|
||||
) : wallets = ObservableList<WalletListItem>(),
|
||||
multiWalletGroups = ObservableList<WalletGroup>(),
|
||||
singleWalletsList = ObservableList<WalletListItem>() {
|
||||
singleWalletsList = ObservableList<WalletListItem>(),
|
||||
expansionTileStateTrack = ObservableMap<int, bool>() {
|
||||
setOrderType(_appStore.settingsStore.walletListOrder);
|
||||
reaction((_) => _appStore.wallet, (_) => updateList());
|
||||
updateList();
|
||||
|
@ -40,6 +41,18 @@ abstract class WalletListViewModelBase with Store {
|
|||
@observable
|
||||
ObservableList<WalletListItem> singleWalletsList;
|
||||
|
||||
@observable
|
||||
ObservableMap<int, bool> expansionTileStateTrack;
|
||||
|
||||
@action
|
||||
void updateTileState(int index, bool isExpanded) {
|
||||
if (expansionTileStateTrack.containsKey(index)) {
|
||||
expansionTileStateTrack.update(index, (value) => isExpanded);
|
||||
} else {
|
||||
expansionTileStateTrack.addEntries({index: isExpanded}.entries);
|
||||
}
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get shouldRequireTOTP2FAForAccessingWallet =>
|
||||
_appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
|
@ -100,8 +113,8 @@ abstract class WalletListViewModelBase with Store {
|
|||
// delete all wallets from walletInfoSource:
|
||||
await _walletInfoSource.clear();
|
||||
|
||||
// add wallets from wallets list in order of wallets list, by name:
|
||||
for (WalletListItem wallet in wallets) {
|
||||
// Reorder single wallets using the singleWalletsList
|
||||
for (WalletListItem wallet in singleWalletsList) {
|
||||
for (int i = 0; i < walletInfoSourceCopy.length; i++) {
|
||||
if (walletInfoSourceCopy[i].name == wallet.name) {
|
||||
await _walletInfoSource.add(walletInfoSourceCopy[i]);
|
||||
|
@ -111,6 +124,20 @@ abstract class WalletListViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
// Reorder wallets within multi-wallet groups
|
||||
for (WalletGroup group in multiWalletGroups) {
|
||||
for (WalletInfo walletInfo in group.wallets) {
|
||||
for (int i = 0; i < walletInfoSourceCopy.length; i++) {
|
||||
if (walletInfoSourceCopy[i].name == walletInfo.name) {
|
||||
await _walletInfoSource.add(walletInfoSourceCopy[i]);
|
||||
walletInfoSourceCopy.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the list of wallets and groups
|
||||
updateList();
|
||||
}
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "سيؤدي هذا الإجراء إلى حذف هذه المحفظة. هل ترغب في الاستمرار؟",
|
||||
"confirm_fee_deduction": "تأكيد خصم الرسوم",
|
||||
"confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟",
|
||||
"confirm_passphrase": "تأكيد عبارة المرور",
|
||||
"confirm_sending": "تأكيد الإرسال",
|
||||
"confirm_silent_payments_switch_node": "العقدة الحالية لا تدعم المدفوعات الصامتة \\ ncake wallet سوف تتحول إلى عقدة متوافقة ، فقط للمسح الضوئي",
|
||||
"confirmations": "التأكيدات",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "تغير المبلغ",
|
||||
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
|
||||
"passphrase": "عبارة الممر (اختياري)",
|
||||
"passphrases_doesnt_match": "لا تتطابق عبارات المرور ، يرجى المحاولة مرة أخرى",
|
||||
"password": "كلمة المرور",
|
||||
"paste": "لصق",
|
||||
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Този портфейл ще бъде изтрит. Искате ли да продължите?",
|
||||
"confirm_fee_deduction": "Потвърдете приспадането на таксите",
|
||||
"confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?",
|
||||
"confirm_passphrase": "Потвърдете парола",
|
||||
"confirm_sending": "Потвърждаване на изпращането",
|
||||
"confirm_silent_payments_switch_node": "Текущият ви възел не поддържа Silent Payments \\ Ncake Wallet ще премине към съвместим възел, само за сканиране",
|
||||
"confirmations": "потвърждения",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Промени сума",
|
||||
"pairingInvalidEvent": "Невалидно събитие при сдвояване",
|
||||
"passphrase": "Passphrase (по избор)",
|
||||
"passphrases_doesnt_match": "Пасифрази не съвпадат, моля, опитайте отново",
|
||||
"password": "Парола",
|
||||
"paste": "Поставяне",
|
||||
"pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Tato akce smaže tuto peněženku. Přejete si pokračovat?",
|
||||
"confirm_fee_deduction": "Potvrďte odpočet poplatků",
|
||||
"confirm_fee_deduction_content": "Souhlasíte s odečtením poplatku z výstupu?",
|
||||
"confirm_passphrase": "Potvrďte přístupovou frázi",
|
||||
"confirm_sending": "Potvrdit odeslání",
|
||||
"confirm_silent_payments_switch_node": "Váš aktuální uzel nepodporuje tiché platby \\ Ncake peněženka se přepne na kompatibilní uzel, pouze pro skenování",
|
||||
"confirmations": "Potvrzení",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Přepsat částku",
|
||||
"pairingInvalidEvent": "Neplatná událost párování",
|
||||
"passphrase": "Passphrase (volitelné)",
|
||||
"passphrases_doesnt_match": "Passfrázy se neshodují, zkuste to znovu",
|
||||
"password": "Heslo",
|
||||
"paste": "Vložit",
|
||||
"pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Diese Aktion löscht diese Wallet. Möchten Sie fortfahren?",
|
||||
"confirm_fee_deduction": "Gebührenabzug bestätigen",
|
||||
"confirm_fee_deduction_content": "Stimmen Sie zu, die Gebühr von der Ausgabe abzuziehen?",
|
||||
"confirm_passphrase": "Passphrase bestätigen",
|
||||
"confirm_sending": "Senden bestätigen",
|
||||
"confirm_silent_payments_switch_node": "Ihr aktueller Knoten unterstützt keine stillen Zahlungen \\ NCAKE Wallet wechselt zu einem kompatiblen Knoten, nur zum Scannen",
|
||||
"confirmations": "Bestätigungen",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Paarung ungültiges Ereignis",
|
||||
"passphrase": "Passphrase (optional)",
|
||||
"passphrases_doesnt_match": "Passphrasen stimmen nicht überein, bitte versuchen Sie es erneut",
|
||||
"password": "Passwort",
|
||||
"paste": "Einfügen",
|
||||
"pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
|
||||
|
@ -475,8 +477,8 @@
|
|||
"placeholder_transactions": "Ihre Transaktionen werden hier angezeigt",
|
||||
"please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
|
||||
"please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
|
||||
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
|
||||
"please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.",
|
||||
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
|
||||
"please_select": "Bitte auswählen:",
|
||||
"please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.",
|
||||
"please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "This action will delete this wallet. Do you wish to continue?",
|
||||
"confirm_fee_deduction": "Confirm Fee Deduction",
|
||||
"confirm_fee_deduction_content": "Do you agree to deduct the fee from the output?",
|
||||
"confirm_passphrase": "Confirm passphrase",
|
||||
"confirm_sending": "Confirm sending",
|
||||
"confirm_silent_payments_switch_node": "Your current node does not support silent payments\\nCake Wallet will switch to a compatible node, just for scanning",
|
||||
"confirmations": "Confirmations",
|
||||
|
@ -297,8 +298,8 @@
|
|||
"failed_authentication": "Failed authentication. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Features",
|
||||
"fee_rate": "Fee rate",
|
||||
"fee_less_than_min": "Selected Fee is less than the minimum, please increase the fees to be able to send the transaction",
|
||||
"fee_rate": "Fee rate",
|
||||
"fetching": "Fetching",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Fiat Balance",
|
||||
|
@ -332,6 +333,8 @@
|
|||
"haven_app": "Haven by Cake Wallet",
|
||||
"haven_app_wallet_text": "Awesome wallet for Haven",
|
||||
"help": "help",
|
||||
"hide": "Hide",
|
||||
"hidden_addresses": "Hidden Addresses",
|
||||
"hidden_balance": "Hidden Balance",
|
||||
"hide_details": "Hide Details",
|
||||
"high_contrast_theme": "High Contrast Theme",
|
||||
|
@ -461,6 +464,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Pairing Invalid Event",
|
||||
"passphrase": "Passphrase (Optional)",
|
||||
"passphrases_doesnt_match": "Passphrases do not match, please try again",
|
||||
"password": "Password",
|
||||
"paste": "Paste",
|
||||
"pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
|
||||
|
@ -681,6 +685,7 @@
|
|||
"setup_your_debit_card": "Set up your debit card",
|
||||
"share": "Share",
|
||||
"share_address": "Share address",
|
||||
"show": "Show",
|
||||
"shared_seed_wallet_groups": "Shared Seed Wallet Groups",
|
||||
"show_details": "Show Details",
|
||||
"show_keys": "Show seed/keys",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Esta acción eliminará esta billetera. ¿Desea continuar?",
|
||||
"confirm_fee_deduction": "Confirmar la deducción de la tarifa",
|
||||
"confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?",
|
||||
"confirm_passphrase": "Confirmar la frase de pases",
|
||||
"confirm_sending": "Confirmar envío",
|
||||
"confirm_silent_payments_switch_node": "Su nodo actual no admite pagos silenciosos \\ ncake billet cambiará a un nodo compatible, solo para escanear",
|
||||
"confirmations": "Confirmaciones",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Evento de emparejamiento no válido",
|
||||
"passphrase": "Passfrase (opcional)",
|
||||
"passphrases_doesnt_match": "Las frases de contrato no coinciden, intente nuevamente",
|
||||
"password": "Contraseña",
|
||||
"paste": "Pegar",
|
||||
"pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Cette action va supprimer ce portefeuille (wallet). Souhaitez-vous contnuer ?",
|
||||
"confirm_fee_deduction": "Confirmer la déduction des frais",
|
||||
"confirm_fee_deduction_content": "Acceptez-vous de déduire les frais de la production?",
|
||||
"confirm_passphrase": "Confirmer la phrase passante",
|
||||
"confirm_sending": "Confirmer l'envoi",
|
||||
"confirm_silent_payments_switch_node": "Votre nœud actuel ne prend pas en charge les paiements silencieux \\ ncake qui passera à un nœud compatible, juste pour la numérisation",
|
||||
"confirmations": "Confirmations",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Remplacer le montant",
|
||||
"pairingInvalidEvent": "Événement de couplage non valide",
|
||||
"passphrase": "Phrase de passe (facultative)",
|
||||
"passphrases_doesnt_match": "Les phrases de passe ne correspondent pas, veuillez réessayer",
|
||||
"password": "Mot de passe",
|
||||
"paste": "Coller",
|
||||
"pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Wannan aikin zai share wannan walat. Kuna so ku ci gaba?",
|
||||
"confirm_fee_deduction": "Tabbatar da cire kudade",
|
||||
"confirm_fee_deduction_content": "Shin kun yarda ku cire kuɗin daga fitarwa?",
|
||||
"confirm_passphrase": "Tabbatar da kalmar wucewa",
|
||||
"confirm_sending": "Tabbatar da aikawa",
|
||||
"confirm_silent_payments_switch_node": "Kumburinku na yanzu ba ya goyan bayan biyan shiru da shiru \\ NCADA Wallet zai canza zuwa kumburi mai dacewa, don bincika",
|
||||
"confirmations": "Tabbatar",
|
||||
|
@ -463,6 +464,7 @@
|
|||
"overwrite_amount": "Rubuta adadin",
|
||||
"pairingInvalidEvent": "Haɗa Lamarin mara inganci",
|
||||
"passphrase": "Passphrase (Zabi)",
|
||||
"passphrases_doesnt_match": "Passphrases bai dace ba, don Allah sake gwadawa",
|
||||
"password": "Kalmar wucewa",
|
||||
"paste": "Manna",
|
||||
"pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "यह क्रिया इस वॉलेट को हटा देगी। क्या आप जारी रखना चाहते हैं?",
|
||||
"confirm_fee_deduction": "शुल्क कटौती की पुष्टि करें",
|
||||
"confirm_fee_deduction_content": "क्या आप आउटपुट से शुल्क में कटौती करने के लिए सहमत हैं?",
|
||||
"confirm_passphrase": "पासफ़्रेज़ की पुष्टि करें",
|
||||
"confirm_sending": "भेजने की पुष्टि करें",
|
||||
"confirm_silent_payments_switch_node": "आपका वर्तमान नोड मूक भुगतान का समर्थन नहीं करता है \\ ncake वॉलेट एक संगत नोड पर स्विच करेगा, बस स्कैनिंग के लिए",
|
||||
"confirmations": "पुष्टिकरण",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना",
|
||||
"passphrase": "पासफ्रेज़ (वैकल्पिक)",
|
||||
"passphrases_doesnt_match": "PassPhrases मेल नहीं खाता, कृपया पुनः प्रयास करें",
|
||||
"password": "पारण शब्द",
|
||||
"paste": "पेस्ट करें",
|
||||
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Ovom ćete radnjom izbrisati ovaj novčanik. Želite li nastaviti?",
|
||||
"confirm_fee_deduction": "Potvrdite odbitak naknade",
|
||||
"confirm_fee_deduction_content": "Slažete li se da ćete odbiti naknadu od izlaza?",
|
||||
"confirm_passphrase": "Potvrdite prolaznu frazu",
|
||||
"confirm_sending": "Potvrdi slanje",
|
||||
"confirm_silent_payments_switch_node": "Vaš trenutni čvor ne podržava tiha plaćanja \\ ncake novčanik prebacit će se na kompatibilni čvor, samo za skeniranje",
|
||||
"confirmations": "Potvrde",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Nevažeći događaj uparivanja",
|
||||
"passphrase": "Prolaznica (neobavezno)",
|
||||
"passphrases_doesnt_match": "Prolazne fraze se ne podudaraju, pokušajte ponovo",
|
||||
"password": "Lozinka",
|
||||
"paste": "Zalijepi",
|
||||
"pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Այս գործողությունը կջնջի այս դրամապանակը: Ցանկանու՞մ եք շարունակել։",
|
||||
"confirm_fee_deduction": "Հաստատեք միջնորդավճար հանումը",
|
||||
"confirm_fee_deduction_content": "Դուք համաձայն եք միջնորդավճար հանել արդյունքից?",
|
||||
"confirm_passphrase": "Հաստատեք գաղտնաբառը",
|
||||
"confirm_sending": "Հաստատեք ուղարկումը",
|
||||
"confirm_silent_payments_switch_node": "Ձեր ընթացիկ հանգույցը չի աջակցում Լուռ վճարումներին\nCake Wallet-ը կանցնի համատեղելի հանգույց, միայն սկանավորման համար",
|
||||
"confirmations": "Հաստատումներ",
|
||||
|
@ -453,6 +454,7 @@
|
|||
"overwrite_amount": "Գրեք գումարը",
|
||||
"pairingInvalidEvent": "Սխալ միացում",
|
||||
"passphrase": "Պարող արտահայտություն (Ոչ պարտադիր)",
|
||||
"passphrases_doesnt_match": "Անհատները չեն համընկնում, խնդրում ենք կրկին փորձել",
|
||||
"password": "Գաղտնաբառ",
|
||||
"paste": "Տեղադրել",
|
||||
"pause_wallet_creation": "Հնարավորություն ստեղծել Haven Դրամապանակ ընթացիկ դադարեցված է",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Tindakan ini akan menghapus dompet ini. Apakah Anda ingin melanjutkan?",
|
||||
"confirm_fee_deduction": "Konfirmasi pengurangan biaya",
|
||||
"confirm_fee_deduction_content": "Apakah Anda setuju untuk mengurangi biaya dari output?",
|
||||
"confirm_passphrase": "Konfirmasi frasa sandi",
|
||||
"confirm_sending": "Konfirmasi pengiriman",
|
||||
"confirm_silent_payments_switch_node": "Node Anda saat ini tidak mendukung pembayaran diam \\ ncake Wallet akan beralih ke simpul yang kompatibel, hanya untuk pemindaian",
|
||||
"confirmations": "Konfirmasi",
|
||||
|
@ -463,6 +464,7 @@
|
|||
"overwrite_amount": "Timpa jumlah",
|
||||
"pairingInvalidEvent": "Menyandingkan Acara Tidak Valid",
|
||||
"passphrase": "Frasa sandi (opsional)",
|
||||
"passphrases_doesnt_match": "Sandi tidak cocok, coba lagi",
|
||||
"password": "Kata Sandi",
|
||||
"paste": "Tempel",
|
||||
"pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Questa azione cancellerà questo portafoglio. Desideri continuare?",
|
||||
"confirm_fee_deduction": "Conferma la detrazione delle commissioni",
|
||||
"confirm_fee_deduction_content": "Accetti di detrarre la commissione dall'output?",
|
||||
"confirm_passphrase": "Conferma passphrase",
|
||||
"confirm_sending": "Conferma l'invio",
|
||||
"confirm_silent_payments_switch_node": "Il tuo nodo corrente non supporta i pagamenti silenziosi \\ ncake Wallet passerà a un nodo compatibile, solo per la scansione",
|
||||
"confirmations": "Conferme",
|
||||
|
@ -463,6 +464,7 @@
|
|||
"overwrite_amount": "Sovrascrivi quantità",
|
||||
"pairingInvalidEvent": "Associazione evento non valido",
|
||||
"passphrase": "Passphrase (opzionale)",
|
||||
"passphrases_doesnt_match": "Le passphrasi non corrispondono, riprova",
|
||||
"password": "Password",
|
||||
"paste": "Incolla",
|
||||
"pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "このアクションにより、このウォレットが削除されます。 続行しますか?",
|
||||
"confirm_fee_deduction": "料金控除を確認します",
|
||||
"confirm_fee_deduction_content": "出力から料金を差し引くことに同意しますか?",
|
||||
"confirm_passphrase": "パスフレーズを確認します",
|
||||
"confirm_sending": "送信を確認",
|
||||
"confirm_silent_payments_switch_node": "現在のノードはサイレントペイメントをサポートしていません\\ ncakeウォレットは、スキャン用に互換性のあるノードに切り替えます",
|
||||
"confirmations": "確認",
|
||||
|
@ -462,6 +463,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "ペアリング無効イベント",
|
||||
"passphrase": "パスフレーズ(オプション)",
|
||||
"passphrases_doesnt_match": "パスフレーズは一致しません。もう一度やり直してください",
|
||||
"password": "パスワード",
|
||||
"paste": "ペースト",
|
||||
"pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "이 작업은이 지갑을 삭제합니다. 계속 하시겠습니까?",
|
||||
"confirm_fee_deduction": "수수료 공제를 확인하십시오",
|
||||
"confirm_fee_deduction_content": "출력에서 수수료를 공제하는 데 동의하십니까?",
|
||||
"confirm_passphrase": "암호를 확인하십시오",
|
||||
"confirm_sending": "전송 확인",
|
||||
"confirm_silent_payments_switch_node": "현재 노드는 무음 지불을 지원하지 않습니다 \\ ncake 지갑은 스캔을 위해 호환 가능한 노드로 전환됩니다.",
|
||||
"confirmations": "확인",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "잘못된 이벤트 페어링",
|
||||
"passphrase": "암호화 (선택 사항)",
|
||||
"passphrases_doesnt_match": "패스 프레이즈가 일치하지 않습니다. 다시 시도하십시오",
|
||||
"password": "암호",
|
||||
"paste": "풀",
|
||||
"pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "ဤလုပ်ဆောင်ချက်သည် ဤပိုက်ဆံအိတ်ကို ဖျက်လိုက်ပါမည်။ ဆက်လုပ်လိုပါသလား။",
|
||||
"confirm_fee_deduction": "အခကြေးငွေကိုနှုတ်ယူခြင်း",
|
||||
"confirm_fee_deduction_content": "output မှအခကြေးငွေကိုယူရန်သဘောတူပါသလား။",
|
||||
"confirm_passphrase": "passphrase အတည်ပြုပါ",
|
||||
"confirm_sending": "ပေးပို့အတည်ပြုပါ။",
|
||||
"confirm_silent_payments_switch_node": "သင်၏လက်ရှိ node သည်အသံတိတ်ငွေပေးချေမှုကိုမပံ့ပိုးပါဟု \\ t",
|
||||
"confirmations": "အတည်ပြုချက်များ",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "ပမာဏကို ထပ်ရေးပါ။",
|
||||
"pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။",
|
||||
"passphrase": "passphrase (optional)",
|
||||
"passphrases_doesnt_match": "passphrases မကိုက်ညီဘူး, ကျေးဇူးပြုပြီးထပ်ကြိုးစားပါ",
|
||||
"password": "စကားဝှက်",
|
||||
"paste": "ငါးပိ",
|
||||
"pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Met deze actie wordt deze portemonnee verwijderd. Wilt u doorgaan?",
|
||||
"confirm_fee_deduction": "Bevestig de aftrek van de kosten",
|
||||
"confirm_fee_deduction_content": "Stemt u ermee in om de vergoeding af te trekken van de output?",
|
||||
"confirm_passphrase": "Bevestig Passaspherase",
|
||||
"confirm_sending": "Bevestig verzending",
|
||||
"confirm_silent_payments_switch_node": "Uw huidige knooppunt ondersteunt geen stille betalingen \\ ncake -portemonnee schakelt over naar een compatibele knoop",
|
||||
"confirmations": "Bevestigingen",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis",
|
||||
"passphrase": "PassaspHRASE (optioneel)",
|
||||
"passphrases_doesnt_match": "Passaspelfiaal komt niet overeen, probeer het opnieuw",
|
||||
"password": "Wachtwoord",
|
||||
"paste": "Plakken",
|
||||
"pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Ta czynność usunie ten portfel. Czy chcesz kontynuować?",
|
||||
"confirm_fee_deduction": "Potwierdź odliczenie opłaty",
|
||||
"confirm_fee_deduction_content": "Czy zgadzasz się odliczyć opłatę od wyników?",
|
||||
"confirm_passphrase": "Potwierdź hasło",
|
||||
"confirm_sending": "Potwierdź wysłanie",
|
||||
"confirm_silent_payments_switch_node": "Twój obecny węzeł nie obsługuje cichych płatności \\ NCAKE Portfel przełączy się na kompatybilny węzeł, tylko do skanowania",
|
||||
"confirmations": "Potwierdzenia",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Nadpisz ilość",
|
||||
"pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania",
|
||||
"passphrase": "PassPhraza (opcjonalnie)",
|
||||
"passphrases_doesnt_match": "Passfrazy nie pasują, spróbuj ponownie",
|
||||
"password": "Hasło",
|
||||
"paste": "Wklej",
|
||||
"pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Esta ação excluirá esta carteira. Você deseja continuar?",
|
||||
"confirm_fee_deduction": "Confirme dedução da taxa",
|
||||
"confirm_fee_deduction_content": "Você concorda em deduzir a taxa da saída?",
|
||||
"confirm_passphrase": "Confirme a senha",
|
||||
"confirm_sending": "Confirmar o envio",
|
||||
"confirm_silent_payments_switch_node": "Seu nó atual não suporta pagamentos silenciosos \n A Cake Wallet mudará para um nó compatível, apenas para escanear",
|
||||
"confirmations": "Confirmações",
|
||||
|
@ -463,6 +464,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Emparelhamento de evento inválido",
|
||||
"passphrase": "Senha (opcional)",
|
||||
"passphrases_doesnt_match": "Passagases não correspondem, por favor tente novamente",
|
||||
"password": "Senha",
|
||||
"paste": "Colar",
|
||||
"pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Это действие удалит кошелек. Вы хотите продолжить?",
|
||||
"confirm_fee_deduction": "Подтвердите вычет платы",
|
||||
"confirm_fee_deduction_content": "Согласны ли вы вычесть плату из вывода?",
|
||||
"confirm_passphrase": "Подтвердите Passfrase",
|
||||
"confirm_sending": "Подтвердить отправку",
|
||||
"confirm_silent_payments_switch_node": "Ваш текущий узел не поддерживает Silent Payments \\ ncake Wallet переключится на совместимый узел, только для сканирования",
|
||||
"confirmations": "Подтверждения",
|
||||
|
@ -462,6 +463,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Недействительное событие сопряжения",
|
||||
"passphrase": "Passfrase (необязательно)",
|
||||
"passphrases_doesnt_match": "Пасфразы не совпадают, попробуйте еще раз",
|
||||
"password": "Пароль",
|
||||
"paste": "Вставить",
|
||||
"pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "การดำเนินการนี้จะลบกระเป๋านี้ คุณต้องการดำเนินการต่อหรือไม่?",
|
||||
"confirm_fee_deduction": "ยืนยันการหักค่าธรรมเนียม",
|
||||
"confirm_fee_deduction_content": "คุณตกลงที่จะหักค่าธรรมเนียมจากผลลัพธ์หรือไม่?",
|
||||
"confirm_passphrase": "ยืนยันวลีรหัสผ่าน",
|
||||
"confirm_sending": "ยืนยันการส่ง",
|
||||
"confirm_silent_payments_switch_node": "โหนดปัจจุบันของคุณไม่รองรับการชำระเงินแบบเงียบ \\ ncake กระเป๋าเงินจะเปลี่ยนเป็นโหนดที่เข้ากันได้เพียงเพื่อการสแกน",
|
||||
"confirmations": "การยืนยัน",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "เขียนทับจำนวน",
|
||||
"pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง",
|
||||
"passphrase": "ข้อความรหัสผ่าน (ไม่บังคับ)",
|
||||
"passphrases_doesnt_match": "Passphrases ไม่ตรงกันโปรดลองอีกครั้ง",
|
||||
"password": "รหัสผ่าน",
|
||||
"paste": "วาง",
|
||||
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Tatanggalin ng pagkilos na ito ang wallet na ito. Gusto mo bang magpatuloy?",
|
||||
"confirm_fee_deduction": "Kumpirmahin ang pagbabawas ng fee",
|
||||
"confirm_fee_deduction_content": "Sumasang-ayon ka bang bawasan ang fee mula sa output?",
|
||||
"confirm_passphrase": "Kumpirma ang passphrase",
|
||||
"confirm_sending": "Kumpirmahin ang pagpapadala",
|
||||
"confirm_silent_payments_switch_node": "Ang iyong kasalukuyang node ay hindi sumusuporta sa tahimik na pagbabayad \\ nCake Wallet ay lilipat sa isang katugmang node, para lamang sa pag-scan",
|
||||
"confirmations": "Mga kumpirmasyon",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "I-overwrite ang halaga",
|
||||
"pairingInvalidEvent": "Pairing Invalid Event",
|
||||
"passphrase": "Passphrase (opsyonal)",
|
||||
"passphrases_doesnt_match": "Ang mga passphrases ay hindi tumutugma, mangyaring subukang muli",
|
||||
"password": "Password",
|
||||
"paste": "I-paste",
|
||||
"pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Bu eylem, bu cüzdanı silecek. Devam etmek istiyor musun?",
|
||||
"confirm_fee_deduction": "Ücret kesintisini onaylayın",
|
||||
"confirm_fee_deduction_content": "Ücreti çıktıdan düşürmeyi kabul ediyor musunuz?",
|
||||
"confirm_passphrase": "Parola onaylayın",
|
||||
"confirm_sending": "Göndermeyi onayla",
|
||||
"confirm_silent_payments_switch_node": "Mevcut düğümünüz sessiz ödemeleri desteklemiyor \\ nCake cüzdanı, sadece tarama için uyumlu bir düğüme geçecektir",
|
||||
"confirmations": "Onay",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Miktarın üzerine yaz",
|
||||
"pairingInvalidEvent": "Geçersiz Etkinliği Eşleştirme",
|
||||
"passphrase": "Passfrase (isteğe bağlı)",
|
||||
"passphrases_doesnt_match": "Passfrases eşleşmiyor, lütfen tekrar deneyin",
|
||||
"password": "Parola",
|
||||
"paste": "Yapıştır",
|
||||
"pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Ця дія видалить гаманець. Ви хочете продовжити?",
|
||||
"confirm_fee_deduction": "Підтвердьте відрахування комісії",
|
||||
"confirm_fee_deduction_content": "Чи погоджуєтесь ви вирахувати комісію з сумми одержувача?",
|
||||
"confirm_passphrase": "Підтвердьте пасфрази",
|
||||
"confirm_sending": "Підтвердити відправлення",
|
||||
"confirm_silent_payments_switch_node": "Ваш поточний вузол не підтримує мовчазні платежі \\ ncake Wallet перейде на сумісний вузол, лише для сканування",
|
||||
"confirmations": "Підтвердження",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "Недійсна подія сполучення",
|
||||
"passphrase": "Пасофрази (необов’язково)",
|
||||
"passphrases_doesnt_match": "Пасофрази не відповідають, спробуйте ще раз",
|
||||
"password": "Пароль",
|
||||
"paste": "Вставити",
|
||||
"pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "اس کارروائی سے یہ پرس حذف ہو جائے گا۔ کیا آپ جاری رکھنا چاہتے ہیں؟",
|
||||
"confirm_fee_deduction": "فیس میں کٹوتی کی تصدیق کریں",
|
||||
"confirm_fee_deduction_content": "کیا آپ آؤٹ پٹ سے فیس کم کرنے پر راضی ہیں؟",
|
||||
"confirm_passphrase": "پاسفریز کی تصدیق کریں",
|
||||
"confirm_sending": "بھیجنے کی تصدیق کریں۔",
|
||||
"confirm_silent_payments_switch_node": "آپ کا موجودہ نوڈ خاموش ادائیگیوں کی حمایت نہیں کرتا ہے۔",
|
||||
"confirmations": "تصدیقات",
|
||||
|
@ -463,6 +464,7 @@
|
|||
"overwrite_amount": "رقم کو اوور رائٹ کریں۔",
|
||||
"pairingInvalidEvent": "ﭧﻧﻮﯾﺍ ﻂﻠﻏ ﺎﻧﺎﻨﺑ ﺍﮌﻮﺟ",
|
||||
"passphrase": "پاسفریز (اختیاری)",
|
||||
"passphrases_doesnt_match": "پاسفریز مماثل نہیں ہیں ، براہ کرم دوبارہ کوشش کریں",
|
||||
"password": "پاس ورڈ",
|
||||
"paste": "چسپاں کریں۔",
|
||||
"pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ",
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
"confirm_fee_dedction_content": "Bạn có đồng ý trừ phí từ đầu ra không?",
|
||||
"confirm_fee_deduction": "Xác nhận Khấu trừ Phí",
|
||||
"confirm_fee_deduction_content": "Bạn có đồng ý khấu trừ phí từ đầu ra không?",
|
||||
"confirm_passphrase": "Xác nhận cụm mật khẩu",
|
||||
"confirm_sending": "Xác nhận gửi",
|
||||
"confirm_silent_payments_switch_node": "Nút hiện tại của bạn không hỗ trợ thanh toán im lặng\\nCake Wallet sẽ chuyển sang một nút tương thích chỉ để quét",
|
||||
"confirmations": "Xác nhận",
|
||||
|
@ -454,6 +455,7 @@
|
|||
"overwrite_amount": "Ghi đè số tiền",
|
||||
"pairingInvalidEvent": "Sự kiện ghép nối không hợp lệ",
|
||||
"passphrase": "Cụm từ bảo mật (Tùy chọn)",
|
||||
"passphrases_doesnt_match": "Vòng thông không khớp, vui lòng thử lại",
|
||||
"password": "Mật khẩu",
|
||||
"paste": "Dán",
|
||||
"pause_wallet_creation": "Khả năng tạo ví Haven hiện đang bị tạm dừng.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "Ìṣe yìí máa yọ àpamọ́wọ́ yìí kúrò. Ṣé ẹ fẹ́ tẹ̀síwájú?",
|
||||
"confirm_fee_deduction": "Jẹrisi iyọkuro owo",
|
||||
"confirm_fee_deduction_content": "Ṣe o gba lati yọkuro idiyele naa kuro ni iṣejade?",
|
||||
"confirm_passphrase": "Jẹrisi kọwe",
|
||||
"confirm_sending": "Jẹ́rìí sí ránṣẹ́",
|
||||
"confirm_silent_payments_switch_node": "Ilode rẹ ti lọwọlọwọ ko ṣe atilẹyin awọn sisanwo ti o dakẹ \\ owet apamọwọ yoo yipada si oju-ọrọ ibaramu, o kan fun Scning",
|
||||
"confirmations": "Àwọn ẹ̀rí",
|
||||
|
@ -462,6 +463,7 @@
|
|||
"overwrite_amount": "Pààrọ̀ iye owó",
|
||||
"pairingInvalidEvent": "Pipọpọ Iṣẹlẹ Ti ko tọ",
|
||||
"passphrase": "Ọrọ kukuru (iyan)",
|
||||
"passphrases_doesnt_match": "Awọn ọrọ kukuru ko baamu, jọwọ gbiyanju lẹẹkansi",
|
||||
"password": "Ọ̀rọ̀ aṣínà",
|
||||
"paste": "Fikún ẹ̀dà yín",
|
||||
"pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.",
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
"confirm_delete_wallet": "此操作将刪除此钱包。确定吗?",
|
||||
"confirm_fee_deduction": "确认费用扣除",
|
||||
"confirm_fee_deduction_content": "您是否同意从产出中扣除费用?",
|
||||
"confirm_passphrase": "确认密码",
|
||||
"confirm_sending": "确认发送",
|
||||
"confirm_silent_payments_switch_node": "您当前的节点不支持无声付款\\ ncake钱包将切换到兼容节点,仅用于扫描",
|
||||
"confirmations": "确认",
|
||||
|
@ -461,6 +462,7 @@
|
|||
"overwrite_amount": "Overwrite amount",
|
||||
"pairingInvalidEvent": "配对无效事件",
|
||||
"passphrase": "密码(可选)",
|
||||
"passphrases_doesnt_match": "密码不匹配,请重试",
|
||||
"password": "密码",
|
||||
"paste": "粘贴",
|
||||
"pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。",
|
||||
|
|
|
@ -8,7 +8,7 @@ if [[ ! -d "monero_c" ]];
|
|||
then
|
||||
git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip
|
||||
cd monero_c
|
||||
git checkout 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
|
||||
git checkout 6eb571ea498ed7b854934785f00fabfd0dadf75b
|
||||
git reset --hard
|
||||
git submodule update --init --force --recursive
|
||||
./apply_patches.sh monero
|
||||
|
|
|
@ -5,13 +5,14 @@ import 'utils/translation/translation_utils.dart';
|
|||
/// flutter packages pub run tool/append_translation.dart "hello_world" "Hello World!"
|
||||
|
||||
void main(List<String> args) async {
|
||||
if (args.length != 2) {
|
||||
if (args.length < 2) {
|
||||
throw Exception(
|
||||
'Insufficient arguments!\n\nTry to run `./append_translation.dart greetings "Hello World!"`');
|
||||
'Insufficient arguments!\n\nTry to run `./append_translation.dart "greetings" "Hello World!"`');
|
||||
}
|
||||
|
||||
final name = args.first;
|
||||
final text = args.last;
|
||||
final text = args[1];
|
||||
final force = args.last == "--force";
|
||||
|
||||
print('Appending "$name": "$text"');
|
||||
|
||||
|
@ -20,7 +21,7 @@ void main(List<String> args) async {
|
|||
final fileName = getArbFileName(lang);
|
||||
final translation = await getTranslation(text, lang);
|
||||
|
||||
appendStringToArbFile(fileName, name, translation);
|
||||
appendStringToArbFile(fileName, name, translation, force: force);
|
||||
}
|
||||
|
||||
print('Alphabetizing all files...');
|
||||
|
|
|
@ -304,10 +304,14 @@ class Subaddress {
|
|||
Subaddress({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.address});
|
||||
required this.address,
|
||||
required this.received,
|
||||
required this.txCount});
|
||||
final int id;
|
||||
final String label;
|
||||
final String address;
|
||||
final String? received;
|
||||
final int txCount;
|
||||
}
|
||||
|
||||
class MoneroBalance extends Balance {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
void appendStringToArbFile(String fileName, String name, String text) {
|
||||
void appendStringToArbFile(String fileName, String name, String text, {bool force = false}) {
|
||||
final file = File(fileName);
|
||||
final arbObj = readArbFile(file);
|
||||
|
||||
if (arbObj.containsKey(name)) {
|
||||
if (arbObj.containsKey(name) && !force) {
|
||||
print("String $name already exists in $fileName!");
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue